目录
一、入门准备:3 分钟搭好 BeautifulSoup 环境
1.3 第一个 BeautifulSoup 程序:解析 HTML 字符串
二、核心用法:BeautifulSoup 的 5 种 “找数据” 方式
2.2 方式 2:find () 查找单个标签(按条件筛选)
2.3 方式 3:find_all () 查找多个标签(批量提取)
2.4 方式 4:CSS 选择器(select ())—— 前端开发者最爱
3.1 提取文本:.text vs .string vs .strings
3.2 提取属性:[] vs get ()—— 避免属性不存在报错
3.3 提取 HTML 代码:.prettify () vs .encode ()
四、实战项目:用 BeautifulSoup 爬取静态网页数据
4.1 项目 1:爬取豆瓣 Top250 电影(静态网页经典案例)
五、避坑指南:BeautifulSoup 常见错误及解决方案
5.1 错误 1:解析器报错(如 “Couldn't find a tree builder with the features you requested”)
5.2 错误 2:提取不到数据(find () 返回 None,find_all () 返回空列表)
5.3 错误 3:中文乱码(提取的文本是 “大ä¸å½” 等乱码)
5.4 错误 4:KeyError(提取属性时报错 “KeyError: 'href'”)
5.5 错误 5:.string 返回 None(明明标签有文本)
5.6 错误 6:爬取豆瓣 / 知乎时返回 403(禁止访问)

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
引言:为什么你爬取网页后,却拿不到想要的数据?
如果你用 Python Requests 爬取过网页,大概率遇到过这样的困惑:“明明用response.text拿到了 HTML 代码,却怎么都提取不出里面的电影标题、商品价格?”“HTML 里全是<div> <span>标签,嵌套一层又一层,手动找数据像在‘拆快递盒’?”“复制粘贴 HTML 里的内容太麻烦,想批量提取却不知道从何下手?”

其实,问题的核心不是你不会 “找数据”,而是少了一个 “HTML 拆解工具”——Python BeautifulSoup。
BeautifulSoup 是 Python 解析 HTML/XML 的 “神器”,它能把杂乱的 HTML 代码转换成结构化的对象,让你像 “查字典” 一样轻松提取标签、属性和文本。不管是爬取静态网页的新闻标题、电商商品信息,还是处理本地 HTML 文件,BeautifulSoup 都能帮你把 “复杂的标签嵌套” 变成 “简单的数据提取”。
这篇文章不会讲晦涩的理论,而是从 “零基础能上手” 的角度,带你从环境搭建到实战项目,一步步掌握 BeautifulSoup。每个知识点都配 “完整代码 + 注释 + 运行结果”,你跟着复制粘贴就能跑通,遇到的 “解析器报错”“文本提取混乱” 等问题,也会教你怎么解决 —— 看完这篇,你再也不用对着 HTML 代码 “抓瞎” 了。
一、入门准备:3 分钟搭好 BeautifulSoup 环境

在写第一行解析代码前,咱们先把 “工具” 准备好。BeautifulSoup 本身只是一个解析库,需要搭配 “解析器” 才能工作,这里详细讲环境搭建的每一步,确保你不会卡在 “安装失败” 上。
1.1 先搞懂:BeautifulSoup 和解析器的关系
BeautifulSoup 的核心作用是 “解析 HTML 结构”,但它自己不具备 “解析 HTML 语法” 的能力,需要依赖第三方解析器。就像你有一本英文书(HTML),需要一个翻译(解析器)才能看懂内容。
Python 常用的解析器有 3 种,各有优缺点,初学者优先选lxml(速度快、兼容性好):
| 解析器 | 优点 | 缺点 | 安装命令 |
|---|---|---|---|
| lxml | 速度最快、支持 HTML/XML、容错性强 | 需要额外安装 | pip install lxml |
| html.parser | Python 内置、无需安装 | 速度较慢、容错性一般 | 无需安装(Python 自带) |
| html5lib | 完全符合 HTML5 标准、容错性极强 | 速度最慢 | pip install html5lib |
推荐选择:优先用lxml解析器,安装简单且速度快;如果lxml安装失败(比如 Windows 环境缺少依赖),再用 Python 内置的html.parser(无需安装,直接用)。
1.2 环境搭建步骤(Windows/Mac 通用)
不管你用 Windows 还是 Mac,跟着以下步骤来,保证能顺利搭好环境:
步骤 1:安装 Python(已安装的跳过)
-
Windows 用户:
- 去 Python 官网(https://www.python.org/downloads/)下载 3.8 + 版本,安装时务必勾选 “Add Python to PATH”(重点!避免后续配置环境变量)。
- 按下
Win+R,输入cmd打开命令提示符,输入python --version,如果显示 “Python 3.8.x” 或更高版本,说明安装成功。
-
Mac 用户:
- 打开 “终端”(Launchpad→其他→终端),输入
python3 --version,如果显示版本号(如 3.9.6),说明系统自带 Python3;如果没有,去官网下载安装。 - 安装后,终端输入
python3 --version验证,显示版本号即成功。
- 打开 “终端”(Launchpad→其他→终端),输入
步骤 2:安装 BeautifulSoup4
BeautifulSoup 的最新版本是 BeautifulSoup4(简称 bs4),打开命令行 / 终端,输入对应命令:
- Windows:
pip install beautifulsoup4 - Mac/Linux:
pip3 install beautifulsoup4
验证安装:命令行输入python(Mac 输python3)进入 Python 交互环境,然后输入from bs4 import BeautifulSoup,如果没有报错,说明 BeautifulSoup 安装成功!
步骤 3:安装 lxml 解析器(推荐)
继续在命令行 / 终端输入安装命令:
- Windows:
pip install lxml - Mac/Linux:
pip3 install lxml
验证解析器:在 Python 交互环境中输入以下代码,没有报错即成功:
from bs4 import BeautifulSoup
# 用lxml解析器创建BeautifulSoup对象
soup = BeautifulSoup("<html><body><p>测试</p></body></html>", "lxml")
# 提取p标签的文本
print(soup.p.text) # 输出:测试
小坑提醒:如果lxml安装失败(比如 Windows 提示 “缺少 C++ 编译环境”),不用慌 —— 直接用 Python 内置的html.parser解析器,把代码中的"lxml"换成"html.parser"即可,比如:
soup = BeautifulSoup("<html><p>测试</p></html>", "html.parser")
1.3 第一个 BeautifulSoup 程序:解析 HTML 字符串
环境搭好后,写第一个程序 —— 解析一段简单的 HTML 字符串,提取里面的文本和标签。这个例子能帮你快速理解 BeautifulSoup 的核心用法:
# 1. 导入BeautifulSoup
from bs4 import BeautifulSoup
# 2. 定义要解析的HTML字符串(模拟爬取到的网页内容)
html = """
<html>
<head>
<title>我的第一个HTML页面</title>
</head>
<body>
<div class="content">
<p id="intro">BeautifulSoup真好用!</p>
<a href="https://www.youkuaiyun.com" class="link">访问优快云</a>
<ul>
<li>学习HTML解析</li>
<li>学习数据提取</li>
<li>学习实战项目</li>
</ul>
</div>
</body>
</html>
"""
# 3. 创建BeautifulSoup对象(指定解析器为lxml)
# 语法:BeautifulSoup(要解析的内容, 解析器)
soup = BeautifulSoup(html, "lxml")
# 4. 提取数据
# 4.1 提取title标签的文本
title_text = soup.title.text
print("title标签内容:", title_text) # 输出:title标签内容:我的第一个HTML页面
# 4.2 提取id为intro的p标签文本
intro_text = soup.find("p", id="intro").text
print("p标签内容:", intro_text) # 输出:p标签内容:BeautifulSoup真好用!
# 4.3 提取class为link的a标签的href属性
link_href = soup.find("a", class_="link")["href"] # class是Python关键字,所以用class_
link_text = soup.find("a", class_="link").text
print("a标签文本:", link_text) # 输出:a标签文本:访问优快云
print("a标签链接:", link_href) # 输出:a标签链接:https://www.youkuaiyun.com
# 4.4 提取ul下的所有li标签文本
li_list = soup.find("ul").find_all("li") # 先找ul,再找里面所有li
print("\nul下的li标签内容:")
for li in li_list:
print("-", li.text)
运行结果:
title标签内容: 我的第一个HTML页面
p标签内容: BeautifulSoup真好用!
a标签文本: 访问优快云
a标签链接: https://www.youkuaiyun.com
ul下的li标签内容:
- 学习HTML解析
- 学习数据提取
- 学习实战项目
关键说明:
soup = BeautifulSoup(html, "lxml"):把 HTML 字符串转换成 BeautifulSoup 对象,后续所有操作都基于这个对象;soup.title:直接通过 “标签名” 获取第一个匹配的标签;find():查找第一个符合条件的标签(如按 id、class 筛选);find_all():查找所有符合条件的标签,返回列表;.text:获取标签内的所有文本;["href"]:获取标签的属性值(如 a 标签的 href、img 标签的 src)。
二、核心用法:BeautifulSoup 的 5 种 “找数据” 方式

BeautifulSoup 的核心就是 “查找标签” 和 “提取数据”,最常用的有 5 种方式:直接通过标签名查找、find()查找单个标签、find_all()查找多个标签、CSS 选择器(select())、嵌套查找。掌握这 5 种方式,能解决 90% 的 HTML 解析需求。
2.1 方式 1:直接通过标签名查找(最简单)
如果要找的标签在 HTML 中是 “唯一” 的(比如<title> <head> <body>),可以直接用soup.标签名获取,这是最简单的方式。
示例:解析 HTML 头部信息
from bs4 import BeautifulSoup
html = """
<html>
<head>
<meta charset="UTF-8">
<title>优快云技术博客</title>
<meta name="keywords" content="Python, BeautifulSoup, 爬虫">
</head>
<body>
<h1>欢迎学习BeautifulSoup</h1>
</body>
</html>
"""
soup = BeautifulSoup(html, "lxml")
# 1. 获取title标签(唯一)
title = soup.title
print("title标签对象:", title) # 输出:<title>优快云技术博客</title>
print("title标签文本:", title.text) # 输出:优快云技术博客
# 2. 获取head标签下的第一个meta标签
meta1 = soup.head.meta
print("\n第一个meta标签:", meta1) # 输出:<meta charset="utf-8"/>
print("meta标签的charset属性:", meta1["charset"]) # 输出:utf-8
# 3. 获取body下的h1标签
h1 = soup.body.h1
print("\nh1标签文本:", h1.text) # 输出:欢迎学习BeautifulSoup
注意:如果标签不唯一(比如多个<p> <a>),soup.标签名只会返回 “第一个” 匹配的标签,比如:
html = "<p>第一个p</p><p>第二个p</p>"
soup = BeautifulSoup(html, "lxml")
print(soup.p.text) # 只输出:第一个p(不会返回第二个p)
2.2 方式 2:find () 查找单个标签(按条件筛选)
当标签不唯一时,用find()按条件筛选 “第一个” 符合要求的标签。常用的筛选条件有:标签名、id、class、属性(如 href、src)。
2.2.1 按 id 筛选(最精准)
HTML 中 id 属性是唯一的(一个页面中不会有两个相同 id 的标签),按 id 筛选是最精准的方式,用id="目标id"作为参数:
from bs4 import BeautifulSoup
html = """
<div>
<p id="info1">这是第一个段落</p>
<p id="info2">这是第二个段落</p>
<p>这是第三个段落</p>
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 查找id为info2的p标签
p_info2 = soup.find("p", id="info2")
print("id为info2的p标签:", p_info2) # 输出:<p id="info2">这是第二个段落</p>
print("标签文本:", p_info2.text) # 输出:这是第二个段落
2.2.2 按 class 筛选(常用)
class 属性用于给标签分类(一个页面中可以有多个相同 class 的标签),用class_="目标class"作为参数(注意 class 是 Python 关键字,所以加下划线class_):
from bs4 import BeautifulSoup
html = """
<div class="article">
<h2 class="title">Python BeautifulSoup教程</h2>
<p class="content">这是一篇详细的教程</p>
<p class="content">包含很多示例代码</p>
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 1. 查找class为title的h2标签(第一个符合条件的)
h2_title = soup.find("h2", class_="title")
print("h2标签文本:", h2_title.text) # 输出:Python BeautifulSoup教程
# 2. 查找class为content的p标签(只返回第一个)
p_content = soup.find("p", class_="content")
print("第一个content的p标签:", p_content.text) # 输出:这是一篇详细的教程
2.2.3 按属性筛选(灵活)
如果要按其他属性筛选(如 a 标签的 href、img 标签的 src),用attrs={"属性名": "属性值"}作为参数,适合筛选没有 id 和 class 的标签:
from bs4 import BeautifulSoup
html = """
<div>
<a href="https://www.youkuaiyun.com" target="_blank">优快云官网</a>
<a href="https://www.python.org" target="_self">Python官网</a>
<img src="test.jpg" alt="测试图片" width="200">
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 1. 查找href为"https://www.python.org"的a标签
a_python = soup.find("a", attrs={"href": "https://www.python.org"})
print("Python官网链接文本:", a_python.text) # 输出:Python官网
# 2. 查找alt为"测试图片"的img标签
img_test = soup.find("img", attrs={"alt": "测试图片"})
print("图片的src属性:", img_test["src"]) # 输出:test.jpg
print("图片的宽度:", img_test["width"]) # 输出:200
2.3 方式 3:find_all () 查找多个标签(批量提取)
find_all()和find()用法类似,但会返回 “所有” 符合条件的标签,结果是一个列表。批量提取数据时(如所有 li 标签、所有 a 标签),必须用find_all()。
2.3.1 提取所有相同标签
比如提取页面中所有的<a>标签、<li>标签,直接指定标签名即可:
from bs4 import BeautifulSoup
html = """
<ul class="menu">
<li><a href="/home">首页</a></li>
<li><a href="/article">文章</a></li>
<li><a href="/video">视频</a></li>
<li><a href="/download">下载</a></li>
</ul>
"""
soup = BeautifulSoup(html, "lxml")
# 1. 提取所有a标签(返回列表)
all_a = soup.find_all("a")
print("所有a标签的文本和链接:")
for a in all_a:
text = a.text
href = a["href"]
print(f"- 文本:{text},链接:{href}")
# 2. 提取ul下的所有li标签
all_li = soup.find("ul", class_="menu").find_all("li") # 先找ul,再找里面的li
print("\nul下的所有li标签文本:")
for li in all_li:
# li标签的文本包含a标签的文本,用.text获取所有子标签文本
print("-", li.text)
运行结果:
所有a标签的文本和链接:
- 文本:首页,链接:/home
- 文本:文章,链接:/article
- 文本:视频,链接:/video
- 文本:下载,链接:/download
ul下的所有li标签文本:
- 首页
- 文章
- 视频
- 下载
2.3.2 按 class 批量提取
比如提取所有 class 为 “news-item” 的新闻条目,用class_参数:
from bs4 import BeautifulSoup
html = """
<div class="news-list">
<div class="news-item">
<h3>Python 3.12版本发布</h3>
<p class="time">2024-05-20</p>
</div>
<div class="news-item">
<h3>BeautifulSoup实战技巧</h3>
<p class="time">2024-05-21</p>
</div>
<div class="ad-item">
<h3>广告:Python课程优惠</h3>
</div>
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 提取所有class为news-item的div标签
all_news = soup.find_all("div", class_="news-item")
print(f"共找到{len(all_news)}条新闻:")
for news in all_news:
# 提取新闻标题(h3标签)
title = news.find("h3").text
# 提取新闻时间(p标签,class为time)
time = news.find("p", class_="time").text
print(f"- 标题:{title},时间:{time}")
运行结果:
共找到2条新闻:
- 标题:Python 3.12版本发布,时间:2024-05-20
- 标题:BeautifulSoup实战技巧,时间:2024-05-21
2.3.3 按多个条件筛选
find_all()支持同时按多个条件筛选,比如 “标签名是 a,且 class 是 link,且 href 包含‘csdn’”:
from bs4 import BeautifulSoup
html = """
<a href="https://www.youkuaiyun.com" class="link">优快云官网</a>
<a href="https://www.python.org" class="link">Python官网</a>
<a href="https://blog.youkuaiyun.com" class="blog-link">优快云博客</a>
"""
soup = BeautifulSoup(html, "lxml")
# 筛选条件:a标签 + class是link + href包含"csdn"
# 用attrs传递多个属性,href包含"csdn"用字符串的in判断
all_target_a = []
for a in soup.find_all("a", class_="link"):
if "csdn" in a["href"]:
all_target_a.append(a)
print("符合条件的a标签:")
for a in all_target_a:
print(f"- 文本:{a.text},链接:{a['href']}") # 只输出优快云官网
2.4 方式 4:CSS 选择器(select ())—— 前端开发者最爱
如果你有前端基础(会写 CSS),用select()方法会更顺手。select()支持 CSS 选择器语法(如.class、#id、标签名、父标签>子标签),返回所有符合条件的标签列表。
常用 CSS 选择器语法对照
| CSS 选择器 | 作用 | BeautifulSoup 示例 |
|---|---|---|
#id | 按 id 筛选 | soup.select("#info") |
.class | 按 class 筛选 | soup.select(".content") |
标签名 | 按标签名筛选 | soup.select("a") |
父标签>子标签 | 筛选父标签下的直接子标签 | soup.select("div>p") |
标签1,标签2 | 同时筛选标签 1 和标签 2 | soup.select("h2,p") |
标签[属性=值] | 按属性筛选 | soup.select('a[href="https://youkuaiyun.com"]') |
示例:用 CSS 选择器提取数据
from bs4 import BeautifulSoup
html = """
<div class="container">
<h1 id="title">Python BeautifulSoup教程</h1>
<div class="content">
<p>这是教程的第一部分</p>
<p>这是教程的第二部分</p>
</div>
<div class="links">
<a href="https://www.youkuaiyun.com" class="link">优快云</a>
<a href="https://www.python.org" class="link">Python</a>
</div>
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 1. 按id筛选(#title)
title = soup.select("#title")[0] # select()返回列表,取第一个元素
print("标题:", title.text) # 输出:Python BeautifulSoup教程
# 2. 按class筛选(.content)
content_p = soup.select(".content p") # .content下的所有p标签
print("\n.content下的p标签:")
for p in content_p:
print("-", p.text)
# 3. 父标签>子标签(.links>a)
links_a = soup.select(".links>a") # .links下的直接a子标签
print("\n.links下的a标签:")
for a in links_a:
print(f"- 文本:{a.text},链接:{a['href']}")
# 4. 按属性筛选(a[class="link"])
link_a = soup.select('a[class="link"]')
print("\nclass为link的a标签数量:", len(link_a)) # 输出:2
运行结果:
标题: Python BeautifulSoup教程
.content下的p标签:
- 这是教程的第一部分
- 这是教程的第二部分
.links下的a标签:
- 文本:优快云,链接:https://www.youkuaiyun.com
- 文本:Python,链接:https://www.python.org
class为link的a标签数量: 2
小技巧:如果不确定 CSS 选择器是否正确,可以在 Chrome 浏览器中测试:
- 打开目标网页,按
F12打开开发者工具; - 切换到 “Elements” 标签,按
Ctrl+F(Mac 按Cmd+F); - 输入 CSS 选择器(如
#title、.content),能匹配到标签说明选择器正确。
2.5 方式 5:嵌套查找 —— 处理复杂 HTML 结构
实际网页的 HTML 结构往往很复杂(标签嵌套多层),比如 “div→ul→li→a”,这时候需要 “逐层查找”,也就是嵌套查找。
示例:嵌套查找豆瓣电影列表项
from bs4 import BeautifulSoup
# 模拟豆瓣Top250电影的一条HTML结构
html = """
<div class="item">
<div class="pic">
<em class="title">1</em>
<img src="https://example.com/img1.jpg" alt="肖申克的救赎">
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="title">肖申克的救赎</a>
</div>
<div class="bd">
<div class="star">
<span class="rating_num">9.7</span>
</div>
<p class="year">1994</p>
</div>
</div>
</div>
"""
soup = BeautifulSoup(html, "lxml")
# 嵌套查找步骤:
# 1. 先找到最外层的.item标签
movie_item = soup.find("div", class_="item")
# 2. 在.item下找排名(.pic>.title)
rank = movie_item.find("div", class_="pic").find("em", class_="title").text
print("排名:", rank) # 输出:1
# 3. 在.item下找电影名称(.info>.hd>.title)
movie_title = movie_item.find("div", class_="info").find("div", class_="hd").find("a", class_="title").text
print("电影名称:", movie_title) # 输出:肖申克的救赎
# 4. 在.item下找评分(.info>.bd>.star>.rating_num)
rating = movie_item.find("div", class_="info").find("div", class_="bd").find("div", class_="star").find("span", class_="rating_num").text
print("评分:", rating) # 输出:9.7
# 5. 在.item下找上映年份(.info>.bd>.year)
year = movie_item.find("div", class_="info").find("div", class_="bd").find("p", class_="year").text
print("上映年份:", year) # 输出:1994
关键思路:复杂结构不要 “一步到位”,而是 “逐层缩小范围”—— 先找到最外层的父标签,再在父标签内找子标签,这样能避免提取到其他无关标签的数据。
三、数据提取细节:文本、属性、HTML 的正确获取方式

找到标签后,需要提取里面的数据(文本、属性、HTML 代码),这部分有很多细节容易踩坑,比如.text和.string的区别、属性不存在时的报错处理,这里详细讲清楚。
3.1 提取文本:.text vs .string vs .strings
BeautifulSoup 提供 3 种提取文本的方式,适用场景不同,用错了会导致提取不到数据或数据混乱。
| 方法 | 作用 | 适用场景 |
|---|---|---|
.text | 获取标签内所有子标签的文本,合并成字符串 | 标签包含嵌套子标签(如 div 包含 p、a) |
.string | 只获取标签直系的文本,如果有子标签返回 None | 标签没有子标签(纯文本标签) |
.strings | 获取标签内所有文本,返回生成器(逐个输出) | 需要逐个处理子标签文本 |
示例:3 种文本提取方式对比
from bs4 import BeautifulSoup
html = """
<div class="test">
这是外层文本
<p>这是p标签的文本</p>
<a href="#">这是a标签的文本</a>
</div>
"""
soup = BeautifulSoup(html, "lxml")
test_div = soup.find("div", class_="test")
# 1. .text:获取所有文本,合并成一个字符串
print(".text提取结果:")
print(test_div.text.strip()) # 输出:这是外层文本 这是p标签的文本 这是a标签的文本
# strip()用于去除前后空白(换行、空格)
# 2. .string:有子标签,返回None
print("\n.string提取结果:")
print(test_div.string) # 输出:None
# 3. .strings:返回生成器,逐个输出文本
print("\n.strings提取结果:")
for text in test_div.strings:
clean_text = text.strip()
if clean_text: # 过滤空文本(换行、空格)
print("-", clean_text)
运行结果:
.text提取结果:
这是外层文本 这是p标签的文本 这是a标签的文本
.string提取结果:
None
.strings提取结果:
- 这是外层文本
- 这是p标签的文本
- 这是a标签的文本
实战建议:
- 90% 的场景用
.text,尤其是需要合并所有文本时(如提取新闻内容、商品描述); - 只有标签是 “纯文本无嵌套” 时(如
<span>9.7</span>),才用.string; - 需要逐个处理子标签文本时(如分别提取标题、时间、作者),用
.strings。
3.2 提取属性:[] vs get ()—— 避免属性不存在报错
提取标签的属性(如 a 标签的 href、img 标签的 src)时,常用两种方式:标签["属性名"]和标签.get("属性名"),区别在于 “属性不存在时的处理”。
示例:两种属性提取方式对比
from bs4 import BeautifulSoup
html = """
<a href="https://www.youkuaiyun.com" class="link">优快云官网</a>
<a class="link">无链接的a标签</a>
<img src="test.jpg" alt="测试图片">
"""
soup = BeautifulSoup(html, "lxml")
# 1. 用["属性名"]提取(属性不存在会报错)
a1 = soup.find("a", href="https://www.youkuaiyun.com")
print("a1的href:", a1["href"]) # 输出:https://www.youkuaiyun.com
a2 = soup.find("a", class_="link", href=None) # 找没有href的a标签
# print(a2["href"]) # 报错:KeyError: 'href'(因为a2没有href属性)
# 2. 用get()提取(属性不存在返回None,不会报错)
print("a2的href:", a2.get("href")) # 输出:None
# 可以指定默认值(属性不存在时返回默认值)
print("a2的href(默认值):", a2.get("href", "无链接")) # 输出:无链接
# 3. 提取img标签的属性
img = soup.find("img")
print("\nimg的src:", img.get("src")) # 输出:test.jpg
print("img的width(默认值):", img.get("width", "100")) # 输出:100(img没有width属性)
实战建议:优先用get()提取属性,尤其是属性可能不存在的场景(如部分 a 标签没有 href、部分 img 标签没有 alt),避免代码报错崩溃。
3.3 提取 HTML 代码:.prettify () vs .encode ()
有时候需要提取标签内的 HTML 代码(比如保存网页片段),用.prettify()或直接转换为字符串。
示例:提取 HTML 代码
from bs4 import BeautifulSoup
html = """
<div class="content">
<p>这是<p>嵌套</p>的p标签</p>
<a href="#">链接</a>
</div>
"""
soup = BeautifulSoup(html, "lxml")
content_div = soup.find("div", class_="content")
# 1. 提取原始HTML字符串
raw_html = str(content_div)
print("原始HTML:")
print(raw_html)
# 2. 提取格式化的HTML(缩进对齐,更易读)
prettified_html = content_div.prettify()
print("\n格式化HTML:")
print(prettified_html)
# 3. 提取标签内的所有HTML(包括子标签)
inner_html = "".join([str(child) for child in content_div.children])
print("\n标签内的HTML:")
print(inner_html)
运行结果(格式化 HTML 部分):
格式化HTML:
<div class="content">
<p>
这是
<p>
嵌套
</p>
的p标签
</p>
<a href="#">
链接
</a>
</div>
适用场景:
- 保存网页片段到文件时,用
.prettify()格式化后更易读; - 需要进一步处理 HTML 代码时,用
str(标签)获取原始字符串。
四、实战项目:用 BeautifulSoup 爬取静态网页数据

学完基础用法后,做两个实战项目 —— 爬取 “豆瓣 Top250 电影” 和 “优快云 博客文章列表”,完整覆盖 “Requests 爬取 HTML + BeautifulSoup 解析 + 数据保存” 的全流程,你跟着跑通就能掌握实际应用。
4.1 项目 1:爬取豆瓣 Top250 电影(静态网页经典案例)
豆瓣 Top250 电影页面(https://movie.douban.com/top250)是静态网页,数据直接包含在 HTML 中,适合练手。目标是提取 “排名、电影名称、评分、上映年份、导演、主演”,并保存到 CSV 文件。
步骤 1:分析页面结构
- 打开豆瓣 Top250 页面,按
F12打开开发者工具; - 找到电影列表的外层标签:每个电影项是
<div class="item">; - 在
.item标签内,找到各数据对应的标签:- 排名:
<em class="title">; - 电影名称:
<span class="title">(第一个); - 评分:
<span class="rating_num">; - 上映年份:
<div class="bd">下的<p>标签,文本中包含年份(如 “1994”); - 导演 / 主演:
<div class="bd">下的第一个<p>标签,文本中包含 “导演:姓名”“主演:姓名”。
- 排名:
步骤 2:完整代码实现
import requests
from bs4 import BeautifulSoup
import csv
import time
def get_douban_movie(page):
"""
爬取豆瓣Top250第page页的电影数据
page:页码(0~9,共10页,每页25部电影)
"""
# 1. 定义请求参数
url = "https://movie.douban.com/top250"
params = {
"start": page * 25, # 起始位置:0→第1页,25→第2页,...,225→第10页
"filter": ""
}
# 设置请求头,模拟浏览器(否则豆瓣会返回403)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Referer": "https://www.douban.com/"
}
# 2. 用Requests爬取HTML
try:
response = requests.get(url, params=params, headers=headers, timeout=10)
if response.status_code != 200:
print(f"第{page+1}页请求失败,状态码:{response.status_code}")
return []
html = response.text
except Exception as e:
print(f"第{page+1}页爬取出错:{e}")
return []
# 3. 用BeautifulSoup解析HTML
soup = BeautifulSoup(html, "lxml")
# 找到所有电影项(.item)
movie_items = soup.find_all("div", class_="item")
movies = []
for item in movie_items:
# 3.1 提取排名
rank = item.find("em", class_="title").text
# 3.2 提取电影名称(第一个.title的文本,排除外文名称)
title_tag = item.find("span", class_="title")
title = title_tag.text if title_tag else "未知名称"
# 3.3 提取评分
rating_tag = item.find("span", class_="rating_num")
rating = rating_tag.text if rating_tag else "0.0"
# 3.4 提取上映年份(从.bd下的p标签文本中提取,如"1994")
bd_p = item.find("div", class_="bd").find("p")
year = "未知年份"
if bd_p:
p_text = bd_p.text.strip()
# 从文本中提取4位数字(年份)
for part in p_text.split():
if part.isdigit() and len(part) == 4:
year = part
break
# 3.5 提取导演和主演(从.bd下的p标签文本中提取)
director = "未知导演"
actor = "未知主演"
if bd_p:
p_text = bd_p.text.strip()
# 导演部分:"导演: 弗兰克·德拉邦特 Frank Darabont"
if "导演:" in p_text:
director_part = p_text.split("导演:")[1].split("主演:")[0].strip()
director = director_part.split()[0] # 只取中文导演名
# 主演部分:"主演: 蒂姆·罗宾斯 Tim Robbins, 摩根·弗里曼 Morgan Freeman"
if "主演:" in p_text:
actor_part = p_text.split("主演:")[1].strip()
actor = actor_part.split(",")[0].split()[0] # 只取第一个主演
# 3.6 保存到字典
movies.append({
"排名": rank,
"电影名称": title,
"评分": rating,
"上映年份": year,
"导演": director,
"主演": actor
})
print(f"第{page+1}页爬取完成,共{len(movies)}部电影")
return movies
# -------------- 主程序:爬取所有10页并保存到CSV --------------
if __name__ == "__main__":
# 爬取第1~10页(page从0到9)
all_movies = []
for page in range(10):
movies = get_douban_movie(page)
all_movies.extend(movies)
# 控制请求频率,避免被封IP(每爬一页休息2秒)
time.sleep(2)
# 保存到CSV文件
csv_file = "douban_top250_movies.csv"
# 用utf-8-sig编码,避免Excel打开时中文乱码
with open(csv_file, "w", encoding="utf-8-sig", newline="") as f:
# 定义CSV表头
fieldnames = ["排名", "电影名称", "评分", "上映年份", "导演", "主演"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() # 写入表头
writer.writerows(all_movies) # 写入数据
print(f"\n全部爬取完成!共{len(all_movies)}部电影,已保存到{csv_file}")
步骤 3:运行结果
- 运行代码后,会生成
douban_top250_movies.csv文件; - 用 Excel 打开文件,能看到 250 部电影的完整数据,示例如下:
| 排名 | 电影名称 | 评分 | 上映年份 | 导演 | 主演 |
|---|---|---|---|---|---|
| 1 | 肖申克的救赎 | 9.7 | 1994 | 弗兰克・德拉邦特 | 蒂姆・罗宾斯 |
| 2 | 霸王别姬 | 9.6 | 1993 | 陈凯歌 | 张国荣 |
| 3 | 阿甘正传 | 9.5 | 1994 | 罗伯特・泽米吉斯 | 汤姆・汉克斯 |
4.2 项目 2:解析本地 HTML 文件(离线数据处理)
有时候你已经保存了网页的 HTML 文件(比如用浏览器 “另存为” 保存的页面),需要离线提取数据,这时候用 BeautifulSoup 直接解析本地文件即可,无需联网。
步骤 1:准备本地 HTML 文件
- 打开任意网页(如 优快云 博客首页),按
Ctrl+S(Mac 按Cmd+S)保存网页,选择 “网页,仅 HTML”; - 假设保存的文件名为
csdn_blog.html,放在代码同一文件夹下。
步骤 2:完整代码实现(提取博客标题和链接)
from bs4 import BeautifulSoup
def parse_local_html(file_path):
"""
解析本地HTML文件,提取优快云博客的标题和链接
file_path:本地HTML文件路径
"""
# 1. 读取本地HTML文件
try:
# 用utf-8编码打开,避免中文乱码
with open(file_path, "r", encoding="utf-8") as f:
html = f.read()
except FileNotFoundError:
print(f"错误:文件{file_path}不存在")
return []
except Exception as e:
print(f"读取文件出错:{e}")
return []
# 2. 用BeautifulSoup解析HTML
soup = BeautifulSoup(html, "lxml")
# 分析优快云博客首页结构:博客标题在<a class="blog-title" ...>标签中
blog_links = soup.find_all("a", class_="blog-title")
blogs = []
for link in blog_links:
# 提取标题(.text)
title = link.text.strip()
# 提取链接(get("href"))
href = link.get("href", "无链接")
# 过滤空标题
if title:
blogs.append({
"博客标题": title,
"链接": href
})
return blogs
# -------------- 主程序:解析本地文件并打印结果 --------------
if __name__ == "__main__":
# 本地HTML文件路径(替换成你的文件路径)
html_file = "csdn_blog.html"
blogs = parse_local_html(html_file)
if blogs:
print(f"共提取到{len(blogs)}篇博客:")
for i, blog in enumerate(blogs, 1):
print(f"{i}. 标题:{blog['博客标题']}")
print(f" 链接:{blog['链接']}")
print("-" * 80)
else:
print("未提取到博客数据")
步骤 3:运行结果
共提取到15篇博客:
1. 标题:Python Requests爬虫实战:爬取知乎回答
链接:https://blog.youkuaiyun.com/xxx/article/details/xxx
--------------------------------------------------------------------------------
2. 标题:BeautifulSoup解析HTML的5种方法
链接:https://blog.youkuaiyun.com/xxx/article/details/xxx
--------------------------------------------------------------------------------
...
实战技巧:如果解析后发现提取不到数据,可能是 HTML 文件中标签的 class 或结构与网页不同(浏览器保存的 HTML 可能包含额外代码),需要重新用开发者工具查看本地 HTML 的标签结构,调整筛选条件。
五、避坑指南:BeautifulSoup 常见错误及解决方案

在实际解析中,你可能会遇到各种错误,这里总结 6 个最常见的问题及解决方案,帮你快速排查。
5.1 错误 1:解析器报错(如 “Couldn't find a tree builder with the features you requested”)
现象:运行代码时提示 “Couldn't find a tree builder with the features you requested: lxml. Do you need to install a parser library?”原因:没有安装指定的解析器(比如指定了 “lxml”,但没安装 lxml 库)。解决方案:
- 安装对应的解析器:
pip install lxml(如果是 html5lib,用pip install html5lib); - 改用 Python 内置的解析器:把
BeautifulSoup(html, "lxml")换成BeautifulSoup(html, "html.parser")(无需安装)。
5.2 错误 2:提取不到数据(find () 返回 None,find_all () 返回空列表)
现象:明明 HTML 中有目标标签,却提取不到,find()返回 None,find_all()返回空列表。常见原因及解决方案:
-
标签名或属性写错(比如把 “div” 写成 “divv”,“class_” 写成 “class”):
- 检查标签名、class、id 是否与 HTML 完全一致(区分大小写,HTML 标签不区分,但解析器可能区分);
- 用 Chrome 开发者工具复制标签的 class 或 id,避免手动输入错误。
-
标签嵌套层级错误(比如找 “div>p”,但实际是 “div>span>p”):
- 用
soup.prettify()打印格式化的 HTML,查看标签的实际嵌套层级; - 先找外层父标签,再在父标签内找子标签(嵌套查找)。
- 用
-
动态加载的内容(BeautifulSoup 只能解析静态 HTML,动态 JS 加载的内容不在 HTML 中):
- 用 Requests 爬取的 HTML 中没有目标标签,说明数据是 JS 动态加载的;
- 解决方案:用 Selenium 或 Playwright 模拟浏览器运行 JS,获取动态加载后的 HTML。
5.3 错误 3:中文乱码(提取的文本是 “大ä¸å½” 等乱码)
现象:提取的中文文本显示为乱码(如 “大ä¸å½”)。原因:HTML 的编码与 BeautifulSoup 的解码方式不一致(比如 HTML 是 GBK 编码,BeautifulSoup 用 UTF-8 解码)。解决方案:
- 在用 Requests 爬取时,先设置正确的编码:
response = requests.get(url) response.encoding = response.apparent_encoding # 自动检测编码 html = response.text - 解析本地 HTML 文件时,用正确的编码打开:
with open("test.html", "r", encoding="gbk") as f: # 如果是GBK编码,指定encoding="gbk" html = f.read()
5.4 错误 4:KeyError(提取属性时报错 “KeyError: 'href'”)
现象:用标签["属性名"]提取属性时,报错 “KeyError: 'href'” 或 “KeyError: 'src'”。原因:部分标签没有该属性(比如有的 a 标签没有 href,有的 img 标签没有 src)。解决方案:
- 用
get()方法提取属性,属性不存在时返回 None 或默认值:href = a.get("href") # 不存在返回None href = a.get("href", "无链接") # 不存在返回“无链接” - 提取前先判断属性是否存在:
if "href" in a.attrs: href = a["href"] else: href = "无链接"
5.5 错误 5:.string 返回 None(明明标签有文本)
现象:标签内有文本,但标签.string返回 None。原因:标签包含子标签,.string只返回 “无嵌套子标签” 的文本,有子标签时返回 None。解决方案:
- 用
.text获取所有子标签的文本(90% 的场景用这个):text = 标签.text.strip() - 如果需要获取直系文本(排除子标签文本),用
.next_sibling或.previous_sibling(适合复杂嵌套):# 示例:<div>外层文本<span>子标签</span></div> div_tag = soup.find("div") # 获取“外层文本” direct_text = div_tag.contents[0].strip() # contents返回子节点列表,第一个是外层文本
5.6 错误 6:爬取豆瓣 / 知乎时返回 403(禁止访问)
现象:用 Requests 爬取豆瓣、知乎等网站时,返回 403 状态码,提示 “Forbidden”。原因:网站识别你是爬虫,拒绝提供服务(缺少请求头,尤其是 User-Agent)。解决方案:
- 设置完整的请求头,模拟浏览器:
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", "Referer": "https://www.douban.com/" # 模拟从豆瓣首页跳转 } response = requests.get(url, headers=headers) - 如果还是 403,添加 Cookie(从浏览器复制登录后的 Cookie):
headers["Cookie"] = "你的豆瓣Cookie" # 从Chrome开发者工具复制 - 控制请求频率,每爬一页休息 2~5 秒,避免被封 IP。
六、总结与进阶学习建议
6.1 本文核心知识点总结
- 环境搭建:安装 BeautifulSoup4 和解析器(优先 lxml,备选 html.parser);
- 核心查找方法:5 种查找标签的方式(标签名、find ()、find_all ()、CSS 选择器、嵌套查找);
- 数据提取:文本提取(.text 为主)、属性提取(get () 为主)、HTML 提取(str ()/.prettify ());
- 实战能力:能结合 Requests 爬取静态网页,用 BeautifulSoup 解析并保存数据(如 CSV);
- 避坑技巧:解决解析器报错、数据提取不到、中文乱码、403 禁止访问等常见问题。
6.2 进阶学习方向
- 处理动态网页:BeautifulSoup 只能解析静态 HTML,动态 JS 加载的数据需要学Selenium或Playwright,模拟浏览器运行 JS 获取完整 HTML;
- 复杂数据清洗:提取到的文本可能包含空白、特殊字符,需要学re(正则表达式) 清理数据(如提取手机号、邮箱、日期);
- 数据存储:除了 CSV,还可以学MySQL或MongoDB,将解析后的数据保存到数据库,方便后续查询和分析;
- 批量解析与并发:学多线程 / 多进程,提高批量解析 HTML 文件的效率;学Scrapy 框架,实现高并发的网页爬取与解析;
- CSS 选择器进阶:深入学习 CSS 选择器(如伪类选择器
:nth-child()、属性包含选择器[href*="csdn"]),提高标签查找效率。
6.3 学习建议
- 多练实战:从简单的静态网页开始(如豆瓣 Top250、博客园文章),逐步挑战复杂结构的网页,不要只看理论;
- 善用工具:熟练使用 Chrome 开发者工具(查看 HTML 结构、复制 CSS 选择器),这是解析 HTML 的 “利器”;
- 查看文档:遇到问题先查 BeautifulSoup 官方文档(https://www.crummy.com/software/BeautifulSoup/bs4/doc/),文档中有更详细的方法说明和示例;
- 记录问题:把遇到的错误(如解析器报错、数据提取不到)和解决方案记录下来,形成自己的 “避坑手册”,下次遇到类似问题能快速解决。
最后,BeautifulSoup 的核心是 “解析 HTML 结构,提取目标数据”,它本身不难,难的是 “分析网页结构” 和 “解决反爬问题”。只要多动手、多分析,你很快就能熟练掌握,让它成为你爬取静态网页数据的 “得力助手”!

1万+

被折叠的 条评论
为什么被折叠?



