Beautiful Soup 是一个用来从HTML与XML文档中抓取数据的Python库,它提供了从HTML与XML文档中检索、抓取与修改数据的最傻瓜式的方法,能为我们的开发节省很多时间,本文翻译自 Beautiful Soup Documentation,但并非完全地逐字翻译,如果你想查阅原版英文文档,请点击前面的链接,本文绝大多数示例都是在Python的命令行工具中进行的。

功能演示与快速入门

下面这个摘录自《爱丽丝仙境梦游记》的故事段落是我们最开始要进行处理的HTML文档:

html_doc = """
<html><head><title>The Dormouse's story</title></head>

<p><b>The Dormouse's story</b></p>

<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p>...</p>
"""

上面的代码并没有进行合理的缩进,那是因为我最开始想像大家展示一下 Beautiful Soup提供给我们的第一个有用的功能——美化代码结构:

>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(html_doc)
>>> print(soup.prettify())
<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
 <body>
  <p>
   <b>
    The Dormouse's story
   </b>
  </p>
  <p>
   Once upon a time there were three little sisters; and their names were
   <a href="http://example.com/elsie" id="link1">
    Elsie
   </a>
   ,
   <a href="http://example.com/lacie" id="link2">
    Lacie
   </a>
   and
   <a href="http://example.com/tillie" id="link3">
    Tillie
   </a>
   ;
and they lived at the bottom of a well.
  </p>
  <p>
   ...
  </p>
 </body>
</html>

下面的这些示例则展示了如何通过Beautiful Soup获取HTML文档中的数据:

>>> soup.title
<title>The Dormouse's story</title>
>>> soup.title.name
'title'
>>> soup.title.string
u"The Dormouse's story"
>>> soup.title.parent.name
'head'
>>> soup.p
<p><b>The Dormouse's story</b></p>
>>> soup.p['class']
['title']
>>> soup.a
<a href="http://example.com/elsie" id="link1">Elsie</a>
>>> soup.find_all('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup.find(id='link3')
<a href="http://example.com/tillie" id="link3">Tillie</a>

还有一个我们用得最多的功能那就是从一个网页中获取所有网址,来看看Beautiful Soup做这个事情有多简单:

>>> 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 storyThe 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.
...
>>> print(soup.p.get_text())
The Dormouse's story

安装 Beautiful Soup

看了上面的这些功能演示是不是已经心动了,心动不如行动,先安装Beautiful Soup吧。

如果你使用的是Debian或者Ubuntu Linux,你可以使用系统的包管理工具直接安装:

$ apt-get install python-bs4

Beautiful Soup 4以在PyPi中发布了,所以,如果你不能使用系统的包管理工具安装它,那也可以使用Python的安装工具 easy_install 或者 pip 安装,在PyPi中的包名称为 beautifulsoup4,该包能同时工作于 Python 2.x 与 Python 3.x。

$ easy_install beautifulsoup4

或者

$ pip install beautifulsoup4
如果你使用的某一个软件是基于 Beautiful Soup 3 的,那么你可能需要了解一下怎么安装它,查阅:http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html以获得更多的帮助信息,但是如果你是准备基于Beautiful Soup 创建新的应用,那么我强烈建议你使用 Beautiful Soup 4。

如果你的机器上面连 easy_install 或者 pip 工具都没有,那你还可以直接下载安装包使用包里的setup.py 安装,下载地址为:http://www.crummy.com/software/BeautifulSoup/download/4.x/,下载完成之后,将其解压至某个目录下,运行:

$ python setup.py install

如果还是安装失败,那要么你可以选择检查是哪里出了问题,再尝试安装,或者,最简单的办法,Beautiful Soup 4所使用的许可证允许你将它的所有代码直接包装进你自己的应用中,这样就不再需要安装它了。

Beautiful Soup 4是在 Python2.7与Python3.2上开发的,但是它应该是可以运行在其它版本中的。

安装之后的一些问题

Beautiful Soup包本身是 Python 2 的代码,当你安装至Python 3之后,它会自动转换成为Python 3代码,但是如果你不安装该包,代码则不会被转换,在Windows机器上会报出你安装了错误的版本。

  • 如果你得到 ImportError “No module named HTMLParser” ,则表示你在 Python 3中运行Python 2版本的代码。
  • 如果你得到 ImportError “No module named html.parser” ,则表示你在 Python 2中运行Python 3版本的代码。
  • 如果上面两个错误你都有,那最好的办法是删除所有现有安装,然后重新安装一次试试。
  • 如果你得到 SyntaxError “Invalid syntax” on the line ROOT_TAG_NAME = u'[document]' ,你需要转换Python 2的代码至Python 3代码,你可以通过安装脚本来转换: $ python3 setup.py install

或者使用Python 的 2to3 转换脚本进行:

$ 2to3-3.2 -w bs4

安装解析器

Beautiful Soup 支持Python标准库中的HTML的解析器,但是你同样还可以使用很多其它的第三方解析器,比如 lxml parser 。根据你的系统不同,你可以使用下面中的某一种方式安装 lxml:

$ apt-get install python-lxml
$ easy_install lxml
$ pip install lxml

如果你使用 Python 2,另一个纯Python的HTML解析器 html5lib parser 也是很不错的选择,它可以像浏览器一样的解析HTML文档,用下面的任何一种方法安装它:

$ apt-get install python-html5lib
$ easy_install html5lib
$ pip install html5lib

下表简单地说明了每一种解析器优点与缺点:

解析器 典型用法 优点 缺点
Python标准库 html.parser BeautifulSoup(markup, “html.parser”) 内置,不错的速度,宽容(2.7.3 与 3.2 ) 不是太宽容
lxml HTML parser BeautifulSoup(markup, “lxml”) 非常的快 , 宽容 依赖于外部C库
lxml XML parser BeautifulSoup(markup, [“lxml”, “xml”]) , BeautifulSoup(markup, “xml”) 非常的快,只支持正确的XML文档 依赖于第三方C库
html5lib BeautifulSoup(markup, html5lib) 宽容,像浏览器一样解析页面,操作HTML5文档 非常慢,依赖第三方库,仅支持 Python 2

如果可以的话,我建议你安装 lxml 库,如果你使用的是 2.7.3或者3.2.2以前的版本,建议你使用 lxml 或者 html5lib,早期版本的Python的HTML Parser并不是非常的好。

注意:不同的解析器会为其一份文档创建不同的 tree 。

创建 Soup

要解析一个文档,你可以将该文档的所有字符串或者一个打开的文件对象传送给BeautifulSoup构造器:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('index.html'))

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

