​Beautiful Soup 4.12.0 文档(三)

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

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

指定文档解析器

如果仅是想要解析HTML文档,只需要创建 BeautifulSoup 对象时传入文档就可以了。Beautiful Soup 会自动选择一个解析器来解析文档。同时还可以使用额外参数,来指定文档解析器。

BeautifulSoup 第一个参数应该是要被解析的文档字符串或是文件句柄 – 待解析文件的句柄, 第二个参数用来标识怎样解析文档。


如果不指定解析器,默认使用已安装的 最佳 HTML 解析器。Beautiful Soup 把 lxml 解析器排在第一, 然后是 html5lib, 然后是 Python 标准库。在下面两种条件下解析器优先顺序会变化:


要解析的文档是什么类型: 目前支持, “html”,“xml”,和 “html5”

指定使用哪种解析器: 目前支持,“lxml”,“html5lib”,和 “html.parser”(Python 标准库)


安装解析器 章节介绍了可以使用哪种解析器,以及如何安装。

如果指定的解析器没有安装,Beautiful Soup会自动选择其它方案。目前只有 lxml 解析器支持XML文档的解析, 在没有安装 lxml 库的情况下,无法自动选择 XML 文档解析器,手动指定 lxml 也不行。

解析器之间的区别

Beautiful Soup 为不同的解析器提供了相同的接口,但解析器本身时有区别的。同一篇文档被不同的解析器解析后 可能会生成不同结构的文档。区别最大的是 HTML 解析器和 XML 解析器,看下面片段被解析成 HTML 结构:

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

# <a><b></b></a>

因为空标签 <b /> 不符合 HTML 标准,html.parser 解析器把它解析成一对儿 <b></b>。


同样的文档使用 XML 解析结果如下(解析 XML 需要安装 lxml 库)。注意,空标签 <b /> 依然被保留, 并且文档前添加了 XML 头,而不是被包含在 <html> 标签内:


print(BeautifulSoup("<a><b/></a>", "xml"))

# <?xml version="1.0" encoding="utf-8"?>

# <a><b/></a>

HTML 解析器之间也有区别,如果被解析的HTML文档是标准格式,那么解析器之间没有任何差别。 只是解析速度不同,结果都会返回正确的文档树。


但是如果被解析文档不是标准格式,那么不同的解析器返回结果可能不同。下面例子中,使用 lxml 解析错误格式的文档,结果 </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> 标签,而是自动补全了标签,还给文档树添加了 <head> 标签。


使用 pyhton 内置库解析结果如下:


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

# <a></a>

与 lxml [7] 库类似的,Python 内置库忽略掉了 </p> 标签,与 html5lib 库不同的是标准库没有 尝试创建符合标准的文档格式或将文档片段包含在 <body> 标签内,与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, 'html.parser')

soup.h1

# <h1>Sacré bleu!</h1>

soup.h1.string

# 'Sacr\xe9 bleu!'

这不是魔术(但很神奇),Beautiful Soup 用了 编码自动检测 子库来识别当前 文档编码并转换成 Unicode 编码。BeautifulSoup 对象的 .original_encoding 属性记录了 自动识别编码的结果:


soup.original_encoding

'utf-8'

编码自动检测 功能大部分时候都能猜对编码格式,但有时候也会出错。有时候即使 猜测正确,也是在逐个 字节的遍历整个文档后才猜对的,这样很慢。如果预先知道文档编码,可以设置编码参数 来减少自动检查编码 出错的概率并且提高文档解析速度。在创建 BeautifulSoup 对象的时候设置 from_encoding 参数。


下面一段文档用了 ISO-8859-8 编码方式,这段文档太短,结果 Beautiful Soup 以为文档是用 ISO-8859-7 编码:


markup = b"<h1>\xed\xe5\xec\xf9</h1>"

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

print(soup.h1)

# <h1>νεμω</h1>

print(soup.original_encoding)

# iso-8859-7

通过传入 from_encoding 参数来指定编码方式:


soup = BeautifulSoup(markup, 'html.parser', from_encoding="iso-8859-8")

print(soup.h1)

# <h1>םולש</h1>

print(soup.original_encoding)

# iso8859-8

如果仅知道文档采用了 Unicode 编码,但不知道具体编码。可以先自己猜测,猜测错误(依旧是乱码)时, 可以把错误编码作为 exclude_encodings 参数,这样文档就不会尝试使用这种编码了解码了。


