当前位置: 首页 > news >正文

网页数据的解析提取之Beautiful Soup

前面博客介绍了正则表达式的相关用法,只是一旦正则表达式写得有问题,得到的结果就可能不是我们想要的了。而且每一个网页都有一定的特殊结构和层级关系,很多节点都用id或 class 作区分所以借助它们的结构和属性来提取不也可以吗?

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

Beautiful Soup简介

简单来说,Beautiful Soup是 Python 的一个 HTML或 XML的解析库,我们用它可以方便地从网页中提取数据,其官方解释如下:

Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以无须很多代码就可以写出一个完整的应用程序。 Beautiful Soup 自动将输人文档转换为 Unicode 编码,将输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定具体的编码方式,这时你仅仅需要说明一下原始编码方式就可以了。 Beautiful Soup 已成为和 Ixml、html5lib 一样出色的 Python 解释器,为用户灵活提供不同的解析策略或强劲的速度。

总而言之,利用 Beautiful Soup 可以省去很多烦琐的提取工作,提高解析网页的效率。

解释器

实际上,BeautifulSoup在解析时是依赖解析器的,它除了支持 Python 标准库中的 HTML解析器还支持一些第三方解析器(例如 Ixml )。下表列出了 Beautiful Soup 支持的解析器。

在这里插入图片描述
通过上表的对比可以看出,LXML,解析器有解析 HTML和 XML的功能,而且速度快、容错能力强,所以推荐使用它。

使用 LXML,解析器,只需在初始化 Beautiful Soup 时,把第二个参数改为 lxml 即可:

from bs4 import BeautifulSoupsoup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)

在后面,统一用这个解析器演示 Beautiful Soup 的用法实例。

准备工作

在开始之前,请确保已经正确安装好 Beautiful Soup 和 lxml 这两个库。Beautiful Soup 直接使用pip3 安装即可,命令如下:

pip3 install beautifulsoup4

另外,我们使用的是Ixml这个解析器,所以还需要额外安装lxml这个库。

pip3 install lxml

以上两个库都安装完成后,就可以进行接下来的学习了。

基本使用

下面首先通过实例看看 Beautiful Soup 的基本用法:

from bs4 import BeautifulSouphtml = '''
<html><head><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/lacie" 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)

运行结果如下:

<html><head><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 class="sister" href-="" id="link1"><!-- Elsie -->&lt;</a><a class="sister" href-="" id="link2">Lacie</a>and<a class="sister" href-="" id-="">Tillie</a>;
and they lived at the bottom of a well.</p><p class="story">...</p></body>
</html>The Dormouse's story

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

之后就可以调用 soup 的各个方法和属性解析这串 HTML 代码了。

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

然后调用 soup.title.string,这实际上是输出 HTML 中 title 节点的文本内容。所以,通过soup.title 选出 HTML 中的 title节点,再调用 string属性就可以得到 title节点里面的文本了。你看,我们通过简单调用几个属性就完成了文本提取,是不是非常方便?

节点选择器

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

下面再用一个例子详细说明选择节点的方法:

from bs4 import BeautifulSouphtml = '''
<html><head><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/lacie" 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.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><title>The Dormouse's story</title></head>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

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

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

提取信息

上面演示了通过调用 string 属性获取文本的值,那么如何获取节点名称?如何获取节点属性的值呢?接下来我们就统一梳理一下信息的提取方式。

  1. 获取名称

利用 name属性可以获取节点的名称。还是以上面的文本为例,先选取 title 节点,再调用name 属性就可以得到节点名称:

print(soup.title.name)

运行结果:

title
  1. 获取属性

一个节点可能有多个属性,例如 id 和 class 等,选择这个节点元素后,可以调用 attrs 获取其所有属性:

print(soup.p.attrs)
print(soup.p.attrs['name'])

运行结果:

{'class': ['title'], 'name': 'dromouse'}
dromouse

可以看到,调用 attrs属性的返回结果是字典形式,包括所选择节点的所有属性和属性值。因此要获取 name 属性,相当于从字典中获取某个键值,只需要用中括号加属性名就可以了。例如通过attrs[‘name’]获取 name 属性。

