搜索剖析树

优质
小牛编辑
125浏览
2023-12-01

Beautiful Soup提供了许多方法用于浏览一个剖析树,收集你指定的TagNavigableString

有几种方法去定义用于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>

还有, 这里的两个方法(findAllfind)仅对Tag对象以及 顶层剖析对象有效,但 NavigableString不可用。 这两个方法在Searching 剖析树内部同样可用。

The basic find method: findAll(name, attrs, recursive, text, limit, **kwargs)

方法findAll 从给定的点开始遍历整个树,并找到满足给定条件所有Tag以及NavigableStringfindall函数原型定义如下:

findAll(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

这些参数会反复的在这个文档中出现。其中最重要的是name参数 和keywords参数(译注:就是**kwargs参数)。

  • 参数name 匹配tags的名字,获得相应的结果集。 有几种方法去匹配name,在这个文档中会一再的用到。

    1. 最简单用法是仅仅给定一个tag name值。下面的代码寻找文档中所有的 <B> Tag:

      soup.findAll('b')
      # [<b>one</b>, <b>two</b>]
      
    2. 你可以传一个正则表达式。下面的代码寻找所有以b开头的标签:

      import re
      tagsStartingWithB = soup.findAll(re.compile('^b'))
      [tag.name for tag in tagsStartingWithB]
      # [u'body', u'b', u'b']
      
    3. 你可以传一个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>]
      
    4. 你可以传一个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就很有用了。

    5. 你可以传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), 特殊值TrueNone,或者一个可调用的以属性值为参数的对象(注意:这个值可能为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>]
    

    特殊值TrueNone更让人感兴趣。 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,TrueNone, 一个以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

通常,当你看到一个搜索方法的名字由复数构成 (如findAllfindNextSiblings)时, 这个方法就会存在limit参数,并返回一个list的结果。但你 看到的方法不是复数形式(如findfindNextSibling)时, 你就可以知道这函数没有limit参数且返回值是单一的结果。

first哪里去了?

早期的Beautiful Soup 版本有一些firstfetch以及fetchPrevious方法。 这些方法还在,但是已经被弃用了,也许不久就不在存在了。 因为这些名字有些令人迷惑。新的名字更加有意义: 前面提到了,复数名称的方法名,比如含有All的方法名,它将返回一个 多对象。否则,它只会返回单个对象。