更多课程 选择中心

Python培训
美国上市教育机构

400-111-8989

Python培训

Python爬虫之BeautifulSoup解析之路

  • 发布: xiaoyu
  • 来源:Python网络爬虫分享
  • 时间:2018-01-22 10:58

上一篇分享了正则表达式的使用,相信大家对正则也已经有了一定的了解。它可以针对任意字符串做任何的匹配并提取所需信息。

但是我们爬虫基本上解析的都是html或者xml结构的内容,而非任意字符串。正则表达式虽然很强大灵活,但是对于html这样结构复杂的来说,写pattern的工作量会大大增加,并且有任意一处出错都得不到匹配结果,比较麻烦。

本篇将介绍一款针对html和xml结构,操作简单并容易上手的解析利器—BeautifulSoup。

BeautifulSoup的介绍

第一次使用BeautifulSoup的时候就在想:这个名字有什么含义吗?美味的汤?于是好信也在网上查了一下。

来看,官方文档是这么解释的:

BeautifulSoup: We called him Tortoise because he taught us

意思是我们叫他乌龟因为他教了我们,当然这里Tortoise是Taught us的谐音。BeautifulSoup这个词来自于《爱丽丝漫游仙境》,意思是“甲鱼汤”。上面那个官方配图也是来自于《爱丽丝漫游仙境》,看来是没跑了,估计是作者可能很喜欢这部小说吧,因而由此起了这个名字。

好,让我们看看真正的BeautifulSoup是什么?

BeautifulSoup是Python语言中的模块,专门用于解析html/xml,非常适合像爬虫这样的项目。它有如下几个使其强大的特点:

  • 它提供了几个超级简单的方法和Pythonic的语句来实现强大的导航、搜索、修改解析树的功能。

  • 它会自动把将要处理的文档转化为Unicode编码,并输出为utf-8的编码,不需要你再考虑编码的问题。

  • 支持Python标准库中的HTML解析器,还支持第三方的模块,如 lxml解析器 。

BeautifulSoup的安装

目前BeautifulSoup的最新发型版本是BeautifulSoup4,在Python中以bs4模块引入。

博主使用的Python3.x,可以使用 pip3 install bs4 来进行安装,也可以通过官方网站下载来安装,链接:https://www.crummy.com/software/BeautifulSoup/具体安装步骤不在此叙述了

以为安装完了吗?还没有呢。

上面介绍BeautifulSoup的特点时说到了,BeautifulSoup支持Python标准库的解析器html5lib,纯Python实现的。除此之外,BeautifulSoup还支持lxml解析器,为了能达到更好的解析效果,建议将这两个解析器也一并安装上。

根据操作系统不同,可以选择下列方法来安装lxml:

$ apt-get install Python-lxml

$ easy_install lxml

$ pip install lxml

另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:

$ apt-get install Python-html5lib

$ easy_install html5lib

$ pip install html5lib

下面列出上面提到解析器的使用方法。

解析器

使用方法

Python标准库

BeautifulSoup(markup, "html.parser")

lxml HTML解析器

BeautifulSoup(markup, "lxml")

lxml HTML解析器

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

BeautifulSoup(markup, "xml")

html5lib

BeautifulSoup(markup, "html5lib")

推荐使用lxml作为解析器,lxml是用C语言库来实现的,因此效率更高。在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定。

BeautifulSoup的文档对象创建

首先引入bs4库,也就是BeautifulSoup在Python中的模块。

    

from bs4 import BeautifulSoup

    好了,我们来看一下官方提供的例子,这段例子引自《爱丽丝漫游记》。

html_doc = """

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

"""

#FormatImgID_1#

假设以上html_doc就是我们已经下载的网页,我们需要从中解析并获取感兴趣的内容。

首先的首先,我们需要创建一个BeautifulSoup的文档对象,依据不同需要可以传入“字符串”或者“一个文件句柄”。

传入“字符串”

soup = BeautifulSoup(html_doc)

传入“文件句柄”,打开一个本地文件