首先,文档会被转换为Unicode编码,HTML 元素也会被转换为Unicode字符串。

>>> BeautifulSoup("这是中文")
<html><body><p>这是中文</p></body></html>

Beautiful Soup 接着使用一个可用的解析器分析文档,如果不是特别指定的话,它会默认使用一个HTML解析器。

对象类型

Beautiful Soup 将HTML文档转换成为一个 Python 对象树,但是你只需要处理这下面这四种类型的对象即可。

标签 (Tag)

一个 Tag 对象对应于 XML 或 HTML 文档中的某一个标签:

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

Tag对象有许多的方法与属性可供我们使用,这些会在本文下方专门的章节做介绍。

名称(Name)

任何一个Tag都有一个名称,通过 .name 属性访问:

>>> tag.name
'b'

如果你修改了它的名称,那么此次修改会体现到任何一个由Beautiful Soup 创建的HTML元素上:

>>> tag.name = 'blockquote'
>>> tag
<blockquote>Extremely bold</blockquote>

属性(Attributes)

一个Tag可以有不限数量的属性,如 <b class=“boldest”> 就有一个名为 class 的属性,它的值为boldest ,你可以像字典一样访问Tag的属性,比如:

>>> tag['class']
['boldest']

如果你想获取整个Tag的所有属性的字典,可以使用 .attrs

>>> tag.attrs
{'class': ['boldest']}

你可以添加、删除或者修改Tag的属性:

>>> tag['class'] = 'verybold'
>>> tag['id'] = 1
>>> tag
<blockquote id="1">Extremely bold</blockquote>

>>> del tag['class']
>>> del tag['id']
>>> tag
<blockquote>Extremely bold</blockquote>

多值的属性

HTML 4 定义了一些可以有多个值的属性,HTML 5又移除了一些,但是最后却又定义了更多的新的,最常见的可多值的属性是 class ,其它的还有比如 rel , rev , accept-charset , headers 以及accesskey 等,Beautiful Soup将多值以列表(list)的方式进行处理:

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

如果一个属性,在文档中看似是一个多值的,但是却没有在任何一个HTML标准中被定义为多值属性,则 Beautiful Soup 会按标准执行,即将其按单个值进行处理:

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

当Tag重新转为HTML代码时,多个值会被合并:

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

但是如果你使用的是 xml 解析器,那么不存在多值属性:

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

可操纵字符串(NavigableString)

Beautiful Soup 将 Tag 中的字符串内容称之为 NavigableString :

>>> tag.string
u'Extremely bold'
>>> type(tag.string)
<class 'bs4.element.NavigableString'>

NavigableString 与 Unicode 字符类型,当然它还提供有很多特殊的功能,这些我也会在下面的章节中作详细介绍,你可以使用 unicode() 将其转换为 Unicode 字符:

>>> unicode_string = unicode(tag.string)
>>> unicode_string
u'Extremely bold'
>>> type(unicode_string)
<type 'unicode'>

你不能直接编辑字符,但是你可以使用一个新字符替换它:

>>> tag.string.replace_with("No langer bold")
u'Extremely bold'
>>> tag
<blockquote>No langer bold</blockquote>

BeautifulSoup 对象

BeautifulSoup 对象则是文档本身,很多时候你可以像Tag的那样操作它,但是因为它并不是一个真正的Tag,所以它没有名称,也没有属性,但是我们经常需要获取它的名称,所以,它有一个名称,为 [document]

>>> soup.name
u'[document]'

注释以及其它一些字符

Tag , NavigableString 以及 BeautifulSoup 提供了几乎所有我们在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?'

当一个 Comment 对象以 HTML 元素出现的时候,它有自己的特殊格式:

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

Beautiful Soup 同样定义了 XML 文档中会出现的各种各样的元素,比如 CData ,ProcessingInstruction , Declaration 以及 Doctype 等,像 Comment 一样,所有这些类都是NavigableString 的子类,但是都有它们自己的特性,下面这是一个 CData 示例:

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

浏览树对象 (Navigating the tree)

“三姐妹”的HTML文档又来了:

html_doc = """
<html><head><title>The Dormouse's story</title></head>

<p><b>The Dormouse's story</b></p>

<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p>...</p>
"""

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)

我还是将使用这个文档来演示如何在树对象中游走。

往内在子元素中检索(Going Down)

Tag 可以包含字符串或者其它 Tag,这些被包裹的元素称为 Tag 的子元素,Beautiful Soup 提供了很多属性用来在 Tag 的子元素中进行导航或者遍历子元素。

注意:Beautiful Soup 字符串没有上面说的这些任何一种属性,因为字符串无法包含子元素。

使用 Tag 名称

最简单的检索树的方法是使用标签名称,如果你想获取 <head> 标签 ,只需要: soup.head :

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

你可以像上面这一样一直往后加添加标签的名称,比如我想获取 <body> 中的 <b> ,则可以使用 soup.body.b :

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

使用标签浏览的局限在于我们总是只能得到第一个为该名称的子标签:

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

如果你想得到所有某一个名称的标签,你需要使用搜索树的方法,比如 find_all() :

>>> soup.find_all('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

.contents 与 .children

标签的所有子元素可以通过一个名为 .contents 的列表访问到:

>>> head_tag = soup.head
>>> head_tag
<head><title>The Dormouse's story</title></head>
>>> head_tag.contents
[<title>The Dormouse's story</title>]
>>> title_tag = head_tag.contents[0]
>>> title_tag
<title>The Dormouse's story</title>
>>> title_tag.contents
[u"The Dormouse's story"]

BeautifulSoup 对象只有一个子元素,即 <html> 标签:

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

字符串没有任何 .contents ,因为它无法包裹其它数据:

>>> text = title_tag.contents[0]
>>> text.contents
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/bs4/element.py", line 667, in __getattr__
    self.__class__.__name__, attr))
AttributeError: 'NavigableString' object has no attribute 'contents'

除了通过列表 .contents 来访问子元素外,你还可能通过遍历访问它们,使用 .children :

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

.descendants

descendants 是后代的意思,它不像 .contents 或者 .children 只能访问到父元素的下一级子元素,它能访问到父元素内的所有子元素,比如:

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

我们只得到了一个子元素,但是:

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

可以看到,当得到的子元素有其自己的子元素,那么也会一并的遍历到,这是因为 <head> 虽然只有一个子元素 <title> ,但是 <title> 也有一个子元素,这个子元素同样是 <head> 的后代:

>>> len(list(soup.children))
1
>>> len(list(soup.descendants))
23

.string

如果一个标签有且只有一个子元素,而该子元素是一个 NavigableString ,那么可以直接使用 .string访问到这个子元素:

>>> title_tag.string
u"The Dormouse's story"

如果一个元素有且仅有一个子元素,而该子元素同样有且仅有一个子元素并且该子元素是一个NavigableString ,那么该字符同样是最开始父元素的 .string :

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

但是如果一个元素有多个字符的话,因为我们无法通过一个属性来指定多个值,所以我们直接将该元素的 .string 设定为 None

>>> print(soup.html.string)
None

.strings 与 stripped_strings

像上面说过的,我们无法使用 .string 名称指定多个值,但是却可以使用 .strings 访问到,它是一个生成器(Generator):

>>> for string in soup.strings: print(repr(string))
...
u"The Dormouse's story"
u"The Dormouse's story"
u'n'
u'Once upon a time there were three little sisters; and their names weren'
u'Elsie'
u',n'
u'Lacie'
u' andn'
u'Tillie'
u';nand they lived at the bottom of a well.'
u'n'
u'...'

我在上面的示例中使用了 repr 函数是因为我想告诉你 .strings 会将任何字符串都囊括在内,这在很多时候是不需要的,所以还提供了另一个生成器 .stripped_strings :

>>> for string in soup.stripped_strings: print(repr(string))
...
u"The Dormouse's story"
u"The Dormouse's story"
u'Once upon a time there were three little sisters; and their names were'
u'Elsie'
u','
u'Lacie'
u'and'
u'Tillie'
u';nand they lived at the bottom of a well.'
u'...'

所有空字符串包括只有换行符等等都被去除掉了。

向外在父元素中检索(Going Up)

就像“族谱”一样,任何一个标签一定有它的父元素:

.parent

你可以访问通过 .parent 访问一个标签的父元素,在上面的示例HTML文档中, <head> <title> 的父元素:

>>> title_tag.parent
<head><title>The Dormouse's story</title></head>

