为什么这个xpath在Python中使用lxml会失败?
这是我想要获取数据的一个网页示例。 http://www.makospearguns.com/product-p/mcffgb.htm
我从Chrome的开发者工具中获取了xpath,Firefox中的firepath也能找到它,但使用lxml时,它返回的'text'却是一个空列表。
from lxml import html
import requests
site_url = 'http://www.makospearguns.com/product-p/mcffgb.htm'
xpath = '//*[@id="v65-product-parent"]/tbody/tr[2]/td[2]/table[1]/tbody/tr/td/table/tbody/tr[2]/td[2]/table/tbody/tr[1]/td[1]/div/table/tbody/tr/td/font/div/b/span/text()'
page = requests.get(site_url)
tree = html.fromstring(page.text)
text = tree.xpath(xpath)
用
print(tree.text_content().encode('utf-8'))
打印出树形结构的文本显示数据是存在的,但似乎xpath没有找到它。我是不是漏掉了什么?我尝试过的其他网站用lxml和从Chrome开发者工具获取的xpath都能正常工作,但有几个网站却返回了空列表。
3 个回答
我遇到过类似的问题(在Chrome中复制XPath时会插入tbody元素)。正如其他人所说的,你需要查看实际的页面源代码,虽然浏览器给出的XPath是一个不错的起点。我发现通常删除tbody标签可以解决这个问题。为了测试这个,我写了一个小的Python工具脚本来测试XPath:
#!/usr/bin/env python
import sys, requests
from lxml import html
if (len(sys.argv) < 3):
print 'Usage: ' + sys.argv[0] + ' url xpath'
sys.exit(1)
else:
url = sys.argv[1]
xp = sys.argv[2]
page = requests.get(url)
tree = html.fromstring(page.text)
nodes = tree.xpath(xp)
if (len(nodes) == 0):
print 'XPath did not match any nodes'
else:
# tree.xpath(xp) produces a list, so always just take first item
print (nodes[0]).text_content().encode('ascii', 'ignore')
(这是Python 2.7的代码,如果你看到“print”没有正常工作,那就是这个版本)
这个xpath写得完全不对。
这里有个页面的片段:
<form id="vCSS_mainform" method="post" name="MainForm" action="/ProductDetails.asp?ProductCode=MCFFGB" onsubmit="javascript:return QtyEnabledAddToCart_SuppressFormIE();">
<img src="/v/vspfiles/templates/MAKO/images/clear1x1.gif" width="5" height="5" alt="" /><br />
<table width="100%" cellpadding="0" cellspacing="0" border="0" id="v65-product-parent">
<tr>
<td colspan="2" class="vCSS_breadcrumb_td"><b>
<a href="http://www.makospearguns.com/">Home</a> >
你可以看到,id
为"v65-product-parent"
的元素是一个table
类型,并且它有一个子元素tr
。
这样的id
只能有一个元素(否则就会出现不合法的xml)。
这个xpath期望在给定的元素(table)下有一个tbody
作为子元素,但整个页面里没有这个元素。
你可以通过以下方式来测试:
>>> "tbody" in page.text
False
Chrome是如何得到这个XPath的?
如果你直接下载这个页面:
$ wget http://www.makospearguns.com/product-p/mcffgb.htm
然后查看它的内容,你会发现里面没有一个叫tbody
的元素。
但是如果你使用Chrome开发者工具,你会找到一些。
这是怎么回事呢?
这种情况通常发生在JavaScript参与进来时,它会在浏览器中生成一些页面内容。但正如LegoStormtroopr所提到的,这次并不是这种情况,而是浏览器自己修改了文档,使其变得正确。
如何获取在浏览器中动态修改的页面内容?
你需要给某种浏览器一个机会。例如,如果你使用selenium
,你就能获取到这些内容。
byselenium.py
from selenium import webdriver
from lxml import html
url = "http://www.makospearguns.com/product-p/mcffgb.htm"
xpath = '//*[@id="v65-product-parent"]/tbody/tr[2]/td[2]/table[1]/tbody/tr/td/table/tbody/tr[2]/td[2]/table/tbody/tr[1]/td[1]/div/table/tbody/tr/td/font/div/b/span/text()'
browser = webdriver.Firefox()
browser.get(url)
html_source = browser.page_source
print "test tbody", "tbody" in html_source
tree = html.fromstring(html_source)
text = tree.xpath(xpath)
print text
这段代码会打印出什么
$ python byselenimum.py
test tbody True
['$149.95']
总结
当涉及到浏览器内部的变化时,Selenium非常好用。不过它有点重,如果你能用更简单的方法做到,就用那个方法。Lego Stormtroopr提出了一个更简单的解决方案,适用于直接获取的网页。
1. 浏览器经常会修改HTML
浏览器经常会对接收到的HTML进行修改,以确保它是“有效的”。比如说,如果你给浏览器发送了这样一段无效的HTML:
<table>
<p>bad paragraph</p>
<tr><td>Note that cells and rows can be unclosed (and valid) in HTML
</table>
为了能够正确显示,浏览器会帮忙尝试把它变成有效的HTML,可能会把它转换成:
<p>bad paragraph</p>
<table>
<tbody>
<tr>
<td>Note that cells and rows can be unclosed (and valid) in HTML</td>
</tr>
</tbody>
</table>
之所以会这样改变,是因为<p>
标签不能放在<table>
标签里面,而<tbody>
标签是推荐使用的。不同的浏览器对源代码的修改方式可能会有很大差异。有的浏览器会把无效的元素放在表格前面,有的放在后面,还有的放在单元格里面等等……
2. XPath并不是固定的,它们在指向元素时是灵活的。
使用这段“固定”的HTML:
<p>bad paragraph</p>
<table>
<tbody>
<tr>
<td>Note that cells and rows can be unclosed (and valid) in HTML</td>
</tr>
</tbody>
</table>
如果我们想要找到<td>
单元格中的文本,以下所有方法都能大致找到正确的信息:
//td
//tr/td
//tbody/tr/td
/table/tbody/tr/td
/table//*/text()
而且这个列表还可以继续……
不过,一般来说,浏览器会给你提供最精确(但灵活性最差)的XPath,它会列出DOM中的每一个元素。在这种情况下:
/table[0]/tbody[0]/tr[0]/td[0]/text()
3. 结论:浏览器提供的XPath通常不太有用
这就是为什么开发者工具生成的XPath在尝试使用原始HTML时,常常会给你错误的XPath。
解决办法是,始终参考原始HTML,并使用灵活但精确的XPath。
检查实际包含价格的HTML:
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<font class="pricecolor colors_productprice">
<div class="product_productprice">
<b>
<font class="text colors_text">Price:</font>
<span itemprop="price">$149.95</span>
</b>
</div>
</font>
<br/>
<input type="image" src="/v/vspfiles/templates/MAKO/images/buttons/btn_updateprice.gif" name="btnupdateprice" alt="Update Price" border="0"/>
</td>
</tr>
</table>
如果你想要找到价格,其实只有一个地方可以查看!
//span[@itemprop="price"]/text()
这样做会返回:
$149.95