Beautiful Soup 文件

"The Fish-Footman began by producing from under his arm a great letter, nearly as large as himself."

Beautiful Soup 是一个用于从HTML和XML文件中提取数据的python库。它与您最喜欢的解析器一起工作,提供导航、搜索和修改解析树的惯用方法。它通常可以节省程序员数小时或数天的工作时间。

这些说明用例子说明了 Beautiful Soup 4的所有主要特征。我向您展示了这个库的好处,它是如何工作的,如何使用它,如何让它做您想要做的事情,以及当它违反了您的期望时应该做什么。

本文中的示例在python 2.7和python 3.2中的工作方式应该相同。

您可能正在查找 Beautiful Soup 3 . 如果是这样,您应该知道 Beautiful Soup 3不再被开发,并且 Beautiful Soup 4被推荐用于所有新项目。如果您想了解 Beautiful Soup 3和 Beautiful Soup 4之间的区别,请参见 Porting code to BS4 .

本文档已由Beautiful Soup用户翻译成其他语言:

得到帮助

如果你对 Beautiful Soup 有疑问,或者遇到问题, send mail to the discussion group . 如果您的问题涉及解析HTML文档,请务必提及 what the diagnose() function says 关于那个文件。

快速启动

这是一个HTML文档,我将在整个文档中用作示例。这是一个故事的一部分 Alice in Wonderland ::

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>

一个常见的任务是提取页面标记中找到的所有URL::

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

如果您使用的是Debian或Ubuntu Linux的最新版本,您可以使用System Package Manager安装漂亮的soup:

$ apt-get install python-bs4 (对于Python 2)

$ apt-get install python3-bs4 (对于Python 3)

BeautifulSoup4是通过pypi发布的,因此如果您不能用系统打包程序安装它,您可以用 easy_installpip . 包名称是 beautifulsoup4 和同一个包在python 2和python 3上工作。确保使用正确版本的 pipeasy_install 对于您的python版本(这些可能被命名为 pip3easy_install3 如果使用的是python 3)。

$ easy_install beautifulsoup4

$ pip install beautifulsoup4

(The BeautifulSoup 包可能是 not what you want. That's the previous major release, Beautiful Soup 3 . 很多软件都使用BS3,所以它仍然可用,但是如果您正在编写新的代码,则应该安装 beautifulsoup4

如果你没有 easy_installpip 已安装,您可以 download the Beautiful Soup 4 source tarball 安装时 setup.py .

$ python setup.py install

如果所有其他方法都失败了,那么 Beautiful Soup 的许可证允许您用您的应用程序打包整个库。你可以下载tarball,复制它 bs4 目录到应用程序的代码库中,使用漂亮的soup而不安装它。

我使用python 2.7和python 3.2来开发 Beautiful Soup ,但它应该与其他最新版本一起使用。

安装后的问题

Beautiful Soup 被打包成python 2代码。当您安装它与python 3一起使用时,它会自动转换为python 3代码。如果不安装包,代码将不会被转换。也有关于安装了错误版本的Windows计算机的报告。

如果你得到 ImportError “没有名为htmlparser的模块”,您的问题是您正在运行python 3下的代码的python 2版本。

如果你得到 ImportError “没有名为html.parser的模块”,您的问题是您在python 2下运行代码的python 3版本。

在这两种情况下,最好的办法是从系统中完全删除漂亮的soup安装(包括解压缩tarball时创建的任何目录),然后再次尝试安装。

如果你得到 SyntaxError 行上的“无效语法” ROOT_TAG_NAME = u'[document]' ,您需要将python 2代码转换为python 3。您可以通过安装程序包来执行此操作:

$ python3 setup.py install

或者手动运行python的 2to3 上的转换脚本 bs4 目录:

$ 2to3-3.2 -w bs4

安装分析器

BeautifulSoup支持包含在Python标准库中的HTML解析器,但它也支持许多第三方Python解析器。一个是 lxml parser . 根据您的设置,您可以使用以下命令之一安装LXML:

$ apt-get install python-lxml

$ easy_install lxml

$ pip install lxml

另一种选择是纯 Python html5lib parser 以Web浏览器的方式解析HTML。根据您的设置,您可以使用以下命令之一安装html5lib:

$ apt-get install python-html5lib

$ easy_install html5lib

$ pip install html5lib

此表总结了每个解析器库的优点和缺点:

语法分析器 典型用法 优势 缺点
python的html.parser BeautifulSoup(markup, "html.parser")
  • 包括电池
  • 体面速度
  • 宽大(从python 2.7.3和3.2开始)
  • 不太宽松(在python 2.7.3或3.2.2之前)
LXML的HTML分析器 BeautifulSoup(markup, "lxml")
  • 非常快
  • 宽大的
  • 外部C依赖
LXML的XML分析器 BeautifulSoup(markup, "lxml-xml") BeautifulSoup(markup, "xml")
  • 非常快
  • 当前唯一支持的XML分析器
  • 外部C依赖
HTML5LIB BeautifulSoup(markup, "html5lib")
  • 非常宽大
  • 以与Web浏览器相同的方式分析页面
  • 创建有效的HTML5
  • 非常缓慢
  • 外部python依赖项

如果可以,我建议您安装并使用LXML来提高速度。如果您使用的是2.7.3之前的python 2版本,或者3.2.2之前的python 3版本,那么 essential 安装lxml或html5lib——Python的内置HTML解析器在旧版本中不是很好。

注意,如果一个文档无效,不同的解析器将为它生成不同的 Beautiful Soup 树。见 Differences between parsers 有关详细信息。

Making the soup

要解析文档,请将其传递到 BeautifulSoup 构造函数。可以传入字符串或打开的文件句柄:

from bs4 import BeautifulSoup

with open("index.html") as fp:
    soup = BeautifulSoup(fp)

soup = BeautifulSoup("<html>data</html>")

首先,文档转换为Unicode,HTML实体转换为Unicode字符:

BeautifulSoup("Sacr&eacute; bleu!")
<html><head></head><body>Sacré bleu!</body></html>

然后,BeautifulSoup使用可用的最佳分析器解析文档。它将使用HTML解析器,除非您特别告诉它使用XML解析器。(见 Parsing XML

物体种类

漂亮的soup将复杂的HTML文档转换为复杂的python对象树。但是你只需要处理大约四个问题 kinds 对象: TagNavigableStringBeautifulSoupComment .

Tag

A Tag 对象对应于原始文档中的XML或HTML标记::

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>

标签有很多属性和方法,我将在 Navigating the treeSearching the tree . 目前,标签最重要的特性是它的名称和属性。

名字

每个标记都有一个名称,可作为 .name ::

tag.name
# u'b'

如果更改标记的名称,则更改将反映在由Beautiful Soup生成的任何HTML标记中:

tag.name = "blockquote"
tag
# <blockquote class="boldest">Extremely bold</blockquote>

属性

标记可以具有任意数量的属性。标签 <b id="boldest"> 具有值为“boldest”的属性“id”。通过将标记视为字典,可以访问标记的属性:

tag['id']
# u'boldest'

你可以直接用 .attrs ::

tag.attrs
# {u'id': 'boldest'}

可以添加、删除和修改标记的属性。同样,这是通过将标签视为字典来完成的:

tag['id'] = 'verybold'
tag['another-attribute'] = 1
tag
# <b another-attribute="1" id="verybold"></b>

del tag['id']
del tag['another-attribute']
tag
# <b></b>

tag['id']
# KeyError: 'id'
print(tag.get('id'))
# None

多值属性

HTML4定义了一些可以有多个值的属性。HTML5删除了其中的一些,但还定义了一些。最常见的多值属性是 class (也就是说,一个标记可以有多个CSS类)。其他包括 relrevaccept-charsetheadersaccesskey . Beautiful Soup 将多值属性的值作为列表显示:

css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]

css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]

