更新日期: 2021.04.11
本节学习内容 :掌握解析库 bs4 的常用方法。
目录
- 1. bs4 是什么?能帮我完成什么任务?
- 2. 学习资料来源 (官网)
- 3. 为什么使用 bs4 需指定解析器?怎么选择?
- 4. 准备工作
- 5. 开始使用 - 构造 bs4 实例
- 6. bs4 实例中有哪些可操控的对象?
- 6.1 Tag 标签对象
- 6.2 NavigableString 字符串对象
- 6.3 BeautifulSoup 文档级别对象
- 6.4 其他
- 7. 获取目标标签 - 遍历文档
- 7.1 获取当前节点信息 .name
- 7.2 向下遍历
- 7.2.1 列表形式子节点 .contents
- 7.2.2 生成器形式子节点 .children
- 7.2.3 所有子孙节点 .descendants
- 7.2.4 子孙节点的字符串信息 .strings & stripped_strings
- 7.3 向上遍历
- 7.3.1 父节点 .parent
- 7.3.2 所有上级节点 .parents
- 7.4 横向遍历
- 7.4.1 相邻的一个兄弟节点 .next_sibling & .previous_sibling
- 7.4.2 相邻的多个兄弟节点 .next_siblings & .previous_siblings
- 7.5 按解析顺序遍历 .next_element(s) & .previous_element(s)
- 8. 获取目标标签 - 直接搜索
- 8.1 辅助搜索工具
- 8.1.1 字符串
- 8.1.2 正则表达式
- 8.1.3 列表
- 8.1.4 True
- 8.1.5 自定义搜索方法 ~
- 8.2 开始搜索大法 .find_all
- 8.2.1 标签名字
- 8.2.2 关键字/ 属性名称
- 8.2.3 CCS (类名)
- 8.2.4 字符串
- 8.2.5 混合筛选
- 8.2.6 限制返回数量
- 8.2.7 仅返回子节点
- 8.3. 搜索大法 - 除 find_all 之外的 find 方法
- 8.4. 搜索大法 - 使用CSS选择器
- 9. 获取标签的信息
- 9.1 获取标签内的字符串信息
- 9.2 获取标签的属性和属性值
- 10. 其他
- 10.1 省略 .find_all() 直接调用标签
- 10.2 优雅的输出 prettify()
- 10.3 bs4 怎样确定解码规则
- 10.4 解析部分文档 - SoupStrainer
- 10.5 破解乱码 - UnicodeDammit
- 10.6 常见的解析错误 (这些全都不是我的错,哈哈~)
- 10.7 解析xml文档
- 10.8 处理重复的属性
- 10.9 bs4 的其他功能
- 11. 总结~
1. bs4 是什么?能帮我完成什么任务?
BeautifulSoup是Python的一个用于解析网页 html 和 xml 代码的库。
主要工作原理是先定位目标标签,再获取目标字符串或属性值。是获取网页信息的利器。
2. 学习资料来源 (官网)
打开官网找到 BeautifulSoup (bs4),当前版本是4.9.3 (Oct 3, 2020), 英文版介绍是基于4.9.0, 链接: https://www.crummy.com/software/BeautifulSoup/bs4/doc/。
中文版介绍是基于4.4.0 (Sep 29, 2015), 链接: https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/。
先学习中文版, 再看看英文版的变化,不同之处以 4.9 开头作为标记。
3. 为什么使用 bs4 需指定解析器?怎么选择?
bs4 支持使用多种解析器,可以解析html, xml 和 html5 格式的文档,不同解析器的主要特点见下表。
如果文档格式不正确, 其被不同的解析器解析后返回的结果可能是不一样的。因为对于错误的格式,各解析器有自己的容错和解析原则。即使是格式正确的文档,如果没有使用相应的解析器,也可能无法获得预期结果。
在公布源代码时应标明所依赖的解析器类型,以避免给阅读者带来不必要的麻烦。
4. 准备工作
pip 安装 bs4 及相关的解析器 (lxml, html5)。
5. 开始使用 - 构造 bs4 实例
导入 bs4 库后,使用BeautifulSoup() 命令将目标文档构造为 bs4 实例,然后针对这个实例对象进行操作。
目标文档可以是一个网页代码的片段,也可以从文件中读取。
from bs4 import BeautifulSoup
# 从文件中读取。练习新技能时,先把网页代码存下来再玩,不要反复地爬取~~
with open("index.html") as html:
soup = BeautifulSoup(html, 'html.parser')
如不指定解析器,bs4 会自动选择它认为合适的解析器来解析。见下例,bs4 选择了 lxml 作为解析器,并提示了可能出现的问题,以及解决方法。然后输出了解析结果。
from bs4 import BeautifulSoup
soup = BeautifulSoup("<html>Alice</html>")
print(soup)
'''
GuessedAtParserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml").
This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.
To get rid of this warning, pass the additional argument 'features="lxml"' to the BeautifulSoup constructor.
<html><body><p>Alice</p></body></html>
'''
如指定解析器,bs4 会直接使用指定的解析器来解析,如下。
soup = BeautifulSoup("Sacré!","lxml")
print(soup) # <html><body><p>Sacré!</p></body></html>
6. bs4 实例中有哪些可操控的对象?
bs4 实例化文档后将其转换为树形结构, 主要有三类对象:Tag , NavigableString , BeautifulSoup。
6.1 Tag 标签对象
Tag 对象与网页源文档中的 tag 相同。即一对尖括号之内的全部内容,含尖括号和标签的名称,以及这个标签内部的全部信息,如属性名称和值,字符串等。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>',"lxml")
print(type(soup.b)) # <class 'bs4.element.Tag'>
标签最重要的属性是 name (名字)和 attributes (属性)。
name属性
每个标签都有名字 (可以重名…), 通过 .name 方法获取相应名字的标签。还可以给标签改名。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>',"lxml")
print(soup.b.name) # b
tag = soup.b
print(tag) # <b class="boldest">Extremely bold</b>
tag.name = "a"
print(tag) # <a class="boldest">Extremely bold</blockquote>
# 改名之后,b标签就消失了,变成了a标签...
attributes 属性
一个标签可有多个属性。针对属性的操作方法与字典相同。
可使用 tag[‘属性名称’] 获取属性相应的值,也可使用 .attrs 获取属性的所有 “名称~值” 信息。
soup = BeautifulSoup('<b class="boldest">Extremely Black</b>',"lxml")
tag = soup.b
print(tag['class']) # ['boldest']
print(tag.attrs) # {'class': ['boldest']}
标签的属性可被添加, 删除和修改。
soup = BeautifulSoup('<b class="boldest">Extremely Black</b>',"lxml")
tag = soup.b
tag['class'] = 'verybold'
tag['id'] = 1
print(tag) # <b class="verybold" id="1">Extremely Black</b>
del tag['class']
del tag['id']
print(tag) # <b>Extremely Black</b>
多值属性:
- HTML4和 HTML5都支持多值属性,最常见的多值属性是 class。
- 一个标签可拥有多个CSS的 class 属性,并同时拥有其他属性,如 rel , rev , accept-charset , headers , accesskey等。
- 4.9.0 可通过 “multi_valued_attributes=None” 在解析时禁止识别多值属性。
soup = BeautifulSoup('<p class="body strikeout"></p>','lxml')
print(soup.p['class']) # ['body', 'strikeout'] 多值属性的返回类型是列表list
soup = BeautifulSoup('<p class="body strikeout"></p>','lxml',multi_valued_attributes=None)
print(soup.p['class']) # body strikeout,禁止识别多值属性
soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>','lxml')
soup.a['rel'] = ['index', 'contents'] # 可以通过列表同时更新标签的多个属性
print(soup.a) # <a rel="index contents">homepage</a>
单值属性:
- 即使某个属性看起来有多个值, 但在解析所得的文档中没有发现它的属性被定义为多值,bs4就不会认为这是一个多值属性,并会将这个属性作为一个字符串返回。
- 例如XML不支持多值属性,如使用XML解析,而原文档是HTML4或HTML5 编写的,解析得到的文档中标签将不会包含多值属性。所以,注意:选择合适的解析器~~
- 4.9.0 可使用 “multi_valued_attributes argument” 强制解析器识别为多值属性,即使是在使用XML解析器。
id_soup = BeautifulSoup('<p id="my id"></p>','lxml') # lxml以为id是单值属性
print(id_soup.p['id']) # my id
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml') # XML不支持多属性
print(xml_soup.p['class']) # body strikeout
id_soup = BeautifulSoup('<p id="my id"></p>','xml',multi_valued_attributes={ '*' : 'id'})
print(id_soup.p['id']) # ['my', 'id'] # 两个属性~
6.2 NavigableString 字符串对象
NavigableString 是 bs4 用来管理标签内的字符串的类。
是 Python 中字符串类型的子类,继承了所有字符串的属性和方法。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b
print(type(tag.string)) # <class 'bs4.element.NavigableString'>
print(tag.string) # Extremely bold
NavigableString 可以被替换,使用.replace_with 或直接给 tag.string 赋值。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b
tag.string.replace_with("No longer bold")
print(tag) # <b class="boldest">No longer bold</b>
tag.string = "It is black"
print(tag) # <b class="boldest">It is black</b>
tag.string = tag.string + " revised"
print(tag) # <b class="boldest">It is black revised</b>
6.3 BeautifulSoup 文档级别对象
BeautifulSoup 对象表示一个文档的全部内容,也可以把它看作一个标签对象。
它支持遍历和搜索中的大部分方法,但由于它并不是一个具体的标签, 所以仅有一个特殊的 .name 属性,值为 [document]。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
print(soup.name) # [document]
4.9.0 把 BeautifulSoup 作为一个标签,可以在更高的层面上操作文档,例如使用一个 BeautifulSoup 对象替换另一个 BeautifulSoup 对象的部分内容。
doc = BeautifulSoup("<document><content/>INSERT FOOTER HERE</document", "lxml")
footer = BeautifulSoup("<footer>Here's the footer</footer>", "lxml")
doc.find(text="INSERT FOOTER HERE").replace_with(footer)
print(doc)
# <?xml version="1.0" encoding="utf-8"?>
# <document><content/><footer>Here's the footer</footer></document>
6.4 其他
以上三类对象几乎涵盖了网页文档中的所有内容, 还有一些特殊的对象类型,如文档的注释 (Comment 对象)。
Comment 对象是一个特殊类型的 NavigableString对象,也可以通过 print (soup.b.string) 方法输出,不过格式比较特别。
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'lxml')
print(type(soup.b.string)) # <class 'bs4.element.Comment'>
print(soup.b.string) # Hey, buddy. Want to buy a used parser?
print(soup.b.prettify())
'''
<b>
<!--Hey, buddy. Want to buy a used parser?-->
</b>
'''
4.9.0 bs4 还定义了其他的类,这些类使用方法和 NavigableString 一样,作用都是使我们可以方便的通过标签获取信息。(html5lib 解析器不含这些元素)
- Style 类: CCS样式表
- Script 类:Javascript
- Template 类:html 模板
- CData, ProcessingInstruction, Declaration, 以及 Doctype
以下例子使用 CDATA block 替换了 comment 部分的内容。具体用途还不清楚~~
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'html.parser')
comment = soup.b.string
type(comment) # <class 'bs4.element.Comment'>
cdata = CData("A CDATA block")
comment.replace_with(cdata)
print(soup.b) # <b><![CDATA[A CDATA block]]></b>
7. 获取目标标签 - 遍历文档
本节演示主要采用 ”爱丽丝梦游仙境” 文档的片段,如下,后续不再重复文档代码。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
7.1 获取当前节点信息 .name
以下两种方法效果相同,因为 find_all 是 bs4 中最常用的方法,可以省略不写。
- 通过标签名字使用 .name 方法获取目标标签。
- 使用 find_all (“name”) 方法获取全部同名标签。
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
print(soup.head) # <head><title>The Dormouse's story</title></head>
print(soup.title) # <title>The Dormouse's story</title>
print(soup.head.title) # <title>The Dormouse's story</title> # 可嵌套使用多层标签名字
print(soup.find_all("a")) # 获所有符合条件的标签
'''
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''
7.2 向下遍历
一个标签里可包含多层及多个标签,统称为这个标签的子孙节点。
7.2.1 列表形式子节点 .contents
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
print(soup.head) # <head><title>The Dormouse's story</title></head>
print(soup.head.contents) # [<title>The Dormouse's story</title>]
print(soup.head.contents[0]) # <title>The Dormouse's story</title>
7.2.2 生成器形式子节点 .children
head_tag = soup.head
print(head_tag.contents) # [<title>The Dormouse's story</title>]
title_tag = head_tag.contents[0]
print(title_tag) # <title>The Dormouse's story</title>
for child in title_tag.children:
print(child) # The Dormouse's story
7.2.3 所有子孙节点 .descendants
head_tag = soup.head
for descendant in head_tag.descendants:
print(descendant)
# <title>The Dormouse's story</title>
# The Dormouse's story
print(len(list(soup.children))) # 1
print(len(list(soup.descendants))) # 25
7.2.4 子孙节点的字符串信息 .strings & stripped_strings
- 如标签内只含一个 NavigableString 类节点, 不论是否为其子节点,都可以使用 .string 直接获取到字符串。
- 如标签内的多个节点都包含字符串, 需使用 .strings 循环获取。
- 使用 .stripped_strings 去除字符串中可能包含的空格或空行。
head_tag = soup.head
title_tag = head_tag.contents[0]
for string in soup.strings: # 输出略
print(repr(string))
for string in soup.stripped_strings: # 输出如下
print(repr(string))
'''
"The Dormouse's story"
"The Dormouse's story"
'Once upon a time there were three little sisters; and their names were'
'Elsie'
','
'Lacie'
'and'
'Tillie'
';\nand they lived at the bottom of a well.'
'...'
'''
7.3 向上遍历
7.3.1 父节点 .parent
- “title”标签的父节点是"head’’ 标签。
- “title”标签包含的字符串也有父节点,就是“title”标签。(上一级,就是加一层尖括号)。
- 根节点 “html” 的父节点是 BeautifulSoup 对象,即 html 文档本身。
- BeautifulSoup 的父节点是 None。
title_tag = soup.title
print(title_tag) # <title>The Dormouse's story</title>
print(title_tag.parent) # <head><title>The Dormouse's story</title></head>
print(title_tag.string.parent) # <title>The Dormouse's story</title>
print(type(soup.html.parent)) # <class 'bs4.BeautifulSoup'>
print(soup.parent) # None
7.3.2 所有上级节点 .parents
link = soup.a
print(link) # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
for i in link.parents:
if i is not None: # BeautifulSoup对象的.parent是None
print(i.name)
'''
p
body
html
[document]
'''
7.4 横向遍历
接下来使用以下简单的文档片段。
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
7.4.1 相邻的一个兄弟节点 .next_sibling & .previous_sibling
实际情况中,很多标签的 .next_sibling 和 .previous_sibling 通常是无意义的字符, 比如是两个标签之间的顿号或换行符。
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>", 'lxml')
print(sibling_soup.b.next_sibling) # <c>text2</c>
print(sibling_soup.c.previous_sibling) # <b>text1</b>
7.4.2 相邻的多个兄弟节点 .next_siblings & .previous_siblings
获得 a 节点之后的所有同级节点。
for sibling in soup.a.next_siblings:
print(repr(sibling))
'''
',\n'
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
' and\n'
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
';\nand they lived at the bottom of a well.'
'''
带条件搜索 (id=“link2”) 兄弟节点。
for sibling in soup.find(id="link2").previous_siblings:
print(repr(sibling))
'''
',\n'
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
'Once upon a time there were three little sisters; and their names were\n'
'''
7.5 按解析顺序遍历 .next_element(s) & .previous_element(s)
解析器解析文档一般遵循以下过程。
以下面的 “爱丽丝” 文档为例,解析器首先打开 html 标签, 再打开一个head标签, 接下来打开一个title标签, 添加一段字符串, 然后关闭 title 标签, 再关闭 head 标签,打开 p 标签…
<html><head><title>The Dormouse's story</title></head>
<p class="title"><b>The Dormouse's story</b></p>
# 获取所有id="link3"的a节点之后的节点,不论是子孙还是兄弟
last_a_tag = soup.find("a", id="link3")
for element in last_a_tag.next_elements:
print(repr(element))
'''
'Tillie'
';\nand they lived at the bottom of a well.'
'\n'
<p class="story">...</p>
'...'
'\n'
'''
8. 获取目标标签 - 直接搜索
8.1 辅助搜索工具
8.1.1 字符串
在搜索方法中传入一个字符串参数, bs4 会据此比对所有标签的名字,并以列表形式返回名字与该字符串完全匹配的标签。
print(soup.find_all('a'))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
8.1.2 正则表达式
import re
for tag in soup.find_all(re.compile("^b")): # 找出所有以 b开头的标签
print(tag.name)
# body
# b
for tag in soup.find_all(re.compile("t")): # 找出所有名字中包含't'的标签
print(tag.name)
# html
# title
8.1.3 列表
bs4 会将名字与列表中任一字符串匹配的标签返回。
print(soup.find_all(["a", "b"])) # 返回所有名字包含'a'和'b'的标签
# [<b>The Dormouse's story</b>,
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
8.1.4 True
True 可以匹配任何值, 所以下面代码返回所有有名字的标签的名字。
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# title
# body
# p
# 其他输出略
8.1.5 自定义搜索方法 ~
自定义方法如果匹配失败,会返回 False。
# 找出所有符合 “含有 class 及 id 属性” 条件的标签
def has_class_but_no_id(tag):
return tag.has_attr('class') and tag.has_attr('id')
print(soup.find_all(has_class_but_no_id))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
# 使用正则筛选出href中不含"lacie"的标签
import re
def not_lacie(href):
return href and not re.compile("lacie").search(href)
print(soup.find_all(href=not_lacie))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
# 找出所有符合“前后紧邻的元素都是字符串”条件的标签
from bs4 import NavigableString
def surrounded_by_strings(tag):
if (isinstance(tag.next_element, NavigableString) and isinstance(tag.previous_element, NavigableString)):
return tag
for tag in soup.find_all(surrounded_by_strings):
print(tag.name)
'''
body
p
a
输出略
'''
8.2 开始搜索大法 .find_all
find_all (name, attrs, limit, recursive, string, **kwargs)
find_all () 方法搜索当前标签中的所有标签, 并返回符合搜索条件的标签。
- 搜索条件可以是自定义的过滤器 , 字符串,正则表达式, True,列表等。
- 可以在一个搜索方法中同时设置多个属性的搜索条件。
8.2.1 标签名字
find_all 接收到的参数如果不是关键字或属性名称,比如在处理 ”<a class=“sister” href="http…“ 片段时,提供的不是 class 或 href ,搜索时会把该参数当作标签的名称处理,并返回符合条件的标签。
print(soup.find_all('title')) # [<title>The Dormouse's story</title>]
8.2.2 关键字/ 属性名称
find_all 接收到的参数如果含属性名称,搜索时将直接找到该属性所在的标签,并返回符合筛选条件的标签。
print(soup.find_all(id ="link3")) # 返回所有id ="link3"的标签
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
import re
print(soup.find_all(href = re.compile("elsie"))) # 返回所有href含有"elsie"的标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
print(soup.find_all(id=True)) # 返回所有含id 属性的标签
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.find_all(href=re.compile("elsie"), id='link1'))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
有些属性在搜索时不能直接使用, 比如以下的 data-* 属性。需通过 attrs 参数定义一个字典来搜索。
尝试更改 data-foo,把中划线改为下划线或者去掉都可以搜索,把中划线替换为*/等特殊字符都不可以搜索。比较像Python变量的命名规则。
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
print (data_soup.find_all(data-foo="value"))
'''
File "bs4_study.py", line 20
print (data_soup.find_all(data-foo="value"))
^
SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
'''
data_soup.find_all(attrs={"data-foo": "value"}) # [<div data-foo="value">foo!</div>]
4.9.0 同样,由于 name 是 bs4 的保留关键字,也不能直接搜索,需构造词典来解决。
name_soup = BeautifulSoup('<input name="email"/>', 'html.parser')
print(name_soup.find_all(name="email")) # []
print(name_soup.find_all(attrs={"name": "email"})) # [<input name="email"/>]
8.2.3 CCS (类名)
CSS类名的关键字 class 在Python中是保留字, 不能做为搜索的关键字,需在后面加下划线再使用,即class_ 。
print(soup.find_all("a", class_="sister"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
print(soup.find_all(class_=re.compile("itl")))
# [<p class="title"><b>The Dormouse's story</b></p>]
CCS中标签的 class 属性是多值属性, 可以通过其中一个或全部属性值获取该标签,但不能通过一部分属性值获取。
css_soup = BeautifulSoup('<p class="body strikeout"></p>','lxml')
print(css_soup.find_all("p", class_="strikeout")) # [<p class="body strikeout"></p>]
print(css_soup.find_all("p", class_="body")) # [<p class="body strikeout"></p>]
print(css_soup.find_all("p", class_="body strikeout")) # [<p class="body strikeout"></p>]
# 提供三个属性值当中的两个作为查询条件,返回为空,搜索失败
css_soup = BeautifulSoup('<p class="body strikeout added"></p>','lxml')
print(css_soup.find_all("p", class_="body strikeout")) # []
8.2.4 字符串
搜索文档中的字符串内容,可提供一个参数或参数列表进行匹配,也可以使用正则,自定义搜索等方法。
print(soup.find_all(string="Elsie")) # ['Elsie']
print(soup.find_all(string=["Tillie", "Elsie", "Lacie"])) # ['Elsie', u'Lacie', u'Tillie']
print(soup.find_all(string=["lie", "Elsie", "Lacie"])) # ['Elsie', 'Lacie'], 无法匹配Tillie
print(soup.find_all(string=re.compile("mouse")))
# ["The Dormouse's story", u"The Dormouse's story"], 使用正则还是可以匹配到~
def is_the_only_string_within_a_tag(s):
return (s == s.parent.string)
print(soup.find_all(string=is_the_only_string_within_a_tag))
# ["The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
8.2.5 混合筛选
print(soup.find_all("a", string="Elsie"))
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
8.2.6 限制返回数量
如果文档信息量很大,而我们只需要其中的一部分~
print(soup.find_all("a",limit=2))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
8.2.7 仅返回子节点
使用 find_all() 方法会返回所有满足条件的子孙节点, 如只需搜索子节点, 可设置参数 recursive=False。
只有 find_all() 和 find() 支持 recursive 参数。
8.3. 搜索大法 - 除 find_all 之外的 find 方法
以下搜索方法的使用和 find_all ()类似,不再详述。
- find()
- find_parents() 和 find_parent()
- find_next_siblings() 和 find_next_sibling(),find_previous_siblings() 和 find_previous_sibling()
- find_all_next() 和 find_next(),find_all_previous() 和 find_previous()
8.4. 搜索大法 - 使用CSS选择器
4.9.0 4.7.0版在 bs4 中加入了 SoupSieve 库,可以更方便的处理CSS选择器。
在 Elements 选项卡上点击某个标签的右键, 即可得到以下三种方式的路径。
bs4 支持大部分CSS选择器, 如果已经熟悉了CSS选择器的语法,使用起来会非常方便。不过,如果仅需要使用CSS选择器的功能, 应直接使用 lxml 解析器而不是 bs4,那样速度更快。
- CCS selector path: #su
- xPath path: //*[@id=“su”
- JS path: document.querySelector(’#su’)
9. 获取标签的信息
9.1 获取标签内的字符串信息
# 获取当前标签内的所有文本信息,含子孙节点内的文本
print(soup.python_study.text)
print(soup.python_study.get_text())
# 获取当前标签内的直接文本信息。
# 如果当前标签内没有直接文本信息,其子孙节点中有唯一的文本信息,也可以获取到
print(soup.python_study.string)
9.2 获取标签的属性和属性值
# 获取标签的所有属性信息
print (tag.attrs)
# {'star': '255', 'class': ['M', 'L'], 'teacher': 'alice zhang '}
# 获取某一属性的值
print(target_tag['star']) # 255
10. 其他
10.1 省略 .find_all() 直接调用标签
以下两组代码等价~
soup.find_all("a")
soup("a")
soup.title.find_all(string=True)
soup.title(string=True)
10.2 优雅的输出 prettify()
prettify () 方法将 bs4 的文档树格式化后以Unicode 编码输出, 每个XML/HTML标签都独占一行。很好看,查看代码寻找规律时可以考虑看 prettify 版本~
10.3 bs4 怎样确定解码规则
默认情况下,bs4 会将文档转换为Unicode再处理。
通过 bs4 输出到文档时, 不论之前的输入文档是什么编码方式, 默认输出编码均为UTF-8。
10.4 解析部分文档 - SoupStrainer
如仅需要查找并解析文档的一部分,可使用 SoupStrainer 类定义bs4 的处理范围。
创建一个 SoupStrainer 对象并作为 parse_only 参数提交给 bs4 处理。parse_only可以接受多种类型的参数。
解析部分文档不会节省多少解析时间 (因为本来就只需要解析这一部分~), 但可以节省内存, 而且搜索也会变快。
4.9.0 html5lib 解析器不支持部分解析,因为其工作原理不支持仅获取部分文档,具体原因请百度。
from bs4 import BeautifulSoup
from bs4 import SoupStrainer
only_parse_id_link2 = SoupStrainer(id="link2")
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_parse_id_link2).prettify())
'''
<a class="sister" href="http://example.com/lacie" id="link2">
Lacie
</a>
def is_short_string(string):
return len(string) < 10
only_short_strings = SoupStrainer(string=is_short_string)
10.5 破解乱码 - UnicodeDammit
以下案例中 snowmen 和 quote 的编码比较特殊且不同,因此人工猜测两部分的编码,然后使用不同的规则处理,再合并输出。结果很不好看,哈哈~
UnicodeDammit.detwingle () 方法可以智能的使用不同规则同时处理一篇文档的不同部分,然后转换为UTF-8编码输出。
现阶段 UnicodeDammit.detwingle () 方法只能解码包含在UTF-8编码中的Windows-1252编码内容, 据说这已经可以解决我们最常遇到的问题了。
snowmen = (u"\N{SNOWMAN}" * 3)
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
doc = snowmen.encode("utf8") + quote.encode("windows_1252")
print(doc) # b'\xe2\x98\x83\xe2\x98\x83\xe2\x98\x83\x93I like snowmen!\x94'
print(doc.decode("windows-1252")) # ☃☃☃“I like snowmen!”
from bs4 import UnicodeDammit
new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode("utf8")) # ☃☃☃“I like snowmen!”
10.6 常见的解析错误 (这些全都不是我的错,哈哈~)
解析错误有两种,一种是崩溃, bs4 尝试解析一段文档结果抛出了异常,通常是 HTMLParser.HTMLParseError 。另一种是 bs4 解析后的文档树看起来与原来的内容相差很多。
通常情况下,这些异常产生于所依赖的解析器。如果一个解析器不能很好的解析出当前的文档, 最好的办法就是换一个解析器。
10.7 解析xml文档
解析 xml 文档时,记得选择 xml 解析器。
10.8 处理重复的属性
4.9.1版新规
html.parser 解析器默认返回最后一个属性值,可通过设置 “on_duplicate_attribute” 改变。
html5lib 和 lxml 解析器默认返回第一个属性值。
from bs4 import BeautifulSoup
markup = '<a href="http://url1/" href="http://url2/">'
soup = BeautifulSoup(markup, 'html.parser')
print(soup.a['href']) # http://url2/
soup = BeautifulSoup(markup, 'html.parser', on_duplicate_attribute='ignore')
print(soup.a['href']) # http://url1/
soup = BeautifulSoup(markup, 'lxml')
print(soup.a['href']) # http://url1/
10.9 bs4 的其他功能
以下内容暂时不学,知道有这些功能就可以了~
- 修改文档
- 比较对象是否相同(==, is)
- 复制BeautifulSoup对象 (copy.copy())
- 4.9.0 html.parser 和 html5lib 解析器可以返回目标的行数和位置
11. 总结~
- 终于看完了 … 感觉用起来会很方便,特别想说:这真是给人类使用的语言啊~~~
- 继续学习 lxml 等解析器的动力有点不足,因为 bs4 这么强大,想快点去操练下,哈哈~
- bs4 可以获取属性值和字符串信息,但是,如果需要对字符串做进一步的解析,精准的获取信息,就需要使用正则了,接下来学习 re~~