译者备注: 在没有指定编码的情况下,BS会自己猜测编码,把不正确的编码排除掉,BS就更容易猜到正确编码。


soup = BeautifulSoup(markup, 'html.parser', exclude_encodings=["iso-8859-7"])

print(soup.h1)

# <h1>םולש</h1>

print(soup.original_encoding)

# WINDOWS-1255

猜测的结果 Windows-1255 可能不是 100% 准确,但是 Windows-1255 编码是 ISO-8859-8 的扩展集, 所以猜测结果已经十分接近了,并不影响使用。(exclude_encodings 参数是 4.4.0版本的新功能)


少数情况下(通常是UTF-8编码的文档中包含了其它编码格式的文件),想获得正确的 Unicode 编码就不得不将 文档中少数特殊编码字符替换成特殊 Unicode 编码,“REPLACEMENT CHARACTER” (U+FFFD, �) [9] 。 如果 Beautifu Soup 猜测文档编码时作了特殊字符的替换,那么 Beautiful Soup 会把 UnicodeDammit 或 BeautifulSoup 对象的 .contains_replacement_characters 属性标记为 True 。 这样就可以知道当前文档进行 Unicode 编码后丢失了一部分特殊内容字符。如果文档中包含 � 而 .contains_replacement_characters 属性是 False ,则表示 � 就是文档中原来的字符, 不是转码失败。

输出编码

通过 Beautiful Soup 输出文档时,不管输入文档是什么编码方式,输出编码均为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, 'html.parser')

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 对象或任意节点的 encode() 方法,就像 Python 的字符串 调用 encode() 方法一样:


soup.p.encode("latin-1")

# b'<p>Sacr\xe9 bleu!</p>'


soup.p.encode("utf-8")

# b'<p>Sacr\xc3\xa9 bleu!</p>'

如果文档中包含当前编码不支持的字符,那么这些字符将被转换成一系列 XML 特殊字符引用,下面例子中 包含了 Unicode 编码字符 SNOWMAN:


markup = u"<b>\N{SNOWMAN}</b>"

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

tag = snowman_soup.b

SNOWMAN 字符在 UTF-8 编码中可以正常显示(看上去是 ☃),但有些编码不支持 SNOWMAN 字符,比如 ISO-Latin-1 或 ASCII,那么在这些编码中 SNOWMAN 字符会被转换成 “☃”:


print(tag.encode("utf-8"))

# b'<b>\xe2\x98\x83</b>'


print(tag.encode("latin-1"))

# b'<b>&#9731;</b>'


print(tag.encode("ascii"))

# b'<b>&#9731;</b>'

Unicode, Dammit

译者备注: Unicode Dammit 是 Beautiful Soup 内置库,主要用来猜测文档编码。

编码自动检测 功能可以在 Beautiful Soup 以外使用。当遇到一段未知编码 的文档时,可以通过下面方法把它转换为 Unicode 编码


from bs4 import UnicodeDammit

dammit = UnicodeDammit(b"\xc2\xabSacr\xc3\xa9 bleu!\xc2\xbb")

print(dammit.unicode_markup)

# «Sacré bleu!»

dammit.original_encoding

# 'utf-8'

如果安装了 Python 的 chardet 或 cchardet 库,那么编码检测功能的准确率将大大提高。 输入的字符越多,检测结果越准确,如果事先猜测到一些可能编码,那么可以将猜测的编码作为参数, 这样将优先检测这些编码:


dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])

print(dammit.unicode_markup)

# Sacré bleu!

dammit.original_encoding

# 'latin-1'

编码自动检测 功能中有 2 项功能是 Beautiful Soup 库中用不到的

智能引号

使用 Unicode 时,Beautiful Soup 还会智能的把引号 [10] 转换成 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

# '<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'


UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup

# '<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'

也可以把引号转换为 ASCII 码:


UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup

# '<p>I just "love" Microsoft Word\'s smart quotes</p>'

虽然这个功能很有用,但是 Beautiful Soup 没有使用这种方式。默认情况下,Beautiful Soup 把引号转换成 Unicode:


UnicodeDammit(markup, ["windows-1252"]).unicode_markup

# '<p>I just “love” Microsoft Word’s smart quotes</p>'

矛盾的编码

有时文档的大部分都是用 UTF-8,但同时还包含了 Windows-1252 编码的字符,就像微软的智能引号 [10] 一样。 一些包含多个信息的来源网站容易出现这种情况。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")

