为什么这个xpath在Python中使用lxml会失败?

6 投票
3 回答
6120 浏览
提问于 2025-04-18 07:45

这是我想要获取数据的一个网页示例。 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 个回答

1

我遇到过类似的问题(在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”没有正常工作,那就是这个版本)

3

这个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>
&nbsp; 
<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提出了一个更简单的解决方案,适用于直接获取的网页。

15

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

撰写回答