其实这种方式有点烦琐,还有一种更为简单的获取属性值的方式:不用写 attrs,直接在节点元素后面加中括号,然后传入属性名就可以了。样例如下:

print(soup.p['name' ])
print(soup.p['class'])

运行结果如下:

dromouse
['title']

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

  1. 获取内容

这点在前面也提到过,可以利用 string 属性获取节点元素包含的文本内容,例如用如下实例获取第一个p节点的文本:

print(soup.p.string)

运行结果如下:

The Dormouse's story

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

  1. 嵌套选择

在上面的例子中,我们知道所有返回结果都是 bs4.element.Tag类型,Tag类型的对象同样可以继续调用节点进行下一步的选择。例如,我们获取了 head 节点,就可以继续调用 head 选取其内部的 head节点:

from bs4 import BeautifulSouphtml = '''
<html><head><title>The Dormouse's story</title></head>
<body>
'''soup = BeautifulSoup(html, 'lxml')
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 类型。既然每次返回的结果都相同,那么就可以做嵌套选择了。

最后一行结果输出了 title 节点的 string 属性,也就是节点里的文本内容。

关联选择

在做选择的过程中,有时不能一步就选到想要的节点,需要先选中某一个节点,再以它为基准选子节点、父节点、兄弟节点等,下面就介绍一下如何选择这些节点。

  1. 子节点和子孙节点
    选取节点之后,如果想要获取它的直接子节点,可以调用contents 属性,实例如下:
from bs4 import BeautifulSouphtml = '''
<html>
<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)

运行结果如下:

['Once upon a time there were three little sisters; and their names were\n    ', <a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>, '\n', <a class="sister" href-="" id="link2">Lacie</a>, '\n    and', <a class="sister" href_="" id="link3">Tillie</a>, '\n    and they lived at the bottom of a well.\n    ']

可以看到,返回结果是列表形式。p节点里既包含文本,又包含节点,这些内容会以列表形式统一返回。

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

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

soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):print(i, child)

运行结果如下:

<list_iterator object at 0x000001DF442F5B80>
0 Once upon a time there were three little sisters; and their names were1 <a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
2 3 <a class="sister" href-="" id="link2">Lacie</a>
4 and
5 <a class="sister" href_="" id="link3">Tillie</a>
6 and they lived at the bottom of a well.

还是同样的 HTML 文本,这里调用 children 属性来选择,返回结果是生成器类型。然后,我们用 for 循环输出了相应的内容。

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

soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):print(i, child)

运行结果如下:

**<generator object Tag.descendants at 0x000001ED5B9B5D60>
0 Once upon a time there were three little sisters; and their names were1 <a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
2 3 <span>Elsie</span>
4 Elsie
5 6 7 <a class="sister" href-="" id="link2">Lacie</a>
8 Lacie
9 and
10 <a class="sister" href_="" id="link3">Tillie</a>
11 Tillie
12 and they lived at the bottom of a well.**

你会发现,此时返回结果还是生成器。遍历输出一下可以看到,这次的输出结果中就包含了 span节点,因为 descendants 会递归查询所有子节点,得到所有的子孙节点。

  1. 父节点和祖先节点

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

from bs4 import BeautifulSouphtml = '''
<html>
<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></p><p class="story">...</p>
'''soup = BeautifulSoup(html, 'lxml')
print(soup.a.parent)

运行结果如下:

<p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
</p>

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

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

from bs4 import BeautifulSouphtml = '''
<html>
<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></p><p class="story">...</p>
'''soup = BeautifulSoup(html, 'lxml')
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))

运行结果如下:

<class 'generator'>
[(0, <p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
</p>), (1, <body>
<p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
</p>
<p class="story">...</p>
</body>), (2, <html>
<body>
<p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
</p>
<p class="story">...</p>
</body></html>), (3, <html>
<body>
<p class="story">Once upon a time there were three little sisters; and their names were<a class="sister" href_="" id="link1">
<span>Elsie</span>
</a>
</p>
<p class="story">...</p>
</body></html>)]

可以发现,返回结果是生成器类型。这里用列表输出了其索引和内容,列表中的元素就是a节点的祖先节点。

  1. 兄弟节点

子节点和父节点的获取方式已经介绍完毕,如果要获取同级节点,也就是兄弟节点,又该怎么办呢?实例如下:

from bs4 import BeautifulSouphtml = '''
<html>
<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 HelloPrev Sibling Once upon a time there were three little sisters; and their names wereNext Siblings [(0, '\n    Hello\n    '), (1, <a class="sister" href-="" id="link2">Lacie</a>), (2, '\n    and\n    '), (3, <a class="sister" href_="" id="link3">Tillie</a>), (4, '\n    and they lived at the bottom of a well.\n    ')]
Prev Siblings [(0, 'Once upon a time there were three little sisters; and their names were\n    ')]

