《Python3网络爬虫开发实战》第3章 网页数据的解析获取之Beautiful Soup的使用

3.2 Beautiful Soup的使用(借助网页的结构和属性等特性来解析网页)

3.2.1 Beautiful Soup的简介
(1)概述
Beautiful Soup是Python的一个HTML或XML的解析库,用它可以方便地从网页中提取数据。
(2)功能及应用
Beautiful Soup提供一些简单的、Python式的函数来处理导航、搜索、修改分析树等功能。
(3)特点
作为一个工具箱,通过解析文档为用户提供需要抓取的数据,无须很多代码就可以写出一个完整的应用程序。
Beautiful Soup自动将输入文档转换为Unicode编码,将输出文档转换为utf-8编码。不需要考虑编码方式。
Beautiful Soup已成为和Ixml、html5lib一样出色的Python解释器。

3.2.2 解析器
BeautifulSoup在解析时依赖解析器,它除了支持Python标准库中的HTML解析器,还支持一些第三方解析器(例如lxml)。

使用LXML解析器,只需在初始化Beautiful Soup时,把第二个参数改为1xml即可,如:
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>','lxml')
print(soup.p.string)

3.2.3 准备工作

开始之前,安装好Beautiful Soup和lxml这两个库。Beautiful Soup直接使用pip3安装即可,命令如下:
pip3 install beautifulsoup4;Mac:!pip install beautifulsoup4

3.2.4 基本使用

例3.2 先通过实例看看Beautiful Soup的基本用法:

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,是一个HTML字符串。
接着,将它当作第一个参数传给BeautifulSoup对象,该对象的第二个参数为解析器的类型(这里使用1xml),此时就完成了BeaufulSoup对象的初始化。
然后,将这个对象赋值给soup变量。
之后就可以调用soup的各个方法和属性解析这串HTML代码了。
首先,调用prettify方法。这个方法可以把要解析的字符串以标准的缩进格式输出。需要注意的是,输出结果里包含body和html节点,也就是说对于不标准的HTML字符串BeautifulSoup,可以自动更正格式。
然后,调用soup.title.string,这实际上是输出HTML中title节点的文本内容。
总而言之,通过soup.title选出HTML中的title节点,再调用string属性就可以得到title节点里面的文本了。

3.2.5 节点选择器
(1)原理
直接调用节点的名称即可选择节点,然后调用string属性就可以得到节点内的文本了。这种选择方式速度非常快,当单个节点结构层次非常清晰时,可以选用这种方式来解析。
(2)实例
用一个例子详细说明选择节点的方法:

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">0nce 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)

结果如下:

说明:使用刚才的HTML代码,首先打印出title节点的选择结果,输出结果正是title节点及里面的文字内容。
接下来,输出title节点的类型,是bs4.element.Tag,这是Beautiful Soup中一个重要的数据结构,经过选择器选择的结果都是这种Tag类型。Tag具有一些属性,例如string属性,调用该属性可以得到节点的文本内容,所以类型的输出结果正是节点的文本内容。
输出文本内容后,又尝试选择了head节点,结果是节点加其内部的所有内容。
最后,选择了p节点。不过这次情况比较特殊,因为结果是第一个p节点的内容,后面的几个p节点并没有选取到。也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,后面的其他节点都会忽略。

3.2.6 提取信息

(1)获取名称
利用name属性可以获取节点的名称。以上面的文本为例,先选取title节点,再调用name属性就可以得到节点名称:
print(soup.title.name)#获取名称

(2)获取属性
一个节点可能有多个属性,例如id和class等,选择这个节点元素后,可以调用attrs获取其所有属性:
print(soup.p.attrs)#获取属性
print(soup.p.attrs['name'])

说明:调用attrs属性的返回结果是字典形式,包括所选择节点的所有属性和属性值。因此要获取name属性,相当于从字典中获取某个键值,只需要用中括号加属性名即可。例如通过attrs['name']获取name属性。
另一种简单的获取属性值的方式:不用写attrs,直接在节点元素后面加中括号,然后传入属性名即可。例如:
print(soup.p['name'])#不用写attrs获取属性
print(soup.p['class'])
注意:有的返回结果是字符串,有的返回结果是由字符串组成的列表。例如,name属性的值是唯一的,于是返回结果就是单个字符串。而对于class属性,一个节点元素可能包含多个class,所以返回的就是列表。因此,在实际处理过程中,需要注意判断类型。

(3)获取内容
可以利用string属性获取节点元素包含的文本内容,例如获取第一个p节点的文本:
print(soup.p.string)#获取内容
注意:这里选取的p节点是第一个p节点,获取的文本也是第一个p节点里面的文本。

(1)-(3)结果如下:

(4)嵌套选择

例如,我们获取了head节点,可以继续调用head选取其内部的head节点:

htm = """
<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)

结果如下:

说明:第一行是调用head之后再调用title,而选择的title节点。
第二行打印出了它的类型,可以看到,仍然是bs4.element.Tag类型。
也就是说,我们在Tag类型的基础上再次选择,得到的结果依然是Tag类型。
最后一行结果输出了title节点的string属性,也就是节点里的文本内容.

3.2.7 关联选择
在做选择的过程中,有时不能一步就选到想要的节点,需要先选中某一个节点,再以它为基准选子节点、父节点、兄弟节点等,下面就介绍一下如何选择这些节点。
(1)子节点和子孙节点
选取节点之后,如果想要获取它的直接子节点,可以调用contents属性,实例如下:

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>
            <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.p.contents)

结果如下:

说明:可以看到,返回结果是列表形式。p节点里既包含文本,又包含节点,这些内容会以列表形式统一返回。
注意:列表中的每个元素都是p节点的直接子节点。像第一个a节点里面包含的span节点,就相当于孙子节点,但是返回结果并没有把span节点单独选出来。所以说,contents属性得到的结果是直接子节点组成的列表。
也可以调用children属性得到相应的结果:

from bs4 import BeautifulSoup#调用children属性
soup =BeautifulSoup(html,'lxml')
print(soup.p.children)
for i,child in enumerate(soup.p.children):
    print(i,child)

结果如下:

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

from bs4 import BeautifulSoup#调用descendants属性
soup =BeautifulSoup(html,'lxml')
print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
    print(i,child)

结果如下:

(2)父节点和祖先节点
如果要获取某个节点元素的父节点,可以调用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)

结果如下:

说明:这里我们选择的是第一个a节点的父节点元素。很明显,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)))

结果如下:

(3)兄弟节点
如果要获取同级节点,也就是兄弟节点,实例如下:

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)))

结果如下:

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

(4)提取信息
如果想要获取它们的一些信息,例如文本、属性等,也可以用同样的方法,实例如下:

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'])

结果如下:

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

3.2.8 方法选择器
前面讲的选择方法都是基于属性来选择的,这种方法虽然快,但是在进行比较复杂的选择时,会变得比较烦琐,不够灵活。Beautiful Soup还为我们提供了一些查询方法,例如find_all和find等,调用这些方法,然后传入相应的参数,就可以灵活查询了。
(1)find all
find all,顾名思义就是查询所有符合条件的元素,可以给它传入一些属性或文本来得到符合条件的元素,功能十分强大。它的API如下:
find all(name ,attrs ,recursive ,text ,**kwargs)
(2)name
可以根据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]))

结果如下:

说明:这里我们调用了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'))

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)

结果如下:

#返回结果是列表类型,列表中的每个元素依然是Tag类型。接下来我们就可以遍历每个li节点,并获取它的文本内容了。

(3)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'}))

结果如下:

说明:这里查询的时候,传入的是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'))

结果如下:

(4)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')))

结果如下:

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

(5)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'))

(6)其他方法
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:前者返回节点前面所有符合条件的节点,后者返回前面第一个符合条件的节点。

3.2.9 CSS选择器
Beautiful Soup还提供了另外一种选择器——CSS选择器。使用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]))

结果如下:

说明:这里我们用了3次CSS选择器,返回结果均是由符合CSS选择器的节点组成的列表。例如,select('ul li')表示选择所有ul节点下面的所有li节点,结果便是所有li节点组成的列表。最后一句打印输出了列表中元素的类型,类型依然是Tag类型。(1)嵌套选择
select方法同样支持嵌套选择,例如先选择所有ul节点,再遍历每个ul节点,选择其li节点。
(2)获取属性
既然知道节点是Tag类型,于是获取属性依然可以使用原来的方法。基于上面的HTML文本,这里尝试获取每个ul节点的id属性:

from bs4 import BeautifulSoup#嵌套选择
soup = BeautifulSoup(html,'lxml')
for ul in soup.select('ul'):
    print(ul.select('li'))

from bs4 import BeautifulSoup#获取属性
soup =BeautifulSoup(html,'lxml')
for ul in soup.select('ul'):
    print(ul['id'])
    print(ul.attrs['id'])

结果如下:


(3)获取文本
要获取文本,当然也可以用前面所讲的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)

结果显示:

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

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值