BeautifulSoup 美味的汤
Hello,大家好,我是Connor,一个从无到有的技术小白。上一次我们说到了 Xpath
的使用方法。Xpath
我觉得还是比较绕该怎么办呢???有没有更加简单易懂的方法呢?答案是肯定的,当然有更加简单易懂的方法了,那就是 BeautifulSoup
美味的汤。这个方法对于正则和 Xpath
来说更加的简单方便,更加易懂,能够节省我们大量的分析时间。
1.BeautifulSoup 的简介
BeautifulSoup是一个HTML数据提取库。几乎没有什么数据可以难住BeautifulSoup。只要是你可以获取的到的数据,那么你都可以通过BeautifulSoup简单快捷的进行数据提取。是一款非常适合新手入门使用的数据提取库。当然,作为一个HTML数据提取库,requests
都有了官方中文文档,那么 BeautifulSoup
当然也不能少啊,你可以访问 BeautifulSoup
的官方中文文档:点我走起 >>>
2.BeautifulSoup 的安装
既然我又来说到安装了,那就证明这个库和我们平常想的库不太一样,它具体的安装方法为:
pip install beautifulsoup4
注意,是beautifulsoup4,并不是beautifulsoup,虽然我们beautifulsoup的叫,但人家实际叫beautifulsoup4,一定要记清楚哈。
3.BeautifulSoup 的基础使用
安装完了,下面我们就正式开始使用,老规矩,我们先来一段html文档,然后逐一举例,来看BeautifulSoup如何使用:
首先我们来随意编写一段html代码:
html = """
<html>
<head>
<title>Hello,Wrold</title>
</head>
<body>
<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg" />
<p class="abc">这是一个示例</p>
</div>
</body>
</html>"""
3.1 简单的使用
在进行内容提取之前,我们需要将获取的html内容转换成BeautifulSoup对象:
In [1]: from bs4 import BeautifulSoup
In [2]: soup = BeautifulSoup(html, "html5lib")
In [3]: type(soup)
Out[3]: bs4.BeautifulSoup
可以看的到,我们生成的对象是一个 bs4.BeautfulSoup
对象,我们所有的内容提取都基于这个对象。切记进行内容提取之前先生成 bs4.BeautifulSoup
对象。
3.2 解析器的使用
在上面的语句中,大家可以看到我们使用了一个 html5lib
这是一个解析器,在构造BeautifulSoup
对象的时候,需要用到解析器。BeautifulSoup
支持python自带的解析器和少数第三方解析器。详细对比如下:
Python标准库 | BeautifulSoup(html,"html.parser") | Python的内置标准库。 执行速度适中。 文档容错能力强。 | Python 3.2.2前的版本文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(html, "lxml") | 速度快文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(html, ["lxml","xml"]) BeautifulSoup(html, "xml") | 速度快 唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup,"html5lib") | 最好的容错性 以浏览器的方式解析文档生成HTML5格式的文档 | 速度慢但不依赖外部扩展 |
一般来说,对于速度或性能要求不太高的话,还是建议大家使用 html5lib
来进行解析的,但是当规模达到一定程度的时候,解析速度就会影响到整体项目的快慢了,所以如果你对性能有要求的话,还是推荐使用 lxml
来进行解析的。具体的视情况而定吧。
3.3 节点对象
BeautifulSoup将复杂的HTML文档转换成了一个树状的结构,每个节点都是一个Python对象,所有的对象都可以归纳为四类:Tag
,NavigableString
,BeautifulSoup
,Commnet
。
3.3.1 Tag 对象
Tag
就是我们平时所说的标签,Tag下拥有许多属性和方法,和前端类似,例如 a 标签一定会有它的href属性,某些属性是某些标签所独有的。下面我们来看一下如何提取一个 Tag
对象:
In [1]: soup = BeautifulSoup(html)
In [2]: tag = soup.p
In [3]: type(tag)
Out[3]: bs4.element.Tag
可以看的到,我们生成了一个 Tag
对象,我们再来看看 Tag
对象有哪些属性:
- name属性:
每一个tag标签都有name属性
In [4]: tag.name
Out[4]: 'p'
- Attributes
在html中,某个 Tag
可能有多个属性, Tag
属性使用和字典一样的方法取值:
In [5]: tag["class"]
Out[5]: ['abc']
如果某个 Tag
属性有多个值,那么返回的是一个列表:
In [6]: soup = BeautifulSoup('<p class="body strikeout"></p>')
In [7]: soup.p['class']
Out[7]: ['body', 'strikeout']
- get_text()
通过 get_text()
方法我们可以获取某个 Tag
下所有的文本内容:
In [8]: soup.a.get_text()
Out[8]: '百度一下,你就知道'
3.3.2 NavigableString 对象
NavigableString
的意思是可以遍历的字符串,一般被标签包裹在自种的文本就是NavigableString
格式:
In [9]: soup.p.string
Out[9]: '这是一个示例'
In [10]: type(soup.p.string)
Out[10]: bs4.element.NavigableString
3.3.3 BeautifulSoup 对象
BeautifulSoup
对象就是通过解析网页所得到的对象,我们的 soup
即是 BeautifulSoup
对象:
In [1]: from bs4 import BeautifulSoup
In [2]: soup = BeautifulSoup(html, "html5lib")
In [3]: type(soup)
Out[3]: bs4.BeautifulSoup
3.3.4 Comment 对象
Comment
对象是网页中的注释及特殊字符串,当你提取网页中的注释的时候,它会自动帮你生成Comment
对象:
In [4]: comment = soup.body.span.string
In [5]: type(comment)
Out[5]: bs4.element.Comment
了解了 BeautifulSoup
的基础使用之后,我们来看一下 BeautifulSoup
的进阶用法:
4 BeautifulSoup 的高级用法
4.1 Tag与遍历文档树
Tag
对象可以说 BeautifulSoup
中最为重要的对象,通过 BeautifulSoup
来提取数据基本都围绕着这个对象来进行操作。
首先,一个节点中是可以包含多个子节点和多个字符串的。例如html
节点中包含着head
和body
节点。所以BeautifulSoup
就可以将一个HTML的网页用这样一层层嵌套的节点来进行表示。
使用我们的例子,你可以这样做:
4.1.1 contents 和 children
通过 contents
可以获取某个节点的所有子节点,包括里面的 NavigbleString
对象,获取的子节点是列表格式:
In [5]: soup.head.contents
Out[5]: [<title>Hello,Wrold</title>]
通过 children
也可以获取某个节点的所有子节点,但是返回的是一个迭代器,这种方式使用起来比列表更加的省内存:
In [6]: tags = soup.head.children
In [7]: print(tags)
<list_iterator object at 0x000002E5B44E6860>
In [8]: for tag in tags:
...: print(tag)
...:
<title>Hello,Wrold</title>
4.1.2 descendants
上面的contents
和children
获取的是某个节点的直接子节点,而无法获得子孙节点。通过descendants
可以获得所有子孙节点,返回的结果跟children
一样,需要迭代或者转类型使用。
In [15]: tags = soup.body.descendants
In [16]: for tag in tags:
...: print(tag)
...:
<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
</div>
<span><!--这里是注释的部分--></span>
这里是注释的部分
<a href="https://www.baidu.com">百度一下,你就知道</a>
百度一下,你就知道
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
这是一个示例
通过上图我们可以看得出通过 descendants
首先找出了 body
标签的第一个子节点,然后将子节点中的字符串提取出来。提取出子节点的字符串之后再提取子节点的子节点,再将其内容提取出来。直到该节点不再拥有子节点。这么说可能有些抽象,我们来直接看图:
是不是一目了然了???鬼知道我为了做这个图到底经历了什么…
4.1.3 string 和 strings
我们常常会遇到需要获取某个节点中的文本值的情况,如果这个节点中只有一个字符串,那么使用string
可以正常将其取出。
In [17]: soup.body.a.string
Out[18]: '百度一下,你就知道'
但是如果一个节点下有多个节点中包含有字符串的时候,这时使用 string
方法就无法准确的取出字符串了,它无法确定你要取出的是哪个字符串,这时你需要使用 strings
:
In [19]: strings = soup.body.strings
In [20]: strings
Out[20]: <generator object _all_strings at 0x000002E5B44EA1A8>
In [21]: for string in strings:
...: print(string)
...:
百度一下,你就知道
这是一个示例
使用 strings
也会给你返回一个可迭代对象。当然,你会发现里面有很多的’\n’,’\t’啊等这样的转义字符,上面的程序中没有是因为为了美观我手动去掉了。如果你想要获取的内容中没有转义字符的话,你可以使用 stripped_strings
来去掉内容中的空白:
In [22]: strings = soup.body.stripped_strings
In [23]: strings
Out[23]: <generator object stripped_strings at 0x000002E5B39DF3B8>
In [24]: for string in strings:
...: print(string)
...:
百度一下,你就知道
这是一个示例
4.1.4 父节点 parent 和 parents
有的时候我们也需要去获取某个节点的父节点,就是当前节点的上一层节点:
In [25]: soup.a.parent
Out[25]:
<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
</div>
如果使用 parents
的话将会递归获取该节点的所有父辈元素:
In [26]: soup.a.parents
Out[26]: <generator object parents at 0x000002E5B38C3150>
同样这种方式获取的父辈元素也是一个可迭代对象,需要处理后才能使用
4.1.5 兄弟节点
兄弟节点就是指当前节点同级节点。
next_sibling 和 previous_sibling
兄弟节点选取的方法与当前节点的位置有关,
next_sibling
获取的是当前节点的下一个兄弟节点,previous_sibling
获取的是当前节点的上一个兄弟节点。所以,兄弟节点中排第一个的节点是没有
previous_sibling
的,最后一个节点是没有next_sibling
的。In [27]: soup.head.next_sibling
Out[27]: '\n' In [28]: soup.head.previos_sibling In [29]: soup.body.previous_sibling
Out[29]: '\n'
next_siblings 和 previous_siblings
相对应的,
next_siblings
获取的是下方所有的兄弟节点,previous_siblings
获取的上方所有的兄弟节点。In [30]: [i.name for i in soup.head.next_siblings]
Out[30]: [None, 'body', None] In [31]: [i.name for i in soup.body.next_siblings]
Out[31]: [None] In [32]: [i.name for i in soup.body.previous_siblings]
Out[32]: [None, 'head', None]
4.2. find_all()
在前面我们讲了通过标签的属性来进行标签的访问的方法,大多都只适用于简单的一些场景,所以 BeautifulSoup
还提供了搜索整个文档树的方法,即 find_all()
。该方法基本适用于任何节点:
4.2.1 通过 name 搜索
最简单的使用方式就是使用 name 属性进行搜索,你可以这样做:
In [33]: soup.find_all('a')
Out[33]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
通过 find_all()
方法获取的内容是一个列表对象。如果你给的条件是一个列表,则会匹配列表里的全部标签,例如:
In [34]: soup.find_all(['a','p'])
Out[34]: [<a href="https://www.baidu.com">百度一下,你就知道</a>, <p class="abc">这是一个示例</p>]
通过上面的例子我们可以看得出,我们可以看得到, BeautifulSoup
对象匹配出了所有的 a标签和 p 标签。
4.2.2 通过属性搜索
除了通过 name 属性来进行匹配之外,我们还可以通过属性进行匹配。这个时候我们需要向 find_all()
方法传递一个字典参数:
In [35]: soup.find_all(attrs={'class':'book'})
Out[35]:
[<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
</div>]
如果一个标签有多个参数,为了查找的准确性,你也可以向attrs传递多个参数。
4.2.3 通过文本搜索
在find_all()
方法中,还可以根据文本内容来进行搜索。
In [36]: soup.find_all("a", text="百度一下,你就知道")
Out[36]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
可见找到的都是字符串对象,如果想要找到包含某个文本的tag
,加上tag
名即可。
4.2.4 限制查找范围为子节点
find_all()
方法会默认的去所有的子孙节点中搜索,而如果将 recursive
参数设置为False,则可以将搜索范围限制在直接子节点中:
In [37]: soup.find_all("a",recursive=False)
Out[37]: []
In [38]: soup.find_all("a",recursive=True)
Out[38]: [<a href="https://www.baidu.com">百度一下,你就知道</a>]
4.2.5 通过正则表达式来筛选结果
在BeautifulSoup
中,也是可以与re
模块进行相互配合的,将re.compile编译的对象传入find_all()
方法,即可通过正则来进行搜索。
In [39]: import re
In [40]: soup.find_all(re.compile("b"))
Out[40]:
[<body>
<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
</div>
</body>]
可以看到,通过正则表达式,我们找到了所有以 b 开头的标签。正则怎么用我们会在之后的文章中详细的说的,大家不用着急。当然,正则除了能用在标签上,也可以用在属性上:
In [57]: soup.find_all(attrs={"class":re.compile("a")})
Out[57]: [<p class="abc">这是一个示例</p>]
4.3 CSS选择器
在BeautifulSoup
中,同样也支持使用CSS选择器来进行搜索。使用select()
,在其中传入字符串参数,就可以使用CSS选择器的语法来找到tag:
In [58]: soup.select("title")
Out[58]: [<title>Hello,Wrold</title>]
In [60]: soup.select(".book")
Out[60]:
[<div class="book">
<span><!--这里是注释的部分--></span>
<a href="https://www.baidu.com">百度一下,你就知道</a>
<img src="https://abc.jpg"/>
<p class="abc">这是一个示例</p>
</div>]
下期预告:
Xpath 和 BueatifulSoup 都很好用,但是有时候遇到复杂的选择也很麻烦,有没有能像Jquery一样快速通过css来选择的工具啊???当然有了!!!那就是我们的PyQuery,一个和JQuery就像亲兄弟一样的库,敬请期待下期—PyQuery,一个类似JQuery的库。
好了,这就是今天最美味的汤了,不知道你喝了以后有什么感受,我是Connor,一个从无到有的技术小白,希望你能和我一同进步,一同成长!我们下期再见!
系列文章连接:
Python 爬虫十六式 - 第一式:HTTP协议 >>>
Python 爬虫十六式 - 第二式:urllib 与 urllib3 >>>
Python 爬虫十六式 - 第三式:Requests的用法 >>>
Python 爬虫十六式 - 第四式: 使用Xpath提取网页内容 >>>
Python 爬虫十六式 - 第六式:JQuery的假兄弟-pyquery >>>
Python 爬虫十六式 - 第七式:正则的艺术 >>>