如果属性 looks 就像它有多个值一样,但它不是由HTML标准的任何版本定义的多值属性, Beautiful Soup 将使该属性单独存在:

id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'

将标记转换回字符串时,将合并多个属性值:

rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
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>

你可以使用 `get_attribute_list 获取一个始终是列表、字符串的值,无论它是否为多值属性

id_soup.p.get_attribute_list('id')。# [“我的身份证”]

如果将文档解析为XML,则不存在多值属性:

xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# u'body strikeout'

BeautifulSoup

这个 BeautifulSoup 对象本身表示整个文档。在大多数情况下,您可以将其视为 Tag 对象。这意味着它支持 Navigating the treeSearching the tree .

自从 BeautifulSoup 对象与实际的HTML或XML标记不对应,它没有名称和属性。但有时看一下 .name 所以它被赋予了 .name “ [文件] “:

soup.name
# u'[document]'

注释和其他特殊字符串

TagNavigableStringBeautifulSoup 几乎涵盖了在HTML或XML文件中看到的所有内容,但还有一些剩余的部分。你可能唯一需要担心的是评论:

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>

这个 Comment 对象只是 NavigableString ::

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

但是当它作为HTML文档的一部分出现时, Comment 以特殊格式显示:

print(soup.b.prettify())
# <b>
#  <!--Hey, buddy. Want to buy a used parser?-->
# </b>

beautiful soup为XML文档中可能出现的任何其他内容定义类: CDataProcessingInstructionDeclarationDoctype . 就像 Comment ,这些类是 NavigableString 这会给字符串增加一些额外的内容。下面是一个用CDATA块替换注释的示例:

from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)

print(soup.b.prettify())
# <b>
#  <![CDATA[A CDATA block]]>
# </b>

在树上搜索

漂亮的soup定义了许多搜索解析树的方法,但它们都非常相似。我要花很多时间解释两种最流行的方法: 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() 和类似的方法,我想展示一些可以传递给这些方法的不同过滤器的例子。这些过滤器在整个搜索API中一次又一次地出现。您可以使用它们根据标记的名称、属性、字符串文本或它们的某些组合进行筛选。

一串

最简单的过滤器是字符串。将一个字符串传递给搜索方法, Beautiful Soup 将对该字符串执行匹配。此代码查找文档中的所有<b>标记:

soup.find_all('b')
# [<b>The Dormouse's story</b>]

如果传入一个字节字符串,BeautySoup将假定该字符串编码为UTF-8。您可以通过传入Unicode字符串来避免这种情况。

正则表达式

如果传入正则表达式对象,漂亮的soup将使用 search() 方法。此代码查找名称以字母“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 将允许一个字符串匹配 any 列表中的项目。此代码查找所有标记 and 所有标签:

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 尽可能匹配。此代码查找 all 文档中的标记,但没有文本字符串:

for tag in soup.find_all(True):
    print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p

函数

如果其他匹配项都不适用于您,请定义一个将元素作为其唯一参数的函数。函数应该返回 True 如果参数匹配,并且 False 否则。

这是一个返回 True 如果标记定义了“class”属性,但没有定义“id”属性:

def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')

将此函数传递到 find_all() 您将获得所有的标签:

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>标记,因为这些标记定义了“class”和“id”。它不会拾取像<html>和<title>这样的标记,因为这些标记没有定义“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>]

这个函数可以像您需要的那样复杂。这是一个返回 True 如果标记被字符串对象包围:

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

现在我们准备详细研究一下搜索方法。

find_all()

签名:全部查找 (nameattrsrecursivestringlimit**kwargs

这个 find_all() 方法查找标记的后代并检索 all descendants that match your filters. I gave several examples in Kinds of filters ,但这里还有一些:

soup.find_all("title")
# [<title>The Dormouse's story</title>]

soup.find_all("p", "title")
# [<p class="title"><b>The Dormouse's story</b></p>]

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_all(id="link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

import re
soup.find(string=re.compile("sisters"))
# u'Once upon a time there were three little sisters; and their names were\n'

其中一些看起来应该很熟悉,但另一些是新的。传递值是什么意思 stringid ?为什么 find_all("p", "title") 找到一个带有css类“title”的标记?让我们来看看 find_all() .

这个 name 论点

为传递值 name 你会告诉 Beautiful Soup 只考虑特定名称的标签。文本字符串将被忽略,名称不匹配的标记也将被忽略。

这是最简单的用法:

soup.find_all("title")
# [<title>The Dormouse's story</title>]

回忆起 Kinds of filters 价值在于 name 可以是 a stringa regular expressiona lista functionthe value True .

关键字参数

任何无法识别的参数都将转换为标记某个属性的筛选器。如果为一个名为 id , Beautiful Soup 将根据每个标签的“id”属性过滤:

soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

如果传递的值 href , Beautiful Soup 将根据每个标签的“href”属性过滤:

soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

可以根据筛选属性 a stringa regular expressiona lista functionthe value True .

此代码查找 id 无论值是什么,属性都有一个值:

soup.find_all(id=True)
# [<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_all(href=re.compile("elsie"), id='link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]

有些属性(如HTML 5中的data-*属性)的名称不能用作关键字参数的名称:

data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression

您可以在搜索中使用这些属性,方法是将它们放入字典并将字典传递到 find_all() 作为 attrs 论点:

data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]

不能使用关键字参数来搜索HTML的“name”元素,因为beautiful soup使用 name 参数来包含标记本身的名称。相反,您可以在 attrs 论点:

name_soup = BeautifulSoup('<input name="email"/>')
name_soup.find_all(name="email")
# []
name_soup.find_all(attrs={"name": "email"})
# [<input name="email"/>]

按CSS类搜索

搜索具有特定css类的标记非常有用,但css属性“class”的名称是python中的保留字。使用 class 作为关键字,参数会给您一个语法错误。从BeautifulSoup4.1.2开始,可以使用关键字参数通过CSS类搜索 class_ ::

soup.find_all("a", class_="sister")
# [<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>]

与任何关键字参数一样,可以通过 class_ 字符串、正则表达式、函数或 True ::

soup.find_all(class_=re.compile("itl"))
# [<p class="title"><b>The Dormouse's story</b></p>]

def has_six_characters(css_class):
    return css_class is not None and len(css_class) == 6

soup.find_all(class_=has_six_characters)
# [<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>]

Remember 单个标记的“class”属性可以有多个值。当您搜索与某个CSS类匹配的标记时,您将 any 它的CSS类:

css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>]

css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]

您还可以搜索 class 属性:

css_soup.find_all("p", class_="body strikeout")
# [<p class="body strikeout"></p>]

但搜索字符串值的变体将不起作用:

css_soup.find_all("p", class_="strikeout body")
# []

如果要搜索与两个或多个CSS类匹配的标记,则应使用CSS选择器:

css_soup.select("p.strikeout.body")
# [<p class="body strikeout"></p>]

在老版本的 Beautiful Soup 中,没有 class_ 快捷方式,您可以使用 attrs 上面提到的技巧。创建一个字典,其中“class”的值是要搜索的字符串(或正则表达式或其他类型)::

soup.find_all("a", attrs={"class": "sister"})
# [<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>]

这个 string 论点

string 您可以搜索字符串而不是标记。和一样 name 和关键字参数,您可以传入 a stringa regular expressiona lista functionthe value 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'...']

虽然 string 是为了找到字符串,您可以将它与找到标签的参数结合起来: Beautiful Soup 将找到所有标签 .string 与您的值匹配 string . 此代码查找 .string 是“Elsie”:

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

这个 string 在《 Beautiful Soup 》4.4.0中,争论是新的。在早期版本中,它被称为 text ::

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

这个 limit 论点

find_all() 返回与筛选器匹配的所有标记和字符串。如果文档很大,这可能需要一段时间。如果你不需要 all 结果,您可以为 limit . 这就像SQL中的limit关键字一样工作。它告诉 Beautiful Soup 在找到某个数字后停止收集结果。

“三姐妹”文档中有三个链接,但此代码只找到前两个:

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 论点

如果你打电话 mytag.find_all() Beautiful Soup 将检验所有的后代 mytag :它的孩子,它的孩子,等等。如果你只想让 Beautiful Soup 考虑直接的孩子,你可以通过 recursive=False . 请看这里的区别:

soup.html.find_all("title")
# [<title>The Dormouse's story</title>]

soup.html.find_all("title", recursive=False)
# []

这是文件的一部分:

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

<title>标记位于<html>标记之下,但它不是 directly 在<html>标记下面:正在使用<head>标记。当允许查看<html>标记的所有后代时,beautiful soup会找到<title>标记,但当 recursive=False 将其限制到标记的直接子代,它什么也找不到。

“ Beautiful Soup ”提供了许多树搜索方法(见下文),它们大多采用与 find_all()nameattrsstringlimit 和关键字参数。但是 recursive 参数不同: find_all()find() 是唯一支持它的方法。经过 recursive=False 像这样的方法 find_parents() 不会很有用的。

打标签就像打电话 find_all()

因为 find_all() 是 Beautiful Soup 搜索API中最流行的方法,您可以使用它的快捷方式。如果你治疗 BeautifulSoup 对象或 Tag 对象,就像它是一个函数一样,然后它与调用 find_all() 在那个物体上。这两行代码是等效的:

soup.find_all("a")
soup("a")

这两行也相当于:

soup.title.find_all(string=True)
soup.title(string=True)

find()

签名:查找 (nameattrsrecursivestring**kwargs

这个 find_all() 方法扫描整个文档以查找结果,但有时您只希望找到一个结果。如果您知道一个文档只有一个<body>标记,那么扫描整个文档寻找更多标记是浪费时间。而不是通过 limit=1 每次你打电话 find_all ,您可以使用 find() 方法。这两行代码是 nearly 当量:

soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]

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

唯一的区别是 find_all() 返回包含单个结果的列表,以及 find() 只返回结果。

如果 find_all() 找不到任何内容,它返回空列表。如果 find() 找不到任何东西,它会返回 None ::

print(soup.find("nosuchtag"))
# None

记住 soup.head.title 骗局 Navigating using tag names ?这种把戏是通过不断地打电话 find() ::

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

soup.find("head").find("title")
# <title>The Dormouse's story</title>

find_parents() and find_parent()

签名:查找家长 (nameattrsstringlimit**kwargs

签名:查找父级 (nameattrsstring**kwargs

我花了很多时间来掩盖 find_all()find() . Beautiful Soup API定义了另外十种搜索树的方法,但不要害怕。其中五种方法基本上与 find_all() 其他五个基本相同 find() . 唯一的区别在于它们搜索的是树的哪些部分。

首先让我们考虑一下 find_parents()find_parent() . 记住 find_all()find() 沿着树往下走,看看标签的后代。这些方法恰恰相反:它们按自己的方式工作。 up 树,查看标签(或字符串)的父级。让我们从埋在“三个女儿”文件深处的一根绳子开始,来试试它们:

a_string = soup.find(string="Lacie")
a_string
# u'Lacie'

a_string.find_parents("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

a_string.find_parent("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="link3">Tillie</a>;
#  and they lived at the bottom of a well.</p>

a_string.find_parents("p", class="title")
# []

三个标签中的一个是相关字符串的直接父级,因此我们的搜索会找到它。三个<p>标记中的一个是字符串的间接父级,我们的搜索也发现了这一点。有一个带有css类“title”的标签 somewhere 在文档中,但它不是此字符串的父字符串之一,因此我们无法使用 find_parents() .

你可能把 find_parent()find_parents().parent.parents 前面提到的属性。连接非常牢固。这些搜索方法实际使用 .parents 遍历所有父级,并对照提供的过滤器检查每个父级是否匹配。

find_next_siblings() and find_next_sibling()

签名:找到下一个兄弟姐妹 (nameattrsstringlimit**kwargs

签名:查找下一个兄弟 (nameattrsstring**kwargs

这些方法使用 .next_siblings 遍历树中元素的其他同级。这个 find_next_siblings() 方法返回匹配的所有同级,以及 find_next_sibling() 只返回第一个:

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

first_link.find_next_siblings("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>]

first_story_paragraph = soup.find("p", "story")
first_story_paragraph.find_next_sibling("p")
# <p class="story">...</p>

find_previous_siblings() and find_previous_sibling()

签名:查找以前的兄弟姐妹 (nameattrsstringlimit**kwargs

签名:查找上一个兄弟 (nameattrsstring**kwargs

这些方法使用 .previous_siblings 在树中遍历元素前面的同级元素。这个 find_previous_siblings() 方法返回匹配的所有同级,以及 find_previous_sibling() 只返回第一个:

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

last_link.find_previous_siblings("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

first_story_paragraph = soup.find("p", "story")
first_story_paragraph.find_previous_sibling("p")
# <p class="title"><b>The Dormouse's story</b></p>

find_all_next() and find_next()

签名:查找下一个 (nameattrsstringlimit**kwargs

签名:查找下一个 (nameattrsstring**kwargs

这些方法使用 .next_elements 迭代文档中出现在它后面的任何标记和字符串。这个 find_all_next() 方法返回所有匹配项,并且 find_next() 只返回第一个匹配项::

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

first_link.find_all_next(string=True)
# [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']

first_link.find_next("p")
# <p class="story">...</p>

在第一个示例中,出现了字符串“elsie”,即使它包含在我们开始使用的<a>标记中。在第二个示例中,出现了文档中最后一个<p>标记,尽管它与我们开始使用的<a>标记不在树的同一部分。对于这些方法,最重要的是一个元素匹配过滤器,并且在文档中比开始元素晚出现。

find_all_previous() and find_previous()

签名:查找所有上一个 (nameattrsstringlimit**kwargs

签名:查找上一个 (nameattrsstring**kwargs

这些方法使用 .previous_elements 迭代文档中出现在它前面的标记和字符串。这个 find_all_previous() 方法返回所有匹配项,并且 find_previous() 只返回第一个匹配项::

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

first_link.find_all_previous("p")
# [<p class="story">Once upon a time there were three little sisters; ...</p>,
#  <p class="title"><b>The Dormouse's story</b></p>]

first_link.find_previous("title")
# <title>The Dormouse's story</title>

呼唤 find_all_previous("p") 在文档中找到第一段(class=“title”),但它也找到第二段,即包含我们开始使用的<a>标记的<p>标记。这不应该太令人惊讶:我们正在查看文档中出现的所有标签,这些标签比我们开始使用的标签要早。包含<a>标记的<p>标记必须出现在它包含的<a>标记之前。

CSS选择器

从4.7.0版开始,Beautiful Soup通过 SoupSieve 项目。如果你安装了 Beautiful Soup pip 同时安装了soupsieve,因此您不必做任何额外的事情。

BeautifulSoup 有一个 .select() 方法,该方法使用soupsieve对已分析的文档运行CSS选择器,并返回所有匹配的元素。 Tag 有一个类似的方法,它针对单个标记的内容运行CSS选择器。

(早期版本的 Beautiful Soup 也有 .select() 方法,但只支持最常用的CSS选择器。)

SoupSieve documentation 列出当前支持的所有CSS选择器,但以下是一些基本信息:

您可以找到标签:

soup.select("title")
# [<title>The Dormouse's story</title>]

soup.select("p:nth-of-type(3)")
# [<p class="story">...</p>]

在其他标记下查找标记::

soup.select("body 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.select("html head title")
# [<title>The Dormouse's story</title>]

查找标签 directly 其他标签下方:

soup.select("head > title")
# [<title>The Dormouse's story</title>]

soup.select("p > 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.select("p > a:nth-of-type(2)")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

soup.select("p > #link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select("body > a")
# []

查找标签的同级:

soup.select("#link1 ~ .sister")
# [<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.select("#link1 + .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

按CSS类查找标记::

soup.select(".sister")
# [<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.select("[class~=sister]")
# [<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>]

按ID查找标记:

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

soup.select("a#link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

从选择器列表中查找与任何选择器匹配的标记:

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('a[href]')
# [<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.select('a[href="http://example.com/elsie"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

soup.select('a[href^="http://example.com/"]')
# [<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.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href*=".com/el"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

还有一个方法叫做 select_one() ,它只找到与选择器匹配的第一个标记::

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

如果您已经解析了定义名称空间的XML,那么可以在CSS选择器中使用它们。::

from bs4 import BeautifulSoup
xml = """<tag xmlns:ns1="http://namespace1/" xmlns:ns2="http://namespace2/">
 <ns1:child>I'm in namespace 1</ns1:child>
 <ns2:child>I'm in namespace 2</ns2:child>
</tag> """
soup = BeautifulSoup(xml, "xml")

soup.select("child")
# [<ns1:child>I'm in namespace 1</ns1:child>, <ns2:child>I'm in namespace 2</ns2:child>]

soup.select("ns1|child", namespaces=namespaces)
# [<ns1:child>I'm in namespace 1</ns1:child>]

当处理使用名称空间的CSS选择器时,BeautifulSoup使用它在分析文档时找到的名称空间缩写。您可以通过传入自己的缩略语词典来覆盖此内容:

namespaces = dict(first="http://namespace1/", second="http://namespace2/")
soup.select("second|child", namespaces=namespaces)
# [<ns1:child>I'm in namespace 2</ns1:child>]

所有这些CSS选择器的东西对于已经知道CSS选择器语法的人来说都很方便。您可以使用 Beautiful Soup API来完成所有这些工作。如果CSS选择器是您所需要的全部,那么您应该使用lxml解析文档:它速度更快。但这让你 combine 带有 Beautiful Soup API的CSS选择器。

修改树

BeautifulSoup的主要优势在于搜索解析树,但是您也可以修改树,并将更改作为新的HTML或XML文档来编写。

更改标记名称和属性

我早先讨论过这个,在 Attributes 但它需要重复。可以重命名标记、更改其属性的值、添加新属性和删除属性:

soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b

tag.name = "blockquote"
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>

修改 .string

如果你设置了一个标签 .string 属性,标记的内容将替换为您提供的字符串::

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)

tag = soup.a
tag.string = "New link text."
tag
# <a href="http://example.com/">New link text.</a>

小心:如果标签包含其他标签,它们及其所有内容将被销毁。

append()

可以使用添加到标记的内容 Tag.append() . 就像打电话一样 .append() 在python列表上:

soup = BeautifulSoup("<a>Foo</a>")
soup.a.append("Bar")

soup
# <html><head></head><body><a>FooBar</a></body></html>
soup.a.contents
# [u'Foo', u'Bar']

extend()

从 Beautiful Soup 4.7.0开始, Tag 还支持名为 .extend() ,就像 在python列表上调用 .extend() 一样 :

soup = BeautifulSoup("<a>Soup</a>")
soup.a.extend(["'s", " ", "on"])

soup
# <html><head></head><body><a>Soup's on</a></body></html>
soup.a.contents
# [u'Soup', u''s', u' ', u'on']

insert()

Tag.insert() 就像是 Tag.append() ,但新元素不一定位于其父元素的末尾 .contents . 它将插入到您所说的任何数字位置。它的工作原理就像 .insert() 在python列表上:

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a

tag.insert(1, "but did not endorse ")
tag
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
tag.contents
# [u'I linked to ', u'but did not endorse', <i>example.com</i>]

insert_before() and insert_after()

这个 insert_before() 方法在分析树中的其他内容之前插入标记或字符串::

soup = BeautifulSoup("<b>stop</b>")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.string.insert_before(tag)
soup.b
# <b><i>Don't</i>stop</b>

这个 insert_after() 方法在分析树中的其他内容之后立即插入标记或字符串::

div = soup.new_tag('div')
div.string = 'ever'
soup.b.i.insert_after(" you ", div)
soup.b
# <b><i>Don't</i> you <div>ever</div> stop</b>
soup.b.contents
# [<i>Don't</i>, u' you', <div>ever</div>, u'stop']

clear()

Tag.clear() 删除标记的内容::

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a

tag.clear()
tag
# <a href="http://example.com/"></a>

extract()

PageElement.extract() 从树中删除标记或字符串。它返回提取的标记或字符串:

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a

i_tag = soup.i.extract()

a_tag
# <a href="http://example.com/">I linked to</a>

i_tag
# <i>example.com</i>

print(i_tag.parent)
None

此时,您实际上有两个解析树:一个在 BeautifulSoup 对象,其中一个在提取的标记处建立。你可以继续打电话 extract 在提取的元素的子元素上:

my_string = i_tag.string.extract()
my_string
# u'example.com'

print(my_string.parent)
# None
i_tag
# <i></i>

decompose()

Tag.decompose() 从树中删除标记,然后 completely destroys it and its contents ::

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a

soup.i.decompose()

a_tag
# <a href="http://example.com/">I linked to</a>

replace_with()

PageElement.replace_with() 从树中删除标记或字符串,并将其替换为您选择的标记或字符串:

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a

new_tag = soup.new_tag("b")
new_tag.string = "example.net"
a_tag.i.replace_with(new_tag)

a_tag
# <a href="http://example.com/">I linked to <b>example.net</b></a>

replace_with() 返回被替换的标记或字符串,以便您可以检查它或将其添加回树的另一部分。

wrap()

PageElement.wrap() 在指定的标记中包装元素。它返回新包装:

soup = BeautifulSoup("<p>I wish I was bold.</p>")
soup.p.string.wrap(soup.new_tag("b"))
# <b>I wish I was bold.</b>

soup.p.wrap(soup.new_tag("div")
# <div><p><b>I wish I was bold.</b></p></div>

这种方法在 Beautiful Soup 4.0.5中是新的。

unwrap()

Tag.unwrap() 与…相反 wrap() . 它用标签内的内容替换标签。它有助于去除标记:

markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
a_tag = soup.a

a_tag.i.unwrap()
a_tag
# <a href="http://example.com/">I linked to example.com</a>

喜欢 replace_with()unwrap() 返回被替换的标记。

产量

漂亮的印刷

这个 prettify() 方法将把一个漂亮的soup解析树转换成一个格式良好的unicode字符串,每个标记和每个字符串都有一行:

markup='<a href=“http://example.com/”>i linked to<i>example.com.<i><a>'soup=beautifulsoup(markup)soup.prettify()'<html>n<head>n<head>n<body>n<a href=“http://example.com/”>n…'

print(soup.prettify())<html><head><head><body><a href=“http://example.com/”>i linked to<i>example.com#</a>#</body>#</html>

你可以打电话 prettify() 在顶层 BeautifulSoup 对象,或其上的任何对象 Tag 物体::

print(soup.a.prettify())
# <a href="http://example.com/">
#  I linked to
#  <i>
#   example.com
#  </i>
# </a>

不好看的印刷品

如果您只需要一个字符串,而不需要复杂的格式,您可以调用 unicode()str() 在一 BeautifulSoup 对象,或 Tag 内::

str(soup)
# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'

unicode(soup.a)
# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'

这个 str() 函数返回以UTF-8编码的字符串。见 Encodings 其他选项。

你也可以打电话 encode() 得到一个字节串,和 decode() 获取Unicode。

输出格式化程序

如果为Beautiful Soup提供一个包含“&lQuot;”等HTML实体的文档,则这些实体将转换为Unicode字符:

soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.")
unicode(soup)
# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'

如果然后将文档转换为字符串,则Unicode字符将编码为UTF-8。您将无法取回HTML实体:

str(soup)
# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'

默认情况下,输出时转义的唯一字符是空和号和尖括号。它们会变成“&amp;”、“&lt;”和“&gt;”,这样 Beautiful Soup 不会无意中生成无效的HTML或XML::

soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>")
soup.p
# <p>The law firm of Dewey, Cheatem, &amp; Howe</p>

soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
soup.a
# <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>

您可以通过为 formatter 参数 prettify()encode()decode() . Beautiful Soup 可以识别六种可能的价值 formatter .

默认值为 formatter="minimal" . 字符串的处理只能确保漂亮的soup生成有效的html/xml::

french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
soup = BeautifulSoup(french)
print(soup.prettify(formatter="minimal"))
# <html>
#  <body>
#   <p>
#    Il a dit &lt;&lt;Sacré bleu!&gt;&gt;
#   </p>
#  </body>
# </html>

如果你通过 formatter="html" , Beautiful Soup 将尽可能将Unicode字符转换为HTML实体::

print(soup.prettify(formatter="html"))
# <html>
#  <body>
#   <p>
#    Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;
#   </p>
#  </body>
# </html>

If you pass in ``formatter="html5"``, it's the same as

formatter="html5" 但是 Beautiful Soup 将省略HTML空标记中的结束斜杠,如“br”::

soup = BeautifulSoup("<br>")

print(soup.encode(formatter="html"))
# <html><body><br/></body></html>

print(soup.encode(formatter="html5"))
# <html><body><br></body></html>

如果你通过 formatter=None , Beautiful Soup 在输出时根本不会修改字符串。这是最快的选项,但它可能会导致漂亮的soup生成无效的HTML/XML,如以下示例所示:

print(soup.prettify(formatter=None))
# <html>
#  <body>
#   <p>
#    Il a dit <<Sacré bleu!>>
#   </p>
#  </body>
# </html>

link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
print(link_soup.a.encode(formatter=None))
# <a href="http://example.com/?foo=val1&bar=val2">A link</a>

最后,如果传递函数 formatter 对于文档中的每个字符串和属性值,漂亮的soup将调用该函数一次。你可以在这个函数中做你想做的任何事情。这里有一个格式化程序,它将字符串转换为大写,不执行任何其他操作:

def uppercase(str):
    return str.upper()

print(soup.prettify(formatter=uppercase))
# <html>
#  <body>
#   <p>
#    IL A DIT <<SACRÉ BLEU!>>
#   </p>
#  </body>
# </html>

print(link_soup.a.prettify(formatter=uppercase))
# <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
#  A LINK
# </a>

如果你在写你自己的函数,你应该知道 EntitySubstitution 类中 bs4.dammit 模块。这个类实现了漂亮的soup的标准格式化程序作为类方法:“html”格式化程序是 EntitySubstitution.substitute_html “最小”格式化程序是 EntitySubstitution.substitute_xml . 您可以使用这些函数来模拟 formatter=htmlformatter==minimal 但是做一些额外的事情。

下面是一个示例,尽可能用HTML实体替换Unicode字符,但是 also 将所有字符串转换为大写::

from bs4.dammit import EntitySubstitution
def uppercase_and_substitute_html_entities(str):
    return EntitySubstitution.substitute_html(str.upper())

print(soup.prettify(formatter=uppercase_and_substitute_html_entities))
# <html>
#  <body>
#   <p>
#    IL A DIT &lt;&lt;SACR&Eacute; BLEU!&gt;&gt;
#   </p>
#  </body>
# </html>

最后一个警告:如果创建 CData 对象,始终显示该对象中的文本 exactly as it appears, with no formatting . BeautifulSoup将调用格式化程序方法,以防编写了一个对文档中的所有字符串进行计数的自定义方法,但它将忽略返回值:

from bs4.element import CData
soup = BeautifulSoup("<a></a>")
soup.a.string = CData("one < three")
print(soup.a.prettify(formatter="xml"))
# <a>
#  <![CDATA[one < three]]>
# </a>

get_text()

如果只需要文档或标记的文本部分,则可以使用 get_text() 方法。它以单个Unicode字符串的形式返回文档中或标记下的所有文本:

markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup)

soup.get_text()
u'\nI linked to example.com\n'
soup.i.get_text()
u'example.com'

可以指定用于将文本位连接在一起的字符串::

# soup.get_text("|")
u'\nI linked to |example.com|\n'

您可以告诉 Beautiful Soup ,从每一段文字的开头和结尾去除空白:

# soup.get_text("|", strip=True)
u'I linked to|example.com'

但在那一点上,你可能想使用 .stripped_strings 生成,然后自己处理文本:

[text for text in soup.stripped_strings]
# [u'I linked to', u'example.com']

指定要使用的分析器

如果只需要解析一些HTML,可以将标记转储到 BeautifulSoup 建造师,可能会没事的。 Beautiful Soup 将为您选择一个解析器并解析数据。但是还有一些附加的参数可以传递给构造函数来更改使用的解析器。

第一个论点 BeautifulSoup 构造函数是一个字符串或一个打开的文件句柄——您希望解析的标记。第二个论点是 how 您希望对标记进行分析。

如果不指定任何内容,您将获得安装的最佳HTML解析器。漂亮的soup将lxml的解析器列为最好的,然后是html5lib的,然后是python的内置解析器。您可以通过指定以下选项之一来覆盖此选项:

  • 要分析的标记类型。目前支持的是“html”、“xml”和“html5”。
  • 要使用的分析器库的名称。目前支持的选项有“lxml”、“html5lib”和“html.parser”(python的内置html解析器)。

断面 Installing a parser 对比支持的解析器。

如果您没有安装合适的解析器,那么BeautifulSoup将忽略您的请求并选择不同的解析器。现在,唯一支持的XML解析器是lxml。如果您没有安装lxml,那么请求XML解析器就不会给您一个,而请求“lxml”也不会起作用。

解析器之间的差异

漂亮的soup为许多不同的解析器提供了相同的接口,但是每个解析器都是不同的。不同的解析器将从同一文档创建不同的解析树。最大的区别在于HTML解析器和XML解析器之间。这是一个简短的文档,解析为HTML::

BeautifulSoup("<a><b /></a>")
# <html><head></head><body><a><b></b></a></body></html>

由于空的<b/>标记不是有效的HTML,因此解析器将其转换为<b><b>标记对。

以下是与XML解析的相同文档(运行此文档需要安装lxml)。请注意,空的<b/>标记是单独保留的,并且文档得到了XML声明,而不是放入<html>标记中。::

BeautifulSoup("<a><b /></a>", "xml")
# <?xml version="1.0" encoding="utf-8"?>
# <a><b/></a>

HTML解析器之间也存在差异。如果您给BeautifulSoup一个完美的HTML文档,那么这些差异就不重要了。一个解析器比另一个要快,但它们都会为您提供一个与原始HTML文档完全相同的数据结构。

但是,如果文档没有完全形成,不同的解析器将给出不同的结果。这里有一个使用lxml的HTML解析器解析的短的、无效的文档。请注意,挂起的标签只是被忽略:

BeautifulSoup("<a></p>", "lxml")
# <html><body><a></a></body></html>

以下是使用html5lib解析的同一文档:

BeautifulSoup("<a></p>", "html5lib")
# <html><head></head><body><a><p></p></a></body></html>

HTML5LIB不会忽略悬空</p>标记,而是将其与一个开头的<p>标记配对。此分析器还向文档添加一个空的<head>标记。

下面是用Python的内置HTML解析器解析的同一文档:

BeautifulSoup("<a></p>", "html.parser")
# <a></a>

与html5lib一样,此分析器忽略结束标记。与html5lib不同,此解析器不尝试通过添加<body>标记来创建格式良好的HTML文档。与LXML不同,它甚至不需要添加<html>标记。

由于文档“<a><p>”无效,因此这些技术中没有一种是“正确”处理它的方法。html5lib解析器使用的技术是html5标准的一部分,因此它最有可能是“正确的”方法,但这三种技术都是合法的。

解析器之间的差异会影响您的脚本。如果计划将脚本分发给其他人,或在多台计算机上运行脚本,则应在 BeautifulSoup 构造函数。这将减少用户解析文档与解析文档不同的可能性。

编码

任何HTML或XML文档都是以特定的编码(如ASCII或UTF-8)编写的。但当您将该文档加载到 Beautiful Soup 中时,您会发现它已转换为Unicode::

markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
soup = BeautifulSoup(markup)
soup.h1
# <h1>Sacré bleu!</h1>
soup.h1.string
# u'Sacr\xe9 bleu!'

这不是魔法。(那当然不错。) Beautiful Soup 使用一个名为 Unicode, Dammit 检测文档的编码并将其转换为Unicode。自动检测到的编码可用作 .original_encoding 的属性 BeautifulSoup 对象:

soup.original_encoding
'utf-8'

Unicode,该死的,大多数时候都是正确的猜测,但有时会出错。有时它猜对了,但只有在对文档逐字节搜索了很长时间之后。如果您碰巧提前知道文档的编码,则可以通过将其传递给 BeautifulSoup 构造函数AS from_encoding .

这是一份用ISO-8859-8编写的文件。该文档太短,以致于Unicode无法对其进行锁定,并错误地将其标识为ISO-8859-7::

markup = b"<h1>\xed\xe5\xec\xf9</h1>"
soup = BeautifulSoup(markup)
soup.h1
<h1>νεμω</h1>
soup.original_encoding
'ISO-8859-7'

我们可以通过传递正确的 from_encoding ::

soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
soup.h1
<h1>םולש</h1>
soup.original_encoding
'iso8859-8'

如果你不知道正确的编码是什么,但是你知道Unicode,该死的猜错了,你可以把错误的猜错传给 exclude_encodings ::

soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"])
soup.h1
<h1>םולש</h1>
soup.original_encoding
'WINDOWS-1255'

Windows-1255不是100%正确的,但是编码是ISO-8859-8的兼容超集,所以它足够接近了。 (exclude_encodings 是 Beautiful Soup 4.4.0中的新功能。)

在极少数情况下(通常当UTF-8文档包含用完全不同的编码编写的文本时),获得unicode的唯一方法可能是用特殊的unicode字符“替换字符”(u+fffd,)替换某些字符。如果unicode,dammit需要这样做,它将设置 .contains_replacement_characters 属性到 TrueUnicodeDammitBeautifulSoup 对象。这让您知道Unicode表示不是原始表示的精确表示——一些数据丢失了。如果文档包含,但是 .contains_replacement_charactersFalse ,您将知道最初存在(如本段所示),并且不代表缺少的数据。

输出编码

当您从BeautifulSoup中写出一个文档时,您会得到一个utf-8文档,即使文档没有以utf-8开头。这是一份用拉丁文-1编码的文件:

markup = b'''
 <html>
  <head>
   <meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
  </head>
  <body>
   <p>Sacr\xe9 bleu!</p>
  </body>
 </html>
'''

soup = BeautifulSoup(markup)
print(soup.prettify())
# <html>
#  <head>
#   <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
#  </head>
#  <body>
#   <p>
#    Sacré bleu!
#   </p>
#  </body>
# </html>

请注意,<meta>标记已被重写,以反映文档现在是UTF-8格式的事实。

如果不需要UTF-8,可以将编码传递到 prettify() ::

print(soup.prettify("latin-1"))
# <html>
#  <head>
#   <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
# ...

您还可以在上调用encode()。 BeautifulSoup 对象或 soup 中的任何元素,就像它是一个python字符串一样:

soup.p.encode("latin-1")
# '<p>Sacr\xe9 bleu!</p>'

soup.p.encode("utf-8")
# '<p>Sacr\xc3\xa9 bleu!</p>'

在所选编码中无法表示的任何字符都将转换为数字XML实体引用。这是一个包含unicode字符snowman的文档:

markup = u"<b>\N{SNOWMAN}</b>"
snowman_soup = BeautifulSoup(markup)
tag = snowman_soup.b

snowman字符可以是utf-8文档的一部分(看起来像),但在iso-latin-1或ascii中没有该字符的表示,因此对于这些编码,它转换为“&9731”::

print(tag.encode("utf-8"))
# <b>☃</b>

print tag.encode("latin-1")
# <b>&#9731;</b>

print tag.encode("ascii")
# <b>&#9731;</b>

Unicode,该死

你可以使用unicode,该死的,不用 Beautiful Soup 。当您有一个未知编码的数据并且只希望它成为Unicode时,它是有用的:

from bs4 import UnicodeDammit
dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
print(dammit.unicode_markup)
# Sacré bleu!
dammit.original_encoding
# 'utf-8'

Unicode,如果安装 chardetcchardet python库。你提供的Unicode数据越多,该死的,它就越能准确猜测。如果您对编码可能是什么有自己的怀疑,可以将它们作为列表传递:

dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
print(dammit.unicode_markup)
# Sacré bleu!
dammit.original_encoding
# 'latin-1'

unicode,dammit有两个 Beautiful Soup 不使用的特殊功能。

智能报价

可以使用unicode、dammit将Microsoft智能引号转换为HTML或XML实体:

markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"

UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
# u'<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'

UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
# u'<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'

您还可以将Microsoft智能引号转换为ASCII引号::

UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
# u'<p>I just "love" Microsoft Word\'s smart quotes</p>'

希望你会发现这个功能很有用,但“ Beautiful Soup ”并没有用。 Beautiful Soup 喜欢默认行为,即将Microsoft智能引号转换为Unicode字符以及其他所有字符:

UnicodeDammit(markup, ["windows-1252"]).unicode_markup
# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'

编码不一致

有时文档大部分是UTF-8格式,但包含Windows-1252字符,如(再次)Microsoft智能引号。当一个网站包含来自多个来源的数据时,就会发生这种情况。你可以使用 UnicodeDammit.detwingle() 将这样的文档转换为纯UTF-8。下面是一个简单的例子:

snowmen = (u"\N{SNOWMAN}" * 3)
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
doc = snowmen.encode("utf8") + quote.encode("windows_1252")

这份文件乱七八糟。雪人是UTF-8,引语是windows-1252。您可以显示雪人或引语,但不能同时显示:

print(doc)
# ☃☃☃�I like snowmen!�

print(doc.decode("windows-1252"))
# ☃☃☃“I like snowmen!”

将文档解码为utf-8会引发 UnicodeDecodeError 把它解码成windows-1252会让你胡言乱语。幸运的是, UnicodeDammit.detwingle() 将字符串转换为纯UTF-8,允许您将其解码为Unicode并同时显示雪人和引号:

new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode("utf8"))
# ☃☃☃“I like snowmen!”

UnicodeDammit.detwingle() 只知道如何处理嵌入在UTF-8中的Windows-1252(我想是相反的),但这是最常见的情况。

注意你必须知道打电话 UnicodeDammit.detwingle() 在将数据传递到 BeautifulSoupUnicodeDammit 构造函数。BeautifulSoup假设文档有一个单独的编码,不管它是什么。如果您给它传递一个同时包含UTF-8和Windows-1252的文档,它可能会认为整个文档都是Windows-1252,并且文档的外观 ☃☃☃“I like snowmen!” .

UnicodeDammit.detwingle() 是在 Beautiful Soup 4.1.0的新。

比较对象是否相等

Beautiful Soup 说两个 NavigableStringTag 当对象表示相同的HTML或XML标记时,它们是相等的。在本例中,两个<b>标记被视为相等的,即使它们位于对象树的不同部分,因为它们看起来都像“<b>pizza<b>”:

markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>"
soup = BeautifulSoup(markup, 'html.parser')
first_b, second_b = soup.find_all('b')
print first_b == second_b
# True

print first_b.previous_element == second_b.previous_element
# False

如果要查看两个变量是否引用完全相同的对象,请使用 is ::

print first_b is second_b
# False

复制 Beautiful Soup 对象

你可以使用 copy.copy() 创建任何 TagNavigableString ::

import copy
p_copy = copy.copy(soup.p)
print p_copy
# <p>I want <b>pizza</b> and more <b>pizza</b>!</p>

副本被视为与原始副本相同,因为它表示与原始副本相同的标记,但它不是同一对象:

print soup.p == p_copy
# True

print soup.p is p_copy
# False

唯一真正的区别是,复制品完全脱离了原来 Beautiful Soup 对象树,就像 extract() 已被传唤:

print p_copy.parent
# None

这是因为两个不同 Tag 对象不能同时占用同一空间。

只分析文档的一部分

假设你想用 Beautiful Soup 看看文件的标签。解析整个文档,然后再次检查它以查找<a>标记,这是浪费时间和内存。首先忽略所有不属于<a>标记的内容会更快。这个 SoupStrainer 类允许您选择解析传入文档的哪些部分。你只是创造了一个 SoupStrainer 把它传给 BeautifulSoup 作为 parse_only 争论。

(注意 如果使用html5lib解析器,此功能将不起作用 . 如果您使用html5lib,那么不管怎样,都将解析整个文档。这是因为html5lib在工作时会不断地重新排列解析树,如果文档的某些部分没有真正进入解析树,那么它将崩溃。为了避免混淆,在下面的示例中,我将强制BeautifulSoup使用Python的内置解析器。)

SoupStrainer

这个 SoupStrainer 类采用与典型方法相同的参数 Searching the treenameattrsstring**kwargs . 这里有三个 SoupStrainer 物体::

from bs4 import SoupStrainer

only_a_tags = SoupStrainer("a")

only_tags_with_id_link2 = SoupStrainer(id="link2")

def is_short_string(string):
    return len(string) < 10

only_short_strings = SoupStrainer(string=is_short_string)

我再把“三姐妹”文档带回来一次,我们将看到文档与这三个文档一起解析时的样子。 SoupStrainer 物体::

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

print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
# <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>

print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
# <a class="sister" href="http://example.com/lacie" id="link2">
#  Lacie
# </a>

print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
# Elsie
# ,
# Lacie
# and
# Tillie
# ...
#

你也可以通过 SoupStrainer 包括在 Searching the tree . 这可能不太有用,但我想我会提到:

soup = BeautifulSoup(html_doc)
soup.find_all(only_short_strings)
# [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
#  u'\n\n', u'...', u'\n']

故障排除

diagnose()

如果您无法理解 Beautiful Soup 对文档的作用,请将文档传递到 diagnose() 功能。(BeautifulSoup4.2.0中的新功能。)BeautifulSoup将打印一份报告,向您展示不同的解析器如何处理文档,并告诉您是否缺少一个解析器,BeautifulSoup可以使用它:

from bs4.diagnose import diagnose
with open("bad.html") as fp:
    data = fp.read()
diagnose(data)

# Diagnostic running on Beautiful Soup 4.2.0
# Python version 2.7.3 (default, Aug  1 2012, 05:16:07)
# I noticed that html5lib is not installed. Installing it may help.
# Found lxml version 2.3.2.0
#
# Trying to parse your data with html.parser
# Here's what html.parser did with the document:
# ...

只需查看diagnose()的输出就可以了解如何解决问题。即使没有,也可以粘贴 diagnose() 在寻求帮助时。

分析文档时出错

有两种不同的分析错误。有一些崩溃,您将一个文档放入 Beautiful Soup 中,它会引发一个异常,通常是 HTMLParser.HTMLParseError . 还有一些意想不到的行为,其中一个漂亮的soup解析树看起来与用于创建它的文档大不相同。

几乎所有这些问题都不是 Beautiful Soup 的问题。这并不是因为“ Beautiful Soup ”是一款写得非常好的软件。这是因为漂亮的soup不包含任何解析代码。相反,它依赖于外部解析器。如果一个解析器不能处理某个文档,最好的解决方案是尝试另一个解析器。见 Installing a parser 有关详细信息和分析器比较。

最常见的分析错误是 HTMLParser.HTMLParseError: malformed start tagHTMLParser.HTMLParseError: bad end tag . 它们都是由Python的内置HTML解析器库生成的,解决方案是 install lxml or html5lib.

最常见的意外行为类型是在文档中找不到已知的标记。你看到它进去了,但是 find_all() 收益率 []find() 收益率 None . 这是Python的内置HTML解析器的另一个常见问题,它有时会跳过它不理解的标记。同样,解决办法是 install lxml or html5lib.

版本不匹配问题

  • SyntaxError: Invalid syntax (线上) ROOT_TAG_NAME = u'[document]' ):在python 3下运行beautiful soup的python 2版本,而不转换代码。
  • ImportError: No module named HTMLParser -由运行python 2版本的beautiful soup在python 3下引起。
  • ImportError: No module named html.parser -由运行python 3版本的beautiful soup在python 2下引起。
  • ImportError: No module named BeautifulSoup -在没有安装BS3的系统上运行漂亮的soup 3代码。或者,在不知道包名已更改为的情况下编写漂亮的soup 4代码 bs4 .
  • ImportError: No module named bs4 -由于在没有安装BS4的系统上运行了漂亮的soup 4代码。

解析XML

默认情况下,BeautifulSoup将文档解析为HTML。要将文档解析为XML,请将“xml”作为第二个参数传递给 BeautifulSoup 施工人员:

soup = BeautifulSoup(markup, "xml")

你需要 have lxml installed .

其他分析器问题

  • 如果您的脚本在一台计算机上而不是另一台计算机上工作,或者在一个虚拟环境中而不是另一个虚拟环境中工作,或者在虚拟环境之外而不是在内部工作,那么可能是因为这两个环境具有不同的可用分析器库。例如,您可能在安装了lxml的计算机上开发了该脚本,然后尝试在只安装了html5lib的计算机上运行该脚本。见 Differences between parsers 了解这一点的原因,并通过在 BeautifulSoup 建造师。
  • 因为 HTML tags and attributes are case-insensitive ,所有三个HTML解析器都将标记和属性名转换为小写。也就是说,标记<tag>>被转换为<tag>>。如果要保留混合大小写或大写的标记和属性,则需要 parse the document as XML.

其他

  • UnicodeEncodeError: 'charmap' codec can't encode character u'\xfoo' in position bar (或任何其他 UnicodeEncodeError )-这对 Beautiful Soup 来说不是问题。这个问题主要表现在两种情况下。首先,当您尝试打印控制台不知道如何显示的Unicode字符时。(见 this page on the Python wiki 第二,当你在写一个文件时,输入了一个默认编码不支持的Unicode字符。在这种情况下,最简单的解决方案是使用 u.encode("utf8") .
  • KeyError: [attr] -访问引起的 tag['attr'] 当有问题的标签没有定义 attr 属性。最常见的错误是 KeyError: 'href'KeyError: 'class' . 使用 tag.get('attr') 如果你不确定 attr 定义,就像使用Python字典一样。
  • AttributeError: 'ResultSet' object has no attribute 'foo' -这通常是因为你预料到 find_all() 返回单个标记或字符串。但是 find_all() 返回A _list_ 标签和字符串的数目--a ResultSet 对象。您需要遍历列表并查看 .foo 每一个。或者,如果你真的只想要一个结果,你需要使用 find() 而不是 find_all() .
  • AttributeError: 'NoneType' object has no attribute 'foo' -这通常是因为你打过电话 find() 然后尝试访问 .foo 结果的属性。但在你的情况下, ``find()` 找不到任何东西,所以它回来了 None ,而不是返回标记或字符串。你得弄清楚为什么 find() 呼叫没有返回任何内容。