这段文档很杂乱,snowmen 是 UTF-8 编码,引号是 Windows-1252 编码,直接输出时不能同时显示 snowmen 和引号,因为它们编码不同:


print(doc)

# ☃☃☃�I like snowmen!�


print(doc.decode("windows-1252"))

# ☃☃☃“I like snowmen!”

如果对这段文档用 UTF-8 解码就会产生 UnicodeDecodeError 异常,如果用 Windows-1252 解码就会得到一堆乱码。幸好,UnicodeDammit.detwingle() 方法会把这段字符串转换成 UTF-8 编码,允许我们同时显示出文档中的 snowmen 和引号:


new_doc = UnicodeDammit.detwingle(doc)

print(new_doc.decode("utf8"))

# ☃☃☃“I like snowmen!”

UnicodeDammit.detwingle() 方法只能解码包含在 UTF-8 编码中的 Windows-1252 编码内容, (反过来的话,大概也可以)但这是最常见的用法。


在创建 BeautifulSoup 或 UnicodeDammit 对象前一定要先对文档调用 UnicodeDammit.detwingle() 确保文档的编码方式正确。Beautiful Soup 会假设文档只包含一种编码,如果尝试去解析一段同时包含 UTF-8 和 Windows-1252 编码的文档, 就有可能被误判成整个文档都是 Windows-1252 编码,解析结果就会得到一堆乱码, 比如: ☃☃☃“I like snowmen!”。

UnicodeDammit.detwingle() 方法在 Beautiful Soup 4.1.0 版本中新增。

行编号

html.parser 和 html5lib 解析器可以跟踪原始文档中发现的每个 Tag。查看原始信息可以 使用 Tag.sourceline (行号)和 Tag.sourcepos (标签所在行的起始位置)


markup = "<p\n>Paragraph 1</p>\n    <p>Paragraph 2</p>"

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

for tag in soup.find_all('p'):

   print(repr((tag.sourceline, tag.sourcepos, tag.string)))

# (1, 0, 'Paragraph 1')

# (3, 4, 'Paragraph 2')

注意,这两个解析器的 sourceline 和 sourcepos 会有些许的不同。html.parser 将 标签开始的 小于号作为标签起始符号,而 html5lib 将标签开始的大于号作为标签起始符号


soup = BeautifulSoup(markup, 'html5lib')

for tag in soup.find_all('p'):

   print(repr((tag.sourceline, tag.sourcepos, tag.string)))

# (2, 0, 'Paragraph 1')

# (3, 6, 'Paragraph 2')

可以在 BeautifulSoup 构造函数中配置 store_line_numbers=False 来关闭这个功能


markup = "<p\n>Paragraph 1</p>\n    <p>Paragraph 2</p>"

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

print(soup.p.sourceline)

# None

这个功能在 4.8.1 版本中引入,lxml 解析器不支持这个功能。

比较对象是否相同

两个 NavigableString 或 Tag 对象具有相同的 HTML 或 XML 结构时, Beautiful Soup就判断这两个对象相同。这个例子中,2个 <b> 标签在 BS 中是相同的, 尽管他们在文档树的不同位置,但是具有相同的表象: “<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() 方法可以复制任意 Tag 或 NavigableString 对象

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

源对象和复制对象的区别是源对象在文档树中,而复制后的对象是独立的还没有添加到文档树中。 复制后对象的效果跟调用了 extract() 方法相同。


print p_copy.parent

# None

这是因为相等的对象不能同时插入相同的位置

高级自定义解析

Beautiful Soup 提供多种途径自定义解析器如果解析 HTML 和 XML。本章覆盖了最常用的自定义方法。

解析部分文档

如果仅仅因为想要查找文档中的 <a> 标签而将整片文档进行解析,实在是浪费内存和时间。最快的方法 是从一开始 就把 <a> 标签以外的东西都忽略掉。 SoupStrainer 类可以选择解析哪部分文档内容, 创建一个 SoupStrainer 对象并作为 parse_only 参数给 BeautifulSoup 的构造 方法即可。


(注意,这个功能在 html5lib 解析器中无法使用。如果使用 html5lib 解析器,整篇文档都会被解析, 这是因为 html5lib 会重新排列文档树的结构,如果部分节点不在文档树中,会导致崩溃。为了避免混淆, 下面的例子中 Beautiful Soup 都强制指定使用了 Python 内置解析器。)

SoupStrainer

