项目三 动态网页数据解析
用 XPath(lxml)与 BeautifulSoup 从 HTML 中精准提取标题、时间、来源、正文。
本项目学习目标
- 能看懂 HTML 结构与节点关系
- 能用 XPath 做层级、属性、位置、文本定位
- 能用 BeautifulSoup 的 find / select 提取内容
- 能把提取结果整理成字典或列表
本项目任务导览
动态网页数据解析
使用 XPath 解析"新闻动态"、使用 Beautiful Soup 解析"浪潮云说",掌握从 HTML 文档中精准提取数据的能力。
- 项目导言与学习目标
- 任务 3-1 使用 XPath 解析"新闻动态"
- 任务 3-2 使用 Beautiful Soup 解析"浪潮云说"
- 知识拓展与实例案例(3 例)
采集到网页源码之后,还需要从 HTML 中提取出真正需要的数据。
利用 Requests / Urllib 向目标网页发起请求,获取包含数据的 HTML 源码,这是数据采集的第一步。
在获取的 HTML 中,利用 XPath、Beautiful Soup 等解析技术定位并提取目标数据,这是本项目重点解决的问题。
掌握 XPath 的基本语法与元素定位方法; 掌握 lxml 库的安装与使用; 掌握 Beautiful Soup 解析库的安装与常见解析器; 掌握 Beautiful Soup 的标签选择器、方法选择器与 CSS 选择器。
能够使用 XPath 从 HTML 中提取元素与属性; 能够使用 Beautiful Soup 完成网页数据的精准解析; 能够综合使用解析技术完成数据提取任务。
任务 3-1 使用 XPath 解析"新闻动态"
XPath 是一门在 XML 文档中查找信息的语言,同样适用于 HTML 文档。
一、XPath 简介
XPath(XML Path Language)是一门在 XML/HTML 文档中查找信息的语言,通过路径表达式选取节点或节点集,是网页数据解析的常用技术。
- 打开 cmd 命令窗口;
- 执行 pip install lxml 命令安装 lxml 库;
- 等待依赖包下载安装完成;
- 在 Python 中通过 import lxml 验证安装。
二、etree 常用方法
lxml.etree 提供了对 HTML/XML 文档进行解析的多种方法。
| 方法 | 说明 |
|---|---|
| HTML() | 解析 HTML 字符串,返回 Element 对象。参数:text(HTML 文本)、parser(解析器)、base_url(基准 URL) |
| XML() | 解析 XML 字符串,返回 Element 对象 |
| parse() | 从文件解析 HTML/XML 文档 |
| tostring() | 将 Element 对象序列化为字符串 |
| xpath() | 在元素上执行 XPath 表达式,返回匹配结果 |
| 参数 | 说明 |
|---|---|
| text | 要解析的 HTML 文本 |
| parser | 指定解析器,可选 XMLParser、XMLPullParser、HTMLParser、HTMLPullParser |
| base_url | 基准 URL,用于解析相对路径 |
三、元素定位
| 符号 | 含义 |
|---|---|
| / | 从根节点选取 |
| // | 从任意位置选取匹配的节点 |
| . | 选取当前节点 |
| .. | 选取当前节点的父节点 |
| | | 选取多个路径的并集 |
| * | 匹配任何元素节点 |
| nodeName[n] | 选取第 n 个子节点 |
| last() | 选取最后一个节点 |
| position()<n | 选取位置小于 n 的节点 |
| node() | 匹配任何类型的节点 |
| 表达式 | 含义 |
|---|---|
| //div | 选取文档中所有 div 元素 |
| //div/p/a[2] | 选取所有 div 下 p 下第 2 个 a 元素 |
| //div[@id='a'] | 选取 id 为 a 的 div 元素 |
| //p/text() | 选取所有 p 元素的文本内容 |
四、属性定位
| 写法 | 含义 |
|---|---|
| //div/@属性名 | 选取 div 元素指定属性(如 href、class、id)的值 |
| //div/@* | 选取 div 元素的所有属性 |
| //div[@属性='值'] | 选取具有指定属性值的 div 元素(条件定位) |
五、运算符
- = 等于
- != 不等于
- < 小于
- <= 小于等于
- > 大于
- >= 大于等于
- and 与
- or 或
- not() 非
- + 加
- - 减
- * 乘
- div 除
- mod 取模
六、数据提取
| 提取目标 | 表达式 |
|---|---|
| 属性值提取 | nodeName/@属性名,如 //a/@href |
| 元素内容提取 | nodeName/text(),如 //h3/text() |
# XPath 解析代码示例
from lxml import etree
html = etree.HTML(page_source)
# 提取所有标题文本
titles = html.xpath('//h3/text()')
# 提取所有链接地址
links = html.xpath('//a/@href')七、任务实施(共 7 步)
- 采集"新闻动态"页面 HTML 源码;
- 分析页面结构,确定新闻标题、链接等数据的位置;
- 安装 lxml 库并导入 etree;
- 使用 etree.HTML() 解析页面源码;
- 编写 XPath 表达式提取新闻数据;
- 使用循环遍历提取全部新闻条目;
- 运行程序,输出并验证解析结果。
任务 3-2 使用 Beautiful Soup 解析"浪潮云说"
Beautiful Soup 是 Python 最流行的 HTML/XML 解析库,语法简单、功能强大。
一、安装与常见解析器
pip install beautifulsoup4| 解析器 | 使用方法 | 优势 | 劣势 |
|---|---|---|---|
| Python 标准库 | BeautifulSoup(markup, "html.parser") | 内置标准库、执行速度适中、容错能力强 | 较老版本的容错能力稍差 |
| lxml HTML | BeautifulSoup(markup, "lxml") | 速度快、文档容错能力强 | 需要安装 C 语言库 |
| lxml XML | BeautifulSoup(markup, "lxml-xml") | 速度快、唯一支持 XML 的解析器 | 需要安装 C 语言库 |
| html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性、以浏览器方式解析 | 速度慢、需要额外依赖 |
| 参数 | 说明 |
|---|---|
| markup | 要解析的 HTML/XML 文档(字符串或文件对象) |
| parser | 指定使用的解析器(如 "html.parser"、"lxml") |
二、标签选择器
通过 soup.标签名 直接选取标签,语法为 soup.name.node.parameter。
| 属性 | 说明 |
|---|---|
| name | 标签的名称 |
| attrs['属性'] | 标签的指定属性值,如 attrs['class']、attrs['href'] |
| string | 标签内的文本内容 |
| 属性 | 说明 |
|---|---|
| contents | 直接子节点列表 |
| children | 直接子节点生成器 |
| descendants | 所有后代节点生成器 |
| parent | 父节点 |
| parents | 所有祖先节点生成器 |
| next_sibling | 下一个兄弟节点 |
| previous_sibling | 上一个兄弟节点 |
# 标签选择器示例
from bs4 import BeautifulSoup
soup = BeautifulSoup(page_source, "html.parser")
# 获取第一个 title 标签的文本
print(soup.title.string)
# 获取第一个 a 标签的 href 属性
print(soup.a.attrs['href'])三、方法选择器
| 方法 | 说明 |
|---|---|
| find() | 查找符合条件的第一个节点,返回 Tag 对象或 None |
| find_all() | 查找所有符合条件的节点,返回列表。支持 name、attrs、string、limit 等参数 |
# 方法选择器示例
# 查找所有 div 标签
divs = soup.find_all('div')
# 查找 class 为 title 的第一个标签
title = soup.find(class_='title')
# 查找 id 为 main 的标签
main = soup.find(id='main')四、CSS 选择器
通过 select() 方法使用 CSS 语法选择元素,返回列表。
| 选择器写法 | 含义 |
|---|---|
| select('title') | 选取所有 title 标签 |
| select('.sister') | 选取所有 class 为 sister 的元素 |
| select('#link') | 选取 id 为 link 的元素 |
| select('div p') | 选取 div 内的所有 p 元素(后代选择器) |
| select('div > p') | 选取 div 的直接子 p 元素(子选择器) |
| select('a[href]') | 选取带有 href 属性的 a 元素(属性选择器) |
# CSS 选择器示例
items = soup.select('.news-item a')
for a in items:
print(a.get('href'), a.string)五、任务实施(共 6 步)
- 采集"浪潮云说"页面 HTML 源码;
- 安装并导入 beautifulsoup4;
- 创建 BeautifulSoup 对象并选择解析器;
- 分析页面结构,使用标签选择器 / 方法选择器定位数据;
- 使用 CSS 选择器补充提取复杂结构中的数据;
- 运行程序,输出并验证解析结果。
知识拓展与实例案例
课堂讲解用:3 个可运行实例 + 知识拓展卡片。
知识拓展
实例 1:XPath 提取新闻列表(标题 + 链接)
目标:解析一段新闻列表 HTML,用 XPath 批量提取标题和链接地址。
# xpath_news.py
from lxml import etree
html = """
<div class="news-list">
<a href="/news/1.html">学校召开教学改革会议</a>
<a href="/news/2.html">运动会圆满落幕</a>
<a href="/news/3.html">新图书馆正式开放</a>
</div>
"""
root = etree.HTML(html)
# 提取所有标题文本
titles = root.xpath('//div[@class="news-list"]/a/text()')
# 提取所有链接
links = root.xpath('//div[@class="news-list"]/a/@href')
for t, l in zip(titles, links):
print(t, "→", l)学校召开教学改革会议 → /news/1.html
运动会圆满落幕 → /news/2.html
新图书馆正式开放 → /news/3.html| 代码 | 白话解释 |
|---|---|
| from lxml import etree | 从 lxml 库导入 etree 模块——它负责把 HTML 文本变成一棵"节点树",这样就能用路径查找了。 |
| html = """ ... """ | 三引号表示多行字符串:把一段 HTML 代码原样存进变量 html。实际项目中这里是 requests 抓回来的网页源码。 |
| root = etree.HTML(html) | 让 etree 把 HTML 文本解析成一棵"树",root 是树的根,后面所有查找都从它开始。 |
| //div[@class="news-list"]/a/text() | XPath 路径://div 任意位置的 div;[@class="news-list"] 它的 class 属性等于 news-list;/a 它下面的 a 标签;text() 取文本内容。合起来:找到新闻列表里的所有链接文字。 |
| root.xpath('.../a/@href') | 同上,但 @href 表示取 href 属性的值(链接地址),注意 @ 是"取属性"的固定写法。 |
| for t, l in zip(titles, links): | zip 把两个列表"按位置配对":第一条标题配第一条链接。循环依次取出每一对并打印。 |
实例 2:Beautiful Soup 提取表格数据
目标:解析一个课程表 HTML 表格,用 find_all 提取表头和每一行数据。
# bs_table.py
from bs4 import BeautifulSoup
html = """
<table>
<tr><th>课程</th><th>时间</th><th>教室</th></tr>
<tr><td>Python</td><td>周一 1-2 节</td><td>A101</td></tr>
<tr><td>数据库</td><td>周三 3-4 节</td><td>B203</td></tr>
</table>
"""
soup = BeautifulSoup(html, "html.parser")
# 提取表头
headers = [th.get_text() for th in soup.find_all("th")]
print("表头:", headers)
# 提取每一行
for tr in soup.find_all("tr")[1:]: # 跳过表头行
cells = [td.get_text() for td in tr.find_all("td")]
print("行:", cells)表头: ['课程', '时间', '教室']
行: ['Python', '周一 1-2 节', 'A101']
行: ['数据库', '周三 3-4 节', 'B203']| 代码 | 白话解释 |
|---|---|
| from bs4 import BeautifulSoup | 从 bs4 库导入 BeautifulSoup 类——它是整个解析工作的"入口工具"。 |
| soup = BeautifulSoup(html, "html.parser") | 用 html.parser 解析器把 HTML 文本解析成 BeautifulSoup 对象 soup,之后所有查找都调用 soup 的方法。 |
| soup.find_all("th") | 找出页面中所有 th 标签(表头单元格),返回一个列表。find_all 意为"找出所有",返回的是列表。 |
| th.get_text() | 取出 th 标签里面的文字内容。get_text() 会把标签内所有文本提取出来,去掉尖括号。 |
| for tr in soup.find_all("tr")[1:]: | 找到所有 tr 标签(表格行),[1:] 表示"跳过第一行"(第一行是表头),从第二行开始逐行处理。 |
| cells = [td.get_text() for td in tr.find_all("td")] | 列表推导式:把当前行里每个 td 标签(普通单元格)的文本取出来,组成一个新列表 cells。可以理解成"把这一行的所有单元格文字装进一个列表"。 |
实例 3:CSS 选择器批量提取图片地址
目标:用 select() 按 CSS 选择器提取所有商品图片的 src 属性,并拼接完整 URL。
# bs_css.py
from bs4 import BeautifulSoup
html = """
<div class="goods">
<img class="pic" src="/img/1.jpg" alt="手机">
<img class="pic" src="/img/2.jpg" alt="耳机">
<img class="pic" src="/img/3.jpg" alt="手表">
</div>
"""
soup = BeautifulSoup(html, "html.parser")
base = "https://www.example.com"
# CSS 选择器:类名 .pic,属性选择器 img[class=pic] 也等价
for img in soup.select(".goods img.pic"):
full_url = base + img["src"] # 相对路径 → 完整 URL
print(img["alt"], "→", full_url)手机 → https://www.example.com/img/1.jpg
耳机 → https://www.example.com/img/2.jpg
手表 → https://www.example.com/img/3.jpg| 代码 | 白话解释 |
|---|---|
| base = "https://www.example.com" | 定义网站根地址。网页里的图片常用相对路径(如 /img/1.jpg),需要拼上根地址才能访问。 |
| soup.select(".goods img.pic") | select() 使用 CSS 选择器查找:.goods 表示 class 为 goods 的元素,空格表示"里面的",img.pic 表示 img 标签且 class 含 pic。合起来:goods 区域里所有 class 为 pic 的图片。 |
| for img in soup.select(...): | 对找到的每个 img 标签循环处理。 |
| img["src"] | 取出该 img 标签的 src 属性值(图片地址)。注意字典式的中括号取值语法。 |
| full_url = base + img["src"] | 把根地址和相对路径拼接成完整 URL,如 https://www.example.com/img/1.jpg。 |
| print(img["alt"], "→", full_url) | 打印图片的 alt 文字(替代文本)和完整地址,便于人工核对抓取是否正确。 |