在网页数据抓取、内容解析等场景中,提取HTML文档里的链接文本是高频需求,Python的LXML库结合XPath语法可以高效完成这个任务,还能应对各种复杂的页面结构问题。

环境准备
首先需要安装LXML库,它是基于libxml2和libxslt的Python封装,解析速度快且支持完整的XPath 1.0语法。使用pip命令安装即可:
pip install lxml
基础提取流程
提取链接文本的核心逻辑是先将HTML字符串解析为LXML的Element对象,再通过XPath定位到所有的<a>标签,最后提取标签内的文本内容。
基础示例代码
下面是一个最简单的提取示例,假设我们有一个包含多个链接的HTML片段:
from lxml import etree
# 待解析的HTML内容
html_content = """
<div class="content">
<a href="https://ipipp.com/page1">第一个链接</a>
<a href="https://ipipp.com/page2">第二个链接</a>
<p>普通文本段落</p>
</div>
"""
# 解析HTML为Element对象
html_element = etree.HTML(html_content)
# 使用XPath提取所有a标签的文本
# //a 表示匹配所有a标签,/text() 表示获取标签的直接文本内容
link_texts = html_element.xpath("//a/text()")
print(link_texts)
# 输出结果:['第一个链接', '第二个链接']
处理复杂嵌套结构
实际页面中<a>标签内部可能嵌套其他标签,比如<span>、<strong>等,这时候直接用/text()只能获取到直接子文本,会丢失嵌套标签内的内容。
提取嵌套内容的方法
可以使用string()函数来获取标签内所有的文本内容,忽略内部的嵌套标签:
from lxml import etree
html_content = """
<div>
<a href="https://ipipp.com/test">
<span>外层文本</span>
<strong>内层加粗文本</strong>
直接文本
</a>
</div>
"""
html_element = etree.HTML(html_content)
# 使用string()函数获取a标签内所有文本
# 注意这里需要先获取a标签的元素,再对每个元素调用string()
a_elements = html_element.xpath("//a")
link_texts = [etree.tostring(elem, method="text", encoding="unicode") for elem in a_elements]
print(link_texts)
# 输出结果:['n 外层文本n 内层加粗文本n 直接文本n ']
如果需要去除多余的空白字符,可以对结果做进一步处理:
# 去除首尾空白和中间的换行、多余空格
clean_texts = [text.strip().replace("n", "").replace(" ", "") for text in link_texts]
print(clean_texts)
# 输出结果:['外层文本内层加粗文本直接文本']
过滤无效链接
实际提取时经常会遇到空链接、锚点链接、JavaScript伪链接等无效内容,需要结合<a>标签的href属性进行过滤。
过滤规则示例
常见的过滤条件包括:排除href为空、href以#开头、href以javascript:开头的链接:
from lxml import etree
html_content = """
<div>
<a href="https://ipipp.com/valid">有效链接</a>
<a href="">空链接</a>
<a href="#top">锚点链接</a>
<a href="javascript:void(0)">JS伪链接</a>
</div>
"""
html_element = etree.HTML(html_content)
# 先获取所有a标签的href和文本
a_list = html_element.xpath("//a")
valid_links = []
for a in a_list:
href = a.get("href", "")
text = etree.tostring(a, method="text", encoding="unicode").strip()
# 过滤条件:href不为空,不以#开头,不以javascript:开头
if href and not href.startswith("#") and not href.lower().startswith("javascript:"):
valid_links.append({
"href": href,
"text": text
})
print(valid_links)
# 输出结果:[{'href': 'https://ipipp.com/valid', 'text': '有效链接'}]
应对编码问题
部分HTML页面可能存在编码声明和实际内容编码不一致的情况,解析时会出现乱码,这时候可以在解析时指定编码,或者对HTML内容做预处理。
编码处理示例
from lxml import etree
# 假设HTML内容编码为gbk,但默认按utf-8解析会乱码
html_bytes = "<a href="https://ipipp.com/编码测试">中文链接</a>".encode("gbk")
# 方式1:解析时指定编码
html_element = etree.HTML(html_bytes, parser=etree.HTMLParser(encoding="gbk"))
link_texts = html_element.xpath("//a/text()")
print(link_texts)
# 方式2:先解码再解析
html_str = html_bytes.decode("gbk")
html_element = etree.HTML(html_str)
link_texts = html_element.xpath("//a/text()")
print(link_texts)
常见问题总结
- 提取文本为空:检查XPath路径是否正确,是否被注释标签包裹,或者标签内确实没有文本内容
- 解析报错:检查HTML内容是否完整,是否有未闭合的标签,可尝试使用
etree.HTMLParser(recover=True)开启容错模式 - 相对路径链接:如果需要将相对路径转为绝对路径,可以结合
urllib.parse.urljoin函数处理href属性
注意:网页抓取需要遵守目标网站的robots协议,避免高频请求给服务器造成压力,同时不要抓取涉及隐私、版权的内容。