专栏首页keinYePython 爬虫之网页解析库 BeautifulSoup

Python 爬虫之网页解析库 BeautifulSoup

BeautifulSoup 是一个使用灵活方便、执行速度快、支持多种解析器的网页解析库,可以让你无需编写正则表达式也能从 html 和 xml 中提取数据。BeautifulSoup 不仅支持 Python 内置的 Html 解析器,还支持 lxml、html5lib 等第三方解析器。

以下是对几个主要解析器的对比:

解析器

使用方法

优势

劣势

Python 标准库

BeautifulSoup(markup, "html.parser")

Python的内置标准库 执行速度适中 文档容错能力强

Python 2.7.3 or 3.2.2)前的版本中文档容错能力差

lxml HTML 解析器

BeautifulSoup(markup, "lxml")

速度快 文档容错能力强

需要安装C语言库

lxml XML 解析器

BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml")

速度快 唯一支持XML的解析器

需要安装C语言库

html5lib

BeautifulSoup(markup, "html5lib")

最好的容错性 以浏览器的方式解析文档生成HTML5格式的文档

速度慢 不依赖外部扩展

安装

BeautifulSoup 安装

我们可以通过 pip 来安装 BeautifulSoup4。

pip install BeautifulSoup4

PyPi 中还有一个名字是 BeautifulSoup,它是 BeautifulSoup3 的发布版本,目前已停止维护,不建议使用该版本。

解析器安装

虽然 BeautifulSoup 支持多种解释器,但是综合来考虑的话还是推荐使用 lxml 解释器,因为 lxml 解释器的效率更高且支持所有的 python 版本,我们可以通过 pip 来安装 lxml 解释器。

pip install lxml

使用

BeautifulSoup 将 HTML 文档转化为一个树形结构,树形结构的每个节点都是一个 python 对象,节点的类型可以分为 Tag、NavigableString、BeautifulSoup 和 Comment 四类。

将 html 文本传入 BeautifulSoup 的构造方法即可得到一个文档对象,通过该对象下每一个节点的数据。

from bs4 import BeautifulSoup

html = "<html>data</html>"
soup = BeautifulSoup(html)

节点的访问

Tag

HTML 中的标签在 BeautifulSoup 中我们称之为 Tag,在 Tag 众多属性中最常用也最重要的属性即 name 和 attribute。

name 顾名思义他是 Tag 的名称,比如 <pclass='title'></p> 这段 HTML 中 Tag 的 name 即为 p。

attribute 是 tag 的属性,比如 <pclass='title'></p> 这段 HTML 中 Tag 的 class 属性的值即为 title。

attribute 的操作方法与字典相同,我们可以正常对 tag 的属性进行删除、修改等操作。

以下代码展示了 name 和 attribute 的使用方法。

# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup

html = """
<html>
    <a class="sister" href="http://example.com/lacie" id="link2"></a>
    <p class="story"></p>
</html>
"""

soup = BeautifulSoup(html,features="lxml")
tag1 = soup.a
tag2 = soup.p

print (type(tag1))
print (type(tag2))

print (tag1.name)
print (tag2.name)

print (tag1.attrs)
print (tag1['class'])
tag1['class'] = "brothers"
print (tag1.attrs)

print (tag2.attrs)
print (tag2['class'])

在运行以上代码之前,请先确认安装了 BeautifulSoup 和 lxml 库。以上代码在 python 3.7.0 版本测试,若要在 python 2.7 版本使用请修改 print 部分。

我们可以通过 name 和 attrs 来获取标签的属性等内容,但是在很多情况下我们想要获取的是标签所包含的内容,此时我们就需要使用 string 属性。先来看下以下代码

# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup

html = """<p name="dromouse"><b>The Dormouse's story</b></p>"""

soup = BeautifulSoup(html, features='lxml')

print (soup.p.name)
print (soup.p.string)
print (soup.b.string)

以上代码执行结果如下

p
The Dormouse's story
The Dormouse's story

在这个示例中仅仅通过一行代码 ==soup.p.string== 就获取了标签所包含的字符串,在 Python 爬虫第一篇(urllib+regex) 中使用的正则表达式来获取标签所包含的内容,有兴趣的话可以去看一下。

标签中所包含的字符串无法进行编辑,但是可以使用 replace_with 方法进行替换。

BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称等属性。

Comment

Comment 是一个特殊的 NavigableString。在 html 文件中不可避免的会出现大量的注释部分,由于使用 string 属性会将注释部分作为正常内容输出,而我们往往不需要注释部分的内容,此时就引入了 Comment 对象,BeautifulSoup 将 html 文档中的注释部分自动设置为 Comment 对象,在使用过程中通过判断 string 的类型是否为 Comment 就可以过滤注释部分的内容。

# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup, element

html = """
<p name="dromouse">The Dormouse's story</p>
<b><!--Hey, buddy. Want to buy a used parser?--></b>
"""

soup = BeautifulSoup(html, features='lxml')

print (soup.p.string)
print (soup.b.string)

print (type(soup.p.string))
print (type(soup.b.string))

if type(soup.p.string) != element.Comment:
    print (soup.p.string)
if type(soup.b.string) != element.Comment:
    print (soup.b.string)

以上代码执行结果如下

The Dormouse's story
Hey, buddy. Want to buy a used parser?
<class 'bs4.element.NavigableString'>
<class 'bs4.element.Comment'>
The Dormouse's story

文档的遍历

BeautifulSoup 提供了子孙节点、内容属性「.string 属性」、父节点、兄弟节点、前后节点等多种方式来遍历整个文档。

子孙节点

BeautifulSoup 提供了 contents、children 和 descendants 三种属性来操作子孙节点。通过 contents 和 children 可以获取一个 Tag 的直接节点,contents 返回的是一个 list,children 返回的是一个 list 的生成器,可以通过遍历来获取所有内容。descendants 将获取一个 Tag 的说有子节点,以及子节点的子节点「孙节点」。它也是一个生成器,需要通过遍历来获取内容。

# -*- coding:utf-8 -*-

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('---------- contents ----------')
print (soup.contents)
print ('---------- children ----------')
for index, value in enumerate(soup.children):
    print ("%4d : %s" %(index, value))
print ('---------- descendants ----------')
for index, value in enumerate(soup.descendants):
    print ("%4d : %s" %(index, value))

以上代码演示了 contents、children 和 descendants 属性的使用,执行结果这里就不再贴出来了,有兴趣的或可以自己运行一下获取结果并验证它。

ps: 以上代码均在 python 3.7.0 测试通过。

内容属性

BeautifulSoup 提供了 string、strings 和 strippedstrings 三个属性来获取 Tag 的内容。如果一个 Tag 仅有一个子节点有内容「NavigableString 类型子节点」或其只有一个子节点可以使用 string 属性来获取节点内容。若 Tag 包含多个子节点,且不止一个子节点含有内容,此时需要用到 strings 和 strippedstrings 属性,使用 strings 获取的内容会包含很多的空格和换行,使用 stripped_strings 可以过滤这些空格和换行。

# -*- coding:utf-8 -*-

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('---------- single navigablestring string attributes ----------')
print (soup.title.string)
print (soup.head.string)
print ('---------- multiple navigablestring string attributes ----------')
print (soup.body.string)
print ('---------- multiple navigablestring strings attributes ----------')
for string in soup.body.strings:
    print (string)
print ('---------- multiple navigablestring stripped_strings attributes ----------')
for string in soup.body.stripped_strings:
    print (string)

以上代码展示了 string、strings 和 strippedstrings 属性的应用,需要注意的是当 Tag 不止一个子节点含有内容时,使用 strings 属性将返回 None。strings 和 strippedstrings 返回的是生成器,需要通过迭代获取内容。

父节点

BeautifulSoup 通过 parent 和 parents 来获取 Tag 的父节点。使用 parent 得到的是 Tag 的直接父节点,而 parents 将得到 Tag 的所有父节点,包括 父节点的父节点。

# -*- coding:utf-8 -*-

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('---------- parent attributes ----------')
print (soup.title.parent.name)
print ('---------- parents attributes ----------')
for parent in soup.title.parents:
    print(parent.name)

以上代码的执行结果如下:

---------- parent attributes ----------
head
---------- parents attributes ----------
head
html
[document]

兄弟节点

兄弟节点即和当前节点处在同一级上的节点,BeautifulSoup 通过 nextsibling、previoussibling、nextsiblings 和 previoussiblings 四个属性类获取兄弟节点,nextsibling 和 previoussibling 属性用来获取上一个兄弟节点和下一个兄弟节点,若节点不存在则返回 None。nextsiblings 和 previoussiblings 属性用于对当前节点的兄弟节点机型迭代,通过这两个属性可以获取当前节点的所有兄弟节点。

