Skip to content

Beautiful Soup

对于一个网页来说,都有一定的特殊结构和层级关系,而且很多节点都有idclass来作区分

Beautiful Soup 是一个强大的解析工具,它借助网页的结构和属性等特性来解析网页。有了它,我们不用再去写一些复杂的正则表达式,只需要简单的几条语句,就可以完成网页中某个元素的提取

https://www.crummy.com/software/BeautifulSoup/

中文文档:

https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

简介

简单来说,Beautiful Soup 就是 Python 的一个 HTML 或 XML 的解析库,可以用来方便地从网页中提取数据。

官方解析如下:

Beautiful Soup 提供了一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了

Beautiful Soup 已成为和 lxml、html6lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度

所以说,利用它可以省去很多烦琐的提取工作,提高了解析效率

解析器

Beautiful Soup 解析时实际上依赖解析器,它除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器(比如 lxml)

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, "html.parser") Python 的内置标准库、执行速度适中、文档容错能力强
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快,文档容错能力强 需要安装 C 语言库
lxml XML 解析器 BeautifulSoup(markup, "xml") 速度快,唯一支持 XML 的解析器 需要安装 C 语言库
html5lib BeautifulSoup(markup, "html5lib") 最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档 速度慢、不依赖外部扩展

通过以上对比可以看出,lxml 解析器有解析 HTML 和 XML 的功能,而且速度快,容错能力强,所以推荐使用它

1
2
3
4
5
6
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
"""
Hello
"""

基本用法

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
from bs4 import BeautifulSoup
html = """
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>The Dormouse's story</title>
</head>
<body>
  <p class="title" name="dromouse"><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, 'lxml')
print(soup.prettify)
print(soup.title.string)
'''
from bs4 import BeautifulSoup
html = """
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>The Dormouse's story</title>
</head>
<body>
  <p class="title" name="dromouse"><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, 'lxml')