可以看到,这里调用了4个属性。next_sibling和 previous_sibling 分别用于获取节点的下一个和上一个兄弟节点,next_siblings 和 previous_siblings 则分别返回后面和前面的所有兄弟节点。

  1. 提取信息

前面讲过关联元素节点的选择方法,如果想要获取它们的一些信息,例如文本、属性等,也可以用同样的方法,实例如下:

from bs4 import BeautifulSouphtml = '''
<html>
<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']

如果返回结果是单个节点,那么可以直接调用 string、attrs 等属性获得其文本和属性;如果返回结果是包含多个节点的生成器,则可以先将结果转为列表,再从中取出某个元素,之后调用 string、attrs 等属性即可获取对应节点的文本和属性。

方法选择器

前面讲的选择方法都是基于属性来选择的,这种方法虽然快,但是在进行比较复杂的选择时,会变得比较烦琐,不够灵活。幸好,BeautifulSoup还为我们提供了一些査询方法,例如 find_all和 find等,调用这些方法,然后传人相应的参数,就可以灵活查询了。

  1. find_all

find_all,顾名思义就是查询所有符合条件的元素,可以给它传人一些属性或文本来得到符合条件的元素,功能十分强大。它的API如下:

find all(name ,attrs,recursive ,text ,**kwargs)
  1. name

我们可以根据 name 参数来查询元素,下面用一个实例来感受一下:

from bs4 import BeautifulSouphtml = '''
<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 节点:

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

除了根据节点名查询,我们也可以传人一些属性进行查询,下面用一个实例感受一下:

from bs4 import BeautifulSouphtml = '''
<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>]

这里查询的时候,传入的是 attrs 参数,其属于字典类型。例如,要査询id为 list-1的节点,就可以传入 attrs={‘id’:‘list-1’}作为查询条件,得到的结果是列表形式,列表中的内容就是符合id为 list-1这一条件的所有节点。在上面的实例中,符合条件的元素个数是1,所以返回结果是长度为1的列表。

对于一些常用的属性,例如 id 和 class 等,我们可以不用 attrs 传递。例如,要査询id为list-1的节点,可以直接传人 id这个参数。还是使用上面的文本,只不过换一种方式来查询:
``python
soup = BeautifulSoup(html, ‘lxml’)
print(soup.find_all(id=‘list-1’))
print(soup.find_all(class_=‘element’))


运行结果如下:
```text
[<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’,就可以査询id为list-1的节点元素了。而对于class 来说,由于 class在 Python 里是一个关键字,所以后面需要加一个下划线,即 class_=‘element’,返回结果依然是 Tag对象组成的列表。

  • string(老版本是text)
    string 参数可以用来匹配节点的文本,其传人形式可以是字符串,也可以是正则表达式对象,实例如下:
from bs4 import BeautifulSoup
import rehtml = '''
<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(string=re.compile('link')))

运行结果如下:

['Hello,this is a link', 'Hello,this is a link,too']

这里有两个a节点,其内部包含文本信息。这里在 find_all方法中传入 text 参数,该参数为正则表达式对象,返回结果是由所有与正则表达式相匹配的节点文本组成的列表。

  • find
    除了 find_all方法,还有 find方法也可以査询符合条件的元素,只不过 find方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 会返回由所有匹配的元素组成的列表。实例如下:
from bs4 import BeautifulSouphtml = '''
<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(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))

可以看到,返回结果不再是列表形式,而是第一个匹配的节点元素,类型依然是Tag 类型。

另外还有许多查询方法,用法与介绍过的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:前者返回节点前面所有符合条件的节点,后者返回前面第一个符合条件的节点。

CSS选择器

BeautifulSoup还提供了另外一种选择器–CSS 选择器。如果你熟悉 Web开发,那么肯定对 CSS
选择器不陌生。

使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可。我们用一个实例感受一下:

from bs4 import BeautifulSouphtml = '''
<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.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(‘u1 li’)表示选择所有 u1 节点下面的所有 li 节点,结果便是所有 li 节点组成的列表。

在最后一句中,我们打印输出了列表中元素的类型。可以看到,类型依然是Tag类型。

  1. 嵌套选择

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

soup = BeautifulSoup(html, 'lxml')
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>]

可以看到,正常输出了每个叫节点下所有li节点组成的列表。

  1. 获取属性

既然知道节点是 Tag类型,于是获取属性依然可以使用原来的方法。还是基于上面的 HTML文本,这里尝试获取每个ul节点的id 属性:

soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):print(ul['id'])print(ul.attrs['id'])

运行结果如下:

list-1
list-1
list-2
list-2

可以看到,直接将属性名传入中括号和通过 attrs 属性获取属性值,都是可以成功获取属性的。

  1. 获取文本

要获取文本,当然也可以用前面所讲的 string属性。除此之外,还有一个方法,就是 get_text,实例如下:

soup = BeautifulSoup(html, 'lxml')
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

二者的实现效果完全一致,都可以获取节点的文本值。

总结

到此,Beautiful Soup 的介绍基本就结束了,最后做一下简单的总结。

  • 推荐使用 LXML解析库,必要时使用 html.parser。
  • 节点选择器筛选功能弱,但是速度快。
  • 建议使用 find、find_all方法查询匹配的单个结果或者多个结果口如果对 CSS 选择器熟悉,则可以使用 select 选择法。

相关文章:

网页数据的解析提取之Beautiful Soup

前面博客介绍了正则表达式的相关用法&#xff0c;只是一旦正则表达式写得有问题&#xff0c;得到的结果就可能不是我们想要的了。而且每一个网页都有一定的特殊结构和层级关系&#xff0c;很多节点都用id或 class 作区分所以借助它们的结构和属性来提取不也可以吗? 本篇博客我…...

Ai写作人工智能官网模板源码

Mortal是响应式的Tailwind CSS 模板&#xff0c;适用于AI写作和文案智能生成网站。 可用于撰写博客内容、数字广告文案、技术写作、SEO内容、登陆页面文案、社交媒体内容、电子邮件营销、网站文案等。使用世界上流行的响应式CSS框架Tailwind CSS、HTML5、CSS3 和 Javascript构…...

VSCode 插件开发实战(七):插件支持了哪些事件,以及如何利用和监听这些事件

前言 VSCode 作为现代开发者的首选编辑器之一&#xff0c;其核心优势在于其高度可扩展性。通过自定义插件&#xff0c;开发者可以根据自己的需求对编辑器进行功能扩展和优化。在这些插件开发过程中&#xff0c;事件处理和监听机制尤为重要&#xff0c;它们允许插件在特定事件发…...

现货量化合约跟单系统开发策略指南

随着加密货币市场的日益发展&#xff0c;量化交易和合约跟单已经成为了投资者在市场中获取稳定收益的重要手段。现货量化合约跟单系统作为一种自动化交易工具&#xff0c;可以帮助用户自动执行交易策略&#xff0c;同时也能跟随成功的交易者进行复制交易&#xff0c;从而降低投…...

Flink的Watermark水位线详解

一、Flink的时间语义 Flink有如下三种时间语义&#xff1a; Flink的三种时间语义-CSDN博客 在实际应用中&#xff0c;一般会采用事件时间语义。而正如前面所说的&#xff0c;事件时间语义需要等窗口的数据全部到齐了&#xff0c;才能进行窗口计算。那么&#xff0c;什么时候数…...

香港 GPU 服务器托管引领 AI 创新,助力 AI 发展

