Python 爬虫项目实战:XPath 语法实战抓取科普文章列表数据
前言
在静态网页规模化数据采集体系中,BeautifulSoup 依托 DOM 对象遍历完成节点筛选,在深层嵌套、不规则 HTML 源码场景下代码冗余度偏高,XPath 凭借路径寻址语法、XSLT 底层规范,可通过层级路径、属性匹配、轴运算快速定位零散排布的科普文章条目,成为爬虫领域高效解析方案。XPath 依托 lxml 库完成 HTML 文档加载与节点检索,既兼容残缺破损的非标准页面代码,又能实现单路径一次性提取标题、发布时间、文章简介、详情链接等多字段内容,广泛应用资讯列表、科普专栏类爬虫项目。
本文围绕科普资讯站点列表页实战,从 XPath 语法基础、lxml 文档树构建、路径分类写法、多字段联合提取、异常清洗到本地结构化存储逐层落地,完整搭建requests拉取源码+lxml构建DOM+XPath检索+数据落地的标准化爬虫架构。
配套技术官方参考链接汇总:
- lxml 官方文档:etree 模块 API、XPath 接口使用规范
- W3C XPath1.0 标准文档:XPath 原生语法权威定义
- requests 官方文档:HTTP 请求库参考手册
- Python csv 标准库文档:结构化文件存储语法说明
一、运行环境配置与依赖安装
1.1 依赖组件说明
XPath 无法独立运行在 Python 中,必须借助 lxml 第三方库内置的 etree 引擎完成 HTML 文本解析、DOM 树生成与 XPath 表达式编译执行,项目核心依赖为 requests、lxml。
终端加速安装命令:
bash
运行
pip install requests lxml -i https://pypi.tuna.tsinghua.edu.cn/simple安装校验:执行pip show lxml,查看版本与安装路径即代表部署完成。
1.2 lxml 解析机制简述
lxml 底层封装 C 语言 libxml2 解析内核,相较于 BeautifulSoup 三种解析引擎,在大批量科普列表解析场景具备解析速度优势,可自动修补残缺闭合标签、补齐 HTML 根节点,将不规则网页源码规范化为标准 XML 树形结构,是 XPath 正常寻址的前置条件。
1.3 科普列表页面通用结构特征
科普文章列表页面拥有固定页面范式:外层容器包裹全部文章条目,单篇科普内容统一嵌套在同层级标签中,字段分布固定:文章标题、发布日期、摘要内容、详情跳转链接分属不同子节点,天然适配 XPath 批量循环检索。
二、XPath 基础路径语法与节点寻址原理
2.1 XPath 路径标识基础符号
XPath 依靠路径符号描述节点层级关系,基础标识汇总表格:
表格
| 符号 | 释义 | 使用示例 |
|---|---|---|
| / | 从根节点开始绝对路径寻址 | /html/body/div |
| // | 全局任意位置相对寻址,忽略上层层级 | //li |
| . | 当前节点 | ./a |
| .. | 父级节点 | ../div |
| @ | 匹配标签属性 | @class、@href |
绝对路径从 HTML 根节点逐层书写,页面标签层级微调即失效,爬虫开发中极少使用;// 全局检索为列表爬虫主流写法,不受页面外层标签改版影响,稳定性更强。
2.2 lxml 文档树初始化基础代码
将 requests 获取的网页字符串转为 etree 可识别的 DOM 对象,是 XPath 查询前置操作:
python
运行
from lxml import etree # 模拟科普页面HTML片段 html_str = ''' <div class="article_list"> <li class="item"> <a href="/science/1">宇宙黑洞基础科普</a> <span class="date">2025-12-01</span> <p class="desc">详解黑洞形成原理与天体演化规律</p> </li> <li class="item"> <a href="/science/2">深海地质探测科普</a> <span class="date">2025-12-02</span> <p class="desc">海底板块运动与地热资源科普说明</p> </li> </div> ''' # 构建HTML解析对象,自动修复破损标签 html = etree.HTML(html_str) # XPath全局匹配所有li条目节点 item_nodes = html.xpath('//li[@class="item"]') print(item_nodes)代码原理剖析
etree.HTML()自动补齐<html><body>缺失根标签,标准化文档结构,返回 Element 对象;.xpath("表达式")是 lxml 专属查询接口,接收 XPath 字符串,返回匹配节点列表;- 匹配到的节点对象可再次调用 xpath (),实现基于当前节点的局部路径检索。
2.3 文本与属性取值语法
XPath 提取内容分为标签内部文本、标签属性两类固定写法:
text():提取节点自身内部纯文本;@属性名:提取标签指定属性内容,如 href、class;string():合并当前节点下所有嵌套子标签文本,适配多层嵌套标题。
示例代码:
python
运行
# 提取所有文章标题文本 title_list = html.xpath('//li[@class="item"]/a/text()') # 提取所有文章链接 link_list = html.xpath('//li[@class="item"]/a/@href') # 提取发布时间 date_list = html.xpath('//li[@class="item"]/span[@class="date"]/text()') print("标题:",title_list) print("链接:",link_list) print("日期:",date_list)原理剖析
xpath 查询默认返回列表,无匹配数据时返回空列表,不会抛出异常,相较 BS 空标签报错容错性更强。
三、XPath 筛选进阶语法(科普列表爬虫核心)
3.1 属性精准过滤语法
依靠[@属性='属性值']精准筛选带指定属性的节点,过滤广告、无关栏目条目,是科普列表剔除脏数据核心写法。
xpath
//div[@class="article_list"]/li[@class="item"]表达式含义:查找 class 为 article_list 的 div 下,全部 class 等于 item 的 li 条目,精准锁定科普正文条目,过滤页面侧边栏推荐、广告模块。
3.2 模糊属性匹配语法
站点前端 class 命名带随机后缀、动态字段时使用模糊匹配,三种模糊匹配函数:
表格
| 函数 | 功能 | 适用场景 |
|---|---|---|
| contains (@属性,"关键词") | 属性包含指定字符 | class="item item-123" |
| starts-with (@属性,"开头字符") | 属性以指定字符开头 | id="article_001" |
| ends-with (@属性,"结尾字符") | 属性以指定字符结尾 | href=".html" |
示例代码:
python
运行
# 匹配class包含item的li标签 nodes = html.xpath('//li[contains(@class,"item")]')3.3 局部相对路径循环提取
爬虫工程规范:先批量获取所有条目父节点,再循环单节点内部局部 XPath 取值,缩小检索范围、提升解析效率。
python
运行
item_nodes = html.xpath('//li[contains(@class,"item")]') article_data = [] for node in item_nodes: # .代表当前循环li节点,局部路径检索子标签 title = node.xpath('./a/text()') href = node.xpath('./a/@href') pub_date = node.xpath('./span[@class="date"]/text()') desc = node.xpath('./p[@class="desc"]/text()') # 空列表补全默认值 info = { "标题": title[0].strip() if title else "无标题", "链接": href[0] if href else "无效链接", "发布时间": pub_date[0] if pub_date else "未知日期", "文章简介": desc[0].strip() if desc else "暂无简介" } article_data.append(info)代码原理剖析
./a/text()中.锁定当前循环 li 节点,仅在该 li 内部检索 a 标签,杜绝跨条目串取数据,是列表爬虫标准化写法。
3.4 逻辑运算多条件筛选
使用and/or实现多属性联合筛选,剔除推广、付费栏目科普数据:
xpath
//li[contains(@class,"item") and not(contains(@class,"ad"))]表达式释义:筛选 class 包含 item,同时 class 不包含 ad 的 li,直接过滤广告条目。
四、完整单页科普列表爬虫实战
整合 requests 请求、lxml 解析、XPath 字段提取、数据清洗全链路,以科普资讯列表页为目标:
python
运行
import requests from lxml import etree # 全局请求头配置 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", "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } def crawl_single_science_list(url): """单页科普文章采集函数""" art_list = [] try: resp = requests.get(url,headers=HEADERS,timeout=6) resp.encoding = resp.apparent_encoding # 构建etree解析对象 tree = etree.HTML(resp.text) # XPath批量定位所有文章条目 item_arr = tree.xpath('//div[contains(@class,"article_list")]/li[contains(@class,"item") and not(contains(@class,"ad"))]') for item in item_arr: # 逐个字段提取 title_raw = item.xpath('./a[1]/text()') link_raw = item.xpath('./a[1]/@href') date_raw = item.xpath('./span[@class="date"]/text()') intro_raw = item.xpath('./p[@class="desc"]/text()') # 空值处理与数据清洗 art_dict = { "article_title":title_raw[0].strip() if title_raw else "文章标题缺失", "article_link":link_raw[0] if link_raw else "#", "publish_date":date_raw[0].strip() if date_raw else "未标注发布时间", "article_intro":intro_raw[0].strip().replace("\n","") if intro_raw else "简介暂无" } art_list.append(art_dict) return art_list except Exception as e: print(f"页面抓取异常:{str(e)}") return [] # 调用执行 if __name__ == "__main__": target_url = "https://demo-science.com/list.html" res_data = crawl_single_science_list(target_url) print(f"单页采集科普文章总数:{len(res_data)}")代码原理剖析
- XPath 中
a[1]代表取当前节点下第一个 a 标签,规避条目内多超链接造成字段错位; - 所有 xpath 返回列表做三元判空,避免索引越界报错,完善爬虫容错机制;
- replace 替换简介中换行符,统一文本格式,为后续文件存储预处理。
五、多分页科普栏目批量采集实现
绝大多数科普站点文章分页排布,URL 通过页码参数区分列表页,封装循环分页逻辑实现全栏目数据抓取:
python
运行
import time import csv def crawl_page_range(start,end,base_url): all_data = [] for page in range(start,end+1): # 拼接分页URL page_url = base_url.format(page=page) print(f"正在抓取第{page}页科普数据:{page_url}") page_data = crawl_single_science_list(page_url) if not page_data: print("当前页面无数据,终止分页抓取") break all_data.extend(page_data) # 请求延时规避反爬 time.sleep(1) # 数据落地CSV文件 save_to_csv(all_data,"科普文章汇总.csv") print(f"分页抓取完毕,合计采集{len(all_data)}篇科普文章") # CSV存储函数 def save_to_csv(data,save_path): headers = ["article_title","article_link","publish_date","article_intro"] with open(save_path,"w",encoding="utf-8-sig",newline="") as f: writer = csv.DictWriter(f,fieldnames=headers) writer.writeheader() writer.writerows(data) # 启动分页爬虫 if __name__ == "__main__": base = "https://demo-science.com/list?page={page}" crawl_page_range(1,5,base)代码原理剖析
extend()将每页数据并入总列表,全部页面抓取结束一次性写入文件,减少频繁 IO 损耗;utf-8-sig编码保障 Excel 打开 CSV 中文不乱码,newline 参数消除多余空行;- 页面无数据时 break 跳出循环,避免无效循环请求空页码。
六、XPath 高阶用法:轴运算与多节点组合提取
6.1 常用轴函数实战
爬虫常用轴:child 子节点、parent 父节点、following-sibling 同级后续节点,适配非常规标签排布的科普页面:
xpath
# 选取当前a标签同级下一个span(发布时间) ./a/following-sibling::span[@class="date"]适用于标题标签与时间标签无共同父容器、标签散乱排布的页面。
6.2 多字段一次性拼接提取
利用 XPath 字符串运算,单表达式一次性取出多字段,简化代码行数:
python
运行
# 单表达式批量提取标题与链接 res = tree.xpath('//li[contains(@class,"item")]/concat(a/text(),"|",a/@href)')concat () 函数实现字段分隔拼接,返回字符串列表,后续按分隔符切割拆分数据。
七、数据清洗与异常场景处理
7.1 XPath 爬虫高频异常汇总
表格
| 异常类型 | 诱因 | 解决方案 |
|---|---|---|
| 索引 IndexError | xpath 返回空列表直接取 [0] | 三元表达式判空,无数据填充默认字符串 |
| 编码乱码 | 页面编码非 utf-8/gbk | 使用 apparent_encoding 自动识别编码赋值 |
| 解析报错 | HTML 存在畸形非法标签 | lxml 自动容错修复,极少出现解析中断 |
7.2 科普文本专项清洗规则
- 简介字段多余空格、制表符:使用 strip ()+replace ("","") 清理;
- 标题末尾附带特殊标记:正则或字符串替换剔除无用符号;
- 链接为相对路径:拼接站点域名补全绝对 URL。
python
运行
# 相对链接补全域名示例 domain = "https://demo-science.com" raw_link = "/science/1" full_link = domain + raw_link八、XPath 与 BeautifulSoup 解析方案横向对比
结合科普列表爬虫落地场景,两种解析库优劣对比:
表格
| 解析方案 | 优势 | 短板 | 适用场景 |
|---|---|---|---|
| XPath+lxml | 查询效率高,单路径批量取值,复杂层级适配强 | 语法学习成本偏高 | 大批量资讯、科普列表批量采集 |
| BeautifulSoup | 语法贴近 Python 原生,上手简单,可读性高 | 深层嵌套筛选代码冗长 | 单页面少量数据提取、简易爬虫 |
工程选型:海量栏目批量爬虫优先 XPath,小型单页简易采集选用 BeautifulSoup。
九、项目拓展与下一章节预告
9.1 现有项目拓展方向
- 增量爬虫:读取本地 CSV 历史数据,比对文章链接,只抓取新增科普文章;
- 数据入库:将采集数据写入 SQLite/MySQL,实现数据库持久化;
- 代理 IP 接入:高频率采集场景接入代理池,解决 IP 封禁问题。
9.2 下一章技术方向
下一章节围绕正则表达式实战,实现纯字符串层面筛选网页数字、标题字段,脱离 DOM 解析依赖,适配无规范标签的网页数据提取,完善爬虫三大解析技术栈:BS、XPath、正则。
