Python网络爬虫(二) - 解析静态网页
文章目录
- 一、网页解析技术介绍
- 二、Beautiful Soup库
-
- 1. Beautiful Soup库介绍
- 2. Beautiful Soup库几种解析器比较
- 3. 安装Beautiful Soup库
-
- 3.1 安装 Beautiful Soup 4
- 3.2 安装解析器
- 4. Beautiful Soup使用步骤
- 三、实战:解析豆瓣读书中小说的网页
-
- 1. 提取网页数据
- 2. 保存数据到csv文件
一、网页解析技术介绍
网页解析技术是爬虫获取数据的核心环节,指从网页的HTML/XML等源代码中提取目标信息(如文本、链接、图片、表格数据等)的过程。
二、Beautiful Soup库
1. Beautiful Soup库介绍
Beautiful Soup 是 Python 中一款功能强大的 HTML/XML 解析库,它能够将复杂的网页源代码转换为结构化的树形文档(Parse Tree),并提供简单直观的 API 供开发者遍历、搜索和提取其中的元素与数据。
其核心优势在于:
- 强大的容错能力:能够处理不规范的 HTML 代码(如标签未闭合、属性缺失、嵌套混乱等),自动修复语法错误,生成可解析的结构,非常适合爬取实际场景中格式混乱的网页。
- 直观的操作方式:支持通过标签名、类名(
class
)、ID、属性等多种方式定位元素,语法贴近自然语言,即使是非前端开发背景的开发者也能快速上手。 - 灵活的解析支持:可搭配不同的解析器(如 Python 标准库的
html.parser
、lxml
、html5lib
等),兼顾解析速度、兼容性和功能需求。 - 丰富的功能扩展:提供遍历文档树、搜索文档树、修改文档结构等功能,不仅能提取数据,还能对网页内容进行二次处理。
Beautiful Soup 广泛应用于网络爬虫、数据挖掘、网页内容分析等场景,是 Python 爬虫生态中处理网页解析的核心工具之一。
2. Beautiful Soup库几种解析器比较
Beautiful Soup 本身不直接解析网页,而是依赖第三方解析器完成 HTML/XML 的解析工作。不同解析器在速度、容错性和功能上存在差异,选择合适的解析器能提升解析效率和兼容性。
以下是常用解析器的对比:
lxml
库lxml
库(C 语言编写,安装可能依赖系统环境)lxml
库lxml
库,仅适用于 XML 格式文档html5lib
库html5lib
库3. 安装Beautiful Soup库
Beautiful Soup 库的安装过程简单,支持通过 Python 包管理工具 pip
快速安装,同时需根据需求安装对应的解析器。
3.1 安装 Beautiful Soup 4
Beautiful Soup 目前最新稳定版本为 Beautiful Soup 4(简称 bs4
),安装命令如下:
pip install beautifulsoup4 -i https://mirrors.aliyun.com/pypi/simple/
3.2 安装解析器
根据前文的解析器对比,推荐安装 lxml
解析器(兼顾速度和容错性):
pip install lxml -i https://mirrors.aliyun.com/pypi/simple/
若需要 html5lib
解析器(用于极端不规范网页),安装命令如下:
pip install html5lib -i https://mirrors.aliyun.com/pypi/simple/
4. Beautiful Soup使用步骤
使用 Beautiful Soup 解析网页的核心流程为:创建Beautiful Soup对象 --> 获取标签 --> 提取数据。
4.1 创建Beautiful Soup对象
创建Beautiful Soup对象是使用该库解析网页的第一步,通过将网页内容(HTML/XML)和解析器传入BeautifulSoup
类,生成一个可操作的树形文档对象。这个对象封装了网页的所有元素和结构,提供了丰富的方法用于遍历、搜索和提取数据。
基本语法:
from bs4 import BeautifulSoup# 创建Beautiful Soup对象soup = BeautifulSoup(markup, features, **kwargs)
Beautiful Soup类构造方法的参数说明:
markup
response.text
(网络响应文本)、open(\"page.html\")
(本地文件对象)features
markup
内容\"lxml\"
、\"html.parser\"
、\"html5lib\"
builder
lxml.html.HTMLParser()
parse_only
SoupStrainer
使用SoupStrainer(\"div\", class_=\"content\")
(只解析class为content的div)from_encoding
markup
的编码格式(自动检测失败时手动指定)\"utf-8\"
、\"gbk\"
exclude_encodings
[\"ISO-8859-1\", \"windows-1252\"]
element_classes
{\"div\": MyDivClass, \"a\": MyATagClass}
示例:创建Beautiful Soup对象
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')
4.2 获取标签
4.2.1 通过标签名获取
直接通过标签名称(如 title
、div
、a
等)访问网页中的第一个匹配标签,适用于获取页面中唯一或首个出现的标签。
语法:
tag = soup.标签名 # 获取第一个匹配的标签
示例: 获取title标签
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')title_tag = soup.titleprint(title_tag)
打印结果如下图所示:
4.2.2 通过find()方法获取
find()
方法用于在文档树中查找第一个符合条件的标签,支持通过标签名、属性、文本内容等多条件筛选,灵活性远高于直接通过标签名获取。
语法:
tag = soup.find(name, attrs, recursive, string, **kwargs)
参数说明:
name
name=\"a\"
(查找
标签)、name=[\"div\", \"p\"]
(查找
或
)
attrs
attrs={\"class\": \"info\"}
(查找class为info的标签)recursive
True
(默认)表示搜索所有后代节点,False
仅搜索直接子节点。recursive=False
(仅查找直接子节点)string
string=\"科幻小说\"
、string=re.compile(r\"小说\")
** kwargs
attrs
),如class_
、id
等。class_=\"rating_nums\"
(查找class为rating_nums的标签)示例: 获取第一个出版社所在的标签。
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')tag = soup.find(name=\'div\', attrs={\"class\": \"pub\"})print(tag)
打印结果如下图所示:
4.2.3 通过find_all()方法获取
find_all()
方法用于在文档树中查找所有符合条件的标签,返回一个包含所有匹配结果的列表,是批量提取数据的核心方法。
语法:
tag_list = soup.find_all(name, attrs, recursive, string, limit, **kwargs)
参数说明:
name
find()
,指定标签名称或标签名列表。name=\"a\"
(获取所有
标签)attrs
find()
,通过属性筛选标签。attrs={\"id\": \"content\"}
(获取id为content的所有标签)recursive
find()
,是否递归查找所有子节点。recursive=True
(默认,搜索所有后代节点)string
find()
,按文本内容筛选,返回包含匹配文本的节点列表。string=re.compile(r\"评分\")
(获取所有含“评分”文本的节点)limit
limit
个匹配标签。limit=5
(仅返回前5个匹配结果)** kwargs
find()
,直接传入属性名作为参数(如 class_
、id
)。class_=\"title\"
(获取所有class为title的标签)示例: 获取前5个出版社所在的标签。
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')tag = soup.find_all(name=\'div\', attrs={\"class\": \"pub\"}, limit=5)print(tag)
打印结果如下图所示:
4.2.4 CSS选择器介绍
CSS选择器是一种通过标签名、类名、ID、属性等规则定位HTML元素的语法,广泛应用于前端开发和网页解析。Beautiful Soup的select()
和select_one()
方法支持CSS选择器语法,以下是常用选择器规则:
div
标签
.info
class=\"info\"
的标签#content
id=\"content\"
的标签(ID唯一)a[href]
href
属性的
标签a[href=\"https://example.com\"]
href
属性值为指定URL的
标签a[href*=\"book\"]
href
属性值包含\"book\"的
标签div.info h2
class=\"info\"
的
下的所有
标签
div > p
的直接子元素
(不包含嵌套子元素)
h2 + p
后面紧邻的第一个
标签li:nth-child(2)
子元素4.2.5 通过select_one()方法获取
select_one()
方法通过CSS选择器语法查找第一个符合条件的标签,返回单个标签对象(而非列表),适用于获取唯一或首个匹配的元素。
语法:
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')tag = soup.select_one(selector=\'div .info > .pub\')print(tag)
参数说明:
selector
div.info h2 a
(匹配class为info的div下的h2内的a标签)namespaces
{\"html\": \"http://www.w3.org/1999/xhtml\"}
** kwargs
示例: 获取第一个出版社所在的标签。
tag = soup.find(name=\'div\', attrs={\"class\": \"pub\"})print(tag)
打印结果如下图所示:
4.2.6 通过select()方法获取
select()
方法通过CSS选择器语法查找所有符合条件的标签,返回包含所有匹配结果的列表,是批量提取数据的常用方法。
语法:
tag_list = soup.select(selector, namespaces=None, limit=None,** kwargs)
参数说明:
selector
select_one()
,CSS选择器规则。span.rating_nums
(匹配class为rating_nums的span标签)namespaces
select_one()
,命名空间映射(HTML解析中通常无需设置)。limit
limit
个匹配标签(默认返回所有)。limit=3
(仅返回前3个匹配结果)**kwargs
示例: 获取前5个出版社所在的标签。
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')tag = soup.select(selector=\'div .info > .pub\', limit=5)print(tag)
打印结果如下图所示:
4.3 提取数据
获取标签后,下一步是从标签中提取所需数据,主要包括标签间的文本内容和标签的属性值(如链接、图片地址等)。
4.3.1 获取标签间的内容
标签间的内容通常是网页中显示的文本(如标题、描述、评分等),Beautiful Soup 提供了多种属性用于获取不同格式的文本内容,适用于不同场景:
hello world
)text
tag.text
hello world
string
None
。tag.string
None
(因存在
嵌套标签)strings
list(tag.strings)
[\'hello \', \'world\']
stripped_strings
strings
,但会自动去除每个文本片段的首尾空白字符,并过滤空字符串。list(tag.stripped_strings)
[\'hello\', \'world\']
示例:提取标签中的内容
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')# 使用 CSS 选择器查找第一个 class=\"subject-item\" 的标签(通常是一个 或 )# soup.select_one() 返回匹配的第一个 Tag 对象,如果没有匹配则返回 Noneli_tag = soup.select_one(\'.subject-item\')# 打印该标签及其所有子标签中的**全部文本内容**# .text 属性会递归获取标签内所有字符串,并自动用空格连接# 注意:可能包含多个空格、换行或制表符等空白字符print(li_tag.text)# 遍历 li_tag 下的所有**直接和嵌套的字符串内容**(包括空白字符)# .strings 是一个生成器,返回所有文本节点(包含空白、换行等)# 输出时会看到原始的缩进、换行等格式for string in li_tag.strings: print(string)# 遍历 li_tag 下的所有**去除了首尾空白的非空字符串**# .stripped_strings 是一个生成器,功能类似 .strings# 但会对每个字符串调用 .strip(),去除两端空白,并跳过空字符串# 更适合提取“干净”的文本内容for string in li_tag.stripped_strings: print(string)# 使用更精确的 CSS 选择器查找书籍出版信息# 选择路径:div 下 class=\"info\" 的元素 > 其直接子元素 class=\"pub\"# 通常用于匹配图书的“作者 / 出版社 / 出版年份”等信息tag = soup.select_one(selector=\'div .info > .pub\')# 获取该标签的**直接文本内容**(不包括子标签的文本)# .string 表示标签的直接内容(如果标签只有一个子字符串)# .strip() 去除字符串首尾的空白、换行等字符# 最终得到干净的出版信息文本print(tag.string.strip())
4.3.2 获取标签属性的值
HTML标签通常包含属性(如
中的href
、
中的src
),这些属性值是提取链接、图片地址、类名等信息的关键。
直接通过属性名访问
语法:tag[\"属性名\"]
适用场景:获取已知存在的属性(如href
、src
、class
等)。
示例:获取小说名称的链接
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')a_tag = soup.select_one(\".subject-item .info a\")link = a_tag[\"href\"]print( link)
使用get()
方法
语法:tag.get(属性名, 默认值)
适用场景:属性可能不存在时,避免报错(不存在则返回None
或指定的默认值)。
示例:获取小说名称的链接
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')a_tag = soup.select_one(\".subject-item .info a\")link = a_tag.get(\'href\')print( link)
打印结果如下所示:
https://book.douban.com/subject/36104107/
获取所有属性
语法:tag.attrs
适用场景:获取标签的所有属性及值(返回字典)。
示例:获取小说名称所在a标签的所有属性
from bs4 import BeautifulSoupsoup = BeautifulSoup(markup=open(file=\'./豆瓣读书/小说/0.html\', mode=\'r\', encoding=\'utf-8\'), features=\'lxml\')a_tag = soup.select_one(\".subject-item .info a\")attrs = a_tag.attrsprint(attrs)
{\'href\': \'https://book.douban.com/subject/36104107/\', \'title\': \'长安的荔枝\', \'onclick\': \"moreurl(this,{i:\'0\',query:\'\',subject_id:\'36104107\',from:\'book_subject_search\'})\"}
三、实战:解析豆瓣读书中小说的网页
1. 提取网页数据
代码如下所示:
# 导入 BeautifulSoup 用于解析 HTML 内容from bs4 import BeautifulSoup# 导入 Path 用于跨平台安全地操作文件和目录路径from pathlib import Path# 定义 HTML 文件所在的根目录file_dir = Path(\'./豆瓣读书\')# 使用 rglob 递归查找目录中所有以 .html 结尾的文件(包括子目录)# 例如:./豆瓣读书/page1.html, ./豆瓣读书/2023/page2.html 等html_files = file_dir.rglob(\'*.html\')# 遍历每一个找到的 HTML 文件路径for file_path in html_files: # 确保当前路径是一个文件(而非目录) if file_path.is_file(): # 使用 open() 打开文件,指定编码为 utf-8(防止中文乱码) # 将文件内容传递给 BeautifulSoup,使用 \'lxml\' 作为解析器(速度快、容错性好) soup = BeautifulSoup( markup=open(file=file_path, mode=\'r\', encoding=\'utf-8\'), features=\'lxml\' ) # 使用 CSS 选择器查找页面中所有 class=\"subject-item\" 的元素 # 每个 subject-item 通常代表一本书的信息(如书名、作者、评分等) subject_items = soup.select(\'.subject-item\') # 遍历每一本书的信息条目 for subject_item in subject_items: # 获取书籍链接:查找 .info 下的第一个 标签,提取其 href 属性 href = subject_item.select_one(\'.info a\').get(\'href\') # 获取书籍标题:同上,提取 标签的 title 属性(鼠标悬停时显示的完整书名) title = subject_item.select_one(\'.info a\').get(\'title\') # 获取出版信息:查找 .info 下 class=\"pub\" 的元素,获取其文本内容并去除首尾空白 pub = subject_item.select_one(\'.info .pub\').string.strip() # 获取评分:查找 class=\"rating_nums\" 的元素(可能是评分 8.5、9.0 等) rating = subject_item.select_one(\'.info .rating_nums\') # 安全提取评分文本:如果元素存在且有字符串内容,则去空白;否则设为空字符串 rating = rating.string.strip() if rating and rating.string else \'\' # 获取评分人数:查找 class=\"pl\" 的元素(如 \"(2048人评价)\") rating_num = subject_item.select_one(\'.info .pl\').string.strip() # 获取书籍简介(摘要):查找 .info 的直接子元素 (通常为书籍简介)
plot = subject_item.select_one(\'.info > p\') # 安全提取简介文本 plot = plot.string.strip() if plot and plot.string else \'\' # 查找纸质书购买链接:在 .info 下 class=\"buy-info\" 中的 标签 paper_tag = subject_item.select_one(\'.info .buy-info > a\') # 判断是否找到购买链接标签 if paper_tag: # 提取购买链接的 href(跳转地址) buylinks = paper_tag.get(\'href\') # 提取链接上的文字(通常是价格,如 \"¥39.5\") paper_price = paper_tag.string.strip() else: # 如果没有找到购买信息,则设为空 buylinks = \'\' paper_price = \'\' # 输出提取的关键信息(可根据需要保存到 CSV/数据库等) print(href, title, pub, rating_num, paper_price, buylinks)
部分打印结果如下图所示:

2. 保存数据到csv文件
代码如下所示:
# 导入 BeautifulSoup 用于解析 HTML 内容# 导入 Path 用于跨平台安全地操作文件和目录路径from pathlib import Pathimport pandas as pdfrom bs4 import BeautifulSoup# 定义 HTML 文件所在的根目录file_dir = Path(\'./豆瓣读书\')# 使用 rglob 递归查找目录中所有以 .html 结尾的文件(包括子目录)# 例如:./豆瓣读书/page1.html, ./豆瓣读书/2023/page2.html 等html_files = file_dir.rglob(\'*.html\')# 用于存储所有书籍信息的列表(每本书是一个字典)books = []# 遍历每一个找到的 HTML 文件路径for file_path in html_files: # 确保当前路径是一个文件(而非目录) if file_path.is_file(): # 使用 open() 打开文件,指定编码为 utf-8(防止中文乱码) # 将文件内容传递给 BeautifulSoup,使用 \'lxml\' 作为解析器(速度快、容错性好) soup = BeautifulSoup( markup=open(file=file_path, mode=\'r\', encoding=\'utf-8\'), features=\'lxml\' ) # 使用 CSS 选择器查找页面中所有 class=\"subject-item\" 的元素 # 每个 subject-item 通常代表一本书的信息(如书名、作者、评分等) subject_items = soup.select(\'.subject-item\') # 遍历每一本书的信息条目 for subject_item in subject_items: # 获取书籍链接:查找 .info 下的第一个 标签,提取其 href 属性 href = subject_item.select_one(\'.info a\').get(\'href\') # 获取书籍标题:同上,提取 标签的 title 属性(鼠标悬停时显示的完整书名) title = subject_item.select_one(\'.info a\').get(\'title\') # 获取出版信息:查找 .info 下 class=\"pub\" 的元素,获取其文本内容并去除首尾空白 pub = subject_item.select_one(\'.info .pub\').string.strip() # 获取评分:查找 class=\"rating_nums\" 的元素(可能是评分 8.5、9.0 等) rating = subject_item.select_one(\'.info .rating_nums\') # 安全提取评分文本:如果元素存在且有字符串内容,则去空白;否则设为空字符串 rating = rating.string.strip() if rating and rating.string else \'\' # 获取评分人数:查找 class=\"pl\" 的元素(如 \"(2048人评价)\") rating_num = subject_item.select_one(\'.info .pl\').string.strip() # 获取书籍简介(摘要):查找 .info 的直接子元素 (通常为书籍简介)
plot = subject_item.select_one(\'.info > p\') # 安全提取简介文本 plot = plot.string.strip() if plot and plot.string else \'\' # 查找纸质书购买链接:在 .info 下 class=\"buy-info\" 中的 标签 paper_tag = subject_item.select_one(\'.info .buy-info > a\') # 判断是否找到购买链接标签 if paper_tag: # 提取购买链接的 href(跳转地址) buylinks = paper_tag.get(\'href\') # 提取链接上的文字(通常是价格,如 \"¥39.5\") paper_price = paper_tag.string.strip() else: # 如果没有找到购买信息,则设为空 buylinks = \'\' paper_price = \'\' # 输出提取的关键信息(可根据需要保存到 CSV/数据库等) # print(href, title, pub, rating_num, paper_price, buylinks) # 将这本书的信息存为字典,加入列表 books.append({ \'href\': href, \'title\': title, \'pub\': pub, \'rating\': rating, \'rating_num\': rating_num, \'plot\': plot, \'buylinks\': buylinks, \'paper_price\': paper_price })# 转换为 DataFramebooks_df = pd.DataFrame(books)# 创建保存目录(如果不存在)Path(\'./data\').mkdir(parents=True, exist_ok=True)# 保存为 CSV 文件books_df.to_csv(\'./data/books.csv\', index=False, encoding=\'utf-8-sig\')print(f\"图书数据已保存到:\'./data/books.csv\'\")
保存后的部分数据如下图所示:
