爬虫基础:Beautiful Soup

参考文档:Beautiful Soup 4.2.0 文档

Beautiful Soup 是一个可以从HTML和XML文件中提取数据的Python。它可以实现文档的增删改查操作,我们侧重点是它的查询操作。

安装 Beautiful Soup

你可以根据自己的系统选择下面的安装代码进行安装操作:

$ apt-get install Python-bs4
$ easy_install beautifulsoup4
$ pip install beautifulsoup4

安装解析器

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml。根据操作系统不同,你可以选择下面方法来安装 lxml:

$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml

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

$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib

几种解析器的优缺点

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, "html.parser") 执行速度适中,文档容错能力强 Python2.7.3 or 3.2.3前的版本文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快,文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, "xml") 速度快,唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup, "html5lib")
最高的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 数独慢,不依赖外部扩展

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

如何使用

将一段文档传入 Beautiful Soup 的构造方法,就能得到一个文档的对象,可以传入一段文字或一个文件句柄。

from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))    // 文件句柄
soup = BeautifulSoup("<html>data</html>")   // 文档

文档被转换成 Unicode,并且HTML的实例都被转换成 Unicode 编码,然后,Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器,那么 Beautiful Soup 会选择指定的解析器来解析文档。

对象的种类

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

Tag对象

Tag 对象与 XML 或 HTML 原生文档中的 tag 相同。Tag 有很多方法和属性,其中最重要的属性:name 和 attributes。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b            // 一个 Tag 对象
print(type(tag))        // <class 'bs4.element.Tag'>
print(tag.name)         // tag都有自己的名字:b
print(tag.attrs)        // tag的属性字典:{'class': ['boldest']}
print(tag['class'])     // 属性字典中的'class'值:['boldest']

// tag 的属性可以被添加,删除或修改,属性操作和字典一样
tag['class'] = 'myClsss'
print(tag)              // <b class="myClsss">Extremely bold</b>
tag['id'] = 'custemId'
print(tag)              // <b class="myClsss" id="custemId">Extremely bold</b>

NavigableString对象

字符串常被包含在 tag 内。Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串。

print(type(tag.string))     // <class 'bs4.element.NavigableString'>
print(tag.string)           // Extremely bold

字符串不支持 .contents 或 .string 属性或 find() 方法。

BeautifulSoup对象

该对象表示的是一个文档的全部内容,大部分的适合可以把它当作 Tag 对象。因为 BeautifulSoup 对象并不是真正的 HTML 或 XML 的tag,所以它没有name和attributes属性。但有时查看它时,.name 属性还是可以的。

print(type(soup))   // <class 'bs4.BeautifulSoup'>
print(soup.name)    // [document]

Comment对象

Tag , NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.容易让人担心的内容是文档的注释部分。

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup,'lxml')
comment = soup.b.string
print(type(comment))    // <class 'bs4.element.Comment'>

Comment 对象是一个特殊类型的 NavigableString 对象:

print(comment)  // Hey, buddy. Want to buy a used parser?

但是当它出现在HTML文档中时, Comment 对象会使用特殊的格式输出:

print(soup.b.prettify())
<b>
 <!--Hey, buddy. Want to buy a used parser?-->
</b>

搜索文档树

说完 Beautiful Soup 中的四种对象,接下来介绍一下如何搜索内容。 Beautiful Soup 提供了很多搜索方法,这里着重介绍其中的两个:find()find_all() ,其他的方法和参数以及用法都类似。

过滤器

过滤器作为搜索文档的参数,贯穿整个搜索的 API。过滤器可以被用在 tag 中的 name 中,节点的属性中,字符串中或者他们的混合中。过滤器可以是字符串、正则表达式、列表、True值甚至是方法。

  1. 字符串
soup.find_all('b')  // 查找文档中所有的<b>标签
  1. 正则表达式
import re
// 查找 b 开头的标签
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
    
// body
// b
  1. 列表