SoupStrainer 类接受与典型搜索方法相同的参数: name , attrs , recursive , string , **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 string is not None and 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, 'html.parser')

soup.find_all(only_short_strings)

# ['\n\n', '\n\n', 'Elsie', ',\n', 'Lacie', ' and\n', 'Tillie',

#  '\n\n', '...', '\n']

自定义包含多个值的属性

在 HTML 文档中,像 class 这样的属性的值是一个列表,像 id 这样的属性的值是一个单一字符串, 因为 HTML 标准定义了这些属性的不同行为

markup = '<a class="cls1 cls2" id="id1 id2">'

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

soup.a['class']

# ['cls1', 'cls2']

soup.a['id']

# 'id1 id2'

设置 multi_valued_attributes=None 可以禁用多值的自动识别,然后全部属性的值都变成一个字符串


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

soup.a['class']

# 'cls1 cls2'

soup.a['id']

# 'id1 id2'

如果给 multi_valued_attributes 参数传入一个字典,可以实现一点点解析自定义。如果需要这么做, 查看 HTMLTreeBuilder.DEFAULT_CDATA_LIST_ATTRIBUTES 了解 Beautiful Soup 的默认配置, 这些均是基于 HTML 标准配置的。

(这个功能添加于 Beautiful Soup 4.8.0)

处理重复属性

使用 html.parser 解析器时,可以通过设置 on_duplicate_attribute 参数,来定义当 Beautiful Soup 在 tag 中发现重复的属性名字时如何处理

markup = '<a href="http://url1/" href="http://url2/">'

默认行为是,重名属性会使用最后出现的值


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

soup.a['href']

# http://url2/


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

soup.a['href']

# http://url2/

当 on_duplicate_attribute='ignore' 时,Beautiful Soup 会使用第一个出现的值,然后忽略 后出现的值


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

soup.a['href']

# http://url1/

(lxml 和 html5lib 总是采用这种处理方式,它们的默认行为不能通过 Beautiful Soup 配置。)


如果需要复杂的控制,可以传入一个方法,当属性值重复时会被调用


def accumulate(attributes_so_far, key, value):

   if not isinstance(attributes_so_far[key], list):

       attributes_so_far[key] = [attributes_so_far[key]]

   attributes_so_far[key].append(value)


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

soup.a['href']

# ["http://url1/", "http://url2/"]

这个特性新增于 Beautiful Soup 4.9.1。

实例化自定义子类

当解析器传递给 Beautiful Soup 一个标签或一个字符串后,Beautiful Soup 会实例化为 Tag 或 NavigableString 对象,并包含相关信息。如果想修改默认行为,可以让 Beautiful Soup 实例化 Tag 或 NavigableString 的子类,子类中可以自定义行为


from bs4 import Tag, NavigableString

class MyTag(Tag):

   pass



class MyString(NavigableString):

   pass



markup = "<div>some text</div>"

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

isinstance(soup.div, MyTag)

# False

isinstance(soup.div.string, MyString)

# False


my_classes = { Tag: MyTag, NavigableString: MyString }

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

isinstance(soup.div, MyTag)

# True

isinstance(soup.div.string, MyString)

# True

这种用法可用在于 Beautiful Soup 与测试框架集成。


这个特性新增于 Beautiful Soup 4.8.1。

常见问题

代码诊断

如果想知道 Beautiful Soup 到底怎样处理一份文档,可以将文档传入 diagnose() 方法(Beautiful Soup 4.2.0中新增), Beautiful Soup 会输出一份报告, 说明不同的解析器会怎样处理这段文档,并标出当前的解析过程会使用哪种解析器:


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() 方法的输出结果可能帮助你找到问题的原因,如果不行,还可以把结果复制出来以 便寻求他人的帮助。

文档解析错误

文档解析错误有两种。一种是崩溃,Beautiful Soup 尝试解析一段文档结果却抛除了异常,通常是 HTMLParser.HTMLParseError 。还有一种异常情况,是Beautiful Soup 解析后的文档树 看起来与原来的内容相差很多。

这些错误几乎都不是 Beautiful Soup 的原因,这不是因为 Beautiful Soup 的代码写得多优秀, 而是因为 Beautiful Soup 没有包含任何文档解析代码。异常产生自被依赖的解析器,如果解析器不能 很好的解析出当前的文档,那么最好的办法是换一个解析器。更多细节查看 安装解析器 章节。