.nextsibling 和 .previoussibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行。

# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('---------- next_sibling and previous_sibling ----------')
print (repr(soup.p.next_sibling))
print (repr(soup.p.previous_sibling))
print ('---------- next_siblings and previous_siblings ----------')
for sibling in soup.a.next_siblings:
    print (repr(sibling))
if soup.a.previous_siblings is not None:
    for sibling in soup.a.previous_siblings:
        print (repr(sibling))
else:
    print ('None')

运行结果如下

---------- next_sibling and previous_sibling ----------
'\n'
'\n'
---------- next_siblings and previous_siblings ----------
',\n   '
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
'and\n   '
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>
';\n   and they lived at the bottom of a well.\n  '
'Once upon a time there were three little sisters; and their names were\n   '

前后节点

共有 nextelement、previouselement、nextelements 和 nextelements 四个属性来操作前后节点,和兄弟节点不同的是并不是针对同一层级的节点,而是所有节点不分层级。使用方法和兄弟节点类似,这里不再单独举例说明了。

内容的搜索

BeautifulSoup 提供一下方法用于文档内容的搜索:

  1. find 和 find_all:搜索当前 Tag 及其所有子节点,判断其是否符合过滤条件。
  2. findparent 和 findparents:用来搜索当前 Tag 的父节点,判断其是否符合过滤条件。
  3. findnextsiblings 和 findnextsibling:用来搜索当前 Tag 前面的兄弟节点,判断其是否符合过滤条件。
  4. findprevioussiblings 和 findprevioussibling:用来搜索当前 Tag 后面的兄弟节点,判断其是否符合过滤条件。
  5. findallnext 和 findnext:通过 nextelements 属性对当前 Tag 的之后的节点和字符串进行迭代,并判断其是否符合过滤条件。
  6. findallprevious 和 findprevious:通过previouselements 属性对当前节点前面的节点和字符串进行迭代,并判断其是否符合过滤条件。

以上方法的参数及用法均相同,原理类似,这里以 findall 方法为例进行介绍,其他方法不再一一举例说明。findall 方法的定义如下:

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

name参数 name 参数用于查找所有名字为 name 的 Tag,它会自动忽略掉字符串对象。name 参数不仅仅可以传入字符串,也可以传入正则表达式、列表、True「当需要匹配任何值时可以出入 True」、或者方法。

当 name 参数传入方法时,此方法仅接受一个参数「HTML 文档中的一个节点」,当该方法返回 True 时表示当前元素被找到,反之则返回 False。

以下代码简单介绍 name 参数的使用

# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
import re

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('---------- string ----------')
print (soup.find_all('title'))
print ('---------- regex ----------')
print (soup.find_all(re.compile('^b')))
print ('---------- list ----------')
print (soup.find_all(['b', 'a']))
print ('---------- True ----------')
print (soup.find_all(True))
print ('---------- function ----------')
def has_class_but_no_href(tag):
    return tag.has_attr('class') and not tag.has_attr('href')
print (soup.find_all(has_class_but_no_href))

keyword 参数

如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性.

我们可以使用 keyword 参数来搜索指定名字的属性,可使用的参数值包括字符串、正则表达式、列表和 True。

# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
import re

html = """
<html><head><title>The Dormouse's story</title></head>
 <body>
  <p class="title"><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="link2">Tillie</a>;
   and they lived at the bottom of a well.
  </p>
 </body>
</html>
"""

soup = BeautifulSoup(html, features='lxml')

print ('------------------------------')
print (soup.find_all(id='link1'))
print ('------------------------------')
print (soup.find_all(href=re.compile('tillie')))
print ('------------------------------')
print (soup.find_all(id=True))
print ('------------------------------')
print (soup.find_all(id='link2', href=re.compile('tillie')))

运行结果如下

------------------------------
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
------------------------------
[<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>]
------------------------------
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>]
------------------------------
[<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>]

部分属性在搜索不能使用,比如 HTML5 中的 data-* 属性,此时可以通过 find_all 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的 Tag。

soup.find_all(attrs={"data-foo": "value"})

CSS 选择器

