Python3网络爬虫实战---2九、解析库的使用:BeautifulSoup

上一篇文章: Python3网络爬虫实战---2八、解析库的使用:XPath
下一篇文章: Python3网络爬虫实战---30、解析库的使用:PyQuery

前面咱们介绍了正则表达式的相关用法,可是一旦正则写的有问题,可能获得的就不是咱们想要的结果了,并且对于一个网页来讲,都有必定的特殊的结构和层级关系,并且不少节点都有id或class来对做区分,因此咱们借助于它们的结构和属性来提取不也是能够的吗?css

因此,这一节咱们就介绍一个强大的解析工具,叫作 BeautiSoup,它就是借助网页的结构和属性等特性来解析网页的工具,有了它咱们不用再去写一些复杂的正则,只须要简单的几条语句就能够完成网页中某个元素的提取。html

废话很少说,接下来咱们就来感觉一下 BeautifulSoup 的强大之处吧。html5

1. BeautifulSoup简介

简单来讲,BeautifulSoup 就是 Python 的一个 HTML 或 XML 的解析库,咱们能够用它来方便地从网页中提取数据,官方的解释以下:python

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

因此说,利用它咱们能够省去不少繁琐的提取工做,提升解析效率。正则表达式

2. 准备工做

在开始以前请确保已经正确安装好了 BeautifulSoup 和 LXML,如没有安装能够参考第一章的安装过程。segmentfault

3. 解析器

BeautifulSoup 在解析的时候其实是依赖于解析器的,它除了支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析器好比 LXML,下面咱们对 BeautifulSoup 支持的解析器及它们的一些优缺点作一个简单的对比。浏览器

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

因此经过以上对比能够看出,LXML 这个解析器有解析 HTML 和 XML 的功能,并且速度快,容错能力强,因此推荐使用这个解析器来进行解析。网络

使用 LXML 这个解析器,在初始化 BeautifulSoup 的时候咱们能够把第二个参数改成 lxml 便可,以下:数据结构

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

后面 BeautifulSoup 的用法实例也统一用这个解析器来演示。
4# . 基本使用函数

下面咱们首先用一个实例来感觉一下 BeautifulSoup 的基本使用:

html = """
<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/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>
"""
from bs4 import BeautifulSoup
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="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>
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 属性就能够获得里面的文本了,因此咱们就能够经过简单地调用几个属性就能够完成文本的提取了,是否是很是方便?

5. 节点选择器

刚才咱们选择元素的时候直接经过调用节点的名称就能够选择节点元素了,而后再调用 string 属性就能够获得节点内的文本了,这种选择方式速度很是快,若是单个节点结构话层次很是清晰,能够选用这种方式来解析。

选择元素

下面咱们再用一个例子详细说明一下它的选择方法:

html = """
<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/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>
"""
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><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 类型,这是 BeautifulSoup 中的一个重要的数据结构,通过选择器选择以后,选择结果都是这种 Tag 类型,它具备一些属性好比 string 属性,调用 Tag 的 string 属性,就能够获得节点的文本内容了,因此接下来的输出结果正是节点的文本内容。

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

提取信息

在上面咱们演示了调用 string 属性来获取文本的值,那咱们要获取节点属性值怎么办呢?获取节点名怎么办呢?下面咱们来统一梳理一下信息的提取方式

获取名称

能够利用 name 属性来获取节点的名称。仍是以上面的文本为例,咱们选取 title 节点,而后调用 name 属性就能够获得节点名称。

print(soup.title.name)

运行结果:

title

获取属性

每一个节点可能有多个属性,好比 id,class 等等,咱们选择到这个节点元素以后,能够调用 attrs 获取全部属性。

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

运行结果:

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

能够看到 attrs 的返回结果是字典形式,把选择的节点的全部属性和属性值组合成一个字典,接下来若是要获取 name 属性,就至关于从字典中获取某个键值,只须要用中括号加属性名称就能够获得结果了,好比获取 name 属性就能够经过 attrs['name'] 获得相应的属性值。

其实这样的写法还有点繁琐,还有一种更简单的获取方式,咱们能够不用写 attrs,直接节点元素后面加中括号,传入属性名就能够达到属性值了,样例以下:

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

运行结果:

dromouse
['title']

在这里注意到有的返回结果是字符串,有的返回结果是字符串组成的列表。好比 name 属性的值是惟一的,返回的结果就是单个字符串,而对于 class,一个节点元素可能由多个 class,因此返回的是列表,因此在实际处理过程当中要注意判断类型。

获取内容

能够利用 string 属性获取节点元素包含的文本内容,好比上面的文本咱们获取第一个 p 节点的文本:

print(soup.p.string)

运行结果:

The Dormouse's story

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

嵌套选择

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

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
"""
from bs4 import BeautifulSoup
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 类型,每次返回的结果都相同,因此这样咱们就能够这样作嵌套的选择了。

最后输出了一下它的 string 属性,也就是节点里的文本内容。

关联选择

咱们在作选择的时候有时候不能作到一步就能够选择到想要的节点元素,有时候在选择的时候须要先选中某一个节点元素,而后以它为基准再选择它的子节点、父节点、兄弟节点等等。因此在这里咱们就介绍下如何来选择这些节点元素。

子节点和子孙节点

选取到了一个节点元素以后,若是想要获取它的直接子节点能够调用 contents 属性,咱们用一个实例来感觉一下:

print(soup.p.contents)

运行结果:

[<b>The Dormouse's story</b>]

contents 属性获得的结果是直接子节点的列表。

一样地咱们能够调用 children 属性,获得相应的结果:

print(soup.p.children)
for i,child in enumerate(soup.p.children):
    print(child)

运行结果:

<list_iterator object at 0x10529eef0>
<b>The Dormouse's story</b>

仍是一样的 HTML 文本,在这里咱们调用了 children 属性来进行选择,返回结果能够看到是生成器类型,因此接下来咱们用 for 循环输出了一下相应的内容,内容实际上是同样的,只不过 children 返回的是生成器类型,而 contents 返回的是列表类型。

若是咱们要获得全部的子孙节点的话能够调用 descendants 属性:

print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
    print(child)

运行结果:

<generator object Tag.descendants at 0x103fa5a20>
<b>The Dormouse's story</b>
The Dormouse's story

返回结果仍是生成器,遍历输出一下能够看到descendants 会递归地查询全部子节点,获得的是全部的子孙节点。

父节点和祖先节点

若是要获取某个节点元素的父节点,能够调用 parent 属性:

html = """
<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>
"""
from bs4 import BeautifulSoup
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="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>

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

注意到这里输出的仅仅是 a 节点的直接父节点,而没有再向外寻找父节点的祖先节点,若是咱们要想获取全部的祖先节点,能够调用 parents 属性:

html = """
<html>
    <body>
        <p class="story">
            <a href="http://example.com/elsie" class="sister" id="link1">
                <span>Elsie</span>
            </a>
        </p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))

运行结果:

<class 'generator'>
[(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>
</body>), (2, <html>
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>), (3, <html>
<body>
<p class="story">
<a class="sister" href="http://example.com/elsie" id="link1">
<span>Elsie</span>
</a>
</p>
</body></html>)]

返回结果是一个生成器类型,咱们在这里用列表输出了它的索引和内容,能够发现列表中的元素就是 a 节点的祖先节点。

兄弟节点

上面说明了子节点和父节点的获取方式,若是要获取同级的节点也就是兄弟节点应该怎么办?咱们先用一个实例来感觉一下:

html = """
<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>
"""
from bs4 import BeautifulSoup
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            ')]

能够看到在这里咱们调用了四个不一样的属性,next_sibling 和 previous_sibling 分别能够获取节点的下一个和上一个兄弟元素,next_siblings 和 previous_siblings 则分别返回全部前面和后面的兄弟节点的生成器。

提取信息

在上面咱们讲解了关联元素节点的选择方法,若是咱们想要获取它们的一些信息,好比文本、属性等等也是一样的方法。

html = """
<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>
"""
from bs4 import BeautifulSoup
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 等属性来获取其对应节点等文本和属性。

6. 方法选择器

前面咱们所讲的选择方法都是经过属性来选择元素的,这种选择方法很是快,可是若是要进行比较复杂的选择的话则会比较繁琐,不够灵活。因此 BeautifulSoup 还为咱们提供了一些查询的方法,好比 find_all()、find() 等方法,咱们能够调用方法而后传入相应等参数就能够灵活地进行查询了。

最经常使用的查询方法莫过于 find_all() 和 find() 了,下面咱们对它们的用法进行详细的介绍。

find_all()

find_all,顾名思义,就是查询全部符合条件的元素,能够给它传入一些属性或文原本获得符合条件的元素,功能十分强大。

它的API以下:

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

name

咱们能够根据节点名来查询元素,下面咱们用一个实例来感觉一下:

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

返回结果是列表类型,列表中的每一个元素依然仍是 Tag 类型。

接下来咱们就能够遍历每一个 li 获取它的文本了。

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

attrs

除了根据节点名查询,咱们也能够传入一些属性来进行查询,咱们用一个实例感觉一下:

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>
'''
from bs4 import BeautifulSoup
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 这个参数,仍是上面的文本,咱们换一种方式来查询。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))

运行结果:

[<ul class="list" id="list-1">
<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 组成的列表。

text

text 参数能够用来匹配节点的文本,传入的形式能够是字符串,能够是正则表达式对象,咱们用一个实例来感觉一下:

import re
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>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=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() 返回的是全部匹配的元素组成的列表。

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>
'''
from bs4 import BeautifulSoup
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>

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

另外还有许多的查询方法,用法与前面介绍的 find_all()、find() 方法彻底相同,只不过查询范围不一样,在此作一下简单的说明。

  • find_parents() find_parent()

find_parents() 返回全部祖先节点,find_parent() 返回直接父节点。

  • find_next_siblings() find_next_sibling()
  • find_next_siblings() 返回后面全部兄弟节点,find_next_sibling() 返回后面第一个兄弟节点。
  • find_previous_siblings() find_previous_sibling()

find_previous_siblings() 返回前面全部兄弟节点,find_previous_sibling() 返回前面第一个兄弟节点。

  • find_all_next() find_next()

find_all_next() 返回节点后全部符合条件的节点, find_next() 返回第一个符合条件的节点。

  • find_all_previous() 和 find_previous()

find_all_previous() 返回节点后全部符合条件的节点, find_previous() 返回第一个符合条件的节点

7. CSS选择器

BeautifulSoup 还提供了另一种选择器,那就是 CSS 选择器,若是对 Web 开发熟悉对话,CSS 选择器确定也不陌生,若是不熟悉的话,能够看一下:http://www.w3school.com.cn/cs...

使用 CSS 选择器,只须要调用 select() 方法,传入相应的 CSS 选择器便可,咱们用一个实例来感觉一下:

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>
'''
from bs4 import BeautifulSoup
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'>

在这里咱们用了三次 CSS 选择器,返回的结果均是符合 CSS 选择器的节点组成的列表。例如 select('ul li') 则是选择全部 ul 节点下面的全部 li 节点,结果即是全部的 li 节点组成的列表。

最后一句咱们打印输出了列表中元素的类型,能够看到类型依然是 Tag 类型。

嵌套选择

select() 方法一样支持嵌套选择,例如咱们先选择全部 ul 节点,再遍历每一个 ul 节点选择其 li 节点,样例以下:

from bs4 import BeautifulSoup
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>]

能够看到正常输出了遍历每一个 ul 节点以后,其下的全部 li 节点组成的列表。

获取属性

咱们知道节点类型是 Tag 类型,因此获取属性仍是能够用原来的方法获取,仍然是上面的 HTML 文本,咱们在这里尝试获取每一个 ul 节点的 id 属性。

from bs4 import BeautifulSoup
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 属性获取属性值都是能够成功的。

获取文本

那么获取文本固然也能够用前面所讲的 string 属性,还有一个方法那就是 get_text(),一样能够获取文本值。

from bs4 import BeautifulSoup
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

两者的效果是彻底一致的,均可以获取到节点的文本值。

8. 结语

到此 BeautifulSoup 的使用介绍基本就结束了,最后作一下简单的总结:

  • 推荐使用 LXML 解析库,必要时使用 html.parser。
  • 节点选择筛选功能弱可是速度快。
  • 建议使用 find()、find_all() 查询匹配单个结果或者多个结果。
  • 若是对 CSS 选择器熟悉的话可使用 select() 选择法。
上一篇文章: Python3网络爬虫实战---2八、解析库的使用:XPath
下一篇文章: Python3网络爬虫实战---30、解析库的使用:PyQuery
相关文章
相关标签/搜索