提高性能

Beautiful Soup 永远不会像它坐在上面的解析器那么快。如果响应时间是关键的,如果你按小时支付计算机时间的费用,或者如果有任何其他原因导致计算机时间比程序员时间更有价值,你应该忘记 Beautiful Soup ,直接在上面工作。 lxml .

也就是说,你可以做一些事情来加速 Beautiful Soup 。如果不使用lxml作为底层解析器,我的建议是 start . 与使用html.parser或html5lib相比,漂亮的soup使用lxml解析文档的速度要快得多。

您可以通过安装 cchardet 类库。

Parsing only part of a document 不会为您节省很多时间来分析文档,但它可以节省大量的内存,并且 searching 文档速度快得多。

Beautiful Soup 3

Beautiful Soup 3是前一个版本系列,不再是积极开发。它目前与所有主要的Linux发行版打包在一起:

$ apt-get install python-beautifulsoup

它也通过Pypi出版 BeautifulSoup

$ easy_install BeautifulSoup

$ pip install BeautifulSoup

你也可以 download a tarball of Beautiful Soup 3.2.0 .

如果你跑 easy_install beautifulsoupeasy_install BeautifulSoup ,但您的代码不起作用,您错误地安装了 Beautiful Soup 3。你需要跑步 easy_install beautifulsoup4 .

