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实现的 html5libhtml5lib 的解析方式与浏览器相同,可以选择下列方法来安装 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 属性

tagAttributes 属性的操作方法与字典相同。

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). 还有一些属性 relrevaccept-charsetheadersaccesskey。在 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>

字符串常被包含在 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?

其他对象

CDataProcessingInstructionDeclarationDoctypeComment 对象类似,这些类都是 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" 转载请保留原文链接及作者。

目录