目录
Beautiful Soup介绍
Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。BeautifulSoup会帮你节省数小时甚至数天的工作时间。
以上摘自官网
Beautiful Soup 安装
$ easy_install beautifulsoup4
# or
$ pip install beautifulsoup4
安装解析器
Beautiful Soup 不仅支持 Python 标准库中的 HTML 解析器,还支持很多第三方的解析器,比如 lxml
,html5lib
等。可以选择下列三种方法来安装lxml:
$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml
另一个可供选择的解析器是纯Python实现的 html5lib
, html5lib
的解析方式与浏览器相同,可以选择下列方法来安装html5lib:
$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib
各解析器优缺点如下:
推荐使用lxml
作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml
或html5lib
, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.
注意: 如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的,查看
Beautiful Soup 使用
Beautiful Soup 使用来起来非常简单,将一段文档(一段字符串或一个文件句柄)传入BeautifulSoup 的构造方法,就能得到一个文档的对象,有了该对象之后,就可以对该文档做一些我们想做的操作了。而传入的文本大都是通过爬虫爬取过来的,所以 Beautiful Soup 和 requests 库结合使用体验效果更佳。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"))
soup = BeautifulSoup("<html>data</html>")
首先,文档被转换成Unicode,并且HTML的实例都被转换成Unicode编码
BeautifulSoup("Sacré bleu!")
<html><head></head><body>Sacré bleu!</body></html>
然后,Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器那么Beautiful Soup会选择指定的解析器来解析文档.
对象的种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:Tag
, NavigableString
, BeautifulSoup
, Comment
.
其中:
-
Tag 就是 HTML 的一个标签,比如 div,p ,h1~h6标签等,也是用的最多的一个对象。
-
NavigableString 指标签内部的文字,直译就是可遍历的字符串。
-
BeautifulSoup 指一个文档的全部内容,可以当成一个 Tag 来处理。
-
Comment 是一个特殊的 NavigableString,其输出内容不包括注释内容。
Tag
Tag 对象与XML或HTML原生文档中的tag相同:
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
tag中重要的属性
tag中最重要的属性有:name
和attributes
Name
每个tag都有自己的名字,通过.name
来获取:
tag.name
# u'b'
如果改变了tag的name
,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:
tag.name = "blockquote"
tag
# <blockquote class="boldest">Extremely bold</blockquote>
Attributes
一个tag可能有很多个属性. tag <b class="boldest">
有一个“class”
的属性,值为 “boldest” . tag
的属性的操作方法与字典相同:tag
的属性可以被添加,删除或修改
tag['class']
# u'boldest'
# 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
也可以直接”点”取属性, 比如: .attrs :
tag.attrs
# {u'class': u'boldest'}
字符串常被包含在tag内.Beautiful Soup
用NavigableString
类来包装tag
中的字符串:
tag.string
# u'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>
一个NavigableString
字符串与Python中的Unicode
字符串相同,并且还支持包含在 遍历文档树 和 搜索文档树 中的一些特性. 通过unicode()
方法可以直接将 NavigableString
对象转换成Unicode
字符串:
unicode_string = unicode(tag.string)
unicode_string
# u'Extremely bold'
type(unicode_string)
# <type 'unicode'>
tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用replace_with()
方法:
tag.string.replace_with("No longer bold")
tag
# <blockquote>No longer bold</blockquote>
BeautifulSoup
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作Tag 对象
,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法.
因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name
和attribute
属性.但有时查看它的 .name
属性是很方便的,所以 BeautifulSoup 对象包含了一个值为“[document]”
的特殊属性.name
注释及特殊字符串:
Tag
,NavigableString
, BeautifulSoup
几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象:如文档的注释部分,这就需要Comment
对象会使用特殊的格式输出注释部分:
markup = "<b><!--这是一段注释--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
# Comment 对象会使用特殊的格式输出注释部分
comment
# u'这是一段注释'
遍历文档树
先定义一串 HTML 文本,下面做解析:
html_doc = """
<html><head><title>index</title></head>
<p class="title"><b>商城</b></p>
<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a>
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>
<p class="welcome">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)
子节点
一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.
注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点
获取Tag的名字
操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取<head>
标签,只要用 soup.head
:
soup.head
# <head><title>index</title></head>
soup.title
# <title>商城</title>
可以在文档树的tag
中多次调用这个方法.例如获取<body>
标签中的第一个<b>
标签:
soup.body.b
# <b>商城</b>
通过点取属性的方式只能获得当前名字的第一个tag:
soup.a
# <a href="http://cityShop.com/elsie" class="city" id="home">home</a>
如果想要得到所有的<a>
标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 find_all()
:
soup.find_all('a')
# [<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
# <a href="http://cityShop.com/lacie" class="city" id="design">design</a>
# <a href="http://cityShop.com/tillie" class="city" id="products">products</a>]
.contents 和 .children
tag的.contents
属性可以将tag
的子节点以列表的方式输出:
head_tag = soup.head
head_tag
# <head><title>index</title></head>
head_tag.contents
[<title>index</title>]
title_tag = head_tag.contents[0]
title_tag
# <title>index</title>
title_tag.contents
# [u'index']
tag的.children 生成器,
可以对tag
的子节点进行循环遍历:
for child in title_tag.children:
print(child)
# index
.children
只可以获取 tag 的直接节点,而获取不到子孙节点,.descendants
可以满足。
.contents
和 .children
属性仅包含tag的直接子节点.例如,<head>
标签只有一个直接子节点<title>
但是<title>
标签也包含一个子节点:字符串 “index”
,这种情况下字符串“index”
也属于<head>
标签的子孙节点. .descendants
属性可以对所有tag的子孙节点
进行递归循环 :
for child in head_tag.descendants:
print(child)
# <title>index</title>
# index
父节点
每个tag或字符串都有父节点:被包含在某个tag中
.parent
通过 .parent
属性来获取某个元素的父节点.title
的父标签是 head
,html
的父标签是 BeautifulSoup 对象,而 BeautifulSoup 对象的父标签是 None。
title_tag = soup.title
title_tag
# <title>index</title>
title_tag.parent
# <head><title>index</title></head>
# 文档title的字符串也有父节点:<title>标签
title_tag.string.parent
# <title>index</title>
# 文档的顶层节点比如<html>的父节点是 BeautifulSoup 对象:
html_tag = soup.html
type(html_tag.parent)
# <class 'bs4.BeautifulSoup'>
# BeautifulSoup 对象的 .parent 是None:
print(soup.parent)
# None
.parents
通过元素的 .parents
属性可以递归得到元素的所有父辈节点,下面的例子使用了.parents
方法遍历了<a>
标签到根节点的所有节点.
link = soup.a
link
# <a href="http://cityShop.com/elsie" class="city" id="home">home</a>
for parent in link.parents:
if parent is None:
print(parent)
else:
print(parent.name)
#结果:
# p
# body
# html
# [document]
# None
兄弟节点
.next_sibling 和 .previous_sibling
兄弟节点即同一级别的兄弟元素,在文档树中,使用.next_sibling
和.previous_sibling
属性来查询兄弟节点:
soup = BeautifulSoup(html_doc, "lxml");
p_tag=soup.p
print(p_tag.next_sibling)
print(p_tag.next_sibling.next_sibling)
# 输出结果
<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a>
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>
p 的第一个 next_sibling 是p 和 p 之间的换行符。
.next_siblings 和 .previous_siblings
通过 .next_siblings
和.previous_siblings
属性可以对当前节点的兄弟节点迭代输出:
soup = BeautifulSoup(html_doc, "lxml");
p_tag=soup.p
for p_tag in p_tag.previous_siblings:
print( p_tag)
# 输出结果
<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a>
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>
# 输出结果
<p class="title"><b>商城</b></p>
前进和后退
通过.next_element
和.previous_element
获取指定标签的前一个或者后一个被解析的对象,注意这个和兄弟节点是有所不同的,兄弟节点是指有相同父亲节点的子节点,而这个前一个或者后一个是按照文档的解析顺序来计算的。
比如在实例的文本 html_doc
中,head
的兄弟节点是body
(不考虑换行符),因为他们具有共同的父节点 html
,但是 head
的下一个节点是 title
。即soup.head.next_sibling=title soup.head.next_element=title`。
soup = BeautifulSoup(html_doc, "lxml");
head_tag=soup.head
print(head_tag.next_element)
title_tag=soup.title
print(title_tag.next_element)
# 输出结果
<title>index</title>
index
还需要注意的是title
下一个解析的标签不是body
,而是 title
标签内的内容,因为 html
的解析顺序是打开 title
标签,然后解析内容,最后关闭title
标签。
.previous_element
属性刚好与.next_element
相反,它指向当前被解析的对象的前一个解析对象
:
last_a_tag.previous_element
# u' and\n'
last_a_tag.previous_element.next_element
# <a href="http://cityShop.com/tillie" class="city" id="products">products</a>
另外,同样可以通过.next_elements
和 .previous_elements
来迭代文档树。换行符同样会占用解析顺序,与迭代兄弟节点效果一致。
for element in last_a_tag.next_elements:
print(repr(element))
# u'Tillie'
# u';\欢迎来参观.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None
搜索文档树
Beautiful Soup定义了很多搜索方法,其中比较重要常用的是: find()
和find_all()
.
定义一个文档实例:
html_doc = """
<html><head><title>index</title></head>
<p class="title"><b>商城首页</b></p>
<p class="story">这是我的第三个商城,欢迎来参观
<a href="http://cityShop.com/elsie" class="city" id="home">home</a>
<a href="http://cityShop.com/lacie" class="city" id="design">design</a>
<a href="http://cityShop.com/tillie" class="city" id="products">products</a>
欢迎来参观.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)
使用find_all()
类似的方法可以查找到想要查找的文档内容:
soup.find_all('b')
# 结果
# [<b>商城首页</b>]
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的match()
来匹配内容.下面例子中找出所有以b
开头的标签,这表示<body>
和<b>
标签都应该被找到:
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
找出所有名字中包含”t”
的标签:
for tag in soup.find_all(re.compile("t")):
print(tag.name)
# 结果
# html
# title
传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有<a>
标签和<b>
标签:
soup.find_all(["a", "b"])
# [<b>商城首页</b>
# <a href="http://cityShop.com/elsie" class="city" id="home">home</a>
# <a href="http://cityShop.com/lacie" class="city" id="design">design</a>
# <a href="http://cityShop.com/tillie" class="city" id="products">products</a>]
更详细文档: https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#