The documentation for Beautiful Soup 3 is archived online <http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html> _.

将代码移植到BS4

大多数针对BeautifulSoup3编写的代码只需一个简单的更改就可以针对BeautifulSoup4工作。您只需将包名称从 BeautifulSoupbs4 . 因此:

from BeautifulSoup import BeautifulSoup

变成这样:

from bs4 import BeautifulSoup
  • 如果你得到 ImportError “没有名为BeautifulSoup的模块”,您的问题是您试图运行BeautifulSoup3代码,但您只安装了BeautifulSoup4。
  • 如果你得到 ImportError “没有名为bs4的模块”,您的问题是您试图运行BeautifulSoup4代码,但您只安装了BeautifulSoup3。

虽然BS4主要向后兼容BS3,但其大多数方法已被弃用,并为 PEP 8 compliance . 还有许多其他的重命名和更改,其中一些破坏了向后兼容性。

以下是将BS3代码和习惯转换为BS4时需要了解的内容:

你需要一个解析器

Beautiful Soup 3用 Python 的 SGMLParser ,在python 3.0中已弃用并删除的模块。Beautiful Soup 4 用 html.parser 默认情况下,但是您可以插入lxml或html5lib并使用它。见 Installing a parser 作为比较。

自从 html.parser 不是同一个分析器 SGMLParser 您可能会发现,对于相同的标记,BeautifulSoup4为您提供了不同于BeautifulSoup3的解析树。如果你换掉 html.parser 对于lxml或html5lib,您可能会发现解析树再次发生更改。如果发生这种情况,您需要更新刮削代码来处理新的树。

