​Beautiful Soup 4.12.0 文档(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: ​Beautiful Soup 4.12.0 文档(一)

Beautiful Soup 是一个 可以从 HTML 或 XML 文件中提取数据的 Python 库。它能用你喜欢的解析器和习惯的方式实现 文档树的导航、查找、和修改。它会帮你节省数小时甚至数天的工作时间。

本文介绍了 Beautiful Soup 4 中所有主要特性,并附带例子。文档会展示这个库的适合场景, 工作原理,怎样使用,如何达到预期效果,以及如何处理异常情况。

文档覆盖了 Beautful Soup 4.12.0 版本,文档中的例子使用 Python 3.8 版本编写。

你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,并且自 2020年12月31日以后就停止维护了。 如果想要了解 Beautiful Soup 3 和 Beautiful Soup 4 的不同,参考 迁移到 BS4。

寻求帮助

如果有关于 Beautiful Soup 4 的疑问,或遇到了问题,可以发送邮件到 讨论组。

如果问题中包含要解析的 HTML 代码,那么请在你的问题描述中附带这段HTML文档的 代码诊断 [1]。

如果报告文档中的错误,请指出具体文档的语言版本。

快速开始

下面的一段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>
"""

上面的 爱丽丝 文档经过 Beautiful Soup 的解析后,会得到一个 BeautifulSoup 的对象, 一个嵌套结构的对象:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')

print(soup.prettify())
# <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 class="sister" href="http://example.com/elsie" id="link1">
#     Elsie
#    </a>
#    ,
#    <a class="sister" href="http://example.com/lacie" id="link2">
#     Lacie
#    </a>
#    and
#    <a class="sister" href="http://example.com/tillie" id="link2">
#     Tillie
#    </a>
#    ; and they lived at the bottom of a well.
#   </p>
#   <p class="story">
#    ...
#   </p>
#  </body>
# </html>

这是几个简单的浏览结构化数据的方法:

soup.title
# <title>The Dormouse's story</title>

soup.title.name
# u'title'

soup.title.string
# u'The Dormouse's story'

soup.title.parent.name
# u'head'

soup.p
# <p class="title"><b>The Dormouse's story</b></p>

soup.p['class']
# u'title'

soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

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>]

soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

常见任务之一,就是从文档中找到所有 标签的链接:

for link in soup.find_all('a'):

   print(link.get('href'))

   # http://example.com/elsie

   # http://example.com/lacie

   # http://example.com/tillie

另一种常见任务,是从文档中获取所有文字内容:

print(soup.get_text())

# The Dormouse's story

#

# The Dormouse's story

#

# Once upon a time there were three little sisters; and their names were

# Elsie,

# Lacie and

# Tillie;

# and they lived at the bottom of a well.

#

# ...这是你想要的吗?是的话,继续看下去。

安装 Beautiful Soup

如果你用的是新版的 Debain 或 Ubuntu,那么可以通过系统的软件包管理来安装:

$ apt-get install python3-bs4

Beautiful Soup 4 通过 PyPi 发布,所以如果无法使用系统包管理安装,那么 也可以通过 easy_install 或 pip 来安装。包的名字是 beautifulsoup4。 确保使用的是与 Python 版本对应的 pip 或 easy_install 版本 (他们的名字也可能是 pip3 和 easy_install )。

$ easy_install beautifulsoup4

$ pip install beautifulsoup4

(在 PyPi 中还有一个名字是 BeautifulSoup 的包,但那可能不是你想要的,那是 Beautiful Soup3 版本。因为很多项目还在使用BS3, 所以 BeautifulSoup 包依然有效。但是新项目中,应该安装 beautifulsoup4。)

如果没有安装 easy_install 或 pip ,那也可以 下载 BS4 的源码 , 然后通过 setup.py 来安装。

$ Python setup.py install

如果上述安装方法都行不通,根据 Beautiful Soup 的协议,可以将项目的代码打包在 你的项目中,这样无须安装即可使用。

Beautiful Soup 用 Python 3.10 版本开发,但也可以在当前的其它版本中运行。

安装解析器

Beautiful Soup 支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析器, 其中一个是 lxml parser 。根据安装方法的不同, 可以选择下列方法来安装 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

下表描述了几种解析器的优缺点:

解析器

使用方法

优势

劣势

Python 标准库

BeautifulSoup(markup, "html.parser")

- Python的内置标准库

- 执行速度较快

- 容错能力强

- 速度没有 lxml 快,容错没有 html5lib强

lxml HTML 解析器

BeautifulSoup(markup, "lxml")

- 速度快

- 容错能力强

- 额外的 C 依赖

lxml XML 解析器

BeautifulSoup(markup, ["lxml-xml"])

BeautifulSoup(markup, "xml")

- 速度快

- 唯一支持 XML 的解析器

- 额外的 C 依赖

html5lib

BeautifulSoup(markup, "html5lib")

- 最好的容错性

- 以浏览器的方式解析文档

- 生成 HTML5 格式的文档

- 速度慢

- 额外的 Python 依赖

如果可以,推荐使用 lxml 来获得更高的速度。

注意,如果一段文档格式不标准,那么在不同解析器生成的 Beautiful Soup 数可能不一样。 查看 解析器之间的区别 了解更多细节。

如何使用

解析文档是,将文档传入 BeautifulSoup 的构造方法。也可以传入一段字符串 或一个文件句柄:

from bs4 import BeautifulSoup

with open("index.html") as fp:

   soup = BeautifulSoup(fp, 'html.parser')

soup = BeautifulSoup("a web page", 'html.parser')

首先,文档被转换成 Unicode,并且 HTML 中的实体也都被转换成 Unicode 编码

print(BeautifulSoup("Sacré bleu!", "html.parser"))

# Sacré bleu!

然后,Beautiful Soup 选择最合适的解析器来解析这段文档。如果指定了解析器那么 Beautiful Soup 会选择指定的解析器来解析文档。(参考 解析成XML )。

对象的种类

Beautiful Soup 将复杂的 HTML 文档转换成一个复杂的由 Python 对象构成的树形结构,但处理对象 的过程只包含 4 种类型的对象: Tag, NavigableString, BeautifulSoup, 和 Comment。

Tag Tag 对象与 XML 或 HTML 原生文档中的 tag 相同:


soup = BeautifulSoup('Extremely bold', 'html.parser')

tag = soup.b

type(tag)

#

Tag有很多属性和方法,在 遍历文档树 和 搜索文档树 中有详细解释。 现在介绍一下 tag 中最重要的属性: name 和 attributes。


bs4.name


每个 tag 都有一个名字:


tag.name

# u'b'

如果改变了 tag 的 name,那将影响所有通过当前 Beautiful Soup 对象生成的HTML文档:


tag.name = "blockquote"

tag

#

Extremely bold

bs4.attrs


一个 HTML 或 XML 的 tag 可能有很多属性。tag  有 一个 “id” 的属性,值为 “boldest”。你可以想处理一个字段一样来处理 tag 的属性:


tag = BeautifulSoup('bold', 'html.parser').b

tag['id']

# 'boldest'

也可以直接”点”取属性,比如: .attrs :


tag.attrs

# {u'class': u'boldest'}

tag 的属性可以被添加、删除或修改。再说一次,tag的属性操作方法与字典一样


tag['id'] = 'verybold'

tag['another-attribute'] = 1

tag

#


del tag['id']

del tag['another-attribute']

tag

# bold


tag['id']

# KeyError: 'id'

tag.get('id')

# None

多值属性

HTML 4 定义了一系列可以包含多个值的属性。在 HTML5 中移除了一些,却增加更多。 最常见的多值的属性是 class (一个 tag 可以有多个 CSS class)。还有一些 属性 rel、 rev、 accept-charset、 headers、 accesskey。 默认情况,Beautiful Soup 中将多值属性解析为一个列表:


css_soup = BeautifulSoup('

', 'html.parser')

css_soup.p['class']

# ['body']


css_soup = BeautifulSoup('

', 'html.parser')

css_soup.p['class']

# ['body', 'strikeout']


If an attribute `looks` like it has more than one value, but it's not

a multi-valued attribute as defined by any version of the HTML

standard, Beautiful Soup will leave the attribute alone::


id_soup = BeautifulSoup('

', 'html.parser')

id_soup.p['id']

# 'my id'

如果某个属性看起来好像有多个值,但在任何版本的 HTML 定义中都没有将其定义为多值属性, 那么 Beautiful Soup 会将这个属性作为单值返回


id_soup = BeautifulSoup('

', 'html.parser')

id_soup.p['id']

# 'my id'

将 tag 转换成字符串时,多值属性会合并为一个值


rel_soup = BeautifulSoup('

Back to the homepage

', 'html.parser')

rel_soup.a['rel']

# ['index', 'first']

rel_soup.a['rel'] = ['index', 'contents']

print(rel_soup.p)

#

Back to the homepage

若想强制将所有属性当做多值进行解析,可以在 BeautifulSoup 构造方法中设置 multi_valued_attributes=None 参数:


no_list_soup = BeautifulSoup('

', 'html.parser', multi_valued_attributes=None)

no_list_soup.p['class']

# 'body strikeout'

或者使用 get_attribute_list 方法来获取多值列表,不管是不是一个多值属性:


id_soup.p.get_attribute_list('id')

# ["my id"]

如果以 XML 方式解析文档,则没有多值属性:


xml_soup = BeautifulSoup('

', 'xml')

xml_soup.p['class']

# 'body strikeout'

但是,可以通过配置 multi_valued_attributes 参数来修改:


class_is_multi= { '*' : 'class'}

xml_soup = BeautifulSoup('

', 'xml', multi_valued_attributes=class_is_multi)

xml_soup.p['class']

# ['body', 'strikeout']

可能实际当中并不需要修改默认配置,默认采用的是 HTML 标准:


from bs4.builder import builder_registry

builder_registry.lookup('html').DEFAULT_CDATA_LIST_ATTRIBUTES

class bs4.NavigableString

可遍历的字符串

字符串对应 tag 中的一段文本。Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串:

soup = BeautifulSoup('Extremely bold', 'html.parser')

tag = soup.b

tag.string

# 'Extremely bold'

type(tag.string)

#

一个 NavigableString 对象与 Python 中的Unicode 字符串相同, 并且还支持包含在 遍历文档树 和 搜索文档树 中的一些特性。通过 str 方法可以直接将 NavigableString 对象转换成 Unicode 字符串:


unicode_string = str(tag.string)

unicode_string

# 'Extremely bold'

type(unicode_string)

#

tag 中包含的字符串不能直接编辑,但是可以被替换成其它的字符串,用 replace_with() 方法:


tag.string.replace_with("No longer bold")

tag

#

No longer bold

NavigableString 对象支持 遍历文档树 和 搜索文档树 中定义的大部分属性, 并非全部。尤其是,一个字符串不能包含其它内容(tag 能够包含字符串或是其它 tag),字符串不支持 .contents 或 .string 属性或 find() 方法。


如果想在 Beautiful Soup 之外使用 NavigableString 对象,需要调用 unicode() 方法,将该对象转换成普通的Unicode字符串,否则就算 Beautiful Soup 方法已经执行结束,该对象的输出 也会带有对象的引用地址。这样会浪费内存。


class bs4.BeautifulSoup


BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象, 它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法。


因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性。 但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个 值为 “[document]” 的特殊属性 .name


soup.name

# u'[document]'

注释及特殊字符串

Tag, NavigableString, BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象。容易让人担心的内容是文档的注释部分:

soup = BeautifulSoup(markup, 'html.parser')

comment = soup.b.string

type(comment)

#

Comment 对象是一个特殊类型的 NavigableString 对象:


comment

# u'Hey, buddy. Want to buy a used parser'

但是当它出现在 HTML 文档中时,Comment 对象会使用特殊的格式输出:


print(soup.b.prettify())

#

#  

#

针对 HTML 文档

Beautiful Soup 定义了一些 NavigableString 子类来处理特定的 HTML 标签。 通过忽略页面中表示程序指令的字符串,可以更容易挑出页面的 body 内容。 (这些类是在 Beautiful Soup 4.9.0 版本中添加的,html5lib 解析器不会使用它们)

class bs4.Stylesheet

有一种 NavigableString 子类表示嵌入的 CSS 脚本; 内容是  标签内部的所有字符串。

class bs4.Script

有一种 NavigableString 子类表示嵌入的 JavaScript 脚本; 内容是  标签内部的所有字符串。

class bs4.Template

有一种 NavigableString 子类表示嵌入的 HTML 模板, 内容是 <template> 标签内部的所有字符串。

针对 XML 文档

Beautiful Soup 定义了一些 NavigableString 子类来处理 XML 文档中的特定 字符串。比如 Comment,这些 NavigableString 的子类生成字符 串时会添加额外内容。

class bs4.Declaration


有一种 NavigableString 子类表示 XML 文档开头的 declaration 。


class bs4.Doctype


有一种 NavigableString 子类表示可能出现在 XML 文档开头的 document type declaration 。


class bs4.CData


有一种 NavigableString 子类表示 CData section。


class bs4.ProcessingInstruction


有一种 NavigableString 子类表示 XML 处理指令。

遍历文档树

还是用”爱丽丝”的文档来做例子:

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')

通过这段例子来演示怎样从文档的一段内容找到另一段内容

子节点

tag 可能包含多个字符串或其它的 tag,这些都是这个 Tag 的子节点。Beautiful Soup 提供了许多查找 和操作子节点的方法。

注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点。

Tag 的名字

操作文档树最简单的方法就是告诉它你想获取的 tag 的 name。如果想获取 <head> 标签,只要用 soup.head:

soup.head
# <head><title>The Dormouse's story</title></head>

soup.title
# <title>The Dormouse's story</title>

这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法。下面的代码可以获取 <body> 标签中的 第一个 <b> 标签:


soup.body.b
# <b>The Dormouse's story</b>

通过点取属性的方式只能获得当前名字的第一个tag:


soup.a
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

如果想要得到所有的 <a> 标签,或是比通过名字获取内容更复杂的方法时,就需要用到 搜索文档树 中描述的方法,比如: 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 和 .children

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']

BeautifulSoup 对象一定会包含子节点。下面例子中 <html> 标签就是 BeautifulSoup 对象的子节点:

len(soup.contents)
# 1
soup.contents[0].name
# u'html'

字符串没有 .contents 属性,因为字符串没有子节点:

text = title_tag.contents[0]
text.contents
# AttributeError: 'NavigableString' object has no attribute 'contents'

通过 tag 的 .children 生成器,可以对 tag 的子节点进行循环:

for child in title_tag.children:
    print(child)
    # The Dormouse's story

如果想要修改 tag 的子节点,使用 修改文档树 中描述的方法。不要直接修改 contents 列表: 那样会导致细微且难以定位的问题。

.descendants

.contents 和 .children 属性仅包含 tag 的直接子节点。例如,<head> 标签只有一个直接 子节点 <title>

head_tag.contents
# [<title>The Dormouse's story</title>]

但是 <title> 标签也包含一个子节点:字符串 “The Dormouse’s story”。这种情况下字符串 “The Dormouse’s story” 也属于 <head> 标签的子节点。 .descendants 属性可以对 所有 tag 的子孙节点进行递归循环 [5] ,包括子节点,子节点的子节点:

for child in head_tag.descendants:
    print(child)
    # <title>The Dormouse's story</title>
    # The Dormouse's story

上面的例子中,<head> 标签只有一个子节点,但是有 2 个子孙节点: <head> 标签和 <head> 的子节点。 BeautifulSoup 对象只有一个直接子节点(<html> 节点),却有很多子孙节点:

len(list(soup.children))
# 1
len(list(soup.descendants))
# 25

.string

如果 tag 只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点:

title_tag.string
# u'The Dormouse's story'

如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一 子节点的 .string 结果相同:

head_tag.contents
# [<title>The Dormouse's story</title>]
head_tag.string
# u'The Dormouse's story'

如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None :

print(soup.html.string)
# None

.strings 和 stripped_strings

如果 tag 中包含多个字符串 [2] ,可以使用 .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'...'

父节点

继续分析文档树,每个 tag 或字符串都有父节点: 包含当前内容的 tag

.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>

文档的 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
 

兄弟节点

看一段简单的例子:

sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></a>", 'html.parser')
print(sibling_soup.prettify())
#   <a>
#    <b>
#     text1
#    </b>
#    <c>
#     text2
#    </c>
#   </a>

因为 <b> 标签和 <c> 标签是同一层: 他们是同一个元素的子节点,所以 <b> 和 <c> 可以被称为兄弟节点。 一段文档以标准格式输出时,兄弟节点有相同的缩进级别。在代码中也可以使用这种关系。

.next_sibling 和 .previous_sibling

在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点:

sibling_soup.b.next_sibling
# <c>text2</c>

sibling_soup.c.previous_sibling
# <b>text1</b>

<b> 标签有 .next_sibling 属性,但是没有 .previous_sibling 属性, 因为 <b> 标签在同级节点中是第一个。同理,<c>标签有 .previous_sibling 属性, 却没有 .next_sibling 属性:


print(sibling_soup.b.previous_sibling)
# None
print(sibling_soup.c.next_sibling)
# None

例子中的字符串 “text1” 和 “text2” 不是兄弟节点,因为它们的父节点不同:


sibling_soup.b.string
# u'text1'
print(sibling_soup.b.string.next_sibling)
# None

实际文档中的 tag 的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白。 看看“爱丽丝”文档:

<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>

如果以为第一个 <a> 标签的 .next_sibling 结果是第二个 <a> 标签,那就错了, 真实结果是第一个 <a> 标签和第二个<a> 标签之间的顿号和换行符:

link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
link.next_sibling
# u',\n'

第二个<a>标签是顿号的 .next_sibling 属性:

link.next_sibling.next_sibling
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

.next_siblings 和 .previous_siblings

通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出:

for sibling in soup.a.next_siblings:
    print(repr(sibling))
# ',\n'
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# ' and\n'
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
# '; and they lived at the bottom of a well.'

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>
# ',\n'
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
# 'Once upon a time there were three little sisters; and their names were\n'

回退和前进

看一下“爱丽丝” 文档:

<html><head><title>The Dormouse's story</title></head>

<p class="title"><b>The Dormouse's story</b></p>


HTML解析器把这段字符串转换成一连串的事件: “打开<html>标签”,”打开一个<head>标签”, “打开一个<title>标签”,”添加一段字符串”,”关闭<title>标签”,”打开<p>标签”,等等。 Beautiful Soup提供了重现解析器初始化过程的方法。

.next_element 和 .previous_element

.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag), 结果可能与 .next_sibling 相同,但通常是不一样的。

这是“爱丽丝”文档中最后一个 <a> 标签,它的 .next_sibling 结果是一个字符串, 因为当前的解析过程 [2] 因为当前的解析过程因为遇到了<a>标签而中断了:


last_a_tag = soup.find("a", id="link3")
last_a_tag
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
last_a_tag.next_sibling
# '; and they lived at the bottom of a well.'

但这个 <a> 标签的 .next_element 属性结果是在 <a> 标签被解析之后的解析内容, 不是 <a> 标签后的句子部分,而是字符串 “Tillie”:


last_a_tag.next_element

# u'Tillie'

这是因为在原始文档中,字符串 “Tillie” 在分号前出现,解析器先进入 <a> 标签, 然后是字符串 “Tillie”,然后关闭 </a> 标签,然后是分号和剩余部分。 分号与 <a> 标签在同一层级,但是字符串 “Tillie” 会先被解析。


.previous_element 属性刚好与 .next_element 相反, 它指向当前被解析的对象的前一个解析对象:

last_a_tag.previous_element
# u' and\n'
last_a_tag.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 last_a_tag.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

搜索文档树

Beautiful Soup 定义了很多相似的文档搜索方法,这里着重介绍2个: find() 和 find_all(), 其它方法的参数和用法类似,所以一笔带过。

再以“爱丽丝”文档作为例子:

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')

使用 find_all() 这种过滤方法,就可以检索想要查找的文档内容。

过滤器类型

介绍 find_all() 或类似方法前,先介绍一下这些方法可以使用哪些过滤器的类型 [3], 这些过滤器在搜索的 API 中反复出现。过滤器可以作用在 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

下面代码找出所有名字中包含 “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>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

函数

如果没有合适过滤器,那么还可以定义一个函数方法,参数是一个元素 [4] , 如果这个方法返回 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>]
 

返回结果中只有 <p> 标签,没有 <a> 标签,因为 <a> 标签还定义了”id”, 没有返回 <html> 和 <head>,因为 <html> 和 <head> 中没有定义 “class” 属性。


如果通过方法来筛选特殊属性,比如 href,传入方法的参数应该是对应属性的值, 而不是整个元素。下面的例子是找出那些 a 标签中的 href 属性不匹配指定正则:

def not_lacie(href):
        return href and not re.compile("lacie").search(href)

soup.find_all(href=not_lacie)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
 

标签过滤方法可以使用复杂方法。下面的例子可以过滤出前后都有文字的标签。

from bs4 import NavigableString
def surrounded_by_strings(tag):
    return (isinstance(tag.next_element, NavigableString)
            and isinstance(tag.previous_element, NavigableString))

for tag in soup.find_all(surrounded_by_strings):
    print tag.name
# p
# a
# a
# a
# p

现在来了解一下搜索方法的细节


Beautiful Soup 4.12.0 文档(二):https://developer.aliyun.com/article/1515358


相关文章
|
2月前
|
XML 数据采集 API
MechanicalSoup与BeautifulSoup的区别分析
MechanicalSoup与BeautifulSoup的区别分析
49 2
MechanicalSoup与BeautifulSoup的区别分析
|
7月前
|
XML 数据格式
Beautiful Soup 库提供了许多常用的方法
【5月更文挑战第10天】Beautiful Soup库用于HTML/XML文档解析和操作,提供初始化、查找、提取信息及修改文档的方法。如:find()和find_all()查找元素,.string或.get_text()获取文本,.attrs获取属性,.append()、.insert()、.remove()、.replace_with()、.unwrap()和.wrap()修改文档结构。还有.prettify()格式化输出,.encode()和.decode()处理编码。这些功能组合使用可灵活处理文档信息。
42 1
WK
|
3月前
|
XML 前端开发 API
Beautiful Soup有哪些支持功能
Beautiful Soup是一个强大的Python库,用于从HTML或XML文件中提取数据。它支持多种解析器,如html.parser、lxml和html5lib,能灵活应对不同格式的文档。通过丰富的API,可以轻松遍历解析树,按标签名、属性或字符串内容搜索和提取数据。此外,Beautiful Soup还支持简单的树修改操作,处理不同编码的文档,并具备良好的容错性。从4.0版本起,它引入了CSS选择器,使搜索更加便捷。详尽的官方文档和广泛的社区支持使其成为处理网页数据的理想选择。
WK
52 1
WK
|
3月前
|
XML 移动开发 数据格式
Beautiful Soup支持哪些解析器
Beautiful Soup是一款强大的库,用于解析HTML和XML文档。它支持多种解析器,包括Python标准库中的`html.parser`、lxml的HTML和XML解析器以及html5lib。`html.parser`无需额外安装,但速度较慢;lxml则基于C语言,速度快且支持XPath;html5lib则完全支持HTML5标准,容错性好但速度较慢。用户可通过`features`参数指定解析器,选择最适合需求的解析器可提升效率与准确性。
WK
255 2
|
7月前
|
XML 机器学习/深度学习 移动开发
​Beautiful Soup 4.12.0 文档(三)
​Beautiful Soup 4.12.0 文档(三)
|
7月前
|
XML 前端开发 数据格式
​Beautiful Soup 4.12.0 文档(二)
​Beautiful Soup 4.12.0 文档(二)
|
7月前
|
XML 数据格式
Beautiful Soup 库有哪些常用的方法
Beautiful Soup 库有哪些常用的方法
130 1
|
数据采集 SQL 移动开发
【Python爬虫】Beautifulsoup4中find_all函数
【Python爬虫】Beautifulsoup4中find_all函数
|
XML JSON API
【 ⑨】jsonpath和BeautifulSoup库概述及其对比
【 ⑨】jsonpath和BeautifulSoup库概述及其对比
151 0
|
数据采集 数据安全/隐私保护 Python
Beautifulsoup解析库使用实际案例
Beautifulsoup解析库使用实际案例
下一篇
DataWorks