在Python生态中处理网页数据抽取时,从HTML文档里准确拿到超链接的文字内容是一项基础又容易出错的工作。不少脚本直接用字符串查找去抠<a>标签之间的内容,一旦页面存在嵌套标签或者属性干扰就会提取残缺。lxml库封装了成熟的XML解析引擎,它提供的XPath支持让我们可以用类似文件路径的语法精准选中目标节点,从而稳定拿到链接文本。

lxml解析器的基本使用与HTML容错
lxml中的html模块自带HTMLParser,它能够容忍现实世界里大量不规范的HTML写法,比如未闭合的标签、混用大小写等。我们一般先把网页源码字符串传给html.fromstring方法,它会返回一棵Element树,后续所有的XPath查询都基于这棵树进行。和严格XML模式不同,这种解析方式不会因某处标签错误就整体报错,非常适合爬虫场景。
下面演示如何读取一段含有多个链接的HTML并构建可查询对象。注意源码中的尖括号在Python字符串里不需要额外转义,直接交给解析器即可。如果数据来自文件,也可以用parse函数加载。
from lxml import html page_source = ''' <div class="content"> <a href="/a">首页</a> <a href="/b"><span>产品</span>中心</a> <a href="/c"></a> </div> ''' tree = html.fromstring(page_source) print(type(tree))
上述代码运行后,tree就是一个Element实例。我们可以继续调用xpath方法。需要提醒的是,如果页面用了XHTML严格格式,也可切换为lxml.etree配合相应解析器,但绝大多数抓取任务用html.fromstring就够了。它的容错机制会自动补全缺失结构,使XPath表达式能正常命中。
XPath语法提取链接文本的核心写法
提取链接文本最直接的XPath是//a/text(),这表示从文档任意位置找所有<a>元素,并取它们的直接文本子节点。这种方法简单,但遇到链接内部还包了别的标签(如示例里的“产品中心”)时,text()只能拿到“中心”二字,而“产品”在<span>里被漏掉。这时应改用//a//text()获取全部后代文本再拼接,或用string(.)函数让XPath返回整个节点序列化后的文字。
空链接在网页里很常见,像上面href为/c的<a>就没有文字。用text()会拿到空串,用string(.)则明确返回空字符串而非None,便于后续过滤。以下示例展示两种写法差异,并过滤掉纯空白结果。
links = tree.xpath('//a')
for a in links:
direct_text = a.xpath('text()')
full_text = a.xpath('string(.)')
print('直接文本:', direct_text, '| 完整文本:', full_text.strip())
# 使用 //a//text() 拼接
texts = [''.join(a.xpath('//a//text()')) for a in links]
print('拼接结果:', texts)
从工程角度看,若只需肉眼可读的文字,string(.)配合strip最省事;若要做细致分析,比如区分外层与内层文本,则遍历//a//text()更灵活。XPath还支持谓词过滤,例如//a[@href]只取带href的链接,减少无效节点,提升效率。
性能对比与大规模抽取优化策略
当HTML文档达到几MB、内部链接成千上万时,解析方式的选择直接影响耗时。lxml底层由C语言编写,构建树和走XPath的速度通常数倍于纯Python实现的BeautifulSoup默认解析器。我们在批量采集时应当复用同一个解析树,避免反复解析同一份源码。另外,把XPath表达式写成相对路径(例如先定位//div[@class='content']再在其上做.//a/text())能缩小搜索范围。
下面用一个简单计时对比展示lxml与正则的思路差异。正则不建树,但面对嵌套就容易失误;lxml一次建树后可多次查询。实际项目中推荐用lxml统一管线。
import time
from lxml import html
import re
src = '<div>' + '<a href="#">链接</a>' * 5000 + '</div>'
start = time.time()
t = html.fromstring(src)
t.xpath('//a/text()')
print('lxml耗时:', time.time() - start)
start = time.time()
re.findall(r'<a[^>]*>(.*?)</a>', src)
print('正则耗时:', time.time() - start)
除了解析本身,内存也需注意。超大页面可配合iterparse做流式处理,但提取链接文本通常没到那个量级。若站点使用了命名空间(如SVG里的<a>),XPath要加上对应前缀映射,否则会选不中。掌握这些细节后,用Python加lxml和XPath抽链接文本既稳又块,能适配绝大多数网页结构。
lxmlXPathhtml_link_extraction修改时间:2026-08-15 23:46:14