方法名称

  • renderContents -> encode_contents
  • replaceWith -> replace_with
  • replaceWithChildren -> unwrap
  • findAll -> find_all
  • findAllNext -> find_all_next
  • findAllPrevious -> find_all_previous
  • findNext -> find_next
  • findNextSibling -> find_next_sibling
  • findNextSiblings -> find_next_siblings
  • findParent -> find_parent
  • findParents -> find_parents
  • findPrevious -> find_previous
  • findPreviousSibling -> find_previous_sibling
  • findPreviousSiblings -> find_previous_siblings
  • nextSibling -> next_sibling
  • previousSibling -> previous_sibling

出于同样的原因,对 Beautiful Soup 构造函数的一些参数进行了重命名:

  • BeautifulSoup(parseOnlyThese=...) -> BeautifulSoup(parse_only=...)
  • BeautifulSoup(fromEncoding=...) -> BeautifulSoup(from_encoding=...)

我重命名了一个与python 3兼容的方法:

  • Tag.has_key() -> Tag.has_attr()

我重命名了一个属性以使用更准确的术语:

  • Tag.isSelfClosing -> Tag.is_empty_element

我重命名了三个属性以避免使用对python有特殊意义的单词。与其他的不同,这些变化是 不向后兼容。 如果在BS3中使用这些属性,代码将在BS4上中断,直到您更改它们。

  • UnicodeDammit.unicode -> UnicodeDammit.unicode_markup
  • Tag.next -> Tag.next_element
  • Tag.previous -> Tag.previous_element

