【爬虫】python之BeautifulSoup用法
1.爬虫
网络爬虫是捜索引擎抓取系统的重要组成部分。爬虫的主要目的是将互联网上的网页下载到本地形成一个或联网内容的镜像备份。通过分析和过滤HTML 代码,实现对图片、文字等资源的获取。
2.python类库之BeautifulSoup
利用python写爬虫,可以使用urllib2等库结合正则表达式来实现。但是正则表达式比较复杂。不小心会进入死循环。BeautifulSoup是一个更强大的工具,可以方便的提取出HTML或XML标签中的内容。
现在使用的版本是BS4,可以直接通过pip或easy_install来安装:
$pip install beautifulsoup4
3.BeautifulSoup的使用
先给出官方文档:http://beautifulsoup.readthedocs.io/zh_CN/latest/
这里主要是对官方文档的一个记录。
解释器
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中包括 lxml 和 html5lib 。可以通过pip或easy_install来安装这两个解释器。这几者之间的差别见这里。
创建BeautifulSoup对象
首先要导入bs4库
from bs4 import BeautifulSoup
创建beautifulsoup 对象
soup = BeautifulSoup(html,"html.parser")
另外,我们还可以用本地 HTML 文件来创建对象,例如
soup = BeautifulSoup(open('index.html'))
下面我们来打印一下 soup 对象的内容,格式化输出
print soup.prettify()
对象的种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag
, NavigableString
, BeautifulSoup
, Comment
.
Tag
Tag
对象与XML或HTML原生文档中的tag相同:
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', "html.parser") tag = soup.b type(tag) # <class 'bs4.element.Tag'> print tag #<b class="boldest">Extremely bold</b>
现在介绍一下tag中最重要的属性: name和attributes
1.name
每个tag都有自己的名字,通过 .name
来获取:
tag.name # u'b'
2.attributes
一个tag可能有很多个属性。tag <b class="boldest">
有一个"class"的属性,值为"boldest"。 tag的属性的操作方法与字典相同:
tag['class'] # u'boldest'
也可以直接用“点”取属性,比如: .attrs
:
tag.attrs # {u'class': u'boldest'}
tag的属性可以被添加,删除或修改;tag的属性操作方法与字典一样
tag['class'] = 'verybold' tag['id'] = 1 tag # <blockquote class="verybold" id="1">Extremely bold</blockquote> del tag['class'] del tag['id'] tag # <blockquote>Extremely bold</blockquote> tag['class'] # KeyError: 'class' print(tag.get('class')) # None
NavigableString
既然我们已经得到了标签的内容,那么要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如
soup.string u'Extremely bold'
它的类型是一个 NavigableString,翻译过来叫 可以遍历的字符串,不过我们最好还是称它英文名字吧。
BeautifulSoup
BeautifulSoup
对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag
对象,它支持遍历文档树和搜索文档树中描述的大部分的方法.
因为 BeautifulSoup
对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性。但有时查看它的.name
属性是很方便的,所以 BeautifulSoup
对象包含了一个值为[document]的特殊属性 .name
soup.name # u'[document]'
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'>
b标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。
另外我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断,判断代码如下
if type(soup.b.string)==bs4.element.Comment: print soup.a.string
遍历文档树
拿”爱丽丝梦游仙境”的文档来做例子:
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> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'html.parser')
通过这段例子来演示怎样从文档的一段内容找到另一段内容
子节点
.content
tag的.contents
属性可以将tag的子节点以列表的方式输出:
head_tag = soup.head head_tag # <head><title>The Dormouse's story</title></head> head_tag.contents [<title>The Dormouse's story</title>] title_tag = head_tag.contents[0] title_tag # <title>The Dormouse's story</title> title_tag.contents # [u'The Dormouse's story']
.children
通过tag的 .children
生成器,可以对tag的子节点进行循环:
for child in title_tag.children: print(child) # The Dormouse's story
所有子孙节点
.descendants
.contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,我们也需要遍历获取其中的内容。
or child in soup.descendants: print child
运行结果如下,可以发现,所有的节点都被打印出来了,先生最外层的 HTML标签,其次从 head 标签一个个剥离,以此类推。
节点内容
.string
如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点。如果一个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就无法确定,string 方法应该调用哪个子节点的内容, .string 的输出结果是 None
多个内容
.strings
获取多个内容,不过需要遍历获取,比如下面的例子
for string in soup.strings: print(repr(string)) # u"The Dormouse's story" # u'\n\n' # u"The Dormouse's story" # u'\n\n' # u'Once upon a time there were three little sisters; and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n' # u'...' # u'\n'
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings
可以去除多余空白内容:
for string in soup.stripped_strings: print(repr(string)) # u"The Dormouse's story" # u"The Dormouse's story" # u'Once upon a time there were three little sisters; and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...'
父节点
.parent
通过.parent
属性来获取某个元素的父节点。在例子“爱丽丝”的文档中,<head>标签是<title>标签的父节点:
title_tag = soup.title title_tag # <title>The Dormouse's story</title> title_tag.parent # <head><title>The Dormouse's story</title></head>
文档的顶层节点比如<html>的父节点是 BeautifulSoup
对象:
html_tag = soup.html type(html_tag.parent) # <class 'bs4.BeautifulSoup'>
.parents
通过元素的.parents
属性可以递归得到元素的所有父辈节点,下面的例子使用了.parents
方法遍历了<a>标签到根节点的所有节点。
link = soup.a link # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> for parent in link.parents: if parent is None: print(parent) else: print(parent.name) # p # body # html # [document] # None
兄弟节点
看一段简单的例子:
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>") print(sibling_soup.prettify()) # <html> # <body> # <a> # <b> # text1 # </b> # <c> # text2 # </c> # </a> # </body> # </html
因为<b>标签和<c>标签是同一层:他们是同一个元素的子节点,所以<b>和<c>可以被称为兄弟节点.一段文档以标准格式输出时,兄弟节点有相同的缩进级别.在代码中也可以使用这种关系.
.next_sibling .previous_sibling
sibling_soup.b.next_sibling # <c>text2</c> sibling_soup.c.previous_sibling # <b>text1</b>
.next_siblings
和 .previous_siblings
通过.next_siblings
和.previous_siblings
属性可以对当前节点的兄弟节点迭代输出:
for sibling in soup.a.next_siblings: print(repr(sibling)) # u',\n' # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> # u' and\n' # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> # u'; and they lived at the bottom of a well.' # None for sibling in soup.find(id="link3").previous_siblings: print(repr(sibling)) # ' and\n' # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> # u',\n' # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> # u'Once upon a time there were three little sisters; and their names were\n' # None
前后节点
.next_element .previous_element
与 .next_sibling .previous_sibling 不同,它并不是针对于兄弟节点,而是在所有节点,不分层次
.next_elements .previous_elements
通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样
搜索文档树
Beautiful Soup定义了很多搜索方法,这里着重介绍2个: find()
和 find_all()
find_all( name , attrs , recursive , text , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
name参数
字符串
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的<b>标签:
soup.find_all('b') # [<b>The Dormouse's story</b>]
如果传入字节码参数,Beautiful Soup会当作UTF-8编码,可以传入一段Unicode 编码来避免Beautiful Soup解析编码出错
正则表达式
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match()
来匹配内容.下面例子中找出所有以b开头的标签,这表示<body>和<b>标签都应该被找到:
import re for tag in soup.find_all(re.compile("^b")): print(tag.name) # body # b
列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>标签和<b>标签:
soup.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <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>]
传True
True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
keyword参数
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id
的参数,Beautiful Soup会搜索每个tag的”id”属性.
soup.find_all(id='link2') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
如果传入 href
参数,Beautiful Soup会搜索每个tag的”href”属性:
soup.find_all(href=re.compile("elsie")) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True .
string参数
通过 string
参数可以搜搜文档中的字符串内容.与 name
参数的可选值一样, string
参数接受 字符串 , 正则表达式 , 列表, True . 看例子:
soup.find_all(string="Elsie") # [u'Elsie'] soup.find_all(string=["Tillie", "Elsie", "Lacie"]) # [u'Elsie', u'Lacie', u'Tillie'] soup.find_all(string=re.compile("Dormouse")) [u"The Dormouse's story", u"The Dormouse's story"] def is_the_only_string_within_a_tag(s): ""Return True if this string is the only child of its parent tag."" return (s == s.parent.string) soup.find_all(string=is_the_only_string_within_a_tag) # [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
limit参数
find_all()
方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit
参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit
的限制时,就停止搜索返回结果.
文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量:
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>]
recursive参数
调用tag的 find_all()
方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
.
find( name , attrs , recursive , text , **kwargs )
它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果