BeautifulSoup 使用指北 - 0x01_概览

GitHub@ orca-j35,全部笔记均托管于 python_notes 仓库。
欢迎任何形式的转载,但请务必注明出处。

概述

⚠官方文档中混杂了 Py2 和 Py3 的术语和代码,本笔记针对 Py3 梳理了文档中的内容,在了解 BeautifulSoup 的过程当中,建议将本笔记与官方文档配合食用。css

Beautiful Soup 是一个用来从 HTML 或 XML 文件中提取数据的 Python 库。在使用 BeautifulSoup 时,咱们选择本身喜欢的解析器,从而以本身熟悉的方式来导航、查找和修改解析树。html

相关资源:html5

安装:python

pip install beautifulsoup4

若是遇到安装问题,能够参考:git

若是能顺利执行如下代码,则说明安装成功:github

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string) #> Hello

⚠在安装库和导入库时使用的名称不必定相同,例如: 在安装 BeautifulSoup4 时,使用的名称是 beautifulsoup4;在导入时,使用的名称是 bs4 (路径为 ~\Python\Lib\site-packages\bs4)。shell

若是在使用过程当中遇到本文未涵盖的问题,请参考: https://www.crummy.com/softwa...数据结构

Three sisters

下面这段名为 "Three sisters" 文档是本笔记的 HTML 示例文档(官方文档中也用的这段代码):ide

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

这段 HTML 文档存在 "tag soup",HTML 解析器会自动修复 "tag soup"函数

提升性能

BeautifulSoup 的速度永远会低于其使用的解析器的速度。若是对速度有严格要求,应直接使用 lxml 库来解析。

对 BeautifulSoup 而言,lxml 解析器的速度比 html.parser 或 html5lib 更快。

能够经过安装 cchardet 库来显著提高检测编码方案的速度。

仅解析部分文档并不会节省大量的解析时间,可是能够节省大量内存,并有效提高检索文档的速度。

BeautifulSoup()🛠

🛠BeautifulSoup(self, markup="", features=None, builder=None, parse_only=None, from_encoding=None, exclude_encodings=None, **kwargs)

构造器 BeautifulSoup() 中各参数的含义以下:

  • markup - 要解析的标签(markup),能够是字符串或 file-like 对象。

    from bs4 import BeautifulSoup
    
    with open("index.html") as fp:
        soup = BeautifulSoup(fp)
    
    soup = BeautifulSoup("<html>data</html>")
  • features - 用来设置解析器,可以使用解析器的名称("lxml", "lxml-xml", "html.parser", "html5lib"),或使用标签的类型("html", "html5", "xml")。建议明确给出须要使用的解析器,以便 BeautifulSoup 在不一样的平台和虚拟环境中提供相同的结果。

    默认状况下,BeautifulSoup 会以 HTML 格式解析文档,若是要以 XML 格式解析文档,则需设置 features='xml'。目前支持解析 XML 的解析器仅有 lxml。

    若是没有手动设置解析器,BeautifulSoup 将会在已安装的解析器中选一个最好用的 HTML 解析器,解析器的优先级依次是 lxml’s HTML parser > html5lib's parser > Python’s html.parser。

    若是已手动设置某解析器,可是并为安装该解析器,BeautifulSoup 将忽略该设置并按照优先级选择一个解析器。

  • builder - 不须要使用的参数(A specific TreeBuilder to use instead of looking one up based on features)。
  • parse_only - 以 SoupStrainer 对象做为实参值。在解析文档的过程当中只会考虑与 SoupStrainer 匹配的部分。当咱们只须要解析某部分文档时很是有用,好比因为文档太大而没法放所有放入内存时,即可以考虑只解析某部分文档。
  • from_encoding - 一个字符串,表示被解析的文档的编码。若是 BeautifulSoup 在猜想文档编码时出现错误,请传递此参数。
  • exclude_encodings - 一个字符串列表,表示已知的错误编码。若是你不知道文档编码,但你知道 BeautifulSoup 的猜想出现错误时,请传递此参数。
  • **kwargs - 为了保证向后兼容,构造可接受 BeautifulSoup3 中使用的某些关键字参数,但这些关键字参数在 BeautifulSoup4 中并不会执行任何操做。