soup = BeautifulSoup(open("index.html"))

   

文档首先被转换为Unicode,如果是解析html文档,直接创建对象就可以了(像上面操作那样),这时候BeautifulSoup会选择一个最合适的解析器对文档进行解析。

但同时,BeautifulSoup也支持手动选择解析器,根据指定解析器进行解析(也就是我们安装上面html5lib和lxml的原因)。

手动指定解析器如下:

soup = BeautifulSoup(html_doc, "lxml")

如果仅是想要解析HTML文档,只要用文档创建 BeautifulSoup 对象就可以了。Beautiful Soup会自动选择一个解析器来解析文档。但是还可以通过参数指定使用那种解析器来解析当前文档。

BeautifulSoup 第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档。如果第二个参数为空,那么Beautiful Soup根据当前系统安装的库自动选择解析器,解析器的优先数序: lxml, html5lib, Python标准库。在下面两种条件下解析器优先顺序会变化:

  1. 要解析的文档是什么类型: 目前支持, “html”,
    “xml”, 和 “html5”
  2. 指定使用哪种解析器: 目前支持, “lxml”, “html5lib”, 和 “html.parser”

BeautifulSoup的对象种类

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

  • Tag

  • NavigableString

  • BeautifulSoup

  • Comment

<Tag>

Tag就是html或者xml中的标签,BeautifulSoup会通过一定的方法自动寻找你想要的指定标签。查找标签这部分会在后面“遍历查找树”和“搜索查找树”中介绍,这里仅介绍对象。

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')

tag = soup.b

type(tag)

>>> <class 'bs4.element.Tag'>

Tag标签下也有对象,有两个重要的属性对象:name和attributes。

Name

Name就是标签tag的名字,以下简单操作便可获取。

tag.name

>>> u'b'

Attributes

我们都知道一个标签下可能有很多属性,比如上面那个标签b有class属性,属性值为boldest,那么我们如何获取这个属性值呢?

其实标签的属性操作和Python中的字典操作一样的,如下:

tag['class']

>>> u'boldest'

也可以通过“点”来获取,比如:

tag.attrs

>>> {u'class': u'boldest'}

<NavigableString>

NavigableString是可遍历字符串的意思,其实就是标签内包括的字符串,在爬虫里也是我们主要爬取的对象之一。

在BeautifulSoup中可以非常简单的获取标签内这个字符串。

tag.string

>>> u'Extremely bold'

就这么简单的完成了信息的提取,简单吧。要说明一点,tag中包含的字符串是不能编辑的,但是可以替换。

tag.string.replace_with("No longer bold")

tag

>>><blockquote>No longer bold</blockquote>

<BeautifulSoup>

BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作Tag对象。

soup.name

>>> u'[document]'

BeautifulSoup对象不是一个真正的tag,没有name和attributes,但是却可以查看它的name属性。如上所示,“[document]”为BeautifulSoup文档对象的特殊属性名字。

<Comment>

还有一些对象也是我们需要特殊注意的,就是注释。其实comment对象是一个特殊类型的NavigableString对象,请看下面。

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"

soup = BeautifulSoup(markup)

comment = soup.b.string

type(comment)

>>> <class 'bs4.element.Comment'>

comment

>>> u'Hey, buddy. Want to buy a used parser'

这和NavigableString的使用是一样,同样使用 .string 对标签内字符串进行提取。但是,请看上面comment这个例子,里面字符串是一个comment,有<!--comment-->这样的格式,一样使用了 .string 对其进行提取,得到的结果是去掉了comment标志的里面的字符串。这样的话,当我们并不知道它是否是comment,如果得到以上的结果很有可能不知道它是个comment。

因此,这可能会让我们得到我们不想要的comment,扰乱我们的解析结果。

为了避免这种问题的发生,可以在使用之前首先通过以下代码进行一个简单的判断,然后再进行其它操作。

if type(soup.b.string)==bs4.element.Comment:

    print(soup.b.string)

BeautifulSoup的遍历文档树

