爬虫-BeautifulSoup
bs4 概要
Beautiful Soup 是一个可以轻松从网页中抓取信息的库。它位于 HTML 或 XML 解析器之上,提供用于迭代、搜索和修改解析树的 Python 式语法。
安装指南
安装 Beautiful Soup
1 | pip install beautifulsoup4 |
安装解析器
Beautiful Soup 支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析器, 其中一个是 lxml parser 。
1 | pip install lxml |
另一个可供选择的解析器是纯 Python 实现的 html5lib , html5lib 的解析方式与浏览器相同。
1 | pip install html5lib |
下表描述了几种解析器的优缺点:
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python 标准库 | BeautifulSoup(markup, "html.parser") |
Python的内置标准库;执行速度较快;容错能力强 | 速度没有 lxml 快;容错没有 html5lib强 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
速度快;容错能力强 | 额外的 C 依赖 |
lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) or BeautifulSoup(markup, "xml") |
速度快;唯一支持 XML 的解析器 | 额外的 C 依赖 |
html5lib | BeautifulSoup(markup, "html5lib") |
最好的容错性;以浏览器的方式解析文档;生成 HTML5 格式的文档 | 速度慢;额外的 Python 依赖 |
如果可以,推荐使用 lxml 来获得更高的速度。
注意,如果一段文档格式不标准,那么在不同解析器生成的 Beautiful Soup 数可能不一样。 查看 解析器之间的区别 了解更多细节。
基本使用
基本用法
1 | from bs4 import BeautifulSoup |
搜索文档树
-
find()
和find_all()
1
2
3
4
5
6
7
8
9
10
11
12# 查找所有<a>标签
soup.find_all('a')
# 查找id为"link2"的标签
soup.find(id="link2")
# 查找class为"sister"的所有标签
soup.find_all(class_="sister")
# 使用正则表达式查找
import re
soup.find_all(re.compile("^b")) # 查找所有以b开头的标签 -
CSS选择器
-
标签选择器
1
2
3
4
5# 选择所有<p>标签
soup.select('p')
# 选择所有<div>和<p>标签
soup.select('div, p') -
类选择器
1
2
3
4
5
6
7
8# 选择class为"content"的所有元素
soup.select('.content')
# 选择同时具有"main"和"article"两个class的元素
soup.select('.main.article')
# 选择<p>标签且class为"highlight"的元素
soup.select('p.highlight') -
ID选择器
1
2
3
4
5# 选择id为"header"的元素
soup.select('#header')
# 选择<div>标签且id为"sidebar"的元素
soup.select('div#sidebar') -
属性选择器
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 选择具有href属性的所有<a>标签
soup.select('a[href]')
# 选择href属性值为"http://example.com"的<a>标签
soup.select('a[href="http://example.com"]')
# 选择href属性包含"example"的<a>标签
soup.select('a[href*="example"]')
# 选择href属性以"https"开头的<a>标签
soup.select('a[href^="https"]')
# 选择href属性以".com"结尾的<a>标签
soup.select('a[href$=".com"]') -
组合选择器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 选择<div>内部的所有<p>标签
soup.select('div p')
# 选择id为"main"元素内部的所有class为"article"的元素
soup.select('#main .article')
# 选择<ul>的直接子<li>元素
soup.select('ul > li')
# 选择id为"nav"的元素的直接子<a>标签
soup.select('#nav > a')
# 选择紧跟在<h1>后面的<p>元素
soup.select('h1 + p')
# 选择<h2>之后的所有<p>兄弟元素
soup.select('h2 ~ p') -
伪类选择器
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 选择每个父元素下的第一个<p>标签
soup.select('p:first-of-type')
# 选择每个父元素下的最后一个<li>标签
soup.select('li:last-of-type')
# 选择每个父元素下的第二个<p>标签
soup.select('p:nth-of-type(2)')
# 选择奇数位置的<li>标签
soup.select('li:nth-of-type(odd)')
# 选择偶数位置的<tr>标签
soup.select('tr:nth-of-type(even)')
-
获取标签属性
-
字典方式获取属性
1
2
3
4
5
6
7# 获取单个属性值,属性不存在时会抛出 KeyError
tag['attribute_name']
# 示例:获取<a>标签的href属性
link = soup.find('a')
href = link['href'] # 获取href属性
print(href) -
get()
方法获取属性1
2
3
4
5
6
7# 更安全的获取方式,可设置默认值
tag.get('attribute_name', default=None)
# 示例:安全获取图片的src属性
img = soup.find('img')
src = img.get('src', 'default_image.jpg') # 如果src不存在则返回默认值
print(src) -
获取所有属性
1
2
3
4
5
6
7# 获取标签的所有属性字典
tag.attrs
# 示例:获取<div>的所有属性
div = soup.find('div')
attributes = div.attrs # 返回字典,如 {'class': ['container'], 'id': 'main'}
print(attributes) -
检查属性是否存在
1
2
3
4
5
6# 检查标签是否具有某属性
'attribute_name' in tag.attrs
# 示例:检查是否有class属性
has_class = 'class' in tag.attrs
print(has_class)
获取标签内容
-
获取文本内容
-
string
属性1
2
3
4
5
6# 获取单个字符串内容(仅当标签内只有单个字符串时有效)
tag.string
# 示例:
title = soup.find('title').string
print(title) -
get_text()
/text
方法1
2
3
4
5
6
7
8# 获取标签及其所有子标签的文本内容(常用)
tag.get_text(separator=' ', strip=False)
tag.text # get_text()的快捷方式
# 示例:
paragraph = soup.find('p')
print(paragraph.get_text()) # 获取所有文本,包括子标签的
print(paragraph.get_text('|', strip=True)) # 用|分隔文本并去除空白
-
-
获取多个文本节点
1
2
3
4
5
6
7
8# 获取所有子节点的字符串内容(包括空白)
tag.strings # 生成器,返回所有字符串
tag.stripped_strings # 生成器,返回去除空白的字符串
# 示例:
div = soup.find('div')
for string in div.stripped_strings:
print(string) -
保留HTML结构的获取
1
2
3
4
5
6
7
8# 获取包含HTML标签的内容
str(tag) # 包含标签本身
tag.decode_contents() # 只包含子内容
# 示例:
div = soup.find('div')
print(str(div)) # 输出整个<div>...</div>
print(div.decode_contents()) # 只输出<div>内部的内容
修改文档结构
-
replace_with()
- 替换节点功能:用新内容完全替换当前标签/节点
特点:- 原标签及其所有内容都会被移除
- 可以替换为字符串、标签或其他可解析内容
- 返回被替换的内容
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14from bs4 import BeautifulSoup
html = "<p>原始段落</p>"
soup = BeautifulSoup(html, 'html.parser')
p = soup.p
# 用新标签替换
new_div = soup.new_tag("div")
new_div.string = "新内容"
p.replace_with(new_div)
print(soup) # 输出: <div>新内容</div>
# 用字符串替换
p.replace_with("纯文本内容") -
unwrap()
- 移除父标签功能:移除当前标签但保留其内容
特点:- 相当于将当前标签"解包"
- 保留所有子节点在原位置
- 返回被移除的标签
示例:
1
2
3
4
5
6
7
8
9
10
11
12html = "<div><p><b>加粗文本</b></p></div>"
soup = BeautifulSoup(html, 'html.parser')
p_tag = soup.p
# unwrap() 操作
removed_tag = p_tag.unwrap()
print("当前文档:")
print(soup) # 输出: <div><b>加粗文本</b></div>
print("\n返回值类型:", type(removed_tag)) # 输出: <class 'bs4.element.Tag'>
print("返回值内容:", removed_tag) # 输出: <p></p> (空标签) -
decompose()
- 彻底删除节点功能:从文档树中完全删除当前标签及其所有内容
特点:- 彻底销毁节点,无法恢复
- 不返回任何内容
- 与
extract()
不同,后者会返回被移除的内容
示例:
1
2
3
4
5
6html = "<div><p>保留这段</p><p>删除这段</p></div>"
soup = BeautifulSoup(html, 'html.parser')
to_remove = soup.find_all('p')[1]
to_remove.decompose()
print(soup) # 输出: <div><p>保留这段</p></div> -
clear()
- 清空内容功能:移除当前标签的所有子节点和内容
特点:- 保留标签本身
- 清空所有子节点和文本
- 常用于"重置"标签内容
示例:
1
2
3
4
5
6html = "<div><p>内容1</p><p>内容2</p></div>"
soup = BeautifulSoup(html, 'html.parser')
div = soup.div
div.clear()
print(soup) # 输出: <div></div> -
insert()
- 在指定位置插入功能:在子节点列表的指定位置插入内容
参数:position
:插入位置(基于0的索引)new_content
:要插入的内容
特点:- 可以插入字符串或标签
- 不会替换现有内容,只是插入
示例:
1
2
3
4
5
6
7
8
9
10html = "<ul><li>第一项</li><li>第三项</li></ul>"
soup = BeautifulSoup(html, 'html.parser')
ul = soup.ul
# 在位置1插入新项目
new_li = soup.new_tag("li")
new_li.string = "第二项"
ul.insert(1, new_li)
print(soup)
# 输出: <ul><li>第一项</li><li>第二项</li><li>第三项</li></ul> -
append()
- 末尾添加内容功能:在当前标签的末尾添加新内容
特点:- 可以添加字符串或标签
- 与Python列表的append类似
- 会修改原文档结构
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13html = "<div>开始</div>"
soup = BeautifulSoup(html, 'html.parser')
div = soup.div
# 添加字符串
div.append("中间")
# 添加新标签
new_span = soup.new_tag("span")
new_span.string = "结束"
div.append(new_span)
print(soup) # 输出: <div>开始中间<span>结束</span></div> -
extract()
- 提取节点功能:将指定的标签或字符串从文档树中完全移除,并返回被移除的内容
特点:- 完全移除:节点及其所有内容从原位置删除
- 可复用:返回的节点保持完整,可插入到其他位置
- 破坏性操作:会改变原始文档结构
- 与unwrap()对比:
unwrap()
只移除标签外壳,内容保留在原位extract()
移除整个节点(包括内容和标签)
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from bs4 import BeautifulSoup
html = "<div><p>段落1</p><p id='target'>段落2</p></div>"
soup = BeautifulSoup(html, "html.parser")
# 提取特定段落
target = soup.find(id="target").extract()
print("剩余文档:")
print(soup) # 输出: <div><p>段落1</p></div>
print("\n提取的内容:")
print(target) # 输出: <p id="target">段落2</p>
# 创建新容器并插入到文档开头
new_section = soup.new_tag("section")
new_section.append(target)
# 直接插入到soup对象的最前面
soup.insert(0, new_section)
print("\n插入后的内容:")
print(soup) # <section><p id="target">段落2</p></section><div><p>段落1</p></div> -
wrap()
- 包裹节点功能: 在当前标签或字符串外包裹一个新的父标签
特点:- 非破坏性:保留原有内容不变
- 返回新标签:返回的是新创建的包裹标签
- 可链式调用:支持连续包裹多层
- 与unwrap()相反:添加结构而不是移除结构
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25html = "<p>需要强调的内容</p>"
soup = BeautifulSoup(html, "html.parser")
p = soup.p
# 基础包裹
container = soup.new_tag("div")
container["class"] = "container" # 正确设置 class
p.wrap(container)
# 链式包裹(创建多层结构)
p.wrap(soup.new_tag("article")).wrap(soup.new_tag("section"))
print(soup.prettify())
"""
输出:
<div class="container">
<section>
<article>
<p>
需要强调的内容
</p>
</article>
</section>
</div>
"""
bs4实例
示例1:爬取三国演义
1 | import urllib.request |
示例2:爬取智联
1 | import urllib.request |