发电机

我给了发电机符合PEP8标准的名称,并将其转换为属性:

  • childGenerator() -> children
  • nextGenerator() -> next_elements
  • nextSiblingGenerator() -> next_siblings
  • previousGenerator() -> previous_elements
  • previousSiblingGenerator() -> previous_siblings
  • recursiveChildGenerator() -> descendants
  • parentGenerator() -> parents

所以不是这样:

for parent in tag.parentGenerator():
    ...

你可以这样写:

for parent in tag.parents:
    ...

(但旧代码仍然有效。)

一些发电机用来发电 None 完成后,停下来。那是一只虫子。现在发电机就停了。

有两台新发电机, .strings and .stripped_strings . .strings 生成navigableString对象,以及 .stripped_strings 生成去掉空白的python字符串。

XML

不再有 BeautifulStoneSoup 用于分析XML的类。要解析XML,请将“xml”作为第二个参数传递给 BeautifulSoup 构造函数。出于同样的原因, BeautifulSoup 构造函数不再识别 isHTML 争论。

漂亮的soup对空元素XML标记的处理已经得到了改进。以前当您解析XML时,您必须明确地指出哪些标记被认为是空元素标记。这个 selfClosingTags 不再识别构造函数的参数。相反, Beautiful Soup 认为任何空标签都是空元素标签。如果将子元素添加到空元素标记中,它将不再是空元素标记。