markup = '''
    <b>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')

for tag in soup.find_all(['a','p']):
    print(tag)

// <p class="title">The Dormouse's story</p>
// <a>Once upon a time there were three little sisters</a>
  1. True

True 可以匹配任何值

for tag in soup.find_all(True):
    print(tag.name)

// html
// body
// b
// p
// a
  1. 方法

如果没有合适的过滤器,那么还可以定一个方法,方法只接收一个参数,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则返回 False

def has_class(tag):
    return tag.has_attr('class')

for tag in soup.find_all(has_class):
    print(tag)
    
# <p class="title">The Dormouse's story</p>

find_all()

find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

  • name 参数

name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉。其中,name 参数的值可以是任一类型的过滤器:字符串、正则表达式、列表、Ture或是方法。

markup = '''
    <b>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')
print(soup.find_all('p'))   
# [<p class="title">The Dormouse's story</p>]
  • keyword 参数

如果一个指定名字的参数不是搜索内置的参数名称,搜索时会把该参数当作指定名字tag的属性来搜索。

markup = '''
    <b id='link'>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
    <a class="sister" href="http://example.com/elsie" id="link1">three</a>
    <div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')

print(soup.find_all(id='link')) 
# [<b id="link">Hey, buddy. Want to buy a used parser?</b>]

# class是Python的保留关键字,这里是 class_
print(soup.find_all(class_='title'))
# [<p class="title">The Dormouse's story</p>]

# 可以使用多个指定名字的参数来过滤多个tag的属性
import re
print(soup.find_all(id='link1', href=re.compile('example')))
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
  • attrs 参数

有些tag属性在搜索中不能使用,如HTML5中的 data-* 属性,我们可以使用 attrs 参数定一个字典参数来搜索包含特殊属性的tag。

print(soup.find_all(attrs={'data-foo':'value'}))
# [<div data-foo="value">foo!</div>]
  • text参数

通过 text 参数可以搜索文档中的字符串内容,和 name 参数一样,接受参数有:字符串、正则表达式、列表、True。

print(soup.find_all(text='three'))
# ['three']
import re
print(soup.find_all(text=re.compile('^H.*?$')))
# ['Hey, buddy. Want to buy a used parser?']
  • recursive 参数

Beautiful Soup 会检索当前tag的所有子孙节点,如果你只想搜索tag的直接子节点,可以使用参数 recursive = False。

markup = '''
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
</html>
'''
soup = BeautifulSoup(markup, 'lxml')

print(soup.html.find_all('title'))
# [<title>
#    The Dormouse's story
#   </title>]

print(soup.html.find_all('title', recursive=False))
# []
  • limit 参数
markup = '''
    <b id='link'>Hey, buddy. Want to buy a used parser?</b>
    <p class="title">The Dormouse's story</p>
    <a>Once upon a time there were three little sisters</a>
    <a class="sister" href="http://example.com/elsie" id="link1">three</a>
    <div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')

import re
for str in soup.find_all(text=re.compile('o')):
    print(str)
# Hey, buddy. Want to buy a used parser?
# The Dormouse's story
# Once upon a time there were three little sisters
# foo!

for str in soup.find_all(text=re.compile('o'), limit=1):
    print(str)

# Hey, buddy. Want to buy a used parser?
  • 说明

find_all() 是 Beautiful Soup中最常用的搜索方法,该库还提供了它的简写方法。BeautifulSoup 对象和 tag 对象都可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面代码是等价的:

soup.find_all("a")
soup("a")

soup.title.find_all(text=True)
soup.title(text=True)

find()

find_all()方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果。比如文档中只有一个<body>标签,那么使用 find_all() 方法来查找显然不太合适,如果使用 limit=1 参数不如使用 find()方法。

soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

soup.find('title')
# <title>The Dormouse's story</title>

唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果。
find_all() 方法没有找到目标是返回空列表,find() 方法找不到目标时,返回 None 。

soup.head.title 是 tag的名字 方法的简写。这个简写的原理就是多次调用当前tag的 find() 方法:

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

soup.find("head").find("title")
# <title>The Dormouse's story</title>

输出

prettify() 方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行。

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.prettify())
# <html>
#  <body>
#   <a href="http://example.com/">
#    I linked to
#    <i>
#     example.com
#    </i>
#   </a>
#  </body>
# </html>

Beautiful Soup 功能强大,除了上面提到的 搜索文档树 功能,更有遍历文档树,修改文档树等等功能。更多详细介绍请查阅官方文档 4.2.0

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容