有趣的“getElementById()只接受1个参数(给定2个)”,有时会发生这种情况。有人能解释一下吗?

2024-05-17 23:05:30 发布

您现在位置:Python中文网/ 问答频道 /正文

#-*- coding:utf-8 -*-
import win32com.client, pythoncom
import time

ie = win32com.client.DispatchEx('InternetExplorer.Application.1')
ie.Visible = 1
ie.Navigate('http://ieeexplore.ieee.org/xpl/periodicals.jsp')
time.sleep( 5 )

ie.Document.getElementById("browse_keyword").value ="Computer"
ie.Document.getElementsByTagName("input")[24].click()

import win32com.client, pythoncom
import time

ie = win32com.client.DispatchEx('InternetExplorer.Application')
ie.Visible = 1
ie.Navigate('www.baidu.com')
time.sleep(5)

print 'browse_keword'
ie.Document.getElementById("kw").value ="Computer"
ie.Document.getElementById("su").click()
print 'Done!'

运行第一段代码时,将弹出:

ie.Document.getElementById("browse_keyword").value ="Computer"
TypeError: getElementById() takes exactly 1 argument (2 given)

第二段代码运行正常。使结果不同的区别是什么?


Tags: importclienttimevaluedocumentcomputeriewin32com
3条回答

对Python中实例的方法的调用会自动将实例添加为第一个参数-这就是为什么必须在方法中显式地编写“self”参数的原因。

例如,instance.method(args...)等于Class.method(instance, args...)

从我看来,程序员一定忘了写self关键字,结果破坏了方法。尝试查看库代码内部。

作为COMObject的一种方法,getElementByIdwin32com动态构建。
在我的计算机上,如果url是http://ieeexplore.ieee.org/xpl/periodicals.jsp,它几乎等于

def getElementById(self):
    return self._ApplyTypes_(3000795, 1, (12, 0), (), 'getElementById', None,)

如果网址是www.baidu.com,它几乎相当于

def getElementById(self, v=pythoncom.Missing):
    ret = self._oleobj_.InvokeTypes(1088, LCID, 1, (9, 0), ((8, 1),),v
            )
    if ret is not None:
        ret = Dispatch(ret, 'getElementById', {3050F1FF-98B5-11CF-BB82-00AA00BDCE0B})
    return ret

显然,如果您将参数传递给第一个代码,您将收到一个TypeError。但如果您尝试直接使用它,即调用ie.Document.getElementById(),则不会收到TypeError,而是收到com_error

为什么win32com构建了错误的代码?
让我们看看ieie.Document。它们都是COMObject,更准确地说,是win32com.client.CDispatch实例。CDispatch只是一个包装类。核心是属性_oleobj_,其类型是PyIDispatch

>>> ie, ie.Document
(<COMObject InternetExplorer.Application>, <COMObject <unknown>>)
>>> ie.__class__, ie.Document.__class__
(<class win32com.client.CDispatch at 0x02CD00A0>,
 <class win32com.client.CDispatch at 0x02CD00A0>)
>>> oleobj = ie.Document._oleobj_
>>> oleobj
<PyIDispatch at 0x02B37800 with obj at 0x003287D4>

要构建getElementByIdwin32com需要从_oleobj_获取getElementById方法的类型信息。大致上,win32com使用以下过程

typeinfo = oleobj.GetTypeInfo()
typecomp = typeinfo.GetTypeComp()
x, funcdesc = typecomp.Bind('getElementById', pythoncom.INVOKE_FUNC)
......

funcdesc包含几乎所有的导入信息,例如参数的数量和类型。
如果url是http://ieeexplore.ieee.org/xpl/periodicals.jspfuncdesc.args(),而相应的funcdesc.args应该是((8, 1, None),)

总之,长话短说,win32com检索到了错误的类型信息,从而建立了错误的方法 我不知道该怪谁,PyWin32或IE。但根据我的观察,我发现PyWin32的代码没有任何错误。另一方面,以下脚本在Windows脚本主机中运行得很好。

var ie = new ActiveXObject("InternetExplorer.Application");
ie.Visible = 1;
ie.Navigate("http://ieeexplore.ieee.org/xpl/periodicals.jsp");
WScript.sleep(5000);
ie.Document.getElementById("browse_keyword").value = "Computer";

邓肯已经指出IE的兼容模式可以防止这个问题。不幸的是,似乎无法从脚本启用兼容模式。
但我发现了一个技巧,可以帮助我们绕过这个问题。

首先,您需要访问一个好的站点,它为我们提供一个HTML页面,并从中检索一个正确的Document对象。

ie = win32com.client.DispatchEx('InternetExplorer.Application')
ie.Visible = 1
ie.Navigate('http://www.haskell.org/arrows')
time.sleep(5)
document = ie.Document

然后跳转到不起作用的页面

ie.Navigate('http://ieeexplore.ieee.org/xpl/periodicals.jsp')
time.sleep(5)

现在您可以通过旧的Document对象访问第二页的DOM。

document.getElementById('browse_keyword').value = "Computer"

如果使用新的Document对象,将再次获得TypeError

>>> ie.Document.getElementById('browse_keyword')
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
TypeError: getElementById() takes exactly 1 argument (2 given)

这两种情况之间的差异与您指定的COM名称无关:无论是InternetExplorer.Application还是InternetExplorer.Application.1都会产生完全相同的CLSID,从而为您提供IWebBrowser2接口。运行时行为的差异完全取决于您检索到的URL。

这里的区别可能是,工作的页面是HTML,而另一个页面是XHTML;或者可能只是失败页面中的错误阻止了DOM的正确初始化。不管它是IE9解析器的“特性”。

请注意,如果启用兼容模式,则不会发生这种情况(在下面的第二行单击地址栏中的兼容模式图标之后):

(Pdb) ie.Document.DocumentMode
9.0
(Pdb) ie.Document.getElementById("browse_keyword").value
*** TypeError: getElementById() takes exactly 1 argument (2 given)
(Pdb) ie.Document.documentMode
7.0
(Pdb) ie.Document.getElementById("browse_keyword").value
u''

不幸的是,我不知道如何从脚本切换兼容模式(不能设置documentMode属性)。也许其他人知道?

我认为,错误的参数计数来自COM:Python传入参数,COM对象以误导性错误拒绝调用。

相关问题 更多 >