在当今科技飞速发展的时代&#xff0c;中国人工智能市场呈现出蓬勃发展的态势&#xff0c;对高性能计算资源的需求日益增长&#xff0c;而香港 GPU 服务器托管服务凭借其卓越的优势&#xff0c;成为众多企业的首选&#xff0c;同时其三地灾备方案更是为企业数据安全和业务连续性…...

FFmpeg来从HTTP拉取流并实时推流到RTMP服务器

当使用FFmpeg来从HTTP拉取流并实时推流到RTMP服务器时&#xff0c;你可以使用以下命令&#xff1a; ffmpeg -i http://输入流地址 -c:v copy -c:a copy -f flv rtmp://RTMP服务器地址/应用名称/流名称 这是一个基本的命令示例&#xff0c;其中&#xff1a; - -i http://输入流地…...

vue 集成 webrtc-streamer 播放视频流 - 解决阿里云内外网访问视频流问题

资料&#xff1a; 史上最详细的webrtc-streamer访问摄像机视频流教程-CSDN博客 webrtc目录 前端集成 html文件夹里的webrtcstreamer.js&#xff0c;集成到前端&#xff0c;可以访问webrtc&#xff0c;转换rtsp为webrtc视频流&#xff0c;在前端video中播放 <videoref&quo…...

Spring创建异步线程池方式

在Java 11中&#xff0c;可以通过多种方式创建异步线程池&#xff0c;包括使用原生的ExecutorService和Spring的异步支持&#xff08;如Async注解结合线程池&#xff09;。以下是具体实现方式。 方式 1&#xff1a;使用原生ExecutorService Java 11 的ExecutorService提供灵活…...

《PHP MySQL 插入数据》

《PHP MySQL 插入数据》 介绍 PHP是一种广泛使用的服务器端脚本语言&#xff0c;而MySQL是一种流行的关系型数据库管理系统。在Web开发中&#xff0c;经常需要将用户输入的数据存储到数据库中。本文将详细介绍如何使用PHP和MySQL实现数据的插入操作。 环境准备 在开始之前&…...

2022博客之星年度总评选开始了

作者简介&#xff1a;陶然同学 专注于Java领域开发 熟练掌握Java、js等语言的“Hello World” CSDN原力计划作者、CSDN内容合伙人、Java领域优质作者、Java领域新星作者、51CTO专家、华为云专家、阿里云专家等 &#x1f3ac; 陶然同学&#x1f3a5; 由 陶然同学 原创&#…...

0055. shell命令--useradd

目录 55. shell命令--useradd 功能说明 语法格式 选项说明 选项 退出值 相关文件 /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/skel/ /etc/login.defs /etc/default/useradd 实践操作 注意事项 55. shell命令--useradd 功能说明 useradd 命令是 Lin…...

HTML5适配手机

要使 HTML5 网站适配手机设备&#xff0c;您可以遵循以下几个步骤和最佳实践&#xff1a; 1. 使用视口&#xff08;Viewport&#xff09; 在 HTML 文档的 <head> 部分添加视口元标签&#xff0c;以确保页面在移动设备上正确缩放和显示&#xff1a; <meta name"…...

网络安全 | 5G网络安全:未来无线通信的风险与对策

网络安全 | 5G网络安全&#xff1a;未来无线通信的风险与对策 一、前言二、5G 网络的技术特点2.1 超高速率与低延迟2.2 大容量连接与网络切片 三、5G 网络面临的安全风险3.1 网络架构安全风险3.2 设备终端安全风险3.3 应用场景安全风险3.4 用户隐私安全风险 四、5G 网络安全对策…...

Markov test笔记

补充知识 来源于数学之美第五章&#xff1a; 到了 19 世纪&#xff0c;概率论的发展从相对静止的随机变量的研究发展到随机变量的时间序列 ( s 1 , s 2 , s 3 , … ) (s_1, s_2, s_3, \dots) (s1​,s2​,s3​,…)&#xff0c;即随机过程&#xff08;动态的&#xff09;。这在…...

docker 搭建集群

准备3台机器&#xff1a; #dockermaster 192.168.31.150 sudo hostnamectl set-hostname dockermaster #初始化主节点 docker swarm init --advertise-addr 192.168.31.150 #查看集群是否搭建成功 docker node ls #dockernode1 192.168.31.151 sudo hostnamectl set-hostname …...

