Python BeautifulSoup 实战指南:从 HTML 解析到数据提取,大叔手把手教你不踩坑

目录

引言:为什么你爬取网页后,却拿不到想要的数据?

一、入门准备:3 分钟搭好 BeautifulSoup 环境

1.1 先搞懂:BeautifulSoup 和解析器的关系

1.2 环境搭建步骤(Windows/Mac 通用)

1.3 第一个 BeautifulSoup 程序:解析 HTML 字符串

二、核心用法:BeautifulSoup 的 5 种 “找数据” 方式

2.1 方式 1:直接通过标签名查找(最简单)

2.2 方式 2:find () 查找单个标签(按条件筛选)

2.3 方式 3:find_all () 查找多个标签(批量提取)

2.4 方式 4:CSS 选择器(select ())—— 前端开发者最爱

2.5 方式 5:嵌套查找 —— 处理复杂 HTML 结构

三、数据提取细节:文本、属性、HTML 的正确获取方式

3.1 提取文本:.text vs .string vs .strings

3.2 提取属性:[] vs get ()—— 避免属性不存在报错

3.3 提取 HTML 代码:.prettify () vs .encode ()

四、实战项目:用 BeautifulSoup 爬取静态网页数据

4.1 项目 1:爬取豆瓣 Top250 电影(静态网页经典案例)

4.2 项目 2:解析本地 HTML 文件(离线数据处理)

五、避坑指南: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(禁止访问)

六、总结与进阶学习建议

6.1 本文核心知识点总结

6.2 进阶学习方向

6.3 学习建议

 


 

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.parserPython 内置、无需安装速度较慢、容错性一般无需安装(Python 自带)
html5lib完全符合 HTML5 标准、容错性极强速度最慢pip install html5lib

推荐选择:优先用lxml解析器,安装简单且速度快;如果lxml安装失败(比如 Windows 环境缺少依赖),再用 Python 内置的html.parser(无需安装,直接用)。

1.2 环境搭建步骤(Windows/Mac 通用)

不管你用 Windows 还是 Mac,跟着以下步骤来,保证能顺利搭好环境:

步骤 1:安装 Python(已安装的跳过)

  • Windows 用户

    1. 去 Python 官网(https://www.python.org/downloads/)下载 3.8 + 版本,安装时务必勾选 “Add Python to PATH”(重点!避免后续配置环境变量)。
    2. 按下Win+R,输入cmd打开命令提示符,输入python --version,如果显示 “Python 3.8.x” 或更高版本,说明安装成功。
  • Mac 用户

    1. 打开 “终端”(Launchpad→其他→终端),输入python3 --version,如果显示版本号(如 3.9.6),说明系统自带 Python3;如果没有,去官网下载安装。
    2. 安装后,终端输入python3 --version验证,显示版本号即成功。

步骤 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 和标签 2soup.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 浏览器中测试:

  1. 打开目标网页,按F12打开开发者工具;
  2. 切换到 “Elements” 标签,按Ctrl+F(Mac 按Cmd+F);
  3. 输入 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:分析页面结构

  1. 打开豆瓣 Top250 页面,按F12打开开发者工具;
  2. 找到电影列表的外层标签:每个电影项是<div class="item">
  3. .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:运行结果

  1. 运行代码后,会生成douban_top250_movies.csv文件;
  2. 用 Excel 打开文件,能看到 250 部电影的完整数据,示例如下:
排名电影名称评分上映年份导演主演
1肖申克的救赎9.71994弗兰克・德拉邦特蒂姆・罗宾斯
2霸王别姬9.61993陈凯歌张国荣
3阿甘正传9.51994罗伯特・泽米吉斯汤姆・汉克斯

4.2 项目 2:解析本地 HTML 文件(离线数据处理)

有时候你已经保存了网页的 HTML 文件(比如用浏览器 “另存为” 保存的页面),需要离线提取数据,这时候用 BeautifulSoup 直接解析本地文件即可,无需联网。

步骤 1:准备本地 HTML 文件

  1. 打开任意网页(如 优快云 博客首页),按Ctrl+S(Mac 按Cmd+S)保存网页,选择 “网页,仅 HTML”;
  2. 假设保存的文件名为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 库)。解决方案

  1. 安装对应的解析器:pip install lxml(如果是 html5lib,用pip install html5lib);
  2. 改用 Python 内置的解析器:把BeautifulSoup(html, "lxml")换成BeautifulSoup(html, "html.parser")(无需安装)。

5.2 错误 2:提取不到数据(find () 返回 None,find_all () 返回空列表)