顶层的元素的父元素就像Linux系统中的根目录 / 一样,就是 BeautifulSoup 对象本身:

>>> html_tag = soup.html
>>> type(html_tag.parent)
<class 'bs4.BeautifulSoup'>

而 BeautifulSoup 对象的父元素则被定义为 None :

>>> print(soup.parent)
None

.parents

就像 .descendants 可以遍历到一个元素的所有子元素一样,你还可以使用 .parents 遍历一个元素的所有父元素:

>>> link = soup.a
>>> link
<a href="http://example.com/elsie" id="link1">Elsie</a>
>>> for parent in link.parents:
...     if parent is None:
...         print(parent)
...     else:
...         print(parent.name)
...
p
body
html
[document]

同等级兄弟元素中平行检索(Going Sideways)

考虑一下下面这个HTML文档:

>>> sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
>>> print(sibling_soup.prettify())
<html>
 <body>
  <a>
   <b>
    text1
   </b>
   <c>
    text2
   </c>
  </a>
 </body>
</html>

<b> 与 <c> 是具有同一个父元素的同一级标签,我们称这两个元素为兄弟元素,它们处于文档结构中的同一级,它们之间的这种关系同样可以被用来进行内容的检索。

.next_sibling 与 .previous_sibling

你可以使用 .next_sibling 与 .previous_sibling 检索下一个与上一个元素:

>>> sibling_soup.b.next_sibling
<c>text2</c>
>>> sibling_soup.c.previous_sibling
<b>text1</b>

因为 .next_sibling 与 .previous_sibling 都只是在同一级的兄弟元素中检索,所以,<b> 有 .next_sibling 但是因为它前面没有兄弟元素,所以它的 .previous_sibling 为 None,而 <c> 有 .previous_sibling 但是后面没有兄弟元素,所以它的 .next_sibling 为 None:

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

“text1” 与 “text2” 却不是兄弟元素,因为他们没有同一个父元素:

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

在真实的文档中, .next_sibling 与 .previous_sibling 得到的一般总是一个空白字符串,回到前面三姐妹的那个示例HTML文档中:

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

你可以会认为第一个 <a> 的下一个元素是 Lacie,但是并不是这样的:

>>> link = soup.a
>>> link
<a href="http://example.com/elsie" id="link1">Elsie</a>
>>> link.next_sibling
u',n'
>>> link.next_sibling.next_sibling
<a href="http://example.com/lacie" id="link2">Lacie</a>

这是因为空白也是一个子元素,空白字符串不代表,没有。

.next_siblings 与 .previous_siblings

你可以使用 .next_siblings 与 .previous_siblings 来遍历某一个元素所有前面的兄弟元素或者所有它后面的兄弟元素:

>>> for sibling in soup.a.next_siblings: print(repr(sibling))
...
u',n'
<a href="http://example.com/lacie" id="link2">Lacie</a>
u' andn'
<a href="http://example.com/tillie" id="link3">Tillie</a>
u';nand they lived at the bottom of a well.'
>>> for sibling in soup.find(id='link3').previous_siblings: print(repr(sibling))
...
u' andn'
<a href="http://example.com/lacie" id="link2">Lacie</a>
u',n'
<a href="http://example.com/elsie" id="link1">Elsie</a>
u'Once upon a time there were three little sisters; and their names weren'

根据文档解析器的解析流程进行检索(Going back and forth)

返回来看看最开始的那个 三姐妹 的示例:

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

HTML 解析器对上面这段HTML文档的解析可以以一个一个的事件来表示:

  1. 打开一个 <html> 标签
  2. 打开一个 <head> 标签
  3. 打开一个 <title> 标签
  4. 添加一个字符串 “The Dormouse's story”
  5. 关闭 <title> 标签
  6. 关闭 <head> 标签
  7. 打开一个 <p> 标签
  8. ……

BeautifulSoup 提供工具可以让你根据这种流程对文档进行检索

.next_element 与 .previous_element

.next_element 属性指向当前元素的下一个元素,看上去它与 .next_sibling 似乎有些一样,但是其实是完全不同的两码子事儿,比如在三姐妹的示例中, 最后一个 <a> 的 .next_sibling 是一个字符串:

>>> last_a = soup.find('a', id = 'link3')
>>> last_a
<a href="http://example.com/tillie" id="link3">Tillie</a>
>>> last_a.next_sibling
u';nand they lived at the bottom of a well.'

但是 .next_element 却不一样,根据上面所说的章节的说明, 它的值应该是 Tillie 这个词:

>>> last_a.next_element
u'Tillie'

简单来说,next_sibling 或者 previous_sibling 是根据标签或者元素在文档中的最终位置来定义的,但是 .next_element 与 previous_element 则是根据元素的解析流程来定义的。

.previous_element 与 .next_element 正好相反,它表示在当前元素前面被解析的元素:

>>> last_a.previous_element
u' andn'
>>> last_a.previous_element.next_element
<a href="http://example.com/tillie" id="link3">Tillie</a>
>>> last_a
<a href="http://example.com/tillie" id="link3">Tillie</a>

.next_elements 与 .previous_elements

