用BeautifulSoup来煲美味的汤

基础第三篇:用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来煲美味的汤的介绍就到此为止了,感谢你的赏阅!

原文发布于微信公众号 - 啃饼思录(kbthinking)

原文发表时间:2018-09-26

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏安恒网络空间安全讲武堂

​CTF逆向——常规逆向篇(下)

CTF逆向——常规逆向篇(下) 题目: CrackMe.exe(NSCTF reverse第一题) WHCTF2017 reverse HCTF reverse...

5065
来自专栏Ryan Miao

java中使用junit测试

最初写代码只要功能走通就不管了,然后如果出了什么问题再去修改,这是因为没做测试的工作。测试其实很简单。 1.准备 当前使用idea编写代码,用maven构建工程...

4347
来自专栏余林丰

Java线程安全性中的对象发布和逸出

发布(Publish)和逸出(Escape)这两个概念倒是第一次听说,不过它在实际当中却十分常见,这和Java并发编程的线程安全性就很大的关系。 什么是发布?简...

2409
来自专栏nnngu

经典Java面试题收集

1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象...

4926
来自专栏猿人谷

腾讯2013年实习生笔试题目(附答案)

下面是我在参加2013年腾讯实习生招聘的笔试题目,当然啦,我个人不可能是完全的记住所有题目,部分是摘自网络的。同时,下面也有一些题目我不会的,希望大家一起商量解...

3738
来自专栏从零开始学自动化测试

python爬虫beautifulsoup4系列2

前言 本篇详细介绍beautifulsoup4的功能,从最基础的开始讲起,让小伙伴们都能入门 一、读取HTML页面 1.先写一个简单的html页面,把以下...

3046
来自专栏CDA数据分析师

学完Python基础知识后,你真的会python吗?

前言 最近觉得 Python 太“简单了”,于是在师父川爷面前放肆了一把:“我觉得 Python 是世界上最简单的语言!”。于是川爷嘴角闪过了一丝轻蔑的微笑(内...

36710
来自专栏Java3y

Java锁机制了解一下

2646
来自专栏Coco的专栏

BAT及各大互联网公司2014前端笔试面试题--JavaScript篇

3205
来自专栏Golang语言社区

GoStub框架二次开发实践

序言 要写出好的测试代码,必须精通相关的测试框架。对于Golang的程序员来说,至少需要掌握下面四个测试框架: GoConvey GoStub GoMock M...

40611

扫码关注云+社区

领取腾讯云代金券