爬虫基础: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语言库
html5libBeautifulSoup(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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值