仍然用最开始的《爱丽丝》中的一段话作为例子。

子节点

子节点有 .contents.children 两种用法。

contents

content属性可以将标签所有子节点以列表形式返回。

# <head><title>The Dormouse's story</title></head>

print(soup.head.contents)

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

这样就可以返回一个子节点标签了。当然你也可以通过soup.title来实现,但是当文档结构复杂的时候,比如有不止一个title的话,那这样就不如contents使用来的快了。

head下只有一个标签title,那么如果我们查看一下body下的子标签。

print(soup.body.contents)

>>>

['\n', <p class="title"><b>The Dormouse's story</b></p>, '\n', <p class="story">Once upon a time there were three little sisters; and their ames 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>, '\n', <p class="story">...</p>, '\n']

你会发现这些子节点列表中有很多“\n”,这是因为它把空格包括进去了,所以这里需要注意一下。

children

也可以通过 .chidren 得到相同的结果,只不过返回的children是一个生成器(generator),而不是一个列表。

print(soup.body.children)

>>> <list_iterator object at 0x00000000035B4550>

看到这是一个生成器,因此我们可以for..in..进行遍历,当然也可以得到以上同样的结果。

for child in soup.body.children:
 
 print(child)

子孙节点

子孙节点使用 .descendants 属性。如果子节点可以直接获取标签的直接子节点,那么子孙节点则可以获取所有子孙节点,注意说的是所有,也就是说孙子的孙子都得给我找出来下用面开一个例子。

for child in head_tag.descendants:    print(child)

>>> <title>The Dormouse's story</title>

>>> The Dormouse's stor

title是head的子节点,而title中的字符串是title的子节点,title和title所包含的字符串都是head的子孙节点,因此被循环递归的查找出来。.descendants 的用法和 .children 是一样的,会返回一个生成器,需要for..in..进行遍历。

父节点

父节点使用 .parents 属性实现,可以得到父辈的标签。

title_tag = soup.title

title_tag

>>> <title>The Dormouse's story</title>

title_tag.parent

>>> <head><title>The Dormouse's story</title></head>

title_tag.parent.name

>>> head

获得全部父节点则使用 .parents 属性实现,可以循环得到所有的父辈的节点

link = soup.a

for parent in link.parents:    if parent is None:        print(parent)    else:        print(parent.name)

>>>

p

body

html

[document]

None

可以看到a节点的所有父辈标签都被遍历了,包括BeautifulSoup对象本身的[document]。

兄弟节点

兄弟节点使用 .next_sibling.previous_sibling 属性。

兄弟嘛,不难理解自然就是同等地位的节点了,其中next_sibling 获取下一个兄弟节点,而previous_sibling 获取前一个兄弟节点。

a_tag = soup.find("a", id="link1")

a_tag.next_sibling

>>> ,

a_tag.previous_element

>>>

Once upon a time there were three little sisters; and their names were

兄弟节点可以通过 .next_siblings.previous.sibling 获取所有前后兄弟节点,同样需要遍历获取每个元素。

回退和前进

当然还有一些其它用法,如回退和前进 .next_element.previous_element,它是针对所有节点的回退和前进,不分辈分。

a_tag = soup.find("a", id="link1")

a_tag

>>>

<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,

a_tag.next_element

>>> Elsie

a_tag.previous_element

>>>

Once upon a time there were three little sisters; and their names were

因为使用了回退,将会寻找下一个节点对象而不分辈分,那么这个<a>标签的下一个节点就是它的子节点Elsie,而上一个节点就是上一个标签的字符串对象。find用法会在后续搜索文档树里面详细介绍。

回退和前进也可以寻找所有的前后节点,使用 .next_elements.previous_elements

for elem in last_a_tag.next_elements:

    if elem.name is None:
       
continue

   print(elem.name)

>>>

a

a

p

返回对象同样是生成器,需要遍历获得元素。其中使用了if判断去掉了不需要的None。

节点内容

前面提到过NavigableString对象的 .string 用法,这里在文档遍历再次体会一下其用法。

如果tag只有一个NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点,就像之前提到的一样。而如果一个tag里面仅有一个子节点(比如tag里tag的字符串节点),那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同(如上所示)。

title_tag.string

>>> u'The Dormouse's story'

head_tag.contents

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

head_tag.string

>>> u'The Dormouse's story'

但是如果这个tag里面有多个节点,那就不灵了。因为tag无法确定该调用哪个节点,如下面这种。

print(soup.html.string)

>>> None

如果tag中包含多个字符串,可以使用 .strings 来循环获取,输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容。

上面提介绍的都是如何遍历各个节点,下面我们看看如何搜索我们我们真正想获取的内容,如标签属性等。

BeautifulSoup的搜索文档树

搜索文档树有很多种用法,但使用方法都基本一致。这里只选择介绍一种 .find_all

find_all()

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

find_all() 方法可以搜索当前标签下的子节点,并会经过过滤条件判断是否符合标准,先随便看个例子。

soup.find_all("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="link3">Tillie</a>]

soup.find_all(id="link2")

>>>

[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

通过以上例子,可以发现,我们只要设定好我们的过滤条件,便可轻松的解析我们想要的内容。这些条件如何设定呢?

就是通过find_all()的这些参数来设置的,让我们来看看。

Name参数

name参数就是标签的名字,如上面的例子寻找所有标签<a>,name参数可以是字符串、True、正则表达式、列表、甚至具体方法。

下面举个正则表达式的例子。

import re
soup = BeautifulSoup(html_doc, 'lxml')
for tag insoup.find_all(re.compile("^t")):
   
print(tag.name)

>>> title

可以看到正则表达式的意思是匹配任何以“t”开头的标签名称,就只有title一个。

使用“True”会匹配任何值,使用“列表”会匹配列表中所有的标签项,如果没有合适的过滤条件,还可以自定义一个“方法”

Keyword参数

就如同Python中的关键字参数一样,我们可以搜索指定的标签属性来定位标签。

soup.find_all(id='link2')

>>>

[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

找到了id属性为link2的标签<a>。

soup.find_all(href=re.compile("elsie"))

>>>

[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

找到了href属性里含有“elsie”字样的标签<a>。

也可以同时定义多个关键字条件来过滤匹配结果。

soup.find_all(href=re.compile("elsie"), id='link1')

>>>

[<a class="sister" href="http://example.com/elsie" id="link1">three</a>]

text参数

通过text参数可以搜索匹配的字符串内容,与name的用法相似,也可以使用字符串、True、正则表达式、列表、或者具体方法。

soup.find_all(text="Elsie")>>> [u'Elsie']

soup.find_all(text=re.compile("Dormouse")) >>>

[u"The Dormouse's story", u"The Dormouse's story"]

limit参数

limit参数可以限制返回匹配结果的数量,看下面这个例子。

soup.find_all("a", limit=2)

>>>

[<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>标签,但是通过限制只得到了两个。


recursive参数

find_all()会寻找符合匹配条件的所有子孙节点,如果我们只想找直接的子节点,就可以设置recursive参数来进行限制,recursive=False。

soup.html.find_all("title")

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

soup.html.find_all("title", recursive=False)

>>> [ ]

上面是两种使用recursive和没有使用recursive的情况,可以发现它的作用。

以上就是find_all()所有参数的介绍,其它方法如find(),find_parents()等更多方法与find_all()基本一致,可以举一反三。

总结

以上就是BeautifulSoup的使用方法介绍,主要记住三个部分内容:

  • BeautifulSoup对象种类

  • BeautifulSoup的遍历文档树

  • BeautifulSoup的搜索文档树

更多内容请参考官网文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

预约申请免费试听课

填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:Python学习心得分享,如何快速入门Python
下一篇:如何用 Python 爬取网页制作电子书

Python编程练习三

Python正则表达式练习

Python 2的结束意味着什么

用python做一个划词翻译软件

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省