Python利用lxml和XPath解析HTML详解
本文介绍如何在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(){...});
具体到当前场景的代码实现为:
该操作旨在定位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中必须统一使用小写):
更简便的方法则是像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节点。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