实体

传入的HTML或XML实体总是转换为相应的Unicode字符。Beautiful Soup 3 有许多重叠的处理实体的方法,已经被删除。这个 BeautifulSoup 构造函数不再识别 smartQuotesToconvertEntities 争论。 (Unicode, Dammit 仍然有 smart_quotes_to ,但其默认值现在是将智能引号转换为Unicode。)常量 HTML_ENTITIESXML_ENTITIESXHTML_ENTITIES 已被删除,因为它们配置了一个不再存在的功能(将一些但不是所有实体转换为Unicode字符)。

如果要在输出时将Unicode字符转换回HTML实体,而不是将其转换为UTF-8字符,则需要使用 output formatter .

其他

Tag.string 现在递归操作。如果标记A包含单个标记B而没有其他内容,则a.string与b.string相同。(以前,它不是。)

Multi-valued attributes 喜欢 class 将字符串列表作为其值,而不是字符串。这可能会影响CSS类搜索的方式。

如果你通过了 find* 方法二者 string and 特定于标记的参数,如 name , Beautiful Soup 将搜索符合您的标签特定标准的标签,以及 Tag.string 与您的值匹配 string . 它将 not 找到字符串本身。以前,BeautifulSoup忽略了特定于标签的参数并查找字符串。

这个 BeautifulSoup 构造函数不再识别 markupMassage 争论。现在解析器负责正确处理标记。

很少使用的替代解析器类 ICantBelieveItsBeautifulSoupBeautifulSOAP 已被删除。现在解析器决定如何处理不明确的标记。

这个 prettify() 方法现在返回一个Unicode字符串,而不是字节字符串。