我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list,BeautifulSoup 支持了大部分的 CSS 选择器。

# 通过标签名查找
print (soup.select('title')) 
# 通过类名查找
print (soup.select('.sister'))
# 通过 id 名查找
print (soup.select('#link1'))
# 组合查找
print (soup.select('p #link1'))
# 属性查找
print (soup.select('a[class="sister"]'))

内容的修改

通过 BeautifulSoup 我们可以对 html 文档内容进行插入、删除、修改等等操作。

Tag 的名称和属性的修改

修改 Tag 的名称直接对 name 属性重新赋值即可,修改属性的使用字典的方式进行重新赋值。

# 修改 Tag 的名称
tag.name = block
# 修改 Tag 的 class 属性值
tag['class'] = 'verybold'

Tag 内容的修改

对 Tag 内容进行修改可以直接对 string 属性进行赋值「此时会覆盖掉原有的内容」,若要在当前内容后追加内容可以使用 append 方法,若需要在指定位置增加内容可以使 insert 方法。

新增节点

在 html 文档中新增节点使用 new_tag 方法,

new_tag = soup.new_tag("a", href="http://www.example.com")
tag.append(new_tag)

其他

若要清除 Tag 内容可以使用 clear 方法。使用 extract 方法 和 decompose 方法可以将当前节点从 html 文档中移除。replace_with 方法用来移除内容并使用新的节点替换被移除的内容。wrap 方法 和 unwrap 方法是一对相反的方法,wrap 对指定的节点进行包装,而 unwrap 对指定的节点进行解包。

BeautifulSoup 的功能较多,在本文中对大部分常用内容进行了解释并提供了示例,不过这仍然不算完全,希望可以帮到大家一点。BeautifulSoup 是一个非常优秀的网页解析库,使用 BeautifulSoup 可以大大节省编程的效率。

本文分享自微信公众号 - keinYe(keinYe_zh),作者:keinYe

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 外行学 Python 爬虫 第三篇 内容解析

    从网络上获取网页内容以后,需要从这些网页中取出有用的信息,毕竟爬虫的职责就是获取有用的信息,而不仅仅是为了下来一个网页。获取网页中的信息,首先需要指导网页内容的...

    keinYe
  • Python 基础语法二

    Python 中有六个标准数据类型分别为数字(Number)、字符串(String)、列表(List)、元组(Tuple)、集合(Set)、字典(Diction...

    keinYe
  • Python 基础语法五-控制流

    在开始进行控制流之前,先要学习一下如何将程序保存到文件中。在前面的学习过程中所有的代码都是在命令行环境中运行的,输入一行代码 python 解释器输出一个结果。...

    keinYe
  • 从Trie树到双数组Trie树

    Trie树 原理 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较...

    用户1177380
  • 人群密度估计

    CSRNet: Dilated Convolutional Neural Networks for Understanding the Highly Conge...

    用户1148525
  • 【自然框架】稳定版beta1的Demo —— 四:角色管理。另外 在线演示 终于搞定了

    在线演示   首先感谢“云淡风清”和“恭敬”提供域名、空间和数据库!   感谢大家对我的支持!   在线演示:http://demo.naturefw.co...

    用户1174620
  • 漫谈可视化Prefuse(三)---Prefuse API数据结构阅读有感

      前篇回顾:上篇《漫谈可视化Prefuse(二)---一分钟学会Prefuse》主要通过一个Prefuse的具体实例了解了构建一个Prefuse applic...

    JackieZheng
  • 基于Bootstrap的CSS3响应式滑动侧边栏布局代码解析/附源码下载

    bootstrap-vertical-menu是一款基于Bootstrap的CSS3响应式滑动侧边栏布局模板。该滑动侧边栏布局在大屏幕中以侧边栏的形式存在,在小...

    用户5997198
  • 今天吃什么?这个历史难题就交给它了!

    而知晓程序(微信号 zxcx0101)现在推荐的这款小程序「今天吃什么 Lite」,用最简单粗暴(误)的方式,帮你摆脱选择困难症。

    知晓君
  • Golang语言-基础知识-【环境设置】

    本地环境设置 在这里我们介绍设置Go编程语言环境,需要在你的计算机上的准备以下两个软件,(A)文本编辑器和(B)Go编译器。 文本编辑器 这将用来键入程序。一些...

    李海彬

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动