搜索剖析树
Beautiful Soup提供了许多方法用于浏览一个剖析树,收集你指定的Tag
和NavigableString
。
有几种方法去定义用于Beautiful Soup的匹配项。我们先用深入解释最基本的一种搜索方法findAll
。和前面一样,我们使用下面这个文档说明:
from BeautifulSoup import BeautifulSoup
doc = ['<html><head><title>Page title</title></head>',
'<body><p id="firstpara" align="center">This is paragraph <b>one</b>.',
'<p id="secondpara" align="blah">This is paragraph <b>two</b>.',
'</html>']
soup = BeautifulSoup(''.join(doc))
print soup.prettify()
# <html>
# <head>
# <title>
# Page title
# </title>
# </head>
# <body>
# <p id="firstpara" align="center">
# This is paragraph
# <b>
# one
# </b>
# .
# </p>
# <p id="secondpara" align="blah">
# This is paragraph
# <b>
# two
# </b>
# .
# </p>
# </body>
# </html>
还有, 这里的两个方法(findAll
和 find
)仅对Tag
对象以及 顶层剖析对象有效,但 NavigableString
不可用。 这两个方法在Searching 剖析树内部同样可用。
The basic find method: findAll(
name, attrs, recursive, text, limit, **kwargs)
方法findAll
从给定的点开始遍历整个树,并找到满足给定条件所有Tag
以及NavigableString
。 findall
函数原型定义如下:
findAll(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
这些参数会反复的在这个文档中出现。其中最重要的是name
参数 和keywords参数(译注:就是**kwargs参数)。
参数
name
匹配tags的名字,获得相应的结果集。 有几种方法去匹配name,在这个文档中会一再的用到。最简单用法是仅仅给定一个tag name值。下面的代码寻找文档中所有的 <B>
Tag
:soup.findAll('b') # [<b>one</b>, <b>two</b>]
你可以传一个正则表达式。下面的代码寻找所有以b开头的标签:
import re tagsStartingWithB = soup.findAll(re.compile('^b')) [tag.name for tag in tagsStartingWithB] # [u'body', u'b', u'b']
你可以传一个list或dictionary。下面两个调用是查找所有的<TITLE>和<P>标签。 他们获得结果一样,但是后一种方法更快一些:
soup.findAll(['title', 'p']) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll({'title' : True, 'p' : True}) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
你可以传一个
True
值,这样可以匹配每个tag的name:也就是匹配每个tag。allTags = soup.findAll(True) [tag.name for tag in allTags] [u'html', u'head', u'title', u'body', u'p', u'b', u'p', u'b']
这看起来不是很有用,但是当你限定属性(attribute)值时候,使用
True
就很有用了。你可以传callable对象,就是一个使用
Tag
对象作为它唯一的参数,并返回布尔值的对象。findAll
使用的每个作为参数的Tag
对象都会传递给这个callable对象, 并且如果调用返回True
,则这个tag便是匹配的。下面是查找两个并仅两个属性的标签(tags):
soup.findAll(lambda tag: len(tag.attrs) == 2) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
下面是寻找单个字符为标签名并且没有属性的标签:
soup.findAll(lambda tag: len(tag.name) == 1 and not tag.attrs) # [<b>one</b>, <b>two</b>]
keyword参数用于筛选tag的属性。下面这个例子是查找拥有属性align且值为center的 所有标签:
soup.findAll(align="center") # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
如同
name
参数,你也可以使用不同的keyword参数对象,从而更加灵活的指定属性值的匹配条件。 你可以向上面那样传递一个字符串,来匹配属性的值。你也可以传递一个正则表达式,一个列表(list),一个哈希表(hash), 特殊值True
或None
,或者一个可调用的以属性值为参数的对象(注意:这个值可能为None
)。 一些例子:soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=["center", "blah"]) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=lambda(value): value and len(value) < 5) # [<p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
特殊值
True
和None
更让人感兴趣。True
匹配给定属性为任意值的标签,None
匹配那些给定的属性值为空的标签。 一些例子如下:soup.findAll(align=True) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] [tag.name for tag in soup.findAll(align=None)] # [u'html', u'head', u'title', u'body', u'b', u'b']
如果你需要在标签的属性上添加更加复杂或相互关联的(interlocking)匹配值, 如同上面一样,以callable对象的传递参数来处理
Tag
对象。在这里你也许注意到一个问题。 如果你有一个文档,它有一个标签定义了一个name属性,会怎么样? 你不能使用
name
为keyword参数,因为Beautiful Soup 已经定义了一个name
参数使用。 你也不能用一个Python的保留字例如for
作为关键字参数。Beautiful Soup提供了一个特殊的参数
attrs
,你可以使用它来应付这些情况。attrs
是一个字典,用起来就和keyword参数一样:soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(attrs={'id' : re.compile("para$")}) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
你可以使用
attrs
去匹配那些名字为Python保留字的属性, 例如class
,for
, 以及import
; 或者那些不是keyword参数但是名字为Beautiful Soup搜索方法使用的参数名的属性, 例如name
,recursive
,limit
,text
, 以及attrs
本身。from BeautifulSoup import BeautifulStoneSoup xml = '<person><parent rel="mother">' xmlSoup = BeautifulStoneSoup(xml) xmlSoup.findAll(name="Alice") # [] xmlSoup.findAll(attrs={"name" : "Alice"}) # [parent rel="mother"></parent>]
使用CSS类查找
对于CSS类attrs
参数更加方便。例如class不仅是一个CSS属性, 也是Python的保留字。你可以使用
soup.find("tagName", { "class" : "cssClass" })
搜索CSS class,但是由于有很多这样的操作, 你也可以只传递一个字符串给attrs
。 这个字符串默认处理为CSS的class的参数值。from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("""Bob's <b>Bold</b> Barbeque Sauce now available in <b class="hickory">Hickory</b> and <b class="lime">Lime</a>""") soup.find("b", { "class" : "lime" }) # <b class="lime">Lime</b> soup.find("b", "hickory") # <b class="hickory">Hickory</b>
text
是一个用于搜索NavigableString
对象的参数。 它的值可以是字符串,一个正则表达式, 一个list或dictionary,True
或None
, 一个以NavigableString
为参数的可调用对象:soup.findAll(text="one") # [u'one'] soup.findAll(text=u'one') # [u'one'] soup.findAll(text=["one", "two"]) # [u'one', u'two'] soup.findAll(text=re.compile("paragraph")) # [u'This is paragraph ', u'This is paragraph '] soup.findAll(text=True) # [u'Page title', u'This is paragraph ', u'one', u'.', u'This is paragraph ', # u'two', u'.'] soup.findAll(text=lambda(x): len(x) < 12) # [u'Page title', u'one', u'.', u'two', u'.']
如果你使用
text
,任何指定给name
以及keyword参数的值都会被忽略。recursive
是一个布尔参数(默认为True
),用于指定Beautiful Soup遍历整个剖析树, 还是只查找当前的子标签或者剖析对象。下面是这两种方法的区别:[tag.name for tag in soup.html.findAll()] # [u'head', u'title', u'body', u'p', u'b', u'p', u'b'] [tag.name for tag in soup.html.findAll(recursive=False)] # [u'head', u'body']
当
recursive
为false,只有当前的子标签<HTML>会被搜索。如果你需要搜索树, 使用这种方法可以节省一些时间。设置
limit
参数可以让Beautiful Soup 在找到特定个数的匹配时停止搜索。 文档中如果有上千个表格,但你只需要前四个,传值4到limit
可以让你节省很多时间。 默认是没有限制(limit没有指定值).soup.findAll('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>] soup.findAll('p', limit=100) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
像findall
一样调用tag
一个小捷径。如果你像函数一样调用剖析对象或者Tag
对象, 这样你调用所用参数都会传递给findall
的参数,就和调用findall
一样。 就上面那个文档为例:
soup(text=lambda(x): len(x) < 12)
# [u'Page title', u'one', u'.', u'two', u'.']
soup.body('p', limit=1)
# [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
find(name, attrs, recursive, text, **kwargs)
好了,我们现在看看其他的搜索方法。他们都是有和 findAll
几乎一样的参数。
find
方法是最接近findAll
的函数, 只是它并不会获得所有的匹配对象,它仅仅返回找到第一个可匹配对象。 也就是说,它相当于limit
参数为1的结果集。 以上面的 文档为例:
soup.findAll('p', limit=1)
# [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
soup.find('p', limit=1)
# <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>
soup.find('nosuchtag', limit=1) == None
# True
通常,当你看到一个搜索方法的名字由复数构成 (如findAll
和findNextSiblings
)时, 这个方法就会存在limit
参数,并返回一个list的结果。但你 看到的方法不是复数形式(如find
和findNextSibling
)时, 你就可以知道这函数没有limit参数且返回值是单一的结果。
first
哪里去了?
早期的Beautiful Soup 版本有一些first
,fetch
以及fetchPrevious
方法。 这些方法还在,但是已经被弃用了,也许不久就不在存在了。 因为这些名字有些令人迷惑。新的名字更加有意义: 前面提到了,复数名称的方法名,比如含有All
的方法名,它将返回一个 多对象。否则,它只会返回单个对象。