最常见的解析错误是 HTMLParser.HTMLParseError: malformed start tag 和 HTMLParser.HTMLParseError: bad end tag 。这都是由Python内置的解析器引起的, 解决方法是 安装 lxml 或 html5lib 。


最常见的非预期行为是发现不了一个确定存在稳当中的 Tag。光是用眼睛就能轻易发现,但用 find_all() 方法返回 [] ,用 find() 方法返回 None 。这是 Python 内置解析器的又一个问题: 解析器会跳过那些 它不知道的 tag。解决方法还是 安装 lxml 或 html5lib

版本错误

  • SyntaxError: Invalid syntax (异常位置在代码行: ROOT_TAG_NAME = u'[document]' ), 原因是用 Python2 版本的 Beautiful Soup 未经过代码转换,直接在 Python3 中运行。
  • ImportError: No module named HTMLParser 因为在 Python3 中执行 Python2 版本的 Beautiful Soup。
  • ImportError: No module named html.parser 因为在 Python2 中执行 Python3 版本的 Beautiful Soup
  • ImportError: No module named BeautifulSoup 因为在没有安装 Beautiful Soup3 库的 Python 环境下执行代码, 或忘记了 Beautiful Soup4 的代码需要从 bs4 包中引入。
  • ImportError: No module named bs4 因为当前 Python 环境下还没有安装 Beautiful Soup4。

解析成XML

默认情况下,Beautiful Soup 会将当前文档作为 HTML 格式解析,如果要解析 XML 文档,要在 BeautifulSoup 构造方法中加入第二个参数 “xml”:


soup = BeautifulSoup(markup, "xml")

当然,还需要 安装 lxml

其它解析器的错误

  • 如果同样的代码在不同环境下结果不同,可能是因为两个环境下使用不同的解析器造成的。 例如这个环境中安装了 lxml,而另一个环境中只有 html5lib, 解析器之间的区别 中说明了原因。 修复方法是在 BeautifulSoup 的构造方法中中指定解析器。
  • 因为HTML标签是 大小写敏感 的, 所以解析器会将 tag 和属性都转换成小写。例如文档中的 <TAG></TAG> 会被转换为 <tag></tag> 。 如果想要保留 tag 的大写的话,那么应该将文档 解析成XML 。

杂项错误

UnicodeEncodeError: 'charmap' codec can't encode character '\xfoo' in position bar (或其它类型的 UnicodeEncodeError )的错误, 主要是两方面的原因,第一种是正在使用的终端(console)无法显示部分Unicode,参考 Python wiki ,第二种是向文件 写入时,被写入文件不支持部分 Unicode,这时需要用 u.encode("utf8") 方法将 编码转换为UTF-8。


KeyError: [attr] 因为调用 tag['attr'] 方法而引起,因为这个 tag 没有定义 该属性。出错最多的是 KeyError: 'href' 和 KeyError: 'class' 。如果不确定 某个属性是否存在时,用 tag.get('attr') 方法去获取它,跟获取 Python 字典的 key 一样。


AttributeError: 'ResultSet' object has no attribute 'foo' 错误通常是 因为把 find_all() 的返回结果当作一个 tag 或文本节点使用,实际上返回结果是一个 列表或 ResultSet 对象的字符串,需要对结果进行循环才能得到每个节点的 .foo 属性。 或者使用 find() 方法仅获取到一个节点。


AttributeError: 'NoneType' object has no attribute 'foo' 这个错误通常是 在调用了 find() 方法后直节点取某个属性 foo。但是 find() 方法并没有找到任何 结果,所以它的返回值是 None 。需要找出为什么 find() 的返回值是 None。


AttributeError: 'NavigableString' object has no attribute 'foo' 这种问题 通常是因为吧一个字符串当做一个 tag 来处理。可能在迭代一个列表时,期望其中都是 tag,但实际上 列表里既包含 tag 也包含字符串。

如何提高效率

Beautiful Soup对文档的解析速度不会比它所依赖的解析器更快,如果对计算时间要求很高或者 计算机的时间比程序员的时间更值钱,那么就应该直接使用 lxml 。

换句话说,还有提高 Beautiful Soup 效率的办法,使用lxml作为解析器。Beautiful Soup 用 lxml 做解析器比用 html5lib 或 Python 内置解析器速度快很多。

安装 cchardet 后文档的解码的编码检测 速度会更快。

解析部分文档 不会节省多少解析时间,但是会节省很多内存,并且搜索时也会变得更快。

