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,一定要记清楚哈。

Python 爬虫十六式 - 第五式:BeautifulSoup-美味的汤


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对象,所有的对象都可以归纳为四类:TagNavigableStringBeautifulSoup,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节点中包含着headbody节点。所以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

  上面的contentschildren获取的是某个节点的直接子节点,而无法获得子孙节点。通过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 标签的第一个子节点,然后将子节点中的字符串提取出来。提取出子节点的字符串之后再提取子节点的子节点,再将其内容提取出来。直到该节点不再拥有子节点。这么说可能有些抽象,我们来直接看图:

Python 爬虫十六式 - 第五式:BeautifulSoup-美味的汤

  是不是一目了然了???鬼知道我为了做这个图到底经历了什么…
Python 爬虫十六式 - 第五式:BeautifulSoup-美味的汤


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的库。

Python 爬虫十六式 - 第五式:BeautifulSoup-美味的汤

  好了,这就是今天最美味的汤了,不知道你喝了以后有什么感受,我是Connor,一个从无到有的技术小白,希望你能和我一同进步,一同成长!我们下期再见!

学习一时爽,一直学习一直爽!


系列文章连接:

Python 爬虫十六式 - 第一式:HTTP协议 >>>
Python 爬虫十六式 - 第二式:urllib 与 urllib3 >>>
Python 爬虫十六式 - 第三式:Requests的用法 >>>
Python 爬虫十六式 - 第四式: 使用Xpath提取网页内容 >>>
Python 爬虫十六式 - 第六式:JQuery的假兄弟-pyquery >>>
Python 爬虫十六式 - 第七式:正则的艺术 >>>