Beautiful Soup4 学习
简介
Beautiful Soup 是一个可以从 HTML 或 XML 文件中提取数据的 Python 库。它能够通过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。Beautiful Soup 会帮你节省数小时甚至数天的工作时间。
安装
$ easy_install beautifulsoup4
$ pip install beautifulsoup4
安装解析器
Beautiful Soup
支持 Python
标准库中的 HTML
解析器,还支持一些第三方的解析器,其中一个是 lxml。根据操作系统不同,可以选择下列方法来安装 lxml
:
$ easy_install lxml
$ pip install lxml
另一个可供选择的解析器是纯Python实现的 html5lib,html5lib
的解析方式与浏览器相同,可以选择下列方法来安装 html5lib
:
$ easy_install html5lib
$ pip install html5lib
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, "html.parser") | Python的内置标准库 执行速度适中 文档容错能力强 |
Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快 文档容错能力强 |
需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml") |
速度快 唯一支持XML的解析器 |
需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 |
速度慢 不依赖外部扩展 |
Beautiful Soup 对象
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"), "lxml") # 打开本地文件
soup = BeautifulSoup("<html>data</html>", "lxml")
# 官网上的例子,可以不加 "lxml",实际使用不加会报错
print(soup.prettify()) # 美化输出
"""
<html>
<body>
<p>
data
</p>
</body>
</html>
"""
Tag
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', "lxml")
tag = soup.b
print(tag) # <b class="boldest">Extremely bold</b>
print(type(tag)) # <class 'bs4.element.Tag'>
name 属性
可以多次调用 tag.name.name
print(tag.name) # b
tag.name = "blockquote" # 修改
print(tag) # <blockquote class="boldest">Extremely bold</blockquote>
Attributes 属性
tag
的 Attributes
属性的操作方法与字典相同。
print(tag['class']) # ['boldest']
print(tag.attrs) # {'class': ['boldest']}
# 修改
tag['class'] = 'verybold'
tag['id'] = 1
print(tag) # <blockquote class="verybold" id="1">Extremely bold</blockquote>
# 删除
del tag['class']
del tag['id']
print(tag) # <blockquote>Extremely bold</blockquote>
# 访问不存在的属性
print(tag['class']) # 报错
print(tag.get('class')) # None
多值属性
如果转换的文档是XML格式,那么 tag 中不包含多值属性。
最常见的多值的属性是 class
(一个 tag 可以有多个 CSS 的 class). 还有一些属性 rel
,rev
,accept-charset
,headers
,accesskey
。在 Beautiful Soup 中多值属性的返回类型是 list:
css_soup = BeautifulSoup('<p class="body strikeout"></p>', "lxml")
print(css_soup.p['class']) # ["body", "strikeout"]
css_soup = BeautifulSoup('<p class="body"></p>', "lxml")
print(css_soup.p['class']) # ["body"]
将 tag 转换成字符串时,多值属性会合并为一个值
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>', "lxml")
print(rel_soup.a['rel']) # ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p) # <p>Back to the <a rel="index contents">homepage</a></p>
NavigableString
字符串常被包含在 tag 内。Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串。
下面的一段 HTML 代码将作为例子被多次用到。
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>
"""
soup = BeautifulSoup(html_doc, "lxml")
print(soup.p.b.string) # The Dormouse's story
soup.p.b.string.replace_with("No longer bold") # 替换
print(soup.p.b.string) # No longer bold
soup.p.b.string = "The Dormouse's story" # 替换
print(soup.p.b.string) # The Dormouse's story
如果想在 Beautiful Soup
之外使用 NavigableString
对象,需要调用 unicode()
unicode_string = unicode(tag.string)
print(unicode_string) # u'Extremely bold'
print(type(unicode_string)) # <type 'unicode'>
BeautifulSoup
BeautifulSoup
对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag
对象
print(soup.name) # [document]
Comment
Comment
对象是一个特殊类型的 NavigableString
对象,注释及特殊字符串对象。
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'>
print(comment) # Hey, buddy. Want to buy a used parser?
其他对象
CData
,ProcessingInstruction
,Declaration
,Doctype
与 Comment
对象类似,这些类都是 NavigableString
的子类,只是添加了一些额外的方法的字符串独享。下面是用 CDATA
来替代注释的例子:
from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)
print(soup.b) # <b><![CDATA[A CDATA block]]></b>
遍历文档树
子节点
通过点取属性的方式只能获得当前名字的第一个 tag:
soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
如果想要得到所有的 <a>
标签,或是通过名字得到比一个 tag
更多的内容的时候,就可以用 find_all()
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>]
.contents 将子节点以列表的方式输出
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>]
head_tag.contents[0]
# <title>The Dormouse's story</title>
head_tag.contents[0].contents
# [u'The Dormouse's story']
BeautifulSoup
对象本身一定会包含子节点,也就是说 <html>
标签也是 BeautifulSoup
对象的子节点:
len(soup.contents)
# 1
soup.contents[0].name
# u'html'
.children 子节点进行循环
通过 tag
的 .children
生成器,可以对 tag
的子节点进行循环:
for child in head_tag.contents[0].children:
print(child)
# The Dormouse's story
.descendants 子孙节点进行递归循环
for child in head_tag.descendants:
print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story
len(list(soup.children))
# 1
len(list(soup.descendants)) # 子孙节点 多
# 25
.string
如果 tag 只有一个 NavigableString
类型子节点,那么这个 tag 可以使用 .string
得到子节点。
head_tag.contents[0].string
# u'The Dormouse's story'
如果 tag 包含了多个子节点,tag 就无法确定 .string
方法应该调用哪个子节点的内容,.string
的输出结果是 None
。
print(soup.html.string)
# None
.strings 和 stripped_strings
如果 tag 中包含多个字符串,可以使用 .strings
来循环获取:
for string in soup.find_all('p')[1].strings:
print(repr(string))
"""
'Once upon a time there were three little sisters; and their names were\n'
'Elsie'
',\n'
'Lacie'
' and\n'
'Tillie'
';\nand they lived at the bottom of a well.'
"""
使用 .stripped_strings
可以去除多余空白内容,全部是空格的行会被忽略掉,段首和段末的空白会被删除。
父节点
.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>
文档 title 的字符串也有父节点:<title>
标签
title_tag.string.parent
# <title>The Dormouse's story</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 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
兄弟节点
.next_sibling 和 .previous_sibling
soup.a.next_sibling # 结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符
# u',\n'
soup.a.previous_sibling
# Once upon a time there were three little sisters; and their names were
print(soup.b.previous_sibling) # 没有返回 None
# None
.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
回退和前进
HTML 解析器把这一串字符串转换成一连串的事件: “打开 <html>
标签”,”打开一个 <head>
标签”,”打开一个 <title>
标签”,”添加一段字符串”,”关闭 <title>
标签”,”打开 <p>
标签”,等等。
Beautiful Soup 提供了重现解析器初始化过程的方法。(不含关闭标签)
.next_element 和 .previous_element
.next_element
属性指向解析过程中下一个被解析的对象:
soup.find("a", id="link3").next_sibling
# '; and they lived at the bottom of a well.'
soup.find("a", id="link3").next_element
# u'Tillie'
.previous_element
属性刚好与 .next_element
相反,它指向当前被解析的对象的前一个解析对象:
soup.find("a", id="link3").previous_element
# u' and\n'
soup.find("a", id="link3").previous_element.next_element
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
.next_elements 和 .previous_elements
通过 .next_elements
和 .previous_elements
的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:
for element in soup.find("a", id="link3").next_elements:
print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None
搜索文档树
过滤器
过滤器可以被用在 tag 的 name 中,节点的属性中,字符串中或他们的混合中。
字符串
Beautiful Soup会查找与字符串完整匹配的标签。
soup.find_all('b')
# [<b>The Dormouse's story</b>]
正则表达式
如果传入正则表达式作为参数,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,但是不会返回字符串节点
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p
方法
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数,如果这个方法返回 True
表示当前元素匹配并且被找到,如果不是则反回 False
下面方法校验了当前元素,如果包含 class
属性却不包含 id
属性,那么将返回 True
:
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
将这个方法作为参数传入 find_all()
方法,将得到所有 <p>
标签:
soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
# <p class="story">Once upon a time there were...</p>,
# <p class="story">...</p>]
find_all()
find_all( name , attrs , recursive , string , **kwargs )
name 参数
name 参数
可以查找所有名字为 name 的 tag。
用法如上,name 参数
的值可以使任一类型的 过滤器。
keyword 参数
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字 tag 的属性来搜索。
soup.find_all(id='link2', href=re.compile("lacie"))
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
搜索指定名字的属性时可以使用的参数值包括 字符串,正则表达式,列表,True。
attrs 参数
有些 tag 属性在搜索不能使用,可以通过 find_all()
方法的 attrs
参数定义一个字典参数来搜索包含特殊属性的 tag:
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', "lxml")
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]
按CSS类名搜索
标识 CSS 类名的关键字 class
在 Python 中是保留字,使用 class
做参数会导致语法错误。所以可以使用 class_
。
soup.find_all("a", class_="sister", id="link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
当然也可以使用attrs
参数,soup.find_all("a", attrs={"class": "sister"})
。
string 参数
通过 string 参数
可以搜搜文档中的字符串内容。string 参数
接受 字符串,正则表达式,列表,True,方法 。
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)
print(soup.find_all(string=is_the_only_string_within_a_tag))
# ["The Dormouse's story", "The Dormouse's story", 'Elsie', 'Lacie', 'Tillie', '...']
soup.find_all("a", string="Elsie")
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
limit 参数
使用 limit 参数
限制返回结果的数量。
print(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
。
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]
soup.html.find_all("title", recursive=False)
# []
像调用 find_all() 一样调用tag
BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:
soup.find_all("a")
soup("a")
soup.a # 相当于 soup.find("a")
soup.title.find_all(string=True)
soup.title(string=True)
find()
相当于使用 find_all()
方法并设置 limit=1
参数。
唯一的区别是 find_all()
方法的返回结果是值包含一个元素的列表,而 find()
方法直接返回结果。find_all()
方法没有找到目标是返回空列表,find()
方法找不到目标时,返回 None
find_parents() 和 find_parent()
find_all()
和 find()
只搜索当前节点的所有子节点,孙子节点等。find_parents()
和 find_parent()
用来搜索当前节点的父辈节点,搜索方法与普通 tag 的搜索方法相同,搜索文档搜索文档包含的内容。
find_next_siblings() 和 find_next_sibling()
这2个方法通过 .next_siblings
属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代, find_next_siblings()
方法返回所有符合条件的后面的兄弟节点,find_next_sibling()
只返回符合条件的后面的第一个 tag 节点。
find_previous_siblings() 和 find_previous_sibling()
这2个方法通过 .previous_siblings
属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代,find_previous_siblings()
方法返回所有符合条件的前面的兄弟节点,find_previous_sibling()
方法返回第一个符合条件的前面的兄弟节点。
find_all_next() 和 find_next()
这2个方法通过 .next_elements
属性对当前tag的之后的 tag 和字符串进行迭代,find_all_next()
方法返回所有符合条件的节点,find_next()
方法返回第一个符合条件的节点。
find_all_previous() 和 find_previous()
这2个方法通过 .previous_elements
属性对当前节点前面的 tag 和字符串进行迭代,find_all_previous()
方法返回所有符合条件的节点,find_previous()
方法返回第一个符合条件的节点。
CSS选择器
Beautiful Soup 支持大部分的CSS选择器, 在 Tag 或 BeautifulSoup 对象的 .select()
方法中传入字符串参数,即可使用 CSS 选择器的语法找到 tag。
soup.select("title")
# [<title>The Dormouse's story</title>]
soup.select("p:nth-of-type(3)")
# [<p class="story">...</p>]
# 同时用多种CSS选择器查询元素
soup.select("#link1,#link2")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
# 返回查找到的元素的第一个
soup.select_one(".sister")
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
其他
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:Beautiful Soup4 学习
文章字数:4.6k
本文作者:Bin
发布时间:2018-03-18, 18:13:41
最后更新:2019-08-06, 00:07:35
原始链接:http://coolview.github.io/2018/03/18/Python/Beautiful-Soup-%E5%AD%A6%E4%B9%A0/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。