<h3>问题</h3>
<p>好的,所以可能有一些情况需要在客户端(Python)而不是服务器(browser)端对页面执行一些实质性的处理。例如,如果您已经用Python编写了某种机器学习系统,并且在对它们执行操作之前需要分析整个页面,那么尽管可以通过一堆<code>find_element</code>调用来完成,但这会非常昂贵,因为每个调用都是客户机和服务器之间的往返调用。而重写它以便在浏览器中工作可能代价太高。</p>
<h3>为什么硒的识别物不会“这么做”</h3>
<p>但是,我看不到一种有效的</em>方法来获得DOM<em>与Selenium自己的标识符的序列化。Selenium根据需要在调用<code>find_element</code>或DOM节点从<code>execute_script</code>调用返回(或传递给<code>execute_async_script</code>给脚本的回调)时创建这些标识符。但是如果您调用<code>find_element</code>来获取每个元素的标识符,那么您就回到了原点。我可以想象在浏览器中用所需的信息装饰DOM,但是没有公共API来请求某种预分配<code>WebElement</code>id。事实上,这些标识符的设计是不透明的,所以即使解决方案设法获得所需的信息,我也会担心跨浏览器的可行性和持续的支持。</p>
<h3>解决方案</h3>
<p>然而,有一种方法可以得到一个在双方都能工作的寻址系统:XPath。其思想是将DOM序列化解析为客户端的树,然后获取您感兴趣的节点的XPath,并使用它获取相应的WebElement。因此,如果您需要执行数十次客户机-服务器往返以确定需要执行单击的单个元素,那么您就可以将其减少为对页源的初始查询,再加上对所需XPath的单个<code>find_element</code>调用。</p>
<p>这是一个非常简单的概念证明。它获取Google首页的主输入字段。</p>
<pre><code>from StringIO import StringIO
from selenium import webdriver
import lxml.etree
#
# Make sure that your chromedriver is in your PATH, and use the following line...
#
driver = webdriver.Chrome()
#
# ... or, you can put the path inside the call like this:
# driver = webdriver.Chrome("/path/to/chromedriver")
#
parser = lxml.etree.HTMLParser()
driver.get("http://google.com")
# We get this element only for the sake of illustration, for the tests later.
input_from_find = driver.find_element_by_id("gbqfq")
input_from_find.send_keys("foo")
html = driver.execute_script("return document.documentElement.outerHTML")
tree = lxml.etree.parse(StringIO(html), parser)
# Find our element in the tree.
field = tree.find("//*[@id='gbqfq']")
# Get the XPath that will uniquely select it.
path = tree.getpath(field)
# Use the XPath to get the element from the browser.
input_from_xpath = driver.find_element_by_xpath(path)
print "Equal?", input_from_xpath == input_from_find
# In JavaScript we would not call ``getAttribute`` but Selenium treats
# a query on the ``value`` attribute as special, so this works.
print "Value:", input_from_xpath.get_attribute("value")
driver.quit()
</code></pre>
<p>注:</p>
<ol>
<li><p>上面的代码没有使用<code>driver.page_source</code>,因为Selenium的文档声明不能保证它返回的内容的新鲜度。它可以是当前DOM的状态,也可以是首次加载页面时的DOM状态。</p></li>
<li><p>这个解决方案与<code>find_element</code>在动态内容方面遇到的问题完全相同。如果在进行分析时DOM发生了更改,那么您正在处理DOM的陈旧表示。</p></li>
<li><p>如果在执行分析时必须生成JavaScript事件,并且这些事件会更改DOM,则需要再次获取DOM。(这与前一点类似,但使用<code>find_element</code>调用的解决方案可以通过仔细排序调用序列来避免我在<em>这一</em>点中讨论的问题。)</p></li>
<li><p><code>lxml</code>的树可能在结构上不同于DOM树,这样从<code>lxml</code>获得的XPath就不会寻址DOM中的相应元素。什么<code>lxml</code>进程是浏览器传递给它的HTML的已清理序列化视图。因此,只要代码是为了防止我在第2点和第3点中提到的问题而编写的,我不认为这是可能的情况,但这并非不可能。</p></li>
</ol>