前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用BeautifulSoup来煲美味的汤

用BeautifulSoup来煲美味的汤

作者头像
啃饼思录
发布2018-10-15 17:31:19
1.7K0
发布2018-10-15 17:31:19
举报

基础第三篇:用BeautifulSoup来煲美味的汤

许多人喜欢在介绍正则表达式以后才来介绍本篇BeautifulSoup的用法,但是我觉得BeautifulSoup比正则表达式好用,而且容易上手,非常适合小白入门爬虫,并且可以利用学到的这个知识立即去爬取自己想爬的网站,成就感满满的。好了话不多说,立即进入今天的介绍吧。

你可能会问BeautifulSoup:美味的汤?这个东西能干嘛?为什么起这个名字呢?先来看一下官方的介绍。

BeautifulSoup: We called him Tortoise because he taught us

翻译过来就是:我们叫他乌龟因为他教了我们。是不是还是懵圈,没办法外国人起名字就是这么随意。谁能知道那么厉害的Java竟然是开发者在楼下觉得味道不错的一种咖啡的名字呢,哈哈哈哈。算了,我们不纠结这个问题了,我们还是开始介绍它的安装和使用吧。话不多说,走你!

BeautifulSoup的安装

目前BeautifulSoup已经更新到了BeautifulSoup4,在Python中你只需要以bs4模块引入即可。小编我用的Python的版本是3.6.4,所以可以使用pip3 install bs4 来进行安装,当然了你也可以去官方下载到本地然后再进行安装:链接:https://www.crummy.com/software/BeautifulSoup/,具体的安装我这里就不介绍了,不懂的可以自行百度。

说到这里,你可能还是不知道BeautifulSoup是干嘛的,说白了它其实就是Python的一个HTML或者XML的解析库,但是它在解析的时候实际上还是依赖解析器的,下面就列举一些BeautifulSoup支持的解析器:

解析器

使用方法及特点

Python标准库

BeautifulSoup(markup, "html.parser"),速度适中,容错能力较弱

lxml HTML解析器

BeautifulSoup(markup, "lxml"),速度快,文档容错能力强

lxml XML解析器

BeautifulSoup(markup, ["lxml", "xml"])BeautifulSoup(markup, "xml"),速度快,唯一支持XM链的解析器

html5lib

BeautifulSoup(markup, "html5lib"),速度慢、不依赖外部扩展

通过以上对比可以看出, lxml解析器有解析 HTML 和 XML 的功能, 而且速度快, 容错能力强所以推荐使用它。

接下来教你如何使用BeautifulSoup和lxml进行数据的提取。在此之前,我们需要创建一个BeautifulSoup的文档对象,依据不同需要可以传入“字符串”或者“一个文件句柄”。

当传入“字符串”时,

soup = BeautifulSoup(html_doc,"lxml")

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

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

接下来便是BeautifulSoup的对象种类的介绍,它有4种类型,下面我们分别进行说明。

BeautifulSoup的对象种类

Beautiful Soup实质是将复杂的HTML文档转换成一个复杂的树形结构(因为HTML本身就是DOM),然后每个节点都是Python对象,通过分析可以把所有对象分成4种类型:Tag、NavigableString、BeautifulSoup、Comment。

1、<Tag>

Tag其实就是html或者xml中的标签,BeautifulSoup会通过一定的方法自动寻找你想要的指定标签。看下面这个例子:

soup=BeautifulSoup('<p class="good">Excelent boy</p>')

tag = soup.p

type(tag)

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

其实Tag标签也是有属性的,name和attributes就是非常重要的两个属性。

Name

Name就是标签tag的名字,一个标签的名字是唯一的,我们直接调用tag.name即可简单获取tag的名字。

tag.name

>>> 'p'

Attributes

我们知道一个标签下面可能会有很多属性,比如上面那个标签p有class属性,属性值为good,那么我们如何获取这个属性值呢?我们可以仿照Python中操作字典那样通过key来获取value的值的方法,来获取tag的每个属性对应的值:

tag['class']

>>> 'good'

当然你也是可以通过tag.attrs来获取所有属性(采用tag.attrs['属性名称']获取指定属性值),比如:

tag.attrs

>>> {'class': 'good'}

tag.attrs['class']

>>> 'good'

2、<NavigableString>

NavigableString其实就是可以遍历的字符串(标签内包括的字符串),在BeautifulSoup中可以采用.string的方式来直接获取标签内的字符串。

tag.string

>>> ''Excelent boy'

是不是非常简单。不过要说明的是,tag中包含的字符串是不能编辑的,但是可以替换:

tag.string.replace_with("Bad boy")

tag

>>><blockquote>Bad boy</blockquote>

3、<BeautifulSoup>

BeautifulSoup对象其实它表示的是一个文档的全部内容,不过大部分情况下,我们都是把它当作Tag对象来使用的。例如:

soup.name

>>> '[document]'

但实际上BeautifulSoup对象不是一个真正的tag,前面说了,tag有2个重要的属性name和attributes,它是没有的。但是却可以查看它的name属性,如上面采用soup.name方式获取的“[document]”,我们可以理解为“[document]”是BeautifulSoup对象的特殊属性名字。

4、<Comment>

Comment就是注释,它是一个特殊类型的NavigableString对象,为什么这么说呢,因为我们可以直接采用类似于NavigableString对象获取字符串的方式来获取注释文本,看下面的例子你就明白了:

web_data = "<p><!--hello, everybody. Welcome to the world for python--></p>"

soup = BeautifulSoup(web_data)

comment = soup.p.string

type(comment)

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

comment

>>> ''hello, everybody. Welcome to the world for python'

是不是和NavigableString的使用非常相似,我们这里使用 p.string 对标签内的字符串进行提取。但是这里有一个疑问,就是我们通过这种方式可以得到字符串,但是如果我们获取了字符串,我们反过来是不知道这个字符串是Comment注释,还是正常的标签内的文本。所以我们在爬取数据的时候需要进行判断,如果是Comment对象,我们就不爬了,直接跳过:

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

continue;

说完了4种对象类型,接下来说一下BeautifulSoup如何对文档树进行遍历,从而找到我们想要的数据。

BeautifulSoup遍历文档树

为了更好的介绍这些功能,我采用官方的例子进行说明:这段例子引自《爱丽丝漫游记》。

web_data =

"""

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

"""

我们以本体为起点,先介绍子节点,子孙节点,再介绍父节点,祖宗节点,兄弟节点等信息。

子节点

子节点有就是当前本体的下延,当然就包括直接下延(子节点)和间接下延了(子孙节点) ,首先介绍如何返回所有的子节点,将介绍.contents.children 的用法。

contents

contents可以将标签所有的子节点以列表形式返回。

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

print(soup.head.contents)

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

是不是很简单,当然你也可以使用soup.title同样能实现这个功能,但是你想过没,当文档结构复杂的时候,比方说不止一个title的时候,你还采用soup.title这种方式是不是太慢了,你需要区分那些title的不同,还需要全部输出,用contents直接一步完事,超级easy。如果你不相信可以采用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 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>, '\n', <p class="story">...</p>, '\n']

你会发现.contents返回的是一个列表,而且列表中有很多“\n”,这是因为它把空格也包括进去了,所以如果我们需要提取其中的文本内容,我们还需要采用split()或者sub()来去掉空格,这部分后面实战部分有介绍。

children

我们也可以通过 .chidren 的方式获取所有的子节点,与之不同的是 .chidren返回的是一个生成器(generator),而不是一个列表。

print(soup.body.children)

>>> <list_iterator object at 0x00000000035B4550>

对于生成器,我们可以先采用list(),转化为列表,再进行遍历:

for child in list(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 是一样的,会返回一个生成器,所以需要先转化为list再进行遍历。

父节点

对于父节点,我们可以使用 .parents 得到父标签。

title_tag = soup.title

title_tag

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

title_tag.parents

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

title_tag.parents.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.siblings获取所有前后兄弟节点,返回结果也是一个生成器。

说完了节点的获取,接下来说一下如何提取已经获取的节点的内容呢?

节点内容

前面说过对于NavigableString对象,我们可以采用 .string 来获取文本信息。如果tag只有一个NavigableString 类型的子节点,那么这个tag可以使用 .string 得到文本信息,就像之前提到的一样。看下面的title和head标签:

title_tag.string

>>> 'The Dormouse's story'

head_tag.contents

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

head_tag.string

>>> 'The Dormouse's story'

上面那种方法只适用于tag只有一个NavigableString 类型的子节点情况,如果这个tag里面有多个节点,那就不行了,因为tag无法确定该调用哪个节点,就会出现下面这种输出None的情况:

print(soup.html.string)

>>> None

需要说明的是,如果tag中包含多个字符串,我们可以使用 .strings 来循环获取。如果输出的字符串中包含了很多空格或空行,则可以使用 .stripped_strings 来去除多余的空白内容(包括空格和空行)。

现在有一个问题了,你上面介绍的都是如何遍历各个节点,可是有时候我不需要你进行遍历全部,那样会增加运行时间,我只需要提取我需要的那部分即可,所以我们就可以搜索文档,直接输出满意的结果就行。

BeautifulSoup搜索文档树

搜索文档树有很多方法,match,find,find_all...,这里介绍比较常用的fnd_all()。fnd_all()它可以设置过滤条件,直接返回满足条件的值。

find_all()语法格式:

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

通过一个简单的例子,来感受一下它的魅力:

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

>>>

<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

下面简单介绍一下它的几个重要的参数:name,keywords。

Name参数

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

Keyword参数

这种形式非常类似于我们Python中对字典的操作,通过设置key这个过滤条件来获取指定信息:

soup.find_all(id="link3")

>>>

<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

这里找到了id为link3的a标签信息。当然也是可以采用关键字的方式:

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

>>>

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

这里找到了href属性里含有“lacie”字样的a标签的信息,我们也可以同时定义多个关键字来进行更严格的过滤:

soup.find_all(href=re.compile("lacie"), id='link2')

>>>

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

简单再说一下match和search的用法:你只要记住match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。search则是全局搜索,用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。关于其他方法的介绍请点击阅读原文进行查看吧。

好了本篇关于用BeautifulSoup来煲美味的汤的介绍就到此为止了,感谢你的赏阅!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 啃饼思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 你可能会问BeautifulSoup:美味的汤?这个东西能干嘛?为什么起这个名字呢?先来看一下官方的介绍。
  • BeautifulSoup: We called him Tortoise because he taught us
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档