Skip to main content

搜索树

美丽的Soup定义了很多方法来搜索解析树,但它们都非常相似。我将花很多时间解释两种最流行的方法:find()find_all()。其他方法采用几乎完全相同的参数,所以我将简要介绍它们。

再次,我将使用“三姐妹”文档作为例子:

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

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

<p class="story">...</p>
"""

from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')

通过将过滤器传递到像 find_all() 这样的参数,您可以放大您感兴趣的文档的部分。

种类的过滤器

在详细讨论 find_all() 和类似方法之前,我想展示可以传递到这些方法的不同过滤器的示例。这些过滤器在整个搜索API中一次又一次地显示。您可以使用它们基于标记的名称,属性,字符串的文本或这些的某些组合进行过滤。

字符串

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

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

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

正则表达式

如果你传递一个正则表达式对象,Beautiful Soup将使用它的 search() 方法过滤该正则表达式。此代码查找其名称以字母“b”开头的所有标签;在这种情况下,<body>标记和<b>标记:

import re
for tag in soup.find_all(re.compile("^b")):
    print(tag.name)
# body
# b

此代码查找其名称包含字母’t’的所有标签:

for tag in soup.find_all(re.compile("t")):
    print(tag.name)
# html
# title

一个列表

如果你传递一个列表,Beautiful Soup将允许一个字符串匹配该列表中的 any 项。此代码会找到所有<a>标签 and 所有<b>标签:

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

True

True 匹配它可以的一切。此代码在文档中查找 all 标记,但不包含任何文本字符串:

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

A函数

如果没有其他匹配对您有效,定义一个函数,它接受一个元素作为其唯一的参数。如果参数匹配,函数应返回 True,否则返回 False

这里有一个函数返回 True,如果标签定义了“类”属性,但没有定义“id”属性:

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

将此函数传递到 find_all() 中,您将选择所有<p>标记:

soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
#  <p class="story">Once upon a time there were...</p>,
#  <p class="story">...</p>]

此函数仅选取<p>标记。它不拾取<a>标签,因为这些标签同时定义了“类”和“id”。它不会选择像<html>和<title>的标签,因为这些标签没有定义“类”。

如果你传递一个函数来过滤一个特定的属性,如 href,传递给函数的参数将是属性值,而不是整个标签。这里是一个函数,找到所有 a 标签的 href 属性 才不是 匹配正则表达式:

def not_lacie(href):
    return href and not re.compile("lacie").search(href)
soup.find_all(href=not_lacie)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

该函数可以是复杂的,因为你需要它。这里是一个函数,如果标签被字符串对象包围,则返回 True:

from bs4 import NavigableString
def surrounded_by_strings(tag):
    return (isinstance(tag.next_element, NavigableString)
            and isinstance(tag.previous_element, NavigableString))

for tag in soup.find_all(surrounded_by_strings):
    print tag.name
# p
# a
# a
# a
# p

现在我们准备详细地查看搜索方法。

find_all()

签名:find_all(名称attrs递归字符串限制** kwargs

find_all() 方法查看标记的后代,并检索与您的过滤器匹配的 all 后代。我在 Kinds of filters 中给了几个例子,但这里还有一些:

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

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

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

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

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

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

name 参数

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

这是最简单的用法:

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

Kinds of filters 回忆,name 的值可以是 a stringa regular expressiona lista functionthe value True

关键字参数

任何无法识别的参数将变成对某个标记属性的过滤器。如果你为一个名为 id 的参数传递一个值,Beautiful Soup会对每个标签的’id’属性进行过滤:

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

如果你传递 href 的值,Beautiful Soup会对每个标签的’href’属性进行过滤:

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

您可以根据 a stringa regular expressiona lista functionthe value True 过滤属性。

此代码查找其 id 属性具有值的所有标签,而不管该值是什么:

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

您可以通过传入多个关键字参数一次过滤多个属性:

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

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

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

您可以通过将这些属性放在字典中并将字典作为 attrs 参数传递给 find_all() 来在搜索中使用这些属性:

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

您不能使用关键字参数搜索HTML的’name’元素,因为Beautiful Soup使用 name 参数来包含标记本身的名称。相反,你可以在 attrs 参数中给’name’一个值。

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

通过CSS类进行搜索

搜索具有某个CSS类的标签非常有用,但是CSS属性的名称“类”是Python中的保留字。使用 class 作为关键字参数会给你一个语法错误。从Beautiful Soup 4.1.2,你可以搜索CSS类使用关键字参数 class_:

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

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

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

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

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

记得,单个标签可以为其“类”属性具有多个值。当您搜索与某个CSS类匹配的标记时,您将匹配其CSS类的 any:

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

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

您还可以搜索 class 属性的确切字符串值:

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

但是搜索字符串值的变体不会工作:

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

如果你想搜索匹配两个或更多CSS类的标签,你应该使用一个CSS选择器:

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

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

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

string 参数

使用 string,您可以搜索字符串而不是标签。与 name 和关键字参数一样,您可以传入 a stringa regular expressiona lista functionthe value True。这里有些例子:

soup.find_all(string="Elsie")
# [u'Elsie']

soup.find_all(string=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all(string=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]

def is_the_only_string_within_a_tag(s):
    """Return True if this string is the only child of its parent tag."""
    return (s == s.parent.string)

soup.find_all(string=is_the_only_string_within_a_tag)
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']

虽然 string 是用于查找字符串,您可以将其与找到标签的参数组合:Beautiful Soup将找到 .stringstring 的值匹配的所有标签。此代码找到 .string 为“Elsie”的 <a> 标记:

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

string 参数是美丽的4.4.0新的。在早期版本中,它被称为 text:

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

limit 参数

find_all() 返回与您的过滤器匹配的所有标签和字符串。如果文档较大,则可能需要一段时间。如果你不需要 all 的结果,你可以传递一个数字为 limit。这和SQL中的LIMIT关键字一样。它告诉Beautiful Soup,找到一定数量后停止收集结果。

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

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

recursive 参数

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

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

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

这是文档的那一部分:

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

<title>标签位于<html>标签之下,但它不是<html>标签下的 directly:<head>标签是阻碍的。 Beautiful Soup在允许查看<html>标签的所有后代时发现<title>标签,但是当 recursive=False 将其限制为<html>标签的直接子级时,它找不到任何内容。

Beautiful Soup提供了很多树搜索方法(如下所述),它们大多采用与 find_all() 相同的参数:nameattrsstringlimit 和关键字参数。但是 recursive 参数是不同的:find_all()find() 是唯一支持它的方法。将 recursive=False 传递到诸如 find_parents() 的方法将不是非常有用。

调用标签就像调用 find_all()

因为 find_all() 是Beautiful Soup搜索API中最流行的方法,您可以使用它的快捷方式。如果将 BeautifulSoup 对象或 Tag 对象看作是一个函数,则它与在该对象上调用 find_all() 相同。这两行代码是等价的:

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

这两行也是等效的:

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

find()

签名:find(名称attrs递归字符串** kwargs

find_all() 方法扫描整个文档以查找结果,但有时您只想查找一个结果。如果您知道文档只有一个<body>标记,则浪费时间扫描整个文档以寻找更多内容。不是每次调用 find_all 时都传入 limit=1,而是可以使用 find() 方法。这两行代码是 nearly 等效的:

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

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

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

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

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

记住从 Navigating using tag namessoup.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(名称attrs字符串限制** kwargs

签名:find_parent(名称attrs字符串** kwargs

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

首先让我们考虑 find_parents()find_parent()。记住,find_all()find() 在树上工作,看着标签的后代。这些方法做的相反:他们以 up 的方式工作,查看标签(或字符串)的父级。让我们试试看,从一个深埋在“三个女儿”文档中的字符串开始:

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

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

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

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

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

您可能已经在 find_parent()find_parents() 之间建立了连接,以及前面提到的 .parent.parents 属性。连接非常强大。这些搜索方法实际上使用 .parents 迭代所有的父,并检查每一个对照提供的过滤器,看看它是否匹配。

find_next_siblings()find_next_sibling()

签名:find_next_siblings(名称attrs字符串限制** kwargs

签名:find_next_sibling(名称attrs字符串** kwargs

这些方法使用 .next_siblings 来遍历树中其余元素的兄弟节点。 find_next_siblings() 方法返回所有匹配的兄弟,find_next_sibling() 只返回第一个:

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

first_link.find_next_siblings("a")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
#  <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

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

find_previous_siblings()find_previous_sibling()

签名:find_previous_siblings(名称attrs字符串限制** kwargs

签名:find_previous_sibling(名称attrs字符串** kwargs

这些方法使用 .previous_siblings 对树中的元素的兄弟元素进行迭代。 find_previous_siblings() 方法返回所有匹配的兄弟,find_previous_sibling() 只返回第一个:

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

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

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

find_all_next()find_next()

签名:find_all_next(名称attrs字符串限制** kwargs

签名:find_next(名称attrs字符串** kwargs

这些方法使用 .next_elements 来遍历文档中后面的任何标签和字符串。 find_all_next() 方法返回所有匹配,find_next() 只返回第一个匹配:

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

first_link.find_all_next(string=True)
# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
#  u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']

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

在第一个示例中,字符串“Elsie”显示,即使它包含在从我们开始的<a>标记中。在第二个示例中,文档中的最后一个<p>标记显示,即使它与我们开始的<a>标记不在树的同一部分。对于这些方法,所有重要的是一个元素匹配过滤器,并在文档中比起始元素显示。

find_all_previous()find_previous()

签名:find_all_previous(名称attrs字符串限制** kwargs

签名:find_previous(名称attrs字符串** kwargs

这些方法使用 .previous_elements 来遍历文档中前面的标签和字符串。 find_all_previous() 方法返回所有匹配,find_previous() 只返回第一个匹配:

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

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

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

find_all_previous("p") 的调用在文档中找到了第一段(具有class =“title”的段落),但它还找到第二段,即包含我们开始的<a>标签的<p>标记。这不应该太令人惊讶:我们正在查看在文档中显示的所有标签,而不是我们开始的标签。包含<a>标签的<p>标签必须显示在其包含的<a>标签之前。

CSS选择器

Beautiful Soup支持最常用的CSS选择器。只需将一个字符串传递给 Tag 对象或 BeautifulSoup 对象本身的 .select() 方法。

你可以找到标签:

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

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

查找其他代码下方的代码:

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

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

在其他标签下找到标签 directly:

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

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

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

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

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

查找标签的兄弟姐妹:

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

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

通过CSS类查找标记:

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

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

按ID查找标签:

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

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

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

soup.select(“#link1,#link2”)#[<a class=”sister” href=”http://example.com/elsie” id=”link1”> Elsie </a>,#<a class =“sister”href =“ http://example.com/lacie “id =”link2“> Lacie </a>]

测试属性的存在:

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

按属性值查找标记:

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

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

soup.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

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

匹配语言代码:

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

仅查找与选择器匹配的第一个标记:

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

这对于知道CSS选择器语法的用户来说都很方便。你可以用Beautiful Soup API做所有这些东西。如果CSS选择器是你需要的,你可以直接使用lxml:它的速度要快得多,它支持更多的CSS选择器。但这让你 combine 简单的CSS选择器与美丽的Soup API。

修改树

Beautiful Soup的主要优势在于搜索分析树,但您也可以修改树并将更改写为新的HTML或XML文档。

更改标签名称和属性

我在前面讲过,在 属性,但它承担重复。您可以重命名标记,更改其属性的值,添加新属性和删除属性:

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

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

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

修改 .string

如果您设置了标记的 .string 属性,则标记的内容将替换为您给出的字符串:

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

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

注意:如果标记包含其他标记,则它们及其所有内容都将被销毁。

append()

您可以使用 Tag.append() 添加到标记的内容。它的工作方式就像在Python列表上调用 .append():

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

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

insert()

Tag.insert() 就像 Tag.append(),除了新元素不一定在其父的 .contents 的结尾。它将被插入在你说的任何数字位置。它的工作原理就像 .insert() 在Python列表上:

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

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

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

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

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

soup.i.decompose()

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

replace_with()

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

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

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

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

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

wrap()

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

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

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

这种方法是Beautiful Soup 4.0.5的新功能。

unwrap()

Tag.unwrap()wrap() 相反。它用标签内的任何内容替换标签。它有利于剥离标记:

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

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

replace_with() 一样,unwrap() 返回被替换的标签。