翻译这篇文档

非常感谢欢迎翻译 Beautiful Soup 的文档。翻译内容应当基于 MIT 协议,就像 Beautiful Soup 和 英文文档一样。

有两种方式将翻译内容添加到 Beautiful Soup 的网站上:

  1. 在 Beautiful Soup 代码库上创建一个分支,添加翻译,然后合并到主分支。就像修改源代码一样。
  1. 向 Beautiful Soup 讨论组里发送一个消息,带上翻译的链接,或翻译内容的附件。

使用中文或葡萄牙语的翻译作为模型。尤其注意,请翻译源文件 doc/source/index.rst, 而不是 HTML 版本的文档。这样才能将文档发布为多种格式,而不局限于 HTML。

Beautiful Soup 3

Beautiful Soup 3 是上一个发布版本,目前已经停止维护。Beautiful Soup 3 库目前已经被 几个主要的 linux 发行版添加到源里:

$ apt-get install Python-beautifulsoup

在 PyPi 中分发的包名字是 BeautifulSoup :

$ easy_install BeautifulSoup

$ pip install BeautifulSoup

或通过 Beautiful Soup 3.2.0源码包 安装。

如果是通过 easy_install beautifulsoup 或 easy_install BeautifulSoup 安装,然后代码无法运行,那么可能是安装了错误的 Beautiful Soup 3 版本。 应该这样安装 easy_install beautifulsoup4。

Beautiful Soup 3 的在线文档查看 这里 .

迁移到 BS4

大部分使用 Beautiful Soup 3 编写的代码都可以在 Beautiful Soup 4 上运行, 只有一个小变动。只要把引用包的名字从 BeautifulSoup 改为 bs4,比如:

from BeautifulSoup import BeautifulSoup

修改为:

from bs4 import BeautifulSoup

  • 如果代码抛出 ImportError 异常 “No module named BeautifulSoup”, 原因可能是尝试执行 Beautiful Soup 3,但环境中只安装了 Beautiful Soup 4。
  • 如果代码抛出 ImportError 异常 “No module named bs4”,原因可能是尝试 运行 Beautiful Soup 4 的代码,但环境中只安装了Beautiful Soup 3。

尽管 BS4 兼容绝大部分 BS3 的功能,但 BS3 中的大部分方法已经不推荐使用了,旧方法标记废弃, 并按照 PEP8标准 重新命名了新方法。 虽然有大量的重命名和修改,但只有少数几个方法没有向下兼容。

下面内容就是 BS3 迁移到 BS4 的注意事项:

解析器的变化

Beautiful Soup 3 曾使用 Python 的 SGMLParser 解析器,这个模块在 Python3 中已经被移除了。Beautiful Soup 4 默认使用系统的 html.parser , 也可以使用 lxml 或 html5lib 扩展库代替。查看 安装解析器 章节。

因为解析器 html.parser 与 SGMLParser 不同。BS4 和 BS3 处理相同的文档会 产生不同的对象结构。使用 lxml 或 html5lib 解析文档的时候,如果添加了 html.parser 参数,解析的对象又会发生变化。如果发生了这种情况,只能修改对应的文档处理代码了。

方法名的变化

  • 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=...)

为了适配 Python3,修改了一个方法名:

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

修改了一个属性名,让它看起来更专业点:

  • Tag.isSelfClosing -> Tag.is_empty_element

修改了下面 3 个属性的名字,以免与 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 然后结束。这是个 bug。新版生成器不再返回 None 。

BS4 中增加了 2 个新的生成器, .strings 和 stripped_strings 。 .strings 生成器 返回 NavigableString 对象, .stripped_strings 方法返回去除前后空白的 Python 的 string 对象。

XML

BS4 中移除了解析 XML 的 BeautifulStoneSoup 类。如果要解析一段 XML 文档,使用 BeautifulSoup 构造方法并在第二个参数设置为“xml”。同时 BeautifulSoup 构造 方法也不再识别 isHTML 参数。

Beautiful Soup 处理 XML 空标签的方法升级了。旧版本中解析 XML 时必须指明哪个标签是空标签。 构造方法的 selfClosingTags 参数已经不再使用。新版 Beautiful Soup 将所有空标签解析 为空元素,如果向空元素中添加子节点,那么这个元素就不再是空元素了。

实体