解析器

Beautiful Soup 支持 Python 标准库中的 HTML 解析器,同时还支持一些第三方的解析器(如 lxml):

  • Python’s html.parser - BeautifulSoup(markup,"html.parser")
  • lxml’s HTML parser - BeautifulSoup(markup, "lxml")
  • lxml’s XML parser - BeautifulSoup(markup, "lxml-xml")BeautifulSoup(markup, "xml")
  • html5lib - BeautifulSoup(markup, "html5lib")

默认状况下,BeautifulSoup 会以 HTML 格式解析文档,若是要以 XML 格式解析文档,则需设置 features='xml'。目前支持解析 XML 的解析器仅有 lxml。

若是没有手动设置解析器,BeautifulSoup 将会在已安装的解析器中选一个最好用的 HTML 解析器,解析器的优先级依次是 lxml’s HTML parser > html5lib's parser > Python’s html.parser。

若是已手动设置某解析器,可是并为安装该解析器,BeautifulSoup 将忽略该设置并按照优先级选择一个解析器。

第三方解析器的安装方法和优缺点对比: Installing a parser

建议使用 lxml 解析器来提升解析速度。早于 2.7.3 和 3.2.2 的 Python 版本,必须使用 lxml 和 html5lib 解析器,由于这些版本的内置 HTML 解析器不够稳定。

Note: 若是试图解析无效的 HTML/XML 文档,不一样解析器可能会给出不一样的结果。

有关解析器间的具体差别,详见: Specifying the parser to use

解析 XML 文档

参考: https://www.crummy.com/softwa...

默认状况下,BeautifulSoup 会以 HTML 格式解析文档,若是要以 XML 格式解析文档,则需设置 features='xml'。目前支持解析 XML 的解析器仅有 lxml。

编码

参考: https://www.crummy.com/softwa...

HTML 或 XML 文档可能会采用不一样的编码方案(如 ASCII 或 UTF-8),当你将文档加载到 BeautifulSoup 后,便会自动转换为 Unicode。

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

BeautifulSoup 会使用一个叫作 Unicode, Dammit 的子库来检测文档编码并将其转换为 Unicode。 BeautifulSoup 对象的 .original_encoding 属性记录了自动识别编码的结果:

print(soup.original_encoding)
#> 'utf-8'

在大多数时候,Unicode, Dammit 可以猜想出正确的编码方案,可是偶尔也会犯错。有时候即使猜想正确,但也须要先逐字节遍历文档后才能给出答案,这样很是耗时。若是你知道文档的编码方案,则能够经过 from_encoding 参数来设置编码方案,从而避免错误和延迟。

Here’s a document written in ISO-8859-8. The document is so short that Unicode, Dammit can’t get a lock on it, and misidentifies it as ISO-8859-7:

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

We can fix this by passing in the correct 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 isn’t 100% correct, but that encoding is a compatible superset of ISO-8859-8, so it’s close enough. (exclude_encodings is a new feature in Beautiful Soup 4.4.0.)

若是须要了解更多信息,请阅读: https://www.crummy.com/softwa...

仅解析部分文档

参考: https://www.crummy.com/softwa...

对于仅须要解析 <a> 标签状况而言,先解析整个文档而后再查找 <a> 标签标准过程会浪费大量的时间和内存。若是一开始就忽略掉与 <a> 标签无关的部分,则会有效提高查询速度。

对于仅须要解析部分文档的状况而言,可以使用 SoupStrainer 类筛选出要保留的标签。

仅解析部分文档并不会节省大量的解析时间,可是能够节省大量内存,并有效提高检索文档的速度。

⚠html5lib 解析器不支持该功能,缘由以下:

If you use html5lib, the whole document will be parsed, no matter what. This is because html5lib constantly rearranges the parse tree as it works, and if some part of the document didn’t actually make it into the parse tree, it’ll crash. To avoid confusion, in the examples below I’ll be forcing Beautiful Soup to use Python’s built-in parser.

SoupStrainer🐘

SoupStrainer() 构造器的参数与搜索解析树的方法相同: name, attrs, text, **kwargs,不可将 text 写做 string,对 SoupStrainer() 而言 text 和 string 不能等效使用。

示例 - SoupStrainer 对象的使用方法:

from bs4 import 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>
"""
only_a_tags = SoupStrainer("a")
soup = BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags)
print([f'{type(i)}::{i.name}' for i in soup])
#> ["<class 'bs4.element.Tag'>::a", "<class 'bs4.element.Tag'>::a", "<class 'bs4.element.Tag'>::a"]


only_tags_with_id_link2 = SoupStrainer(id="link2")
soup = BeautifulSoup(
    html_doc, "html.parser", parse_only=only_tags_with_id_link2)
print([f'{type(i)}::{i}' for i in soup])
#> ['<class \'bs4.element.Tag\'>::<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>']


def is_short_string(text: str):
    return len(text) < 10
only_short_strings = SoupStrainer(text=is_short_string)
soup = BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings)
print([repr(i) for i in soup])
#> ["'\\n'", "'\\n'", "'\\n'", "'\\n'", "'Elsie'", "',\\n'", "'Lacie'", "' and\\n'", "'Tillie'", "'\\n'", "'...'", "'\\n'"]

SoupStrainer 可用做搜索解析树的方法的参数,这可能并不常见,但仍是提一下:

def is_short_string(text: str):
    return len(text) < 10


only_short_strings = SoupStrainer(text=is_short_string)
soup = BeautifulSoup(
    html_doc,
    "html.parser",
)
print([repr(i) for i in soup.find_all(only_short_strings)])
#> "'\\n'", "'\\n'", "'\\n'", "'\\n'", "'Elsie'", "',\\n'", "'Lacie'", "' and\\n'", "'Tillie'", "'\\n'", "'...'", "'\\n'"]

对象的种类

参考: Kinds of objects

BeautifulSoup 会将复杂的 HTML 文档转换为复杂的 Python 对象树,树中的每一个节点都是一个 Python 对象,共有四种须要处理对象: Tag, NavigableString, BeautifulSoup, Comment

Tag 🐘

Tag 对象对应于原始文档中的 XML 或 HTML 标记(tag)。

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

Tag 对象拥有不少属性和方法,在 Navigating the treeSearching the tree 中有详细解释。本小节仅介绍 Tag 对象两个最重要的特性。

name

每一个 Tag 对象都有本身的名字,经过 .name 字段访问:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml')
tag = soup.b
print(tag.name)
#> b

若是修改了 Tag 对象的 .name 字段,则会影响 BeautifulSoup 对象生成的 HTML 文档:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml')
tag = soup.b
tag.name = "blockquote"
print(tag)
#> <blockquote class="boldest">Extremely bold</blockquote>
print(soup)
#> <html><body><blockquote class="boldest">Extremely bold</blockquote></body></html>

Attributes

一个 HTML 标签可包含任意数量的属性(attributes)。例如,标签 <b id="boldest"> 包含名为 "id" 的属性,其值为 "boldest"

可将 Tag 对象视做存放标签属性的字典,键值对由属性名和属性值构成,使用方法也与字典相同。另外,还可经过 .attrs 字段来获取存放标签属性的字典。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml')
tag = soup.b
print(tag['id'])  #> boldest
print(tag.get('id'))  #> boldest
print(tag.attrs) #> {'id': 'boldest'}

Tag 对象支持对标签的属性进行添加、删除、修改:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml')
tag = soup.b
tag['id'] = 'verybold'
tag['another-attribute'] = 1
print(tag)
#> <b another-attribute="1" id="verybold">Extremely bold</b>
del tag['id']
del tag['another-attribute']
print(tag)
#> <b>Extremely bold</b>
print(tag.get('id', "Don't have"))
#> Don't have
print(tag['id']
#> KeyError: 'id'

.has_attr() 方法用于判断 Tag 对象是否包含某个属性:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b id="boldest">Extremely bold</b>', 'lxml')
print(soup.b.has_attr('id'))
#> True
print(soup.b.has_attr('class'))
#> False

Multi-valued attributes

HTML 4 中某些属性能够具有多个值,HTML 5 在 HTML 4 的基础上删除了一些多值属性,但又引入了一些多值属性。最多见的多值属性是 class (HTML 标签可持有多个 CSS 类),其它一些多值属性的例子: rel, rev, accept-charset, headers, accesskey

BeautifulSoup 将多值属性的值表示为一个列表:

from bs4 import BeautifulSoup
css_soup = BeautifulSoup('<p class="body"></p>', 'lxml')
print(css_soup.p['class'])
#> ["body"]

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

若是某个属性看起来好像有多个值,但在任何版本的 HTML 定义中都没有被定义为多值属性,那么 BeautifulSoup 会将这个属性做为字符组返回:

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

Tag 转换成字符串时,会对多个属性值进行合并:

print(rel_soup.a['rel'])
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>

`.get_attribute_list() 方法用于获取标签属性列表,不管属性是不是多值属性都会返回一个列表:

id_soup = BeautifulSoup('<p class="body strikeout" id="my id"></p>', 'lxml')
print(id_soup.p['class'])
#> ['body', 'strikeout']
print(id_soup.p.get_attribute_list('class'))
#> ['body', 'strikeout']
print(id_soup.p['id'])
#> my id
print(id_soup.p.get_attribute_list('id'))
#> ['my id']

若是文档以 XML 格式进行解析,则不会包含多值属性:

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

NavigableString 🐘

🐘 bs4.element.NavigableString

NavigableString 继承自 str 类和 PageElement 类,不能对 NavigableString 对象所含字符串进行编辑,可是可使用 replace_with() 方法进行替换:

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml')
tag = soup.b
tag.string.replace_with("No longer bold")
print(tag)
#> <b class="boldest">No longer bold</b>

NavigableString 支持 Navigating the treeSearching the tree 中描述大部分功能,但并不是所有功能。因为 NavigableString 对象只能包含字符串,不能包含其它内容(Tag 对象能够包含字符串或子 tag),因此 NavigableString 不支持 .contents.string 字段,也不支持 find() 方法。在 NavigableString 上调用 name 字段时,会返回 None

若是想要在 BeautifulSoup 外部使用 NavigableString 中的字符串,你应该先调用 str()NavigableString 对象转换为普通的字符串对象。若是不将其转换为普通字符串的话,你将始终持有对整个 BeautifulSoup 解析树的引用,这会浪费大量内存。

可经过 .string 对象获取 NavigableString 对象,详见 .string🔧 小节

BeautifulSoup 🐘

BeautifulSoup 对象表示整个文档,在大部分时候,你能够将其视为 Tag 对象。BeautifulSoup 对象支持 Navigating the treeSearching the tree 中描述大部分功。

因为并无与 BeautifulSoup 对象对应的 HTML/XML tag,所以 BeautifulSoup 对象的 name 字段为 '[document]',而且不包含 HTML attributes。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'lxml')
print(type(soup))
#> <class 'bs4.BeautifulSoup'>
print(soup.name)
#> [document]

注释及特殊字符串

Tag, NavigableString, BeautifulSoup 几乎涵盖了你在 HTML 或 XML 文件中看到的全部内容,可是仍有一些没有覆盖到的内容,好比注释(comment):

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

Comment 类继承自 PreformattedStringPreformattedString 继承自 NavigableString。也就是说 Comment 是一种特殊的 NavigableString 类型。

可是当注释出如今HTML文档中时,Comment 对象会使用特殊的格式输出:

from bs4 import BeautifulSoup
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'lxml')
print(soup.b.prettify())
'''Out:
<b>
 <!--Hey, buddy. Want to buy a used parser?-->
</b>
'''

BeautifulSoup 还为 XML 文档中可能会出现的其它内容定义了各类类:

  • CData
  • ProcessingInstruction
  • Declaration
  • Doctype

Comment 相似,这些类都是 NavigableString 的子类,并进行了一些扩展。下面这个示例中,将使用 CDATA block 来替换 Comment:

from bs4 import BeautifulSoup
from bs4 import CData
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup, 'lxml')
cdata = CData("A CDATA block")
comment = soup.b.string
comment.replace_with(cdata)
print(soup.b.prettify())
'''Out:
<b>
 <![CDATA[A CDATA block]]>
</b>
'''

对象的是否相等

参考: https://www.crummy.com/softwa...

Beautiful Soup says that two NavigableString or Tag objects are equal when they represent the same HTML or XML markup. In this example, the two <b> tags are treated as equal, even though they live in different parts of the object tree, because they both look like “<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

If you want to see whether two variables refer to exactly the same object, use is:

print first_b is second_b
# False

拷贝 BeautifulSoup 对象

参考: https://www.crummy.com/softwa...

You can use copy.copy() to create a copy of any Tag or NavigableString:

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

The copy is considered equal to the original, since it represents the same markup as the original, but it’s not the same object:

print soup.p == p_copy
# True

print soup.p is p_copy
# False

The only real difference is that the copy is completely detached from the original Beautiful Soup object tree, just as if extract() had been called on it:

print p_copy.parent
# None

This is because two different Tag objects can’t occupy the same space at the same time.

输出

扩展阅读: Output encoding

BeautifulSoup 兼容 Py2 和 Py3 ,但 Py2 和 Py3 中的 str 对象并不相同,这会导出输出结果存在差别,在获取输出时需注意区分。

.decode()🔨

该方法会将 BeautifulSoup 对象和 Tag 对象中的内容转换为 Unicode 字符串。

源代码中的注释以下:

def decode(self, indent_level=None,
           eventual_encoding=DEFAULT_OUTPUT_ENCODING,
           formatter="minimal"):
    """Returns a Unicode representation of this tag and its contents.

        :param eventual_encoding: The tag is destined to be
           encoded into this encoding. This method is _not_
           responsible for performing that encoding. This information
           is passed in so that it can be substituted in if the
           document contains a <META> tag that mentions the document's
           encoding.
        """

对 Py3 而言,decode() 将返回 str 对象(Uncode 字符串):

# in Python3
from bs4 import BeautifulSoup
markup = '<a href="http://example.com/">链接到<i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(type(soup.decode()))
#> <class 'str'>
print(soup.decode())
#> <html><body><a href="http://example.com/">链接到<i>example.com</i></a></body></html>

对 Py2 而言,decode() 将返回 Unicode 对象(Uncode 字符串):

# in Python2
>>> markup = u'<a href="http://example.com/">链接到<i>example.com</i></a>'
>>> soup = BeautifulSoup(markup, 'lxml')
>>> print(type(soup.decode()))
<type 'unicode'>
>>> print(soup.decode())
<html><body><a href="http://example.com/">链接到<i>example.com</i></a></body></html>

.encode()🔨

该方法会先将数据结构转换为 Unicode 字符串,再按照指定编码对 Unicode 字符串进行编码,默认采用 UTF-8 编码。源代码以下:

def encode(self, encoding=DEFAULT_OUTPUT_ENCODING,
           indent_level=None, formatter="minimal",
           errors="xmlcharrefreplace"):
    # Turn the data structure into Unicode, then encode the
    # Unicode.
    u = self.decode(indent_level, encoding, formatter)
    return u.encode(encoding, errors)

对 Py3 而言,encode() 将返回以 encoding 编码(默认采用 UTF-8)的 bytes 对象:

# in Python3
from bs4 import BeautifulSoup
markup = '<a href="http://example.com/">链接到<i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(type(soup.encode()))
#> <class 'bytes'>
print(soup.encode())
#> b'<html><body><a href="http://example.com/">\xe8\xbf\x9e\xe6\x8e\xa5\xe5\x88\xb0<i>example.com</i></a></body></html>'

对 Py2 而言,encode() 将返回以 encoding 编码(默认采用 UTF-8)的 str 对象(Py2 和 Py3 中的 str 对象并不相同):

# in Python2
>>> markup = u'<a href="http://example.com/">链接到<i>example.com</i></a>'
>>> soup = BeautifulSoup(markup, 'lxml')
>>> print(soup.encode())
<html><body><a href="http://example.com/">链接到<i>example.com</i></a></body></html>
>>> soup.encode()
'<html><body><a href="http://example.com/">\xe8\xbf\x9e\xe6\x8e\xa5\xe5\x88\xb0<i>example.com</i></a></body></html>'
>>> type(soup.encode())
<type 'str'>

.prettify()🔨

参考: https://www.crummy.com/softwa...

🔨prettify(self, encoding=None, formatter="minimal")

encoding==None 时,prettify() 会将 BeautifulSoup 解析树转换为格式良好的 Unicode 字符串,在字符串中每一个 HTML/XML tag 和 字符串都会独占一行;当 encoding!=None 时,prettify() 会将 BeautifulSoup 解析树编码为格式良好的 bytes 字符串。

prettify() 的源代码以下:

# prettify()的源代码
def prettify(self, encoding=None, formatter="minimal"):
    if encoding is None:
        return self.decode(True, formatter=formatter)
    else:
        return self.encode(encoding, True, formatter=formatter)

示例 - in Py3:

# in Python3
from bs4 import BeautifulSoup
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(type(soup.prettify()))
#> <class 'str'>
print(soup.prettify())
'''Out:
<html>
 <body>
  <a href="http://example.com/">
   I linked to
   <i>
    example.com
   </i>
  </a>
 </body>
</html>
'''

prettify() 适用于 BeautifulSoup 对象和 Tag 对象:

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

示例 - in Py2:

# in Python2
from bs4 import BeautifulSoup
markup = u'<a href="http://example.com/">链接到<i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.prettify())
'''Out:
<html>
 <body>
  <a href="http://example.com/">
   I linked to
   <i>
    example.com
   </i>
  </a>
 </body>
</html>
'''

formatter 参数

参考: Output formatters

若是传递给 BeautifulSoup() 的文档中包含 HTML 实体(entities),那么在输出文档时,这些 HTML 实体将被转换为 Unicode 字符:

# in Python3
from bs4 import BeautifulSoup
soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.", 'lxml')
print(soup)
#> <html><body><p>“Dammit!” he said.</p></body></html>

若是将文档编码为 bytes 对象,encode() 方法会先将 HTML 文档内容转换为 Unicode 字符串(此时 HTML 实体将被转换为 Unicode 字符),而后再将 Unicode 字符串编码为 bytes 对象,默认采用 UTF-8 编码。HTML 实体将以 Unicode 字符的形式编码。

# in Python3
# 注意观察HTML实体的变化
from bs4 import BeautifulSoup
soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.", 'lxml')
print(soup.encode())
#> b'<html><body><p>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</p></body></html>'

print('“'.encode('utf-8'))
#> b'\xe2\x80\x9c'

默认状况下,在输出的 Unicode 字符串中,为了保证 BeautifulSoup 不会在无心中生成无效的 HTML 或 XML,独立的 &(ampersand)和尖括号会以 HTML 实体显示:

# 独立的&会显示为&amp;   &amp;会保持原样
# 独立的<会显示为&lt;    &lt;会保持原样
# 独立的>会显示为&gt;    &gt;会保持原样

# in Python3
from bs4 import BeautifulSoup
soup = BeautifulSoup(
    "<p>The law firm of Dewey, Cheatem, > &gt; < &lt; & &amp; Howe</p>",
    'lxml')
p = soup.p
print(p)
#> <p>The law firm of Dewey, Cheatem, &gt; &gt; &lt; &lt; &amp; &amp; Howe</p>
soup = BeautifulSoup(
    '<a href="http://example.com/?foo=val1&bar=val2">A link</a>', 'lxml')
print(soup.a)
#> <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>

若是须要改变 HTML 实体的呈现方式,便须要向 prettify() , encode() , decode() 传递 formatter 参数。formatter 的实参值有 6 种状况,默认为 formatter="minimal"。另外,__str__() , __unicode__() , __repr__() 在输出时只能采用默认行为,不可修改。

minimal

formatter="minimal" 时,会按照前面叙述的规则来处理字符串,以确保生成有效的 HTML/XML:

# in Python3
from bs4 import BeautifulSoup
french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
soup = BeautifulSoup(french, 'lxml')
print(soup.prettify(formatter="minimal"))
'''Out:
<html>
 <body>
  <p>
   Il a dit &lt;&lt;Sacré bleu!&gt;&gt;
  </p>
 </body>
</html>'''

html

formatter="html" 时,BeautifulSoup 会尽量的将 Unicode 字符传唤为 HTML 实体:

# in Python3
from bs4 import BeautifulSoup
french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt; é</p>"
soup = BeautifulSoup(french, 'lxml')
print(soup.prettify(formatter="html"))
'''Out:
<html>
 <body>
  <p>
   Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt; &eacute;
  </p>
 </body>
</html>'''

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

html5

formatter="html5" 时,BeautifulSoup 会省略 HTML 空 tag 的结束斜杠,例如:

# in Python3
from bs4 import BeautifulSoup
soup = BeautifulSoup("<br>", 'lxml')
print(soup.encode(formatter="html"))
# <html><body><br/></body></html>
print(soup.encode(formatter="html5"))
# <html><body><br></body></html>

None

formatter=None 时,BeautifulSoup 将不会在输出中修改字符串。此时的输出速度最快,但可能会致使 BeautifulSoup 生成无效的 HTML/XML,例如:

# in Python3
from bs4 import BeautifulSoup
french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
soup = BeautifulSoup(french, 'lxml')
print(soup.prettify(formatter=None))
'''Out:
<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 传递一个函数,BeautifulSoup 会为文档中的每一个"字符串"和"属性值"调用一次该函数。你能够在这个函数中作任何你想作的事情。下面这个 formatter 函数会将字符串和属性值转换为大写,并不会执行其它操做:

# in Python3
from bs4 import BeautifulSoup

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

french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
soup = BeautifulSoup(french, 'lxml')
print(soup.prettify(formatter=uppercase))
'''Out:
<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>', 'lxml')
print(link_soup.a.prettify(formatter=uppercase))
'''Out:
<a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
 A LINK
</a>
'''

若是你正在编写 formatter 函数,你应该先了解一下 bs4.dammit 模块中的 EntitySubstitution 类——该类将 BeautifulSoup 中的标准 formatter 实现为类方法:

  • 'html' formatter 对应于 EntitySubstitution.substitute_html
  • 'minimal' formatter 对应于 EntitySubstitution.substitute_xml

你可使用上述函数来模拟 formatter=htmlformatter==minimal,并添加一些你须要的扩展功能。

下面这个示例会尽量的将 Unicode 字符传唤为 HTML 实体,并将全部字符串转换为大写:

from bs4 import BeautifulSoup
from bs4.dammit import EntitySubstitution

def uppercase_and_substitute_html_entities(str):
    return EntitySubstitution.substitute_html(str.upper())

french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt; é</p>"
soup = BeautifulSoup(french, 'lxml')
print(soup.prettify(formatter=uppercase_and_substitute_html_entities))
'''Out:
<html>
 <body>
  <p>
   IL A DIT &lt;&lt;SACR&Eacute; BLEU!&gt;&gt; &Eacute;
  </p>
 </body>
</html>
'''

CData 对象

若是建立建立了一个 CData 对象,则该对象内的文本将始终与其显示彻底一致,并不会进行格式化操做。

Beautiful Soup will call the formatter method, just in case you’ve written a custom method that counts all the strings in the document or something, but it will ignore the return value:
from bs4.element import CData
soup = BeautifulSoup("<a></a>")
soup.a.string = CData("one < three")
print(soup.a.prettify(formatter="xml")) # ?"xml"是什么意思?
# <a>
#  <![CDATA[one < three]]>
# </a>

Non-pretty printing

若是只想获得结果字符串,而且不在乎输出格式,则能够在 BeautifulSoup 对象和 Tag 对象上调用如下方法:

  • __unicode__() - 对应内置函数 unicode(),适用于 Py2
  • __str__() - 对应内置函数 str(),因为 Py2 中的 str 对象不是 Unicode 字符串,因此 str() 在 Py2 和 Py3 中的输出并不相同
  • __repr__() - 对应于内置函数 repr(),因为 Py2 中的 str 对象不是 Unicode 字符串,因此 repr() 在 Py2 和 Py3 中的输出并不相同

这三个方法的源代码以下:

def __repr__(self, encoding="unicode-escape"):
    """Renders this tag as a string."""
    if PY3K:
        # "The return value must be a string object", i.e. Unicode
        return self.decode()
    else:
        # "The return value must be a string object", i.e. a bytestring.
        # By convention, the return value of __repr__ should also be
        # an ASCII string.
        return self.encode(encoding)

def __unicode__(self):
    return self.decode()

def __str__(self):
    if PY3K:
        return self.decode()
    else:
        return self.encode()

if PY3K:
    __str__ = __repr__ = __unicode__

对 Py3 而言,上述三个方法彻底等效,均返回 str 对象(Unicode 字符串):

# in Python3
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup) # 调用__str__方法
#> <html><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>

对 Py2 而言,str() 将返回以 UTF-8 编码的 str 对象(若是须要了解与编码相关的内容,能够参考 Encodings )

# in Python2
>>> markup = u'<a href="http://example.com/">I linked to 示例<i>example.com</i></a>'
>>> soup = BeautifulSoup(markup, 'lxml')
>>> str(soup)
'<html><body><a href="http://example.com/">I linked to \xe7\xa4\xba\xe4\xbe\x8b<i>example.com</i></a></body></html>'

对 Py2 而言,repr() 将返回以 unicode-escape 编码(详见 Text Encodings)的 str 对象:

# in Python2
>>> markup = u'<a href="http://example.com/">I linked to 示例<i>example.com</i></a>'
>>> soup = BeautifulSoup(markup, 'lxml')
>>> repr(soup) # 以ASCII编码,并将Unicode字面值表示为quote形式
'<html><body><a href="http://example.com/">I linked to \\u793a\\u4f8b<i>example.com</i></a></body></html>'

.get_text()🔨

🔨get_text(self, separator="", strip=False, types=(NavigableString, CData))

若是只须要获取文档或 tag 的文本部分,则可使用 get_text() 方法,源代码以下:

def get_text(self, separator="", strip=False,
             types=(NavigableString, CData)):
    """
        Get all child strings, concatenated using the given separator.
        """
    return separator.join([s for s in self._all_strings(
        strip, types=types)])

该方法会将文档或 tag 中的全部文本合并为一个 Unicode 字符串,并返回该字符串:

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

print(soup.get_text())
print(soup.i.get_text())

输出:

I linked to example.com

example.com

separator 参数用于设置分隔符:

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

print(repr(soup.get_text('|')))
#> '\nI linked to |example.com|\n'

strip 参数用于设置是否剥离每段文本开头和结尾处的空白符(whitespace):

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

print(repr(soup.get_text('|', strip=True)))
#> 'I linked to|example.com'

若是须要本身处理文本,则可使用 .stripped_strings 生成器,它会为咱们逐一提取每段文本:

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

print([text for text in soup.stripped_strings])
#> ['I linked to', 'example.com']

.text

text 字段的源代码以下:

text = property(get_text)

欢迎关注公众号: import hello

公众号

相关文章
相关标签/搜索