Python利用lxml和XPath解析HTML详解

Finn
Finn
发布时间:2026-10-12 | 2人浏览过
原创

本文介绍如何在Python中使用lxml模块结合XPath语法解析HTML文档。通过对比jQuery和正则表达式,演示如何定位特定节点、提取文本及处理嵌套结构,并详细解析XPath基本语法如路径分隔、属性过滤及节点函数,帮助开发者掌握高效的网页数据抓取技巧。

在网页抓取任务中,准确定位并分析HTML节点是获取目标信息的核心环节。目前广泛采用的方案是使用lxml模块,该模块主要用于分析XML文档结构,但同样适用于HTML结构的解析。通过调用lxml.html中的xpath功能,可以高效地对HTML文档进行分析,从而提取所需数据。

首先,必须安装支持xpath语法的Python库。目前libxml2官网推荐的Python绑定库为lxml,此外还有beautifulsoup等可选方案。若不愿依赖第三方库,也可通过正则表达式自行构建解析逻辑。本文将以lxml为例进行具体讲解。

假设存在如下HTML文档结构:

<html>
 <body>
  <form>
   <div id='leftmenu'>
    <h3>text</h3>
    <ul id='china'><!-- first location -->
     <li>...</li>
     <li>...</li>
       ......
    </ul>
    <ul id='england'><!-- second location-->
     <li>...</li>
     <li>...</li>
       ......
    </ul>
   </div>
  </form>
 </body>
</html>

直接使用lxml模块进行处理:

 import codecs
 from lxml import etree
 f=codecs.open("ceshi.html","r","utf-8")
 content=f.read()
 f.close()
 tree=etree.HTML(content)

etree模块提供了HTML解析函数,现在可以直接对HTML内容应用xpath查询,这大大简化了开发流程,值得尝试。

在深入使用xpath之前,先对比一下jQuery和正则表达式(RE)的处理方式。

在jQuery中处理此类结构相对简单,特别是当ul节点拥有id属性时(例如<ul id='china'>):

$("#china").each(function(){...});

具体到当前场景的代码实现为:

复制代码 代码如下:
$("#leftmenu").children("h3:contains('text')").next("ul").each(function(){...});

该操作旨在定位id为leftmenu的节点,在其子节点中查找内容包含“text”的h3节点,并获取其紧随其后的第一个ul节点。

若在Python中使用正则表达式(RE)处理,代码复杂度会显著增加:

block_pattern=re.compile(u"<h3>档案</h3>(.*?)<h3>", re.I | re.S)
m=block_pattern.findall(content)
item_pattern=re.compile(u"<li>(.*?)</li>", re.I | re.S)
items=item_pattern.findall(m[0])
for i in items:
  print i

那么,使用xpath该如何实现类似功能呢?其逻辑与jQuery非常相似:

nodes=tree.xpath("/descendant::ul[@id='china']")

若目标节点没有id属性,则需采用类似jQuery的层级定位方法。完整的xpath表达式应如下编写(注意:原HTML文件中的标签可能存在大小写混合,但在xpath中必须统一使用小写):

复制代码 代码如下:
nodes=tree.xpath(u"/html/body/form/div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

更简便的方法则是像jQuery那样直接通过id进行定位:

nodes=tree.xpath(u"//div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

上述两种方法返回的结果中,nodes[0]即为“text”h3节点之后的第一个ul节点,借此可以遍历并列出后续所有ul节点的内容。

若ul节点内部还包含其他深层节点,需要提取更深层次的内容,可通过以下循环遍历并列出所有相关节点的文本:

nodes=nodes[0].xpath("li/a")
for n in nodes:
  print n.text

对比三种方法可见,xpath和jQuery均基于XML语义进行页面解析,而RE则纯粹基于纯文本匹配。对于简单页面,RE足以应对;但当页面结构复杂(如大量DIV嵌套)时,设计合适的RE模式远比编写xpath复杂。特别是在主流CSS页面设计中,关键节点通常带有id属性,这使得xpath相比RE具有显著优势。

附录:基本XPATH语法介绍,更详细内容请参考XPath官方文档。

XPATH采用类似目录树的方式描述XML文档中的路径。例如,使用“/”作为层级分隔符。首个“/”代表文档根节点(注意,并非指最外层的标签节点,而是指文档本身)。例如,对于HTML文件,最外层节点应表示为“/html”。

同样,“..”和“.”分别用于表示父节点和本节点。

XPATH返回的未必是单一节点,而是符合所有条件的节点集合。例如,在HTML文档中使用“/html/head/scrpt”将提取head标签内所有的script节点。

为了缩小定位范围,通常需要添加过滤条件。过滤方法是通过“[”和“]”包裹条件表达式。例如,在HTML文档中使用“/html/body/div[@id='main']”即可提取body中id为main的div节点。

其中@id表示id属性,类似地还可使用@name、@value、@href、@src、@class等属性。

函数text()用于获取节点包含的文本内容。例如,在<div>hello<p>world</p></div>结构中,使用“div[text()='hello']”即可定位该div节点,而world则是p节点的text()内容。

函数position()用于获取节点的位置索引。例如,“li[position()=2]”表示获取第二个li节点,该表达式也可简写为“li[2]”。

需注意数字定位与过滤条件的顺序差异。例如,“ul/li[5][@name='hello']”表示获取ul下第五个li节点,且其name属性必须为hello,否则返回空结果。而“ul/li[@name='hello'][5]”含义不同,它表示寻找ul下第五个name为“hello”的li节点。

此外,“*”可替代所有节点名。例如,使用“/html/body/*/span”可提取body下第二层级的所有span节点,无论其上一级是div、p或其他标签。

前缀“descendant::”可指代任意层级的中间节点,该前缀也可省略,直接用“//”表示。例如,在整个HTML文档中查找id为“leftmenu”的div节点,可使用“/descendant::div[@id='leftmenu']”,或简化为“//div[@id='leftmenu']”。

前缀“following-sibling::”表示同一层级的后续节点。“following-sibling::*”匹配任意后续节点,而“following-sibling::ul”则匹配后续的ul节点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表