🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [📌 上期回顾](#📌 上期回顾)
- [🎯 本节目标](#🎯 本节目标)
- [一、HTML 解析库对比](#一、HTML 解析库对比)
-
- [1.1 常用解析库](#1.1 常用解析库)
- [1.2 为什么选择 BeautifulSoup?](#1.2 为什么选择 BeautifulSoup?)
- [二、BeautifulSoup 基础用法](#二、BeautifulSoup 基础用法)
-
- [2.1 创建解析对象](#2.1 创建解析对象)
- [2.2 解析器对比](#2.2 解析器对比)
- 三、元素查找方法
-
- [3.1 基础查找方法](#3.1 基础查找方法)
- [3.2 CSS 选择器(推荐)](#3.2 CSS 选择器(推荐))
- [3.3 选择器速查表](#3.3 选择器速查表)
- 四、提取数据
-
- [4.1 提取文本内容](#4.1 提取文本内容)
- [4.2 提取属性](#4.2 提取属性)
- [4.3 提取链接(URL)](#4.3 提取链接(URL))
- 五、处理复杂结构
-
- [5.1 遍历列表项](#5.1 遍历列表项)
- [5.2 处理嵌套结构](#5.2 处理嵌套结构)
- [5.3 提取表格数据](#5.3 提取表格数据)
- 六、通用解析器设计
-
- [6.1 配置化解析器](#6.1 配置化解析器)
- [6.2 批量解析列表](#6.2 批量解析列表)
- 七、常见问题与技巧
-
- [7.1 处理不存在的元素](#7.1 处理不存在的元素)
- [7.2 去除多余的空白](#7.2 去除多余的空白)
- [7.3 处理 HTML 实体](#7.3 处理 HTML 实体)
- 八、本节小结
- 九、课后作业(必做,验收进入下一节)
-
- [任务1:CSS 选择器练习](#任务1:CSS 选择器练习)
- 任务2:编写配不同网站编写统一的配置
- 任务3:真实网站解析
- [🔮 下期预告](#🔮 下期预告)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》
订阅后更新会优先推送,按目录学习更高效~
📌 上期回顾
在第三章 | Requests 静态爬取入门《限速与礼貌爬取:并发、延迟、频率控制!》中,我们完成了两段式采集的完整流程,并学习了三种数据存储方案。现在,你已经能够采集数据并保存了。
但是,如何从混乱的 HTML 中精准提取想要的字段?这是爬虫开发中最核心、最耗时的环节!
这一节,我们将系统学习 BeautifulSoup------Python 最流行的 HTML 解析库。掌握它,你就能从任何网页中提取结构化数据!
🎯 本节目标
通过本节学习,你将能够:
- 理解 HTML 解析的原理和常用库对比
- 掌握 BeautifulSoup 的核心用法
- 使用 CSS 选择器精准定位元素
- 处理复杂的嵌套结构和列表数据
- 提取文本、属性、链接等各类信息
- 交付验收:编写一个通用的网页解析器类
一、HTML 解析库对比
1.1 常用解析库
| 库 | 速度 | 易用性 | 功能 | 推荐场景 |
|---|---|---|---|---|
| BeautifulSoup | ⭐⭐ 中等 | ⭐⭐⭐ 简单 | ⭐⭐⭐ 丰富 | 初学者、复杂解析 |
| lxml | ⭐⭐⭐ 快 | ⭐⭐ 中等 | ⭐⭐⭐ 强大 | 大规模数据、XPath |
| pyquery | ⭐⭐ 中等 | ⭐⭐⭐ jQuery风格 | ⭐⭐ 中等 | 熟悉jQuery的开发者 |
| 正则表达式 | ⭐⭐⭐ 最快 | ⭐ 困难 | ⭐ 有限 | 简单模式匹配 |
1.2 为什么选择 BeautifulSoup?
优势:
- ✅ 语法简单,学习曲线平缓
- ✅ 容错能力强(处理不规范的HTML)
- ✅ 支持多种解析器(html.parser、lxml、html5lib)
- ✅ 文档完善,社区活跃
安装:
bash
# 安装 BeautifulSoup4
pip install beautifulsoup4
# 安装 lxml 解析器(推荐,速度快)
pip install lxml
# 可选:安装 html5lib(容错能力最强)
pip install html5lib
二、BeautifulSoup 基础用法
2.1 创建解析对象
python
from bs4 import BeautifulSoup
# HTML 示例
html_doc = """
<html>
<head><title>新闻网站</title></head>
<body>
<div class="news-list">
<div class="news-item" id="news-1">
<h3><a href="/news/123">重大科技突破</a></h3>
<span class="author">张三</span>
<span class="time">2025-01-21</span>
<p class="summary">这是摘要内容...</p>
</div>
<div class="news-item" id="news-2">
<h3><a href="/news/124">经济政策解读</a></h3>
<span class="author">李四</span>
<span class="time">2025-01-20</span>
<p class="summary">这是另一条摘要...</p>
</div>
</div>
</body>
</html>
"""
# 创建 BeautifulSoup 对象
soup = BeautifulSoup(html_doc, 'lxml')
# 参数说明:
# - html_doc: HTML 字符串
# - 'lxml': 解析器选择(推荐 lxml,速度快)
print(soup.prettify()) # 格式化输出
2.2 解析器对比
python
# 1. html.parser(Python 内置,无需安装)
soup = BeautifulSoup(html_doc, 'html.parser')
# 2. lxml(推荐,速度最快)
soup = BeautifulSoup(html_doc, 'lxml')
# 3. html5lib(容错能力最强,但慢)
soup = BeautifulSoup(html_doc, 'html5lib')
# 推荐选择:
# - 一般情况用 lxml
# - HTML 格式混乱时用 html5lib
三、元素查找方法
3.1 基础查找方法
python
# 1. find() - 查找第一个匹配的元素
title = soup.find('title')
print(title) # <title>新闻网站</title>
print(title.text) # 新闻网站
# 2. find_all() - 查找所有匹配的元素
news_items = soup.find_all('div', class_='news-item')
print(f"找到 {len(news_items)} 个新闻") # 找到 2 个新闻
# 3. 按类名查找
authors = soup.find_all('span', class_='author')
for author in authors:
print(author.text) # 张三, 李四
# 4. 按 ID 查找
news_1 = soup.find('div', id='news-1')
print(news_1)
# 5. 按属性查找
links = soup.find_all('a', href=True) # 查找所有有 href 属性的 a 标签
for link in links:
print(link['href']) # /news/123, /news/124
3.2 CSS 选择器(推荐)
python
# select() - 使用 CSS 选择器(最灵活)
# 1. 标签选择器
titles = soup.select('h3')
print(len(titles)) # 2
# 2. 类选择器
news_items = soup.select('.news-item')
print(len(news_items)) # 2
# 3. ID 选择器
news_1 = soup.select('#news-1')
print(news_1)
# 4. 组合选择器
# 选择 .news-item 下的 h3 标签
titles = soup.select('.news-item h3')
# 选择 .news-item 下的 a 标签
links = soup.select('.news-item a')
# 5. 属性选择器
# 选择有 href 属性的 a 标签
links = soup.select('a[href]')
# 选择 href 以 /news/ 开头的 a 标签
links = soup.select('a[href^="/news/"]')
# 6. 伪类选择器
# 选择第一个 .news-item
first_item = soup.select('.news-item:first-child')
# 选择第 n 个元素
second_item = soup.select('.news-item:nth-of-type(2)')
# 7. 直接子元素
# 选择 .news-list 的直接子元素 div
items = soup.select('.news-list > div')
3.3 选择器速查表
python
# CSS 选择器完整示例
class SelectorExamples:
"""CSS 选择器示例集合"""
@staticmethod
def demo(soup):
"""演示各种选择器"""
# 基础选择器
print("=== 基础选择器 ===")
print("标签:", soup.select('h3'))
print("类名:", soup.select('.author'))
print("ID:", soup.select('#news-1'))
# 组合选择器
print("\n=== 组合选择器 ===")
print("后代:", soup.select('div h3')) # div 下所有 h3
print("子元素:", soup.select('div > h3')) # div 的直接子元素 h3
print("相邻:", soup.select('h3 + span')) # h3 后紧邻的 span
# 属性选择器
print("\n=== 属性选择器 ===")
print("存在属性:", soup.select('a[href]'))
print("属性值:", soup.select('a[href="/news/123"]'))
print("开头匹配:", soup.select('a[href^="/news"]'))
print("结尾匹配:", soup.select('a[href$=".html"]'))
print("包含匹配:", soup.select('a[href*="news"]'))
# 伪类选择器
print("\n=== 伪类选择器 ===")
print("第一个:", soup.select('.news-item:first-of-type'))
print("最后一个:", soup.select('.news-item:last-of-type'))
print("第 n 个:", soup.select('.news-item:nth-of-type(2)'))
print("包含文本:", soup.select('h3:contains("科技")')) # 注意:需要 lxml
# 使用
SelectorExamples.demo(soup)
四、提取数据
4.1 提取文本内容
python
# 1. .text 或 .get_text() - 获取纯文本
title = soup.find('h3')
print(title.text) # 重大科技突破
print(title.get_text()) # 同上
# 2. .string - 获取直接文本(不含子标签)
# 如果标签只有一个字符串子节点,返回该字符串
# 否则返回 None
link = soup.find('a')
print(link.string) # 重大科技突破
# 3. .stripped_strings - 去除空白后的所有字符串(生成器)
div = soup.find('div', class_='news-item')
texts = list(div.stripped_strings)
print(texts) # ['重大科技突破', '张三', '2025-01-21', '这是摘要内容...']
# 4. 自定义分隔符
div = soup.find('div', class_='news-item')
text = div.get_text(separator=' | ', strip=True)
print(text) # 重大科技突破 | 张三 | 2025-01-21 | 这是摘要内容...
4.2 提取属性
python
# 1. 字典方式访问属性
link = soup.find('a')
print(link['href']) # /news/123
print(link['class']) # 返回列表(class 可以有多个值)
# 2. .get() 方法(安全,不存在返回 None)
print(link.get('href')) # /news/123
print(link.get('title')) # None
print(link.get('title', '默认值')) # 默认值
# 3. .attrs - 获取所有属性(字典)
print(link.attrs) # {'href': '/news/123'}
# 4. 判断属性是否存在
if link.has_attr('href'):
print("有 href 属性")
4.3 提取链接(URL)
python
from urllib.parse import urljoin
def extract_links(soup, base_url):
"""
提取页面中的所有链接
Args:
soup: BeautifulSoup 对象
base_url: 基础 URL(用于拼接相对路径)
Returns:
list: 完整 URL 列表
"""
links = []
for link in soup.select('a[href]'):
href = link.get('href')
# 跳过锚点和 JavaScript
if href.startswith('#') or href.startswith('javascript:'):
continue
# 拼接完整 URL
full_url = urljoin(base_url, href)
links.append({
'url': full_url,
'text': link.get_text(strip=True),
'title': link.get('title', '')
})
return links
# 使用
base_url = "https://example.com/"
links = extract_links(soup, base_url)
for link in links:
print(f"{link['text']}: {link['url']}")
五、处理复杂结构
5.1 遍历列表项
python
def parse_news_list(html):
"""
解析新闻列表
Returns:
list: 新闻字典列表
"""
soup = BeautifulSoup(html, 'lxml')
news_list = []
# 找到所有新闻项
items = soup.select('.news-item')
for item in items:
# 提取各字段
news = {}
# 标题和链接
title_elem = item.select_one('h3 a')
if title_elem:
news['title'] = title_elem.text.strip()
news['url'] = title_elem.get('href')
# 作者
author_elem = item.select_one('.author')
news['author'] = author_elem.text.strip() if author_elem else ''
# 时间
time_elem = item.select_one('.time')
news['publish_time'] = time_elem.text.strip() if time_elem else ''
# 摘要
summary_elem = item.select_one('.summary')
news['summary'] = summary_elem.text.strip() if summary_elem else ''
news_list.append(news)
return news_list
# 使用
news_list = parse_news_list(html_doc)
for news in news_list:
print(news)
5.2 处理嵌套结构
python
# HTML 示例(嵌套结构)
nested_html = """
<div class="article">
<h1>文章标题</h1>
<div class="meta">
<span class="author">作者:<strong>张三</strong></span>
<span class="category">分类:
<a href="/tech">科技</a>
<a href="/ai">人工智能</a>
</span>
</div>
<div class="content">
<p>第一段内容...</p>
<p>第二段内容...</p>
<ul>
<li>要点1</li>
<li>要点2</li>
</ul>
</div>
</div>
"""
def parse_nested_article(html):
"""解析嵌套的文章结构"""
soup = BeautifulSoup(html, 'lxml')
article = {}
# 标题
title = soup.select_one('h1')
article['title'] = title.text.strip() if title else ''
# 作者(嵌套在 strong 标签中)
author = soup.select_one('.author strong')
article['author'] = author.text.strip() if author else ''
# 分类(多个链接)
categories = soup.select('.category a')
article['categories'] = [cat.text.strip() for cat in categories]
# 正文段落
paragraphs = soup.select('.content p')
article['paragraphs'] = [p.text.strip() for p in paragraphs]
# 要点列表
points = soup.select('.content ul li')
article['key_points'] = [li.text.strip() for li in points]
return article
# 使用
article = parse_nested_article(nested_html)
print(article)
# {
# 'title': '文章标题',
# 'author': '张三',
# 'categories': ['科技', '人工智能'],
# 'paragraphs': ['第一段内容...', '第二段内容...'],
# 'key_points': ['要点1', '要点2']
# }
5.3 提取表格数据
python
# HTML 表格示例
table_html = """
<table class="data-table">
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>职位</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>28</td>
<td>工程师</td>
</tr>
<tr>
<td>李四</td>
<td>32</td>
<td>设计师</td>
</tr>
</tbody>
</table>
"""
def parse_table(html):
"""
解析 HTML 表格
Returns:
list: 字典列表(每行一个字典)
"""
soup = BeautifulSoup(html, 'lxml')
# 提取表头
headers = []
header_row = soup.select_one('thead tr')
if header_row:
headers = [th.text.strip() for th in header_row.select('th')]
# 提取数据行
rows = []
for tr in soup.select('tbody tr'):
cells = [td.text.strip() for td in tr.select('td')]
# 组合成字典
if len(cells) == len(headers):
row_dict = dict(zip(headers, cells))
rows.append(row_dict)
return rows
# 使用
table_data = parse_table(table_html)
print(table_data)
# [
# {'姓名': '张三', '年龄': '28', '职位': '工程师'},
# {'姓名': '李四', '年龄': '32', '职位': '设计师'}
# ]
六、通用解析器设计
6.1 配置化解析器
python
"""
通用 HTML 解析器
基于配置的选择器来提取字段
"""
class ConfigurableParser:
"""配置化的 HTML 解析器"""
def __init__(self, config):
"""
初始化
Args:
config: 字段配置字典
{
'field_name': {
'selector': 'CSS选择器',
'attr': '属性名'(可选,默认提取文本),
'multiple': True/False(可选,是否返回列表)
}
}
"""
self.config = config
def parse(self, html):
"""
解析 HTML
Args:
html: HTML 字符串
Returns:
dict: 提取的字段
"""
soup = BeautifulSoup(html, 'lxml')
result = {}
for field_name, field_config in self.config.items():
result[field_name] = self._extract_field(soup, field_config)
return result
def _extract_field(self, soup, config):
"""提取单个字段"""
selector = config['selector']
attr = config.get('attr')
multiple = config.get('multiple', False)
if multiple:
# 提取多个元素
elements = soup.select(selector)
if attr:
return [elem.get(attr, '') for elem in elements]
else:
return [elem.get_text(strip=True) for elem in elements]
else:
# 提取单个元素
elem = soup.select_one(selector)
if not elem:
return '' if not attr else None
if attr:
return elem.get(attr, '')
else:
return elem.get_text(strip=True)
# ========== 使用示例 ==========
# 定义配置
news_config = {
'title': {
'selector': 'h3 a',
'attr': None # 提取文本
},
'url': {
'selector': 'h3 a',
'attr': 'href' # 提取 href 属性
},
'author': {
'selector': '.author'
},
'publish_time': {
'selector': '.time'
},
'summary': {
'selector': '.summary'
},
'tags': {
'selector': '.tag',
'multiple': True # 返回列表
}
}
# 创建解析器
parser = ConfigurableParser(news_config)
# 解析 HTML
html = """
<div class="news-item">
<h3><a href="/news/123">重大科技突破</a></h3>
<span class="author">张三</span>
<span class="time">2025-01-21</span>
<p class="summary">这是摘要...</p>
<span class="tag">科技</span>
<span class="tag">创新</span>
</div>
"""
result = parser.parse(html)
print(result)
# {
# 'title': '重大科技突破',
# 'url': '/news/123',
# 'author': '张三',
# 'publish_time': '2025-01-21',
# 'summary': '这是摘要...',
# 'tags': ['科技', '创新']
# }
6.2 批量解析列表
python
class ListParser(ConfigurableParser):
"""列表解析器(继承配置化解析器)"""
def __init__(self, item_selector, field_config):
"""
初始化
Args:
item_selector: 列表项的选择器
field_config: 字段配置
"""
super().__init__(field_config)
self.item_selector = item_selector
def parse_list(self, html):
"""
解析列表页
Returns:
list: 解析后的列表项
"""
soup = BeautifulSoup(html, 'lxml')
items = soup.select(self.item_selector)
result_list = []
for item in items:
# 将每个 item 转为字符串,用父类的 parse 方法
item_html = str(item)
item_data = self.parse(item_html)
result_list.append(item_data)
return result_list
# 使用
list_parser = ListParser(
item_selector='.news-item',
field_config=news_config
)
html_list = """
<div class="news-list">
<div class="news-item">
<h3><a href="/news/1">新闻1</a></h3>
<span class="author">张三</span>
<span class="time">2025-01-21</span>
</div>
<div class="news-item">
<h3><a href="/news/2">新闻2</a></h3>
<span class="author">李四</span>
<span class="time">2025-01-20</span>
</div>
</div>
"""
news_list = list_parser.parse_list(html_list)
for news in news_list:
print(news)
七、常见问题与技巧
7.1 处理不存在的元素
python
def safe_extract(soup, selector, attr=None, default=''):
"""
安全提取(避免 None 导致程序崩溃)
Args:
soup: BeautifulSoup 对象
selector: CSS 选择器
attr: 属性名(可选)
default: 默认值
Returns:
提取的值或默认值
"""
elem = soup.select_one(selector)
if not elem:
return default
if attr:
return elem.get(attr, default)
else:
return elem.get_text(strip=True) or default
# 使用
title = safe_extract(soup, 'h1.title', default='无标题')
url = safe_extract(soup, 'a.link', attr='href', default='#')
7.2 去除多余的空白
python
import re
def clean_whitespace(text):
"""清理文本中的多余空白"""
if not text:
return ''
# 替换连续空白为单个空格
text = re.sub(r'\s+', ' ', text)
# 去除首尾空白
text = text.strip()
return text
# 使用
raw_text = soup.get_text()
clean_text = clean_whitespace(raw_text)
7.3 处理 HTML 实体
python
from html import unescape
text = "这是<示例>文本 & 符号"
decoded = unescape(text)
print(decoded) # 这是<示例>文本 & 符号
八、本节小结
本节我们系统学习了 BeautifulSoup 的核心用法:
✅ 基础操作 :创建对象、选择解析器
✅ 元素查找 :find、find_all、CSS 选择器
✅ 数据提取 :文本、属性、链接
✅ 复杂结构 :嵌套、列表、表格
✅ 通用解析器 :配置化设计,提高复用性
✅ 最佳实践:安全提取、清理文本、处理异常
核心原则:
- 优先使用 CSS 选择器:灵活、简洁、易维护
- 安全提取:始终检查元素是否存在
- 配置化设计:用配置文件管理选择器
- 测试驱动:边写边测,确保选择器正确
九、课后作业(必做,验收进入下一节)
任务1:CSS 选择器练习
给定一个新闻网站的 HTML,编写选择器提取:
- 所有新闻标题
- 所有作者名(可能在不同位置)
- 所有分类标签
- 评论数(数字)
- 发布时间(多种格式)
任务2:编写配不同网站编写统一的配置
- 新闻网站(标题、作者、时间、正文)
- 电商网站(商品名、价格、评分、销量)
- 招聘网站(职位、公司、薪资、要求)
要求:只修改配置文件,代码完全复用。
任务3:真实网站解析
选择一个真实的新闻网站:
- 分析列表页和详情页的结构
- 编写完整的解析器类
- 测试至少 10 条数据
- 处理所有可能的异常情况
验收方式:在留言区提交:
- CSS 选择器练习的答案和测试截图
- 三种网站的配置文件和测试结果
- 真实网站解析器代码和数据样例
- 遇到的问题和解决方法
🔮 下期预告
下一节《XPath 进阶:更强大的定位语法》,我们将学习:
- XPath 与 CSS 选择器的对比
- XPath 的轴(axis)概念
- 复杂条件的组合查询
- lxml 库的高级用法
- 何时选择 XPath,何时选择 CSS
预习建议 :
了解 XPath 的基本语法,安装 lxml 库。思考:CSS 选择器无法表达的查询需求有哪些?
💬 BeautifulSoup 是解析的瑞士军刀!掌握它,数据唾手可得! 🎯✨
记住:好的选择器就像好的地址------精准、简洁、不易变化。选择器是爬虫的核心资产,值得你花时间优化!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。