现象:明明 HTML 中有目标标签,却提取不到,find()返回 None,find_all()返回空列表。常见原因及解决方案

  1. 标签名或属性写错(比如把 “div” 写成 “divv”,“class_” 写成 “class”):

    • 检查标签名、class、id 是否与 HTML 完全一致(区分大小写,HTML 标签不区分,但解析器可能区分);
    • 用 Chrome 开发者工具复制标签的 class 或 id,避免手动输入错误。
  2. 标签嵌套层级错误(比如找 “div>p”,但实际是 “div>span>p”):

    • soup.prettify()打印格式化的 HTML,查看标签的实际嵌套层级;
    • 先找外层父标签,再在父标签内找子标签(嵌套查找)。
  3. 动态加载的内容(BeautifulSoup 只能解析静态 HTML,动态 JS 加载的内容不在 HTML 中):

    • 用 Requests 爬取的 HTML 中没有目标标签,说明数据是 JS 动态加载的;
    • 解决方案:用 Selenium 或 Playwright 模拟浏览器运行 JS,获取动态加载后的 HTML。

5.3 错误 3:中文乱码(提取的文本是 “大中国” 等乱码)

现象:提取的中文文本显示为乱码(如 “大中国”)。原因:HTML 的编码与 BeautifulSoup 的解码方式不一致(比如 HTML 是 GBK 编码,BeautifulSoup 用 UTF-8 解码)。解决方案

  1. 在用 Requests 爬取时,先设置正确的编码:
    response = requests.get(url)
    response.encoding = response.apparent_encoding  # 自动检测编码
    html = response.text
    
  2. 解析本地 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)。解决方案

  1. get()方法提取属性,属性不存在时返回 None 或默认值:
    href = a.get("href")  # 不存在返回None
    href = a.get("href", "无链接")  # 不存在返回“无链接”
    
  2. 提取前先判断属性是否存在:
    if "href" in a.attrs:
        href = a["href"]
    else:
        href = "无链接"
    

5.5 错误 5:.string 返回 None(明明标签有文本)

现象:标签内有文本,但标签.string返回 None。原因:标签包含子标签,.string只返回 “无嵌套子标签” 的文本,有子标签时返回 None。解决方案

  1. .text获取所有子标签的文本(90% 的场景用这个):
    text = 标签.text.strip()
    
  2. 如果需要获取直系文本(排除子标签文本),用.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)。解决方案

  1. 设置完整的请求头,模拟浏览器:
    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)
    
  2. 如果还是 403,添加 Cookie(从浏览器复制登录后的 Cookie):
    headers["Cookie"] = "你的豆瓣Cookie"  # 从Chrome开发者工具复制
    
  3. 控制请求频率,每爬一页休息 2~5 秒,避免被封 IP。

六、总结与进阶学习建议

6.1 本文核心知识点总结

  1. 环境搭建:安装 BeautifulSoup4 和解析器(优先 lxml,备选 html.parser);
  2. 核心查找方法:5 种查找标签的方式(标签名、find ()、find_all ()、CSS 选择器、嵌套查找);
  3. 数据提取:文本提取(.text 为主)、属性提取(get () 为主)、HTML 提取(str ()/.prettify ());
  4. 实战能力:能结合 Requests 爬取静态网页,用 BeautifulSoup 解析并保存数据(如 CSV);
  5. 避坑技巧:解决解析器报错、数据提取不到、中文乱码、403 禁止访问等常见问题。

6.2 进阶学习方向

  1. 处理动态网页:BeautifulSoup 只能解析静态 HTML,动态 JS 加载的数据需要学SeleniumPlaywright,模拟浏览器运行 JS 获取完整 HTML;
  2. 复杂数据清洗:提取到的文本可能包含空白、特殊字符,需要学re(正则表达式) 清理数据(如提取手机号、邮箱、日期);
  3. 数据存储:除了 CSV,还可以学MySQLMongoDB,将解析后的数据保存到数据库,方便后续查询和分析;
  4. 批量解析与并发:学多线程 / 多进程,提高批量解析 HTML 文件的效率;学Scrapy 框架,实现高并发的网页爬取与解析;
  5. CSS 选择器进阶:深入学习 CSS 选择器(如伪类选择器:nth-child()、属性包含选择器[href*="csdn"]),提高标签查找效率。

6.3 学习建议

  1. 多练实战:从简单的静态网页开始(如豆瓣 Top250、博客园文章),逐步挑战复杂结构的网页,不要只看理论;
  2. 善用工具:熟练使用 Chrome 开发者工具(查看 HTML 结构、复制 CSS 选择器),这是解析 HTML 的 “利器”;
  3. 查看文档:遇到问题先查 BeautifulSoup 官方文档(https://www.crummy.com/software/BeautifulSoup/bs4/doc/),文档中有更详细的方法说明和示例;
  4. 记录问题:把遇到的错误(如解析器报错、数据提取不到)和解决方案记录下来,形成自己的 “避坑手册”,下次遇到类似问题能快速解决。

最后,BeautifulSoup 的核心是 “解析 HTML 结构,提取目标数据”,它本身不难,难的是 “分析网页结构” 和 “解决反爬问题”。只要多动手、多分析,你很快就能熟练掌握,让它成为你爬取静态网页数据的 “得力助手”!

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值