BeautifulSoup4

BeautifulSoup4

基本操作

导入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from bs4 import BeautifulSoup
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>
"""
soup = BeautifulSoup(html_doc,"html.parser")
解析器使用方法优势劣势
Python标准库BeautifulSoup(markup, "html.parser")Python的内置标准库执行速度适中文档容错能力强Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器BeautifulSoup(markup, "lxml")速度快文档容错能力强需要安装C语言库
lxml XML 解析器BeautifulSoup(markup, ["lxml-xml"])``BeautifulSoup(markup, "xml")速度快唯一支持XML的解析器需要安装C语言库
html5libBeautifulSoup(markup, "html5lib")最好的容错性以浏览器的方式解析文档生成HTML5格式的文档速度慢不依赖外部扩展

标准缩进格式

1
print(soup.prettify())

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<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 class="sister" href="http://example.com/elsie" id="link1">
    Elsie
   </a>
   ,
   <a class="sister" href="http://example.com/lacie" id="link2">
    Lacie
   </a>
   and
   <a class="sister" href="http://example.com/tillie" id="link3">
    Tillie
   </a>
   ;
and they lived at the bottom of a well.
  </p>
  <p class="story">
   ...
  </p>
 </body>
</html>

对象

Tag

每个标签节点就是一个 Tag 对象

1
2
3
soup.head
soup.a 		#输出soup中的第一个a标签
soup.p.b  	#p下的b标签

输出

1
2
3
<head><title>The Dormouse's story</title></head>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
<b>The Dormouse's story</b>

以下是 Tag 对象的部分属性

.name

标签名属性

1
2
soup.head.name
soup.a.name

输出

1
2
head
a

.string

文本内容属性

1
2
soup.head.string
soup.a.string

输出

1
2
The Dormouse's story
Elsie

[‘attributes’]

标签指定属性属性

1
2
soup.a['id']	#返回字符串
soup.a['class']	#多值属性返回list

返回列表

1
2
link1
['sister'] 

如果转换的文档是 XML 格式,那么 tag 中不包含多值属性

.attrs

1
soup.a.attrs

输出字典

1
{'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}

字符串常被包含在 tag 内.Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串

直接使用 .stirng 即可获取

将其转换为 unicode 字符串

1
2
uni_str = unicode(soup.head.string) #在python3中不适用
uni_str = str(soup.head.string)

replace_with()

tag 中包含的字符串不能编辑,但是可以被替换成其它的字符串

1
soup.head.string.replace_with('Harry potter')

BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容

.name

BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性,用.name调用

1
soup.name

输出

1
'[document]'

Comment

Comment 对象是一个特殊类型的 NavigableString 对象,是文档的注释部分

1
2
3
4
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)

当它出现在 HTML 文档中时, Comment 对象会使用特殊的格式输出

1
bs4.element.Comment

遍历文档树

遍历文档树,就是是从根节点 html 标签开始遍历,直到找到目标元素为止,

通过遍历文档树的方式获取标签节点可以直接通过 .标签名的方式获取

  • 缺陷
    • 如果要找的内容在文档的末尾,那要遍历整个文档才能找到它,速度上就慢了
    • 只能获取到与之匹配的第一个子节点

子节点

.contents

Tag

Tag.contents` 属性可以将 tag 的子节点以列表的方式输出

1
2
soup.body.contents
soup.body.contents[1]

以列表输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
['\n',
 <p class="title"><b>The Dormouse's story</b></p>,
 '\n',
 <p class="story">Once upon a time there were three little sisters; and their names were
 <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
 and they lived at the bottom of a well.</p>,
 '\n',
 <p class="story">...</p>,
 '\n']

<p class="title"><b>The Dormouse's story</b></p>

字符串NavigableString没有 .contents 属性,因为字符串没有子节点

1
soup.head.string.cotents

报错

1
AttributeError: 'NavigableString' object has no attribute 'cotents'
BeautifulSoup

BeautifulSoup 对象本身一定会包含子节点,也就是说<html>标签也是 BeautifulSoup 对象的子节点

1
soup.contents

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
['\n',
 <html><head><title>Harry potter</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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
 and they lived at the bottom of a well.</p>
 <p class="story">...</p>
 </body></html>]

.children

通过 tag 的 .children 生成器,可以对 tag 的子节点进行循环

1
2
for child in soup.body.children:
    print(child)

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13


<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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>


<p class="story">...</p>

.descendants

.contents.children 属性仅包含 tag 的直接子节点,而.descendants 生成器可以对所有 tag 的子孙节点进行递归循环

1
2
for child in soup.body.descendants:
    print(child)

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<p class="title"><b>The Dormouse's story</b></p>
<b>The Dormouse's story</b>
The Dormouse's story


<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
Once upon a time there were three little sisters; and their names were

<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
Elsie
,

<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
Lacie
 and

<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
Tillie
;
and they lived at the bottom of a well.


<p class="story">...</p>
...

父节点

.parent

通过 .parent 属性来获取某个元素的父节点

1
2
3
4
soup.title.parent
soup.title.string.parent 	#字符串也有父节点
type(soup.html.parent)		#文档的顶层节点比如<html>的父节点是 BeautifulSoup 对象
print(soup.parent)			#BeautifulSoup 对象的 .parent 是None

输出

1
2
3
4
<head><title>Harry potter</title></head>
<title>Harry potter</title>
bs4.BeautifulSoup
None

.parents

通过元素的 .parents 生成器可以递归得到元素的所有父辈节点

1
2
for parent in soup.b.parents:
    print(parent.name)

输出

1
2
3
4
p
body
html
[document]

兄弟节点

.next_sibling 和 .previous_sibling

1
2
soup.a.next_sibling   				#真实结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符
soup.a.next_sibling.next_sibling

输出

1
2
',\n'
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

.next_siblings 和 .previous_siblings

通过 .next_siblings.previous_siblings 生成器可以对当前节点的兄弟节点迭代输出

1
2
for sibling in soup.a.next_siblings:
    print(repr(sibling))

输出

1
2
3
4
5
',\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.'

搜索文档树

搜索文档树是通过指定标签名来搜索元素,另外还可以通过指定标签的属性值来精确定位某个节点元素

最常用的两个方法就是 find 和 find_all。这两个方法在 BeatifulSoup 和 Tag 对象上都可以被调用

过滤器

过滤器贯穿整个搜索的 API。过滤器可以被用在 tag 的 name 中,节点的属性中,字符串中或他们的混合中

字符串

在搜索方法中传入一个字符串参数,Beautiful Soup 会查找与字符串完整匹配的内容

1
soup.find_all('a')

输出

1
2
3
[<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>]

正则表达式

Beautiful Soup 会通过正则表达式的 match() 来匹配内容

下面代码找出所有名字中包含”t”的标签

1
2
3
import re
for tag in soup.find_all(re.compile("t")):
    print(tag.name)

输出

1
2
html
title

列表

Beautiful Soup 会将与列表中任一元素匹配的内容返回

下面代码找到文档中所有<a>标签和<b>标签

1
soup.find_all(["a", "b"])

输出

1
2
3
4
[<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>]

True

True 可以匹配任何值

下面代码查找到所有的 tag 名

1
2
for tag in soup.find_all(True):
    print(tag.name)

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
html
head
title
body
p
b
p
a
a
a
p

方法

如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 [../../Archive/HomeWork/计算机英语/4] ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False

下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True

1
2
3
def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')
soup.find_all(has_class_but_no_id)

输出

1
2
3
4
5
6
7
[<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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
 and they lived at the bottom of a well.</p>,
 <p class="story">...</p>]	

通过一个方法来过滤一类标签属性的时候, 这个方法的参数是要被过滤的属性的值, 而不是这个标签

下面的例子是找出 href 属性不符合指定正则的 a 标签

1
2
3
def not_lacie(href):
        return href and not re.compile("lacie").search(href)
soup.find_all(href=not_lacie)

输出

1
2
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

find_all()

1
find_all( name , attrs , recursive , string , **kwargs )

find_all() 方法搜索当前 tag 的所有 tag 子节点,并判断是否符合过滤器的条件

任意参数的值可以是任一类型的过滤器,字符串,正则表达式,列表,方法或是 True

返回列表

name

name 参数可以查找所有名字为 name 的 tag,字符串对象会被自动忽略掉

1
soup.find_all("a")

输出

1
2
3
[<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>]

keyword

  1. 如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字 tag 的属性来搜索
1
2
3
soup.find_all(id='link2')
soup.find_all("a",'sister',href=re.compile('^.*?la'))
soup.find_all(id=True)

输出

1
2
3
4
5
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</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>]
  1. 有些 tag 属性在搜索不能使用,比如 HTML5 中的 data-* 属性,但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的 tag

  2. 标识 CSS 类名的关键字 class 在 Python 中是保留字,使用 class 做参数会导致语法错误.从 Beautiful Soup 的 4.1.1 版本开始,可以通过 class_ 参数搜索有指定 CSS 类名的 tag

1
2
3
4
5
soup.find_all("a", class_="sister")

def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 6
soup.find_all(class_=has_six_characters)                 #与第一种方法输出相同结果

输出

1
2
3
[<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>]

string

通过 string 参数可以搜搜文档中的字符串内容

1
2
soup.find_all(string="Elsie") #单独使用时,返回字符串
soup.find_all("a", string="Elsie") #与其他参数混合使用时,返回对应的Tag

输出

1
2
['Elsie']
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

limit

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量

1
soup.find_all("a", limit=2)

文档树中有 3 个 tag 符合搜索条件,但结果只返回了 2 个,因为我们限制了返回数量

1
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>]

recursive

调用 tag 的 find_all() 方法时,Beautiful Soup 会检索当前 tag 的所有子孙节点,如果只想搜索 tag 的直接子节点,可以使用参数 recursive=False

1
2
3
4
5
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]

soup.html.find_all("title", recursive=False)
# []

attrs

传入字典

1
css_class = soup.find_all(attrs={'class':'primaryconsumers'})

简写方式

find_all() 几乎是 Beautiful Soup 中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同

下面代码是等价的

1
2
3
4
5
soup.find_all("a")
soup("a")

soup.title.find_all(string=True)
soup.title(string=True)

find()

find( name , attrs , recursive , string, **kwargs )

find() 方法将返回文档中符合条件的第一个 tag

下面两行代码是等价的

1
2
3
4
5
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>

唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果

find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None

soup.head.title 是 tag 的名字方法的简写.这个简写的原理就是多次调用当前 tag 的 find() 方法

1
2
3
4
5
soup.head.title
# <title>The Dormouse's story</title>

soup.find("head").find("title")
# <title>The Dormouse's story</title>

其他方法

  1. find_parents()和 find_parent()

    前者返回所有祖先节点,后者返回直接父节点。

    1
    2
    3
    
    p_story = soup.find(class_='story')
    for i in p_story.find_parents():
        print(i.name)
    

    输出

    1
    2
    3
    
    body
    html
    [document]
    
  2. find_next_siblings()和 find_next_sibling()

    前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。

  3. find_previous_siblings()和 find_previous_sibling()

    前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。

  4. find_all_next()和 find_next()

    前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。

  5. find_all_previous()和 find_previous()

    前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点

CSS选择器

TagBeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用 CSS 选择器的语法找到 tag

其他

复制Beautiful Soup对象

copy.copy() 方法可以复制任意 TagNavigableString 对象

1
2
import copy
p_copy = copy.copy(soup.p)

复制后的对象跟与对象是相等的, 但指向不同的内存地址

1
2
3
4
5
print soup.p == p_copy
# True

print soup.p is p_copy
# False

get_text()

获取标签里面内容,除了可以使用 .string 之外,还可以使用 get_text 方法,不同的地方在于前者返回的一个 NavigableString 对象,后者返回的是 unicode 类型的字符串。

实际场景中我们一般使用 get_text 方法获取标签中的内容。

1
2
soup.head.get_text()
# 'Harry potter'