C# WPF读写STM32/GD32单片机Flash数据

1.安装jlink 下载你需要的Jlink版本 JLink-Windows-V792k-x86-64 JLink-Windows-V810k-x86-64 https://download.csdn.net/download/hmxm6/90178195 2.Visual Studio创建WPF项目 如果没有这个选项请看 https://blog.csdn.net/hmxm6/article/details/132914337 创建完…...

[图形渲染]【Unity Shader】【游戏开发】 Shader数学基础17-法线变换基础与应用

在计算机图形学中,法线(normal) 是表示表面方向的向量。它在光照、阴影、碰撞检测等领域有着重要作用。本文将介绍如何在模型变换过程中正确变换法线,确保其在光照计算中的正确性,特别是法线与顶点的变换问题。 1. 法线与切线的基本概念 法线(Normal Vector) 法线(或…...

MySQL外键类型与应用场景总结:优缺点一目了然

前言&#xff1a; MySQL的外键简介&#xff1a;在 MySQL 中&#xff0c;外键 (Foreign Key) 用于建立和强制表之间的关联&#xff0c;确保数据的一致性和完整性。外键的作用主要是限制和维护引用完整性 (Referential Integrity)。 主要体现在引用操作发生变化时的处理方式&…...

Axure10

如果还是不行就将字体图标安装在控制面板–字体下 打开原型了之后&#xff0c;icon没有 一定要将字体库放到–》控制面板\外观和个性化\字体 里面...

数据结构(单向循环链表)

循环单向链表 所谓的循环&#xff0c;指得是将链表末尾节点的后继指针指向头结点。比如&#xff0c;单向链表变成循环链表的示意 图如下所示&#xff1a; 循环链表的操作跟普通链表操作基本上是一致的&#xff0c;只要针对循环特性稍作修改即可。 sclist.h #ifndef __SCLIST_…...

springboot项目搭建

springboot搭建 问题描述不够清晰&#xff0c;无法提供具体的代码解决方案。"springboot搭" 这个表述不明确是要进行什么操作&#xff0c;比如搭建项目、搭建环境、搭建服务等。 如果你是想要创建一个基本的Spring Boot项目&#xff0c;可以使用Spring Initializr&…...

五模型对比!Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 光伏功率预测&#xff01;五模型对比&#xff01;Transformer-GRU、Transformer、CNN-GRU、GRU、CNN五模型多变量时间序列预测(Matlab2023b 多输入单输出) 1.程序已经调试好&#xff0c;替换数据集后&#xff0c;仅运…...

02-18.python入门基础一基础算法

&#xff08;一&#xff09;排序算法 简述&#xff1a; 在 Python 中&#xff0c;有多种常用的排序算法&#xff0c;下面为你详细介绍几种常见的排序算法及其原理、实现代码、时间复杂度以及稳定性等特点&#xff0c;并对比它们适用的场景。 冒泡排序&#xff08;Bubble Sor…...

条款19 对共享资源使用std::shared_ptr

目录 一、std::shared_ptr 二、std::shared_ptr性能问题 三、control block的生成时机 四、std::shared_ptr可能存在的问题 五、使用this指针作为std::shared_ptr构造函数实参 六、std::shared_ptr不支持数组 一、std::shared_ptr<T> shared_ptr的内存模型如下图&…...

TCP-UDP调试工具推荐:Socket通信测试教程(附详细图解)

前言 在网络编程与应用开发中&#xff0c;调试始终是一项不可忽视的重要环节。尤其是在涉及TCP/IP、UDP等底层网络通信协议时&#xff0c;如何确保数据能够准确无误地在不同节点间传输&#xff0c;是许多开发者关注的核心问题。 调试的难点不仅在于定位连接建立、数据流控制及…...

算法练习——模拟题

前言&#xff1a;模拟题的特点在于没有什么固定的技巧&#xff0c;完全考验自己的代码能力&#xff0c;因此有助于提升自己的代码水平。如果说一定有什么技巧的话&#xff0c;那就是有的模拟题能够通过找规律来简化算法。 一&#xff1a;替换所有问号 题目要求&#xff1a; 解…...

Windows下播放文件作为麦克风声源的一种方式

近期测试一种外语的ASR识别成功率&#xff0c;样本素材是懂这门语言的同事录制的mp3文件。测试client端原本是从麦克风拾音生成媒体流的。 这样&#xff0c;就需要想办法把mp3文件转换为测试client的输入声音。物理方式上&#xff0c;可以用一根音频线&#xff0c;把电…...

微信流量主挑战:用户数30!新增文档转化功能,解决docker运行jar包报错SimSun找不到的问题(新纪元5)

哎呀&#xff0c;今天忙到飞起&#xff0c;文章晚点更新啦&#xff01;不过好消息是&#xff0c;我们的小程序用户终于突破30啦&#xff0c;感谢大家的支持&#xff01;而且&#xff0c;大家期待已久的文档转化功能明天就要上线啦&#xff0c;目前支持word转pdf&#xff0c;pdf…...

BUU LFI COURSE 1

BUU LFI COURSE 1 启动环境 isset函数检查输入是否为空&#xff0c;使用GET传参file&#xff0c;然后赋值给$str 在调用传参内容 我们是找flag那我们输入?file/flag试试 输入后就得到了flag flag{8c108da2-a579-4ec4-b447-92d9265b8dd4}...

Spark SQL DML语句

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 Spark本地模式安装_spark3.2.2本地模式安装-CSDN博客 DML&#xff08;Data Manipulation Language&#xff0c;数据操作语言&#xff09;操作主要用来对…...

逻辑控制语句

一、逻辑控制语句 条件判断 if循环 for、while 二、条件判断 if 1、语法 if 条件:条件为真的操作条件为真的操作 else:条件为假的操作条件为假的操作 data_01 int(input("数字: "))if data_01 > 10:print("ok!!!")print("正确!!!")prin…...

PlasmidFinder:质粒复制子的鉴定和分型

质粒&#xff08;Plasmid&#xff09;是一种细菌染色体外的线性或环状DNA分子&#xff0c;也是一种重要的遗传元素&#xff0c;它们具有自主复制能力&#xff0c;可以在细菌之间传播&#xff0c;并携带多种重要的基因(如耐药基因与毒力基因等)功能。根据质粒传播的特性&#xf…...

OSCP打靶大冒险之Solidstate:多端口获取信息,shell逃逸,计划任务提权

声明&#xff01; 学习资源来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…...

【Java-tesseract】OCR图片文本识别

文章目录 一、需求二、概述三、部署安装四、技术细节五、总结 一、需求 场景需求:是对识别常见的PNG,JPEG,TIFF,GIF图片识别&#xff0c;环境为离线内网。组件要求开源免费&#xff0c;并且可以集成Java生成接口服务。 二、概述 我不做选型对比了,我筛选测试了下Tesseract(v…...

sqlserver 数据库误删-用mdf和ldf文件恢复

1.准备好需要恢复的文件 2.安装sqlserver数据库&#xff0c;安装设置的实例目录要记清 3.将需要恢复的文件拷到实例所在目录下的DATA文件夹下 D:\安装时的实例目录\MSSQL10_50.MSSQLSERVER\MSSQL\DATA 4.打开 SQL Server Management Stadio执行以下命令 CREATE DATABASE 数…...

机器学习算法基础知识1:决策树

机器学习算法基础知识1&#xff1a;决策树 一、本文内容与前置知识点1. 本文内容2. 前置知识点 二、场景描述三、决策树的训练1. 决策树训练方式&#xff08;1&#xff09;分类原则-Gini&#xff08;2&#xff09;分类原则-entropy&#xff08;3&#xff09;加权系数-样本量&am…...

使用EasyExcel来动态生成表头

本文记录下使用EasyExcel来动态生成表头 文章目录 概述 概述...

梳理你的思路(从OOP到架构设计)_介绍Android的Java层应用框架03

目录 1、认识Android框架的实践技术 4个嫡系基类 誰來創建子類的對象呢? 2、Intent-based Programming 技术 嫡系应用子类之间如何互相沟通呢&#xff1f; 1、认识Android框架的实践技术 4个嫡系基类 • Android框架里提供了4个嫡系的基类&#xff0c;包括&#xff1a;…...

Html——10 关键字和描述

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>淘宝网</title><meta name"keywords" content"我要自学网,自学HTML,自学CSS"/><meta name"description" content"要设置…...

汇编学习笔记

汇编 1. debug指令 -R命令(register) 查看、改变CPU寄存器的内容 r ax 修改AX中的内容 -D命令(display) 查看内存中的内容 -E命令(enter) 改写内存中的内容 -U命令(unassenble反汇编) 将内存中的机器指令翻译成汇编指令 -T命令(trace跟踪) 执行一条机器指令 -A命令…...

【C++】统计正整数的位数:题目解析与代码优化

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述**题目要求&#xff1a;统计正整数的位数** &#x1f4af;我的代码实现**核心逻辑解析** &#x1f4af;老师的代码实现**老师代码逻辑解析** &#x1f4af;我的代码…...

CF2043b-B. Digits

题目链接 题意&#xff1a;给定两个整数n、d&#xff0c;要求找出排列成n!个d之后的数可以被1-9中奇数整除的数 题解&#xff1a; 主要是考察分类讨论&#xff1a; 被3整除&#xff0c;当d能被3整除时一定成立或者n > 3&#xff0c;当n > 3时n!一定包含因数3 被5整除&a…...

[文献阅读]ReAct: Synergizing Reasoning and Acting in Language Models

文章目录 摘要Abstract:思考与行为协同化Reason(Chain of thought)ReAct ReAct如何协同推理 响应Action&#xff08;动作空间&#xff09;协同推理 结果总结 摘要 ReAct: Synergizing Reasoning and Acting in Language Models [2210.03629] ReAct: Synergizing Reasoning an…...

React 高阶组件(HOC)

文章目录 一. 高阶组件&#xff08;HOC&#xff09;的定义二. HOC 的作用和优势三. HOC 的使用方式四. HOC 的注意事项和潜在问题五. 应用场景1. 权限控制与认证2. 数据获取与预加载3. 样式和主题管理4. 性能优化 - 缓存数据或组件渲染结果5. 日志记录与调试辅助 六. 总结 一. …...

module ‘django.db.models‘ has no attribute ‘FieldDoesNotExist‘

module ‘django.db.models’ has no attribute ‘FieldDoesNotExist’ xadmin报错 原因 django与xadmin版本不匹配。 django==3.2.7 xadmin-django==3.0.2解决方案 在xadmin/view/edit.py的388行改为 from django.core import exceptions if self.request_method ==...

仓颉语言实战——1. 类型

仓颉语言实战——1. 类型 仓颉语言&#xff08;Cangjie Language&#xff09;是一个现代化的、简洁而强大的编程语言&#xff0c;它的类型系统为高效开发提供了极大的支持。本篇文章将围绕仓颉语言中的类型系统展开&#xff0c;结合实战代码&#xff0c;帮助开发者快速掌握这一…...

大数据平台开发学习路线及技能

背景 最近项目涉及这方面&#xff0c;特地整理学习路线方便后续学习。 必备技能 一、编程语言 Java&#xff1a;大数据开发的基础语言&#xff0c;具有跨平台能力&#xff0c;可用于编写各种应用。 Python&#xff1a;机器学习和数据分析领域广泛使用的语言&#xff0c;易于…...

python报错ModuleNotFoundError: No module named ‘visdom‘

在用虚拟环境跑深度学习代码时&#xff0c;新建的环境一般会缺少一些库&#xff0c;而一般解决的方法就是直接conda install&#xff0c;但是我在conda install visdom之后&#xff0c;安装是没有任何报错的&#xff0c;conda list里面也有visdom的信息&#xff0c;但是再运行代…...

python-Flask:SQLite数据库路径不正确但是成功访问到了数据库,并对表进行了操作

出现了这个问题&#xff0c;就好像是我要去找在南方的人&#xff0c;然后我刚好不分南北&#xff0c;我认为的方向错了&#xff0c;实则方向对了。 在我针对复盘解决&#xff1a;sqlite3.OperationalError: unrecognized token: “{“-CSDN博客这个内容的时候&#xff0c;又出现…...