Skip to main content

输出

漂亮打印

prettify() 方法将美丽的Soup分析树转换为格式正确的Unicode字符串,每个HTML / XML标记在其自己的行:

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
#    </i>
#   </a>
#  </body>
# </html>

您可以在顶级 BeautifulSoup 对象或其任何 Tag 对象上调用 prettify():

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>

您可以通过为 prettify()encode()decode()formatter 参数提供值来更改此行为。Beautiful Soup识别 formatter 的四个可能的值。

默认值为 formatter="minimal"。字符串只会被处理得足以确保Beautiful 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>

如果你传入 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 的函数,Beautiful 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>

如果你正在编写自己的函数,你应该知道 bs4.dammit 模块中的 EntitySubstitution 类。这个类实现了Beautiful 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。 Beautiful Soup将调用formatter方法,以防你编写一个自定义方法来计算文档中的所有字符串或某些东西,但它会忽略返回值:

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解析器安装。Beautiful Soup将lxml的解析器列为最好的,然后html5lib的,然后Python的内置解析器。您可以通过指定以下选项之一来覆盖此:

  • 您要解析的是什么类型的标记。目前支持的是“html”,“xml”和“html5”。

  • 要使用的解析器库的名称。目前支持的选项是“lxml”,“html5lib”和“html.parser”(Python的内置HTML解析器)。

安装解析器 部分与支持的解析器形成对比。

如果你没有安装合适的解析器,Beautiful Soup会忽略你的请求并选择一个不同的解析器。现在,唯一支持的XML解析器是lxml。如果你没有安装lxml,要求一个XML解析器不会给你一个,并要求“lxml”也不会工作。

解析器之间的区别

Beautiful 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解析器之间也有差异。如果你给Beautiful Soup一个完美的HTML文档,这些差异并不重要。一个解析器会比另一个解析器快,但它们都会给你一个看起来像原始HTML文档的数据结构。

但是如果文档不是完美形式,不同的解析器将给出不同的结果。这里是一个简短的,无效的文档使用lxml的HTML解析器解析。请注意,悬挂</ p>标记会被忽略:

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一样,此解析器忽略关闭</ p>标记。与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。自动检测编码可用作 BeautifulSoup 对象的 .original_encoding 属性:

soup.original_encoding
'utf-8'

Unicode,Dammit大多数时候猜测正确,但有时它会犯错误。有时它猜测正确,但只有在逐个字节搜索文档需要很长时间。如果您事先知道文档的编码,您可以通过将其作为 from_encoding 传递给 BeautifulSoup 构造函数来避免错误和延迟。

这是一个用ISO-8859-8编写的文档。该文档是如此短以致Unicode,Dammit不能得到一个好的锁,并且误认为它是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,Dammit猜测错了,你可以通过错误的猜测为 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 是美丽的4.4.0的新功能。)

在极少数情况下(通常当UTF-8文档包含以完全不同的编码编写的文本时),获取Unicode的唯一方法是使用特殊的Unicode字符“REPLACEMENT CHARACTER”(U+FFFD,�)替换某些字符。如果Unicode,Dammit需要这样做,它将 UnicodeDammitBeautifulSoup 对象上的 .contains_replacement_characters 属性设置为 True。这让您知道Unicode表示不是原始的精确表示 - 一些数据丢失。如果文档包含�,但 .contains_replacement_charactersFalse,那么您将知道�原来是存在的(如本段所述),并且不代表丢失的数据。

输出编码

当您从Beautiful Soup写出文档时,您会得到一个UTF-8文档,即使该文档不是以UTF-8开头的。这是一个用Latin-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" />
# ...

你也可以在 BeautifulSoup 对象或soup中的任何元素上调用encode(),就像它是一个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,Dammit

你可以使用Unicode,Dammit不使用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库,Dammit的猜测会得到更加准确。你提供的数据越多,Unicode,Dammit,它就越准确。如果您对自己的编码可能有疑问,可以将它们作为列表传递:

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(反之亦然,我想),但这是最常见的情况。

请注意,在将数据传递到 BeautifulSoupUnicodeDammit 构造函数之前,您必须知道调用 UnicodeDammit.detwingle()。Beautiful Soup假设文档有一个单一的编码,无论它可能是什么。如果你传递一个包含UTF-8和Windows-1252的文档,它可能会认为整个文档是Windows-1252,并且该文档将看起来像 ☃☃☃“I like snowmen!”

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

比较对象是否相等

Beautiful Soup说,当他们代表相同的HTML或XML标记时,两个 NavigableStringTag 对象是相等的。在此示例中,两个<b>标记被视为相等,即使它们位于对象树的不同部分,因为它们都看起来像“<b>披萨</ 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

唯一的真正的区别是,副本是完全脱离原来的美丽的Soup对象树,就像 extract() 已被调用:

print p_copy.parent
# None

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

仅解析文档的一部分

假设您想使用Beautiful Soup查看文档的<a>标签。这是浪费时间和内存来解析整个文档,然后再次查找<a>标签。它会更快地忽略一切不是一个<a>标签在第一位。 SoupStrainer 类允许您选择传入文档的哪些部分被解析。您只需创建一个 SoupStrainer 并将其作为 parse_only 参数传递给 BeautifulSoup 构造函数。

(注意 如果您使用html5lib解析器,此功能将无法正常工作。如果你使用html5lib,整个文档将被解析,无论是什么,这是因为html5lib不断重新排列解析树,因为它的工作原理,如果文档的某些部分没有真正使它进入解析树,它会崩溃。为了避免混淆,在下面的示例中,我将强制美丽的Soup使用Python的内置解析器。)

SoupStrainer

SoupStrainer 类采用与 搜索树 中的典型方法相同的参数:名称attrs字符串**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 传递到 搜索树 中涵盖的任何方法。这可能不是非常有用,但我想我会提到它:

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