print(soup.prettify)
print(soup.title.string)
'''

这里首先声明变量 hntl,它是一个 HTML 字符串。但是需要注意的是,它并不是一个完整的 HTML 字符串,因为 body 和 html 节点都没有闭合。接着,我们将它当作第一个参数传给 BeautifulSoup 对象,该对象的第二个参数为解析器的类型(这里使用 lxml),此时就完成了 BeautifulSoup 对象的初始化。然后,将这个对象赋值给 soup 变量。

接下来,就可以调用 soup 的各个方法和属性解析这串 HTML 代码了

首先,调用 prettify() 方法。这个方法可以把要解析的字符串以标准的缩进格式输出。这里需要注意的是,输出结果里面包含 body 和 html 节点,也就是说对于不标准的 HTML 字符串,BeautifulSoup 可以自动更正格式。这一步不是由 prettify() 方法做的,而是在初始化 BeautifulSoup 时就完成了

然后调用 soup.title.string,这实际上是输出 HTML 中 title 节点的文本内容。所以,soup.title 可以选出 HTML 中的 title 节点,再调用 string 属性就可以得到里面的文本了

节点选择器

直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析

选择元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
"""
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
<head>
<meta charset="utf-8"/>
<title>The Dormouse's story</title>
</head>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
"""

这里依然选用刚才的 HTML 代码,首先打印输出 title 节点的选择结果,输出结果正是 title 节点加里面的文字内容。接下来,输出它的类型,是 bs4.element.Tag 类型,这是 Beautiful Soup 中的一个重要的数据结构。经过选择器选择后,选择结果都是这种 Tag 类型。Tag 具有一些属性,比如 string 属性,调用该属性,可以得到节点的文本内容,所以接下来的数据结果正是节点的文本内容

接下来,我们又尝试选择了 head 节点,结果也是节点加其内部的所有内容。最后,选择了 p 节点。不过这次情况比较特殊,我们发现结果是第一个 p 节点的内容,后面的几个 p 节点并没有选到。也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,其他的后面节点都会忽略

提取信息

(1) 获取名称

1
2
3
4
print(soup.title.name)
"""
title
"""

(2) 获取属性

1
2
3
4
5
6
print(soup.p.attrs)
print(soup.p.attrs['name'])
"""
{'class': ['title'], 'name': 'dromouse'}
dromouse
"""

可以看到,attrs 的返回结果是字典形式,它把选择的节点的所有属性和属性值组合成一个字典。接下来,如果要获取 name 属性,就相当于从字典中获取某个键值,只需要用中括号加属性名就可以了。

还有一种更简单的获取方式,可以不用谢 attrs,直接在节点元素后面加中括号,传入属性名就可以获取属性值了

1
2
3
4
5
6
print(soup.p['name'])
print(soup.p['class'])
"""
dromouse
['title']
"""

这里需要注意的是,有的返回结果是字符串,有的返回结果是字符串组成的列表。比如,name属性的值是唯一的,返回的结果就是单个字符串。而对于class,一个节点元素可能有多个class,所以返回的是列表。在实际处理过程中,我们要注意判断类型

(3) 获取内容

可以利用string属性获取节点元素包含的文本内容,比如要获取第一个p节点的文本:

1
2
3
4
print(soup.p.string)
"""
The Dormouse's story
"""

再次注意一下,这里选择到的p节点是第一个p节点,获取的文本也是第一个p节点里面的文本

嵌套选择

上面的例子中,我们知道每一个返回结果都是bs4.element.Tag类型,它同样可以继续调用节点进行下一步的选择。比如,我们获取了head节点元素,我们可以继续调用head来选取其内部的节点元素

1
2
3
4
5
6
7
8
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
"""
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
"""

第一行结果是调用head之后再次调用title而选择的title节点元素。然后打印输出了它的类型,可以看到,它仍然是bs4.element.Tag类型。也就是说,我们在Tag类型的基础上再次选择得到的依然还是Tag类型

关联选择

(1) 子节点和子孙节点

选取节点元素之后,如果想要获取它的直接子节点,可以调用contents属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from bs4 import BeautifulSoup
html = """
<!DOCTYPE html>
<html lang="zh">
<head>
  <title>The Dormouse's story</title>
</head>
<body>
  <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"><span>Elsie</span></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, 'lxml')
print(soup.p.contents)
"""
['\n    Once upon a time there were three little sisters; and their names were\n    ', <a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>, ',\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>, ';\n    and they lived at the bottom of a well.\n  ']
"""

可以看到,返回结果是列表形式。p节点里既包含文本,又包含节点,最后会将它们以列表形式统一返回

需要注意的是,列表中的每个元素都是p节点的直接子节点。比如第一个a节点里面包含一层span节点,这相当于孙子节点了,但是返回结果并没有单独把span节点选出来。所以说,contents属性得到的结果是直接子节点的列表

同样,我们可以调用children属性得到相应的结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
print(soup.p.children)
for i, child in enumerate(soup.p.children):
    print(i, child)
"""
<list_iterator object at 0x10e225860>
0 
    Once upon a time there were three little sisters; and their names were

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

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

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

如果要得到所有的子孙节点的话,可以调用descendants属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):
    print(i, child)
"""
<generator object Tag.descendants at 0x10871be58>
0 
    Once upon a time there were three little sisters; and their names were

1 <a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>
2 <span>Elsie</span>
3 Elsie
4 ,

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

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

(2) 父节点和祖先节点

如果要获取某个节点元素的父节点,可以调用parent属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
print(soup.a.parent)
"""
<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"><span>Elsie</span></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>
"""

我们这里选择的是第一个a节点的父节点元素。很明显,它的父节点是p节点,输出结果便是p节点及其内部的内容

需要注意的是,这里输出的仅仅是a节点的直接父节点,而没有再向外寻找父节点的祖先节点。如果想获取所有的祖先节点,可以调用parents属性

 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
34
from bs4 import BeautifulSoup
html = """
<html lang="zh">
<body>
  <p class="story">
    <a href="http://example.com/elsie" class="sister" id="link1"><span>Elsie</span></a>,
  </p>
  <p class="story">...</p>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parents)
print(list(enumerate(soup.a.parents)))
"""
[(0, <p class="story">
<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>,
  </p>), (1, <body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>,
  </p>
<p class="story">...</p>
</body>), (2, <html lang="zh">
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>,
  </p>
<p class="story">...</p>
</body></html>), (3, <html lang="zh">
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1"><span>Elsie</span></a>,
  </p>
<p class="story">...</p>
</body></html>)]
"""

(3) 兄弟节点

 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
from bs4 import BeautifulSoup
html = """
<html lang="zh">
<body>
  <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"><span>Elsie</span></a>
        Hello
    <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>
"""
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling', soup.a.next_sibling)
print('Prev Sibling', soup.a.previous_sibling)
print('Next Siblings', list(enumerate(soup.a.next_siblings)))
print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))
"""
Next Sibling 
        Hello

Prev Sibling 
    Once upon a time there were three little sisters; and their names were

Next Siblings [(0, '\n        Hello\n    '), (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>), (2, ' \n        and\n    '), (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>), (4, ';\n    and they lived at the bottom of a well.\n  ')]
Prev Siblings [(0, '\n    Once upon a time there were three little sisters; and their names were\n    ')]
"""

(4) 提取信息

 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
from bs4 import BeautifulSoup
html = """
<html lang="zh">
<body>
  <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">Bob</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
  </p>
"""
soup = BeautifulSoup(html, 'lxml')
print('Next Sibling:')
print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print('Parent:')  
print(type(soup.a.parents))
print(list(soup.a.parents)[0])
print(list(soup.a.parents)[0].attrs['class'])
"""
Next Sibling:
<class 'bs4.element.Tag'>
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
Lacie
Parent:
<class 'generator'>
<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">Bob</a><a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
</p>
['story']
"""

方法选择器

通过节点进行选择速度非常快,但是如果进行比较复杂的选择的话,它就比较繁琐,不够灵活了幸好,Beautiful Soup 还为我们提供了一些查询方法,比如find_all()find()等,调用它们,然后传入相应的参数,就可以灵活查询了

find_all()

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

(1) name

我们可以通过节点名来查询元素:

 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
from bs4 import BeautifulSoup
html = """
<div class="panel">
  <div class="panel-heading">
    <h4>Hello</h4>
  </div>
  <div class="panel-body">
    <ul class="list" id="list-1">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
      <li class="element">Jay</li>
    </ul>
    <ul class="list list-small" id="list-2">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
    </ul>
  </div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
"""
[<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
<class 'bs4.element.Tag'>
"""

这里我们调用了find_all()方法,传入name参数,其参数值为ul。也就是说,我们想要查询所有ul节点,返回结果是列表类型,长度为2,每个元素依然都是bs4.element.Tag类型
因为都是Tag类型,所以依然可以进行嵌套查询。还是同样的文本,这里查询出所有ul节点后,再继续查询其内部的li节点

1
2
3
4
5
6
for ul in soup.find_all(name='ul'):
    print(ul.find_all(name='li'))
"""
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
"""

接下来,就可以遍历每个li,获取它的文本了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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)
"""
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
Foo
Bar
Jay
[<li class="element">Foo</li>, <li class="element">Bar</li>]
Foo
Bar
"""

(2) attrs

 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
34
from bs4 import BeautifulSoup
html = """
<div class="panel">
  <div class="panel-heading">
    <h4>Hello</h4>
  </div>
  <div class="panel-body">
    <ul class="list" id="list-1" name="elements">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
      <li class="element">Jay</li>
    </ul>
    <ul class="list list-small" id="list-2">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
    </ul>
  </div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={'id': 'list-1'}))
print(soup.find_all(attrs={'name': 'elements'}))
"""
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
"""

对于一些常用的属性,比如idclass等,我们可以不用attrs来传递。比如,要查询idlist-1的节点,可以直接传入id这个参数。还是上面的文本,我们换一种方式来查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
"""
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
"""

这里直接传入id='list-1',就可以查询idlist-1的节点元素了。而对于class来说,由于class在 Python 里是一个关键字,所以后面需要加一个下划线

(3) text
text 参数可用来匹配节点的文本,传入的形式可以是字符串,可以是正则表达式对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import re
from bs4 import BeautifulSoup
html = """
<div class="panel">
  <div class="panel-body">
    <a>Hello, this is a link</a>
    <a>Hello, this is a link, too</a>
  </div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=re.compile('link')))
"""
['Hello, this is a link', 'Hello, this is a link, too']
"""

find()

除了find_all()方法,还有find()方法,只不过后者返回的是单个元素,也就是第一个匹配的元素,而前者返回的是所有匹配的元素组成的列表

 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
34
35
36
from bs4 import BeautifulSoup
html = """
<div class="panel">
  <div class="panel-heading">
    <h4>Hello</h4>
  </div>
  <div class="panel-body">
    <ul class="list" id="list-1">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
      <li class="element">Jay</li>
    </ul>
    <ul class="list list-small" id="list-2">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
    </ul>
  </div>
</div>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
"""
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<class 'bs4.element.Tag'>
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
"""

另外,还有许多其它的查询方法:
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(): 前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点

CSS 选择器

Beautiful Soup 还提供了另外一种选择器,那就是 CSS 选择器。
使用 CSS 选择器时,只需要调用select()方法,传入相应的 CSS 选择器即可

用法

 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
from bs4 import BeautifulSoup
html = """
<div class="panel">
  <div class="panel-heading">
    <h4>Hello</h4>
  </div>
  <div class="panel-body">
    <ul class="list" id="list-1">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
      <li class="element">Jay</li>
    </ul>
    <ul class="list list-small" id="list-2">
      <li class="element">Foo</li>
      <li class="element">Bar</li>
    </ul>
  </div>
</div>
"""
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]))
"""
[<div class="panel-heading">
<h4>Hello</h4>
</div>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
<class 'bs4.element.Tag'>
"""

这里我们用了 3 次 CSS 选择器,返回的结果均是符合 CSS 选择器的节点组成的列表

嵌套选择

select()方法同样支持嵌套选择。例如,先选择所有ul节点,再遍历每个ul节点,选择其li节点

1
2
3
4
5
6
for ul in soup.select('ul'):
    print(ul.select('li'))
"""
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
"""

获取属性

1
2
3
4
5
6
7
8
9
for ul in soup.select('ul'):
    print(ul['id'])
    print(ul.attrs['id'])
"""
list-1
list-1
list-2
list-2
"""

获取文本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
for li in soup.select('li'):
    print('Get Text:', li.get_text())
    print('String:', li.string)
"""
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
Get Text: Jay
String: Jay
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
"""