到现在为止你应该找到规律了吧, .next_elements 与 .previous_elements 检索当前元素的所有后面被解析的以及前面被解析的元素:

>>> for element in last_a.next_elements: print(repr(element))
...
u'Tillie'
u';nand they lived at the bottom of a well.'
u'n'
<p>...</p>
u'...'

在树中搜索(Searching the tree)

Beautiful Soup 定义了许多用来搜索解析树的方法,但是所有这些方法都差不多,我将着重花时间来解释两人个使用频率最多的方法 find() 与 find_all() ,其它的方法基本上都是使用同样的参数定义,所以我将对其它的只作简单的介绍。

同样的,我们还是使用三姐妹的示例:

html_doc = """
<html><head><title>The Dormouse's story</title></head>

<p><b>The Dormouse's story</b></p>

<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p>...</p>
"""

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc)

通过传递一个过滤器到某一个方法(比如 find_all() ),你可以获取到文档中的任何元素。

过滤器类型(Kinds of filters)

在对 find_all() 或者其它相类似的方法进行详细的说明前,我将先向你展示可以传递给这些方法的不同的过滤器类型,这些过滤器在整个搜索API中一次又一次的出现,你可以基于一个标签的名称,或者它的属性,或者一个文本字符串等等创建过滤器。

一个字符串

我简单的过滤器就是一个字符串,通过传递字符串到搜索方法中,Beautiful Soup将返回所有包含该字符的标签的元素:

>>> soup.find_all('b')
[<b>The Dormouse's story</b>]
>>> soup.find_all('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

如果你传递的是一个 byte string, Beautiful Soup 会先将其编码为 UTF-8 字符串,所以你可以直接传递Unicode 字符串。

一个正则表达式

如果你传递一个正则表达式对象, Beautiful Soup 将使用它的 match() 方法对文档进行过滤,下面这段代码将搜索文档中所有以 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 会返回“符合其中任何一个过滤器要求的”元素,下面这段代码将搜索到所有的 <b> 与 <a> 标签:

>>> soup.find_all(['a','b'])
[<b>The Dormouse's story</b>, <a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

True

True 将匹配文档中的所有标签,但是不包括字符串内容:

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

一个函数

如果上面的所有过滤器中没有一个能满足你的要求,那么你还可以提供自己的过滤函数,该函数需要接收一个标签作为参数,而且只允许接收这一个参数,它应该返回 True 或者 FalseTrue 表示匹配, False 表示不匹配。

下面这个示例函数将匹配所有具有 class 属性但是不没有 id 属性的元素:

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

我们使用这个函数对当前文档进行一些搜索过滤:

>>> soup.find_all(has_class_but_no_id)
[<p><b>The Dormouse's story</b></p>, <p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>, <p>...</p>]
>>> for tag in soup.find_all(has_class_but_no_id): print(tag.name)
...
p
p
p

下面我们再定义一个示例函数,它将过滤出所有前后被字符串包裹的元素:

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

函数签名: find_all(name, attrs, recursive, text, limit, kwargs)

find_all() 方法是根据标签的 “ 父-子 ” 关系对文档进行检索,并返回当前匹配过滤器的所有当前元素的子元素,在前面的一些示例中我已经使用过这个函数了,这里再列出一些更多该函数的示例:

>>> soup.find_all('title')
[<title>The Dormouse's story</title>]
>>> soup.find_all('p', 'title')
[<p><b>The Dormouse's story</b></p>]
>>> soup.find_all('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup.find_all(id='link2')
[<a href="http://example.com/lacie" id="link2">Lacie</a>]
>>> import re
>>> soup.find(text=re.compile('sisters'))
u'Once upon a time there were three little sisters; and their names weren'

上面这些示例中,有一些与我之前使用的是一样的,但是有一些却是刚刚才使用的新的,比如 text与 id 这两人个参数分别都表示什么?为什么 find_all('p','title') 返回一个 class 为 title 的 <p> 元素?下面让我们来详细的看看传递给 find_all() 方法的参数。

name 参数

传递一个 name 参数,代表你告诉 Beautiful Soup 你只需要名称为参数所指定的标签,文本字符串以及未匹配名称的标签都将直接忽略。

简单的用法示例:

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

关键字参数

如果一个函数未定义的关键字参数被传递给 find_all() 方法,那么该关键字的键将作为标签的属性进行搜索,而匹配规则是标签的该属性为该关键字参数的值,所以,当我们传递 id=“link2” 时,会匹配到 id 属性值为 link2 的元素:

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

你可以使用一个字符串、正则表达式、列表、一个函数或者一个 True 值来过滤,比如下面的代码找到所有设定了 id 属性的元素:

>>> soup.find_all(id=True)
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

同样的,你还可以一次性通过多个属性对元素进行过滤:

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

以 CSS 类为参数搜索

因为 class 是Python的一个关键字,所以我们无法以上面关键字参数的方式通过 class 属性过滤,但是我们有一个替代方案,那就是使用 class_ :

>>> soup.find_all('a', class_='sister')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

这个时候 class_ 就与前面的关键字参数无异,你同样可以使用一个字符、正则表达式等等的作为该参数的值:

>>> soup.find_all(class_=re.compile('itl'))
[<p><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 href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
注意: class 是一个可多值的属性,当匹配任何其中一个值是,都会算成功匹配,如下示例代码:
>>> css_soup = BeautifulSoup('<p></p>')
>>> css_soup.find_all('p', class_='strikeout')
[<p></p>]
>>> css_soup.find_all('p', class_='body')
[<p></p>]

你也同样可以将完整的 class 值传递给 find_all() :

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

但是对于上例中,如果将两个类返过来写就不能匹配了:

>>> css_soup.find_all('p', class_='strikeout body')
[]

这是很不方便的一件事情,如果我们要通过 class 来搜索元素,这样的方法还需要 class 名称的顺序是统一的,但是我们可以写一个函数:

>>> css_soup = BeautifulSoup('<p></p><p>Another One</p>')
>>> css_soup.find_all('p', class_='body strikeout')
[<p></p>]
>>> css_soup.find_all('p', class_='strikeout body')
[<p>Another One</p>]

对于 class ,Beautiful Soup 还提供了一个快捷方式,那就是 attrs ,传递一个字符串给 attrs 将以 class 对元素进行过滤:

>>> soup.find_all('a', 'sister')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

你同样也可以传递一个正则表达式、函数或者 True,等参数,但是不允许使用字典类型的数据,这些都和使用 class_ 关键字的效果是一样的。

>>> soup.find_all('p', re.compile('itl'))
[<p><b>The Dormouse's story</b></p>]

如果传递的是一个字典的话,它不再仅仅只是当作 class 搜索了,而是以字典中的键为属性名称,值为属性值进行搜索:

>>> soup.find_all(href=re.compile('elsie'), id = 'link1')
[<a href="http://example.com/elsie" id="link1">Elsie</a>]
>>> soup.find_all(attrs={'href': re.compile('elsie'), 'id': 'link1'})
[<a href="http://example.com/elsie" id="link1">Elsie</a>]

使用 text 参数

除了对属性进行过滤外,还可以通过文本内容对元素进行过滤,使用的关键字就是 text ,你可以传递一个字符串、正则表达式、列表、函数或者一个 True 值,下面是一些简单的示例:

>>> soup.find_all(text='Elsie')
[u'Elsie']
>>> soup.find_all(text=['Tillie', 'Elsie', 'Lacie'])
[u'Elsie', u'Lacie', u'Tillie']
>>> soup.find_all(text=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(text=is_the_only_string_within_a_tag)
[u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']

虽然 text 是用来搜索字符串的,但是你可以通过绑定一个标签名称来搜索标签, Beautiful Soup 会搜索所有 .string 值匹配你所指定的字符串的元素:

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

使用 limit 参数

limit 参数可以限定返回的结果集元素的数量,当一个文档十分大的时候,这个参数将十分有用,它所做的工作就像SQL中的 LIMIT 一样,在我们的示例文档中,有三个链接,但是我现在想将结果限定为两个:

>>> soup.find_all('a', limit=2)
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>]

使用 recursive 参数

通常的,当你从一个标签中呼叫 find_all() 方法时,它会搜索该函数的子元素,以及子元素的子元素……但是如果你将 recursive 参数设置为 False ,它将只匹配它自己的第一级子元素,而子元素的子元素则直接跳过:

>>> soup.html.find_all('title')
[<title>The Dormouse's story</title>]
>>> soup.html.find_all('title', recursive = False)
[]

下面这是我们示例的HTML代码片段:

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

<title> 虽然是 <html> 标签的子元素,但是并不是它的直属子元素,所以我们将搜索不到。

Beautiful Soup 提供了很多基于树的搜索方法,这些方法基本上都使用与 find_all() 同样的参数,唯一不同的是 recursive 参数只能被使用在 find_all() 与 find() 这两人个方法中,其它的方法都不支持该参数。

像呼叫 find_all() 一样呼叫一个标签

因为 find_all() 是我们使用得多的 Beautiful Soup 搜索API,所以这里还提供了一个呼叫该方法的快捷方式,如果你将一个 Beautiful Soup 对象或者一个 Tag 对象当作一个函数来对待,那么你得到的结果将和使用该对象呼叫 find_all() 方法得到的结果一样:

>>> soup.find_all('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup('a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

下面这两人个也是一样的:

>>> soup.title.find_all(text=True)
[u"The Dormouse's story"]
>>> soup.title(text=True)
[u"The Dormouse's story"]

find()

函数签名: find(name, attrs, recursive, text, kwargs)

find_all() 搜索呼叫它的对象的所有内容,但是有时候你仅仅只想搜索一个结果,如果你知道整个HTML文档只有一个 <body> 元素,那么搜索整个文档完全是一种浪费,除了每一次你都指定一个limit=1 这个参数外,你还可以使用一个更简单的方法: find() 。

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

这两者之间唯一的区别是 find_a;;() 返回一个结果集(不管结果集中有多少元素),而 find() 只返回匹配到的一个结果,如果未找到任何结果,那么 find_all() 会返回一个空的列表,但是 find() 则返回None。

>>> print(soup.find_all('nothing'))
[]
>>> print(soup.find('nothing'))
None

如果你还记得 soup.head.title 这种方式的检索的话,那么你应该发现,这两者之间得到的结果是一样的,这是因为它本身就是调用的 find() 接口:

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

find_parents() 与 find_parent()

函数签名: find_parents(name, attrs, text, limit, kwargs)

函数签名: find_parent(name, attrs, text, kwargs)

在本文的前面部分已经有很大的篇幅来介绍 find_all() 与 find() 两人个函数了,下面还有十个函数是 Beautiful Soup 提供的搜索API,五个类似于 find_all() ,另外五个类似于 find() 。这些函数之间的唯一区别是他们搜索的范围不同。

首先让我们来看看 find_parents() 与 find_parent() :

>>> a_string.find_parents('a')
[<a href="http://example.com/lacie" id="link2">Lacie</a>]
>>> a_string.find_parents('p')
[<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;

and they lived at the bottom of a well.

] >>> len(a_string.find_parents('p')) 1 >>> print(a_string.find_parents('p', 'title')) []

 

因为 <p> 与 三个 <a> 中的某一个是 a_string 的父元素,所以我们都能搜索得到结果,但是因为 class 为 title 的 <p> 元素并不是a_string的父元素,所以我们这里得不到结果,而 find_parent() 则类似 find() 仅仅只返回一个结果。

>>> a_string.find_parent()
<a href="http://example.com/lacie" id="link2">Lacie</a>
>>> a_string.find_parent('p')
<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

你可能已经与我们前面说过的 .parent 与 .parents 属性联系上的,这两人个属性与我们刚才所了解的find_parent() 与 find_parents() 的关联是很紧密的,这两人个函数本质上就是使用这两个属性进行遍历。

find_next_siblings() 与 find_next_sibling()

函数签名: find_next_siblings(name, attrs, text, limit, kwargs)

函数签名: find_next_sibling(name, attrs, text, kwargs)

这两个函数对 .next_siblings 生成器进行遍历以搜索:

>>> first_link = soup.a
>>> first_link
<a href="http://example.com/elsie" id="link1">Elsie</a>
>>> first_link.find_next_siblings('a')
[<a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> first_link.find_next_sibling('a')
<a href="http://example.com/lacie" id="link2">Lacie</a>

find_previous_siblings() 与 find_previous_sibling()

函数签名: find_previous_siblings(name, attrs, text, limit, kwargs)

函数签名: find_previous_sibling(name, attrs, text, kwargs)

这两个函数对 .previous_siblings 进行遍历以搜索:

>>> last_link = soup.find('a', id='link3')
>>> last_link
<a href="http://example.com/tillie" id="link3">Tillie</a>
>>> last_link.find_previous_siblings('a')
[<a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/elsie" id="link1">Elsie</a>]
>>> last_link.find_previous_sibling('a')
<a href="http://example.com/lacie" id="link2">Lacie</a>

find_all_next() 与 find_next()

函数签名: find_all_next(name, attrs, text, limit, kwargs)

函数签名: find_next(name, attrs, text, kwargs)

这两个函数对 .next_elements 进行遍历以搜索:

>>> first_link = soup.a
>>> first_link
<a href="http://example.com/elsie" id="link1">Elsie</a>
>>> first_link.find_all_next(text=True)
[u'Elsie', u',n', u'Lacie', u' andn', u'Tillie', u';nand they lived at the bottom of a well.', u'n', u'...']
>>> first_link.find_next('p')
<p>...</p>

find_all_previous() 与 find_previous()

函数签名: find_all_previous(name, attrs, text, limit, kwargs)

函数签名: find_previous(name, attrs, text, kwargs)

这两个函数对 .next_previous 进行遍历以搜索:

>>> last_link = soup.find('a', id='link3')
>>> last_link
<a href="http://example.com/tillie" id="link3">Tillie</a>
>>> last_link.find_all_previous('p')
[<p>Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" id="link1">Elsie</a>,
<a href="http://example.com/lacie" id="link2">Lacie</a> and
<a href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>, <p><b>The Dormouse's story</b></p>]
>>> last_link.find_previous('title')
<title>The Dormouse's story</title>

CSS 选择器

CSS 选择器是一个十分强大的工具,Beautiful Soup 也提供了 CSS selector standard 定义的很多选择器,你仅仅只需要将选择器作为一个字符串传递给 .select() 即可:

你可以搜索标签:

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

或者标签中所有的子元素标签:

>>> soup.select('body a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

或者搜索某一个标签的下一层标签:

>>> soup.select('head > title')
[<title>The Dormouse's story</title>]
>>> soup.select('p > a')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup.select('body > a')
[]

通过 CSS class 搜索标签:

>>> soup.select('.sister')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

>>> soup.select('[class~=sister]')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

或者通过ID搜索:

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

通过检测一个标签的属性是否存在搜索:

>>> soup.select('a[href]')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]

或者通过指定标签属性的值来搜索:

>>> soup.select('a[href="http://example.com/elsie"]')
[<a href="http://example.com/elsie" id="link1">Elsie</a>]
>>> soup.select('a[href^="http://example.com/"]')
[<a href="http://example.com/elsie" id="link1">Elsie</a>, <a href="http://example.com/lacie" id="link2">Lacie</a>, <a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup.select('a[href$="tillie"]')
[<a href="http://example.com/tillie" id="link3">Tillie</a>]
>>> soup.select('a[href*=".com/el"]')
[<a href="http://example.com/elsie" id="link1">Elsie</a>]

匹配语言代码:

>>> multilingual_markup = """
...  <p lang="en">Hello</p>
...  <p lang="en-us">Howdy, y'all</p>
...  <p lang="en-gb">Pip-pip, old fruit</p>
...  <p lang="fr">Bonjour mes amis</p>
... """
>>> multilingual_soup = BeautifulSoup(multilingual_markup)
>>> multilingual_soup.select('p[lang|=en]')
[<p lang="en">Hello</p>, <p lang="en-us">Howdy, y'all</p>, <p lang="en-gb">Pip-pip, old fruit</p>]

该功能只要求用户有一定的CSS选择器知识即可使用。

修改树(Modifying the tree)

Beautiful Soup 的主要任务被设计为从现有文档中搜索与检索数据,但是你同样可以对文档的对象树进行修改并将其保存到一新的HTML或者XML文档中。

修改标签名称及属性

在本文的前面部分我就已经使用该功能做过示例,你可以重新为一个标签命名,修改一个标签的属性的值或者添加新属性以及删除已有数据等等:

>>> soup = BeautifulSoup('<b>Extremely bold</b>')
>>> tag = soup.b
>>> tag.name = 'blockquote'
>>> tag
<blockquote>Extremely bold</blockquote>
>>> tag['class'] = 'verybold'
>>> tag
<blockquote>Extremely bold</blockquote>
>>> tag['id'] = 1
>>> tag
<blockquote id="1">Extremely bold</blockquote>
>>> del tag['class']
>>> tag
<blockquote id="1">Extremely bold</blockquote>
>>> del tag['id']
>>> tag
<blockquote>Extremely bold</blockquote>

修改标签的 .string

如果你设置一个标签的 .string 属性,那么它的 contents 值将被修改为你设定的 .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>
注意:如果一个标签包含很多子标签,那么你使用这种方法修改 .string 值将会使用该值替代原来的所有内容,包括它的子标签等等。

追加- append()

你可以通过 Tag.append() 来添加标签的内容,它就像在Python List上使用 .append() 一样:

>>> soup = BeautifulSoup("<a>Foo</a>")
>>> soup.a.append("Bar")
>>> soup
<html><body><a>FooBar</a></body></html>
>>> soup.a.contents
[u'Foo', u'Bar']

BeautifulSoup.new_string() 与 .new_tag()

如果你需要向文档中添加字符串,你可以使用 append() 方法,或者你可以使用工厂方法BeautifulSoup.new_string() :

>>> soup = BeautifulSoup("<b></b>")
>>> tag = soup.b
>>> tag.append("Hello")
>>> new_string = soup.new_string(" there")
>>> tag.append(new_string)
>>> tag
<b>Hello there</b>
>>> tag.contents
[u'Hello', u' there']

如果你想插入一个完整的新的标签而不仅仅是往标签中插入内容,那最好的办法是使用BeautifulSoup.new_tag() 方法:

>>> soup = BeautifulSoup("<b></b>")
>>> original_tag = soup.b
>>> new_tag = soup.new_tag("a", href="http://www.example.com")
>>> original_tag.append(new_tag)
>>> original_tag
<b><a href="http://www.example.com"></a></b>
>>> new_tag.string = 'Link Text.'
>>> original_tag
<b><a href="http://www.example.com">Link Text.</a></b>

该方法仅仅只有第一个参数——标签名称是必须的。

插入- insert()

Tag.insert() 就像 Tag.append() 一样,不同在于它不仅仅只是将新标签添加到父元素 .contents 属性的最后,而可以将新标签添加到任何一个你所指定的位置,它就像 Python list中的 insert() 函数一样:

>>> 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() 与 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() 则是在标签或字符的后面立马插入新内容:

>>> soup.b.i.insert_after(soup.new_string(" ever "))
>>> soup.b
<b><i>Don't</i> ever stop</b>
>>> soup.b.contents
[<i>Don't</i>, u' ever ', 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()

decompose() 方法将一个标签从树中移除,然后 完全的删除它以及它的内容 。

>>> 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)
<i>example.com</i>
>>> 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>

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()
<i></i>
>>> a_tag
<a href="http://example.com/">I linked to example.com</a>

与 replace_with() 一样,unwrap() 返回替换前的原始标签。

输出(Output)

美化后输出

prettify() 方法将一个 Beautiful Soup 解析树格式化为一个美化的 Unicode 字符,每一个标签将独占一行,并进行合理的缩进:

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

标签: none

评论已关闭