AgenBlog

爬虫教程(三)-精准打击的艺术 : 掌握 XPath 终极解析

2026/02/08
14
0

声明!本教程旨在进行技术了解与学习,不可用于恶意获取隐私信息、破坏互联网绿色环境的违法行为

既然我们已经通过教程(二)实现了批量抓取和 CSV 存储,那么现在的你已经能应付大部分结构简单的网站了。但随着你面对的网页越来越复杂,你会发现 BeautifulSoupfindselect 在处理深层嵌套、或者需要根据“文字内容”来反向查找标签时,会显得有些吃力。所以,在教程(三)中,我们将引入爬虫界的“重型武器”:XPath

基本概念

如果说 BeautifulSoup 是用“标签名”在找东西,那么XPath (XML Path Language)就是用“地图坐标”在定位。它不仅能处理 HTML,还能处理 XML,是大数据量爬虫(如 Scrapy 框架)官方指定的解析方式。

而在我们针对爬虫学习的进阶路径中,XPath 是必经之路,原因有三:

  1. 解析速度极快:底层由 C 语言实现,效率远高于纯 Python 编写的解析器。

  2. 定位极其精准:支持层级索引、属性过滤、甚至逻辑运算(如:查找包含“下一页”三个字的按钮)。

  3. 通用性强:几乎所有的编程语言(Java, C#, Python)都支持 XPath,学会一套,到处通用。

Xpath基础语法

Xpath表达式看似复杂,其实类似正则表达式一样,我们只需要记住一些关键的用法即可

表达式

描述

举例

含义

/

从根节点开始选取

/html/body

选取 body 节点

//

从任意位置选取(最常用)

//a

选取页面所有的 a 标签

.

选取当前节点

./span

在当前标签下找 span

@

选取属性

//a/@href

提取所有 a 标签的链接地址

[]

属性筛选(谓语)

//div[@class="content"]

找 class 为 content 的 div

text()

提取标签内的文字

//h1/text()

拿走一级标题的文本

实战尝试:用 XPath 重构我们的爬虫

我们将使用 lxml 库来执行 XPath。它是目前 Python 中处理 XPath 最快、最主流的选择。

import requests
from lxml import etree # 核心库
​
url = 'https://blog.agenblog.cn/archives/hello-halo'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'}
​
response = requests.get(url, headers=headers)
response.encoding = response.apparent_encoding
​
# 1. 将 HTML 字符串转化为可解析的对象
html = etree.HTML(response.text)
​
# 2. 提取标题
# 注意:XPath 返回的是列表,所以通常取 [0]
title = html.xpath('//title/text()')[0]
print(f"标题: {title}")
​
# 3. 提取正文 (定位 class 为 prose 的 section 标签下的所有文本)
# //section[contains(@class, "prose")] 是一个非常强大的技巧,处理包含多个类名的情况
content_list = html.xpath('//section[contains(@class, "prose")]//text()')
content = "".join(content_list).strip()
​
print(f"正文预览: {content[:1000]}...")

我们可以看到我们要的内容被爬取下来了,但是仔细的师傅就会发现多了一些内容:

pre:has(code) {
        filter: blur(10px) !important;
        padding: 1rem !important;
    }

这又是为什么呢?其实,在解析网页时,你可能会遇到一些“不速之客”。比如在抓取 Halo 博客正文时,由于作者将 <style> 样式标签写在了正文容器内,导致我们的 XPath 误以为那是正文内容。

因此,我们利用 XPath 的谓语过滤功能 [not(self::style)],我们可以像给过滤器装上筛网一样,精准地挡住样式代码,只留下人类可读的文字。这也是进阶爬虫开发者必须具备的“数据清晰”意识。”

我们修改原来的语句变成这样:

content_list = html.xpath('//section[contains(@class, "prose")]//*[not(self::style)]/text()')

再运行:

我们就可以只保留我们想要的内容啦。

补充说明

1.为什么 contains() 是神技?

很多前端框架生成的 HTML 标签,其 class 属性往往包含一长串字符(如 class="prose text-gray-900 dark:text-white")。如果你用普通的 find(class_="prose") 可能会匹配失败。但在 XPath 中,我们可以使用 //section[contains(@class, "prose")]。这行代码的意思是:“只要你的类名里包含了 prose 这几个字,我就能把你揪出来。”这种模糊匹配的能力,是爬虫应对复杂主题模板的利器。

2.标题:F12 里的 XPath 小偷

告诉大家一个偷懒的技巧:在浏览器开发者工具(F12)中,右键点击任何一个标签,选择 Copy -> Copy XPath。浏览器会自动为你生成一段路径。虽然这段路径往往过于冗长且脆弱,但它能帮你快速理解这个标签在整棵‘HTML 树’中的位置。

总结

通过本篇,你已经将解析工具从“手术刀”(BeautifulSoup)升级为了“激光雷达”(XPath)。你现在可以用更少的代码、更快的速度处理更复杂的网页。

到目前为止,我们爬取的都是“所见即所得”的静态网页。但如果一个网页的数据是你在 F12 里能看到,但在源码(View Source)里搜不到的呢? 没错,那就是动态加载(Ajax)。

在教程(四)中,我们将正式跨入“进阶期”的深水区:学习如何分析 Network 面板,抓取 XHR 请求,直接从服务器的 API 接口里“截获”最纯净的 JSON 数据!