- Beautiful Soup使用
- 简介
- 安装
- 快速开始
- 对象种类
- 节点的定位
- 信息提取
- 关联选择
- 方法选择器(搜索文档树)
- CSS选择器
- 参考资料
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.
安装-
在终端输入pip install beautifulsoup4。
-
然后安装解析器:pip install lxml。
Beautiful Soup也能支持其他解析器,这里使用lxml解析器。如果有更改解析器的需要,参看其[官方手册](Beautiful Soup 中文文档)。
快速开始下面是一段html代码,后面会被多次用到,这段代码称其为文本1。
html = """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; and they lived at the bottom of a well.
...
"""
# 将上述文本1的代码放在同一py文件下,运行。观察结果 from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.prettify()) print(soup.title.string)
-
对于文本1,观察可知,标签没有闭合。但是在输出信息中,有
-
prettify()方法是将html文本按标准缩进格式返回。
-
tag.string:获取tag标签的文本内容。如果标签tag的内容中含有其他标签,则返回None。
以上仅是一个bs4使用的简单示例。我们从这个示例开始。
对象种类Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment .
-
Tag:是对html中标签(及其内容)和对应方法的封装。
-
NavigableString:字符串常被包含在tag内.Beautiful Soup用 NavigableString 类来包装tag中的字符串。这里注意理解:NavigableString就是对字符串及其方法的封装。
-
BeautifulSoup:对整个html文档内容及其方法的封装。
-
Comment:对注释内容及其方法的封装。Comment 对象是一个特殊类型的 NavigableString 对象。
所以,Beautiful Soup在解析html文档时,按照以上四种类型解析文档,并构成对应的树。
注意,BeautifulSoup在大部分情况下,可以当做Tag来使用。
解析html是为了获取标签中的信息。而要获得目标信息,首先需要定位到目标节点(标签)。
在Bs4中,使用tag.标签名的方式,可以定位到tag对象的第一个指定标签名的Tag对象。
示例:分析文本1
from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.title) print(type(soup.title)) print(soup.title.string) print(type(soup.title.string)) print(soup.head) print(soup.p)
运行结果:
The Dormouse's story The Dormouse's story The Dormouse's story The Dormouse's story
注意节点的类型:
soup.title是bs4.element.Tag类型的对象,soup.title.string是bs4.element.NavigableString类型的对象,其打印结果其实就是一段字符串。
信息提取上面我们简单介绍了一下,如何定位到某个Tag节点。下面来介绍,如果获取该Tag节点的信息。
由于Tag对象是对标签内容及其方法的封装,可以直接通过.的方式访问其信息。
关联选择
- 获取Tag对象的类型
tag.name #获取标签名称:返回当前tag对象的标签名称。如果tag是标签的封装,则返回'p'
- 获取Tag对象的属性
tag['id'] tag.attrs['id'] #获取Tag节点的属性:这两种方式等价。使用中括号加上属性名
- 获取Tag对象的文本内容(更具体的说,可以理解为,获取Tag节点的NavigableString子节点)
tag.string # 返回一个NavigableString对象这个.string需要理解一下,一个Tag对象实际上对应一个标签,但是标签中的内容可以有纯字符串和子标签。这里的纯字符串被解析为NavigableString节点,而子标签被解析为Tag节点。
如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点,即NavigableString类型子节点。
如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同。
如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None。
- 获取Tag中的NavigableString对象以及子节点中的NavigableString对象。
#tag.strings #tag.stripped_strings #会忽略掉空格或空行。 #这两个方法都返回一个生成器 for string in soup.strings: print(string)
有时,不能一步定位到我们想要的节点。而需要先选中某一个节点,再以它为基准选择其子节点,父节点,兄弟节点等。
- 子节点和子孙节点
tag的.contents属性可以将tag的子节点以列表的方式输出:
tag的.children属性可以将tag的子节点以生成器方式输出:
print(soup.p.contents)
#结果为一列表
for child in soup.p.children:
print(child)
#结果为多个子节点的单独输出
.contents和.children都是获取tag节点的直接子节点。如果要获取tag的所有子孙节点,那么使用.descendants属性。
.descendants 属性可以对所有tag的子孙节点进行先序遍历。
for child in soup.descendants:
print(child)
- 父节点和祖先节点
通过 .parent 属性来获取某个元素的父节点.
通过元素的 .parents 属性可以递归得到元素的所有父辈节点.
- 兄弟节点
看一个简单例子:
sibling_soup = BeautifulSoup("text1text2 ")
print(sibling_soup.prettify())
#
#
#
#
# text1
#
#
# text2
#
#
#
#
因为标签和标签是同一层:他们是同一个元素的子节点,所以和可以被称为兄弟节点.一段文档以标准格式输出时,兄弟节点有相同的缩进级别.在代码中也可以使用这种关系.
方法选择器(搜索文档树)在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点:
tag.next_sibling tag.previous_sibling在上述简单例子中,标签有 .next_sibling 属性,但是没有 .previous_sibling 属性,因为标签在同级节点中是第一个.同理,标签有 .previous_sibling 属性,却没有 .next_sibling 属性:
print(sibling_soup.b.previous_sibling) # None print(sibling_soup.c.next_sibling) # None例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:
sibling_soup.b.string # u'text1' print(sibling_soup.b.string.next_sibling) # None值得注意:实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白.
下面给出一个html片段:
Elsie, Lacie, Tillie如果以为第一个标签的 .next_sibling 结果是第二个标签,那就错了,真实结果是第一个标签和第二个标签之间的逗号和换行符:出错的原因是,没有深入理解BeautifulSoup的解析方式,节点应该有四种。
前面所讲的节点定位方法,都是基于html文档的结构,这种方式逻辑清晰,但是当文档结构复杂时,会很繁琐。所以接下来介绍更为强大的API。
下面将介绍两个API函数的使用。
find_all( name , attrs , recursive , string , **kwargs ) find( name , attrs , recursive , string , **kwargs )
- find_all()
参数介绍:
name 参数可以查找所有名字为 name 的tag
attrs参数可以查找指定属性的tag,其中attrs为一字典,元素为属性名:属性值的键值对。
调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False .
通过 string 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, string 参数接受 字符串 , 正则表达式 , 列表, True .
**kwargs参数:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,
各个参数的使用:注:下面的html代码记为文本2
name
html=''' Hello
- Foo
- Bar
- Jay
''' from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find_all(name='ul')) print(type(soup.find_all(name='ul')[0])) for ul in soup.find_all(name='ul'): print(ul.find_all(name='li')) for ul in soup.find_all(name='ul'): print(ul.find_all(name='li')) for li in ul.find_all(name='li'): print(li.string)
- Foo
- Bar
运行结果:
[,
- Foo
- Bar
- Jay
]
- Foo
- Bar
[ Foo ,Bar ,Jay ] [Foo ,Bar ] [Foo ,Bar ,Jay ] Foo Bar Jay [Foo ,Bar ] Foo Bar
- 从上可知,find_all会将查询到的节点以列表形式返回。列表中的元素为bs4.element.Tag类型。
attrs
from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find_all(attrs={'id': 'list-1'})) print(soup.find_all(attrs={'name': 'elements'})) #还可以写成一下等价形式 print(soup.find_all(id='list-1')) print(soup.find_all(class_='element')) #因为class为python关键字,所以使用 #class_来加以区分。string
from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.find_all(string=re.compile('link'))) print(soup.find_all(text=re.compile('link')))#这两条语句等价这里的string=...与text=...等价。使用text=...是,将需要匹配的信息作为要查找的属性。
- find()方法与find_all()类似,不介绍。
如果熟悉Web开发,也可以使用CSS选择器来定位节点。
使用select()方法。tag.select(),选择tag下的指定节点。
以文本2中的html源码为示例:
html='''Hello
- Foo
- Bar
- Jay
- Foo
- Bar
from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.select('.panel .panel-heading')) print(soup.select('ul li')) print(soup.select('#list-2 .element')) print(type(soup.select('ul')[0]))运行结果:
[] [Hello
Foo ,Bar ,Jay ,Foo ,Bar ] [Foo ,Bar ]select方法会将定位到的节点以列表形式返回,列表中的元素为bs4.element.Tag类型。
参考资料
Python3 网络爬虫开发实战(第2版)
Beautiful Soup 中文文档