输入的 HTML 或 XML 实体都会被解析成 Unicode 字符。Beautiful Soup 3 版本中有很多相似处理 实体的方法,在新版中都被移除了。 BeautifulSoup 构造方法也不再接受 smartQuotesTo 或 convertEntities 参数。 编码自动检测 方法依然有 smart_quotes_to 参数,但是默认会将引号转换成 Unicode。内容配置项 HTML_ENTITIES , XML_ENTITIES 和 XHTML_ENTITIES 在新版中被移除。因为它们代表的特性(转换部分而不是全部实体到 Unicode 字符) 已经不再支持。


如果在输出文档时想把 Unicode 字符转回 HTML 实体,而不是输出成 UTF-8 编码,那就需要用到 输出格式 的方法。

迁移杂项

Tag.string 属性现在是一个递归操作。如果 A 标签只包含了一个 B 标签,那么 A 标签的。 string 属性值与 B 标签的 string 属性值相同。


多值属性 比如 class 属性包含一个他们的值的列表,而不是一个字符串。这可能会影响到如何按照 CSS 类名哦搜索 tag。


Tag 对象实现了一个 __hash__ 方法,这样当两个 Tag 对象生成相同的摘要时会被认为相等。这可能会 改变你的脚本行为,如果你吧 Tag 对象放到字典或集合中。


如果使用 find* 方法时同时传入了 string 参数 和一个指定 tag 的参数比如 name 参数 。 Beautiful Soup 会搜索符合指定参数的 tag,并且这个 tag 的 Tag.string 属性包含 string 参数 参数的内容。结果中 不会 包含字符串本身。旧版本中 Beautiful Soup 会忽略掉指定 tag 的参数,只搜索符合 string 的内容。


BeautifulSoup 构造方法不再支持 markupMassage 参数。现在由解析器负责文档的解析正确性。


很少被用到的几个解析器方法在新版中被移除,比如 ICantBelieveItsBeautifulSoup 和 BeautifulSOAP 。 现在由解析器完全负责如何解释模糊不清的文档标记。

prettify() 方法在新版中返回 Unicode 字符串,不再返回字节串。


相关文章
|
3月前
|
XML 数据采集 API
MechanicalSoup与BeautifulSoup的区别分析
MechanicalSoup与BeautifulSoup的区别分析
55 2
MechanicalSoup与BeautifulSoup的区别分析
|
8月前
|
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()处理编码。这些功能组合使用可灵活处理文档信息。
43 1
WK
|
4月前
|
XML 前端开发 API
Beautiful Soup有哪些支持功能
Beautiful Soup是一个强大的Python库,用于从HTML或XML文件中提取数据。它支持多种解析器,如html.parser、lxml和html5lib,能灵活应对不同格式的文档。通过丰富的API,可以轻松遍历解析树,按标签名、属性或字符串内容搜索和提取数据。此外,Beautiful Soup还支持简单的树修改操作,处理不同编码的文档,并具备良好的容错性。从4.0版本起,它引入了CSS选择器,使搜索更加便捷。详尽的官方文档和广泛的社区支持使其成为处理网页数据的理想选择。
WK
61 1
|
8月前
|
XML 前端开发 数据格式
​Beautiful Soup 4.12.0 文档(一)
​Beautiful Soup 4.12.0 文档(一)
|
8月前
|
XML 前端开发 数据格式
​Beautiful Soup 4.12.0 文档(二)
​Beautiful Soup 4.12.0 文档(二)
|
8月前
|
XML 数据格式
Beautiful Soup 库有哪些常用的方法
Beautiful Soup 库有哪些常用的方法
139 1
|
数据采集 SQL 移动开发
【Python爬虫】Beautifulsoup4中find_all函数
【Python爬虫】Beautifulsoup4中find_all函数
|
XML JSON API
【 ⑨】jsonpath和BeautifulSoup库概述及其对比
【 ⑨】jsonpath和BeautifulSoup库概述及其对比
154 0
|
前端开发 API Python
在Python中如何使用BeautifulSoup进行页面解析
在Python中如何使用BeautifulSoup进行页面解析
|
Python
Python使用BeautifulSoup4修改网页内容实战
最近有个小项目,需要爬取页面上相应的资源数据后,保存到本地,然后将原始的HTML源文件保存下来,对HTML页面的内容进行修改将某些标签整个给替换掉。对于这类需要对HTML进行操作的需求,最方便的莫过于BeautifulSoup4的库了。
123 0

热门文章

最新文章