2011年4月12日火曜日

解析木を検索する

Beautiful Soup は、指定した基準にマッチするタグと可航文字を集めながら、解析木を航行する多くのメソッドを提供します。

Beautiful Soup オブジェクトにマッチする基準を定義する方法は、いくつかあります。
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>

ちなみに、本セクションで取り上げる2つのメソッド (findAllfind)は、タグオブジェクトと、トップレベルの解析オブジェクトのみで使用でき、可航文字オブジェクトでは使用できません。「解析木の内部を検索する」で定義されているメソッドは、可航文字オブジェクトでも利用できます。


基本検索メソッド: findAll(name, attrs, recursive, text, limit, **kwargs)



findAll メソッドは解析木を航行し、与えられた地点から開始し、あなたが渡した基準にマッチする、あらゆるタグと可航文字オブジェクトを検索します。

findall メソッドの書き方はこうです:

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


これらの引数は、Beautiful Soup API中で、繰り返し現れます。
最も重要な引数は、 name と、キーワード引数です。

  • name 引数は name で、タグの集合を制限します。
    name を制限する方法がいくつかあり、これらは Beautiful Soup API 全体で、何度も繰り返し現れます。


    1. 最も簡単な使用方法は、タグ名を渡すことです。このコードは、ドキュメント中の全てのタグを検索します:
      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. リストやディクショナリで渡すことも出来ます。これら2つの呼び出しは、全ての<TITLE>と、<P>タグを見つけます。
      それらは同じように動作しますが、2番目の呼び出しの方がより高速です。
      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 を渡すことが出来ます:
      これは、いわば、あらゆるタグにマッチします。
      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']
      
      これは、役に立つように見えませんが、 True は属性値を制限する際に、非常に役に立ちます。

    5. Tag オブジェクトを唯一の引き数として取る、呼び出し可能オブジェクトを渡すことが出来ます、これはブール値を返します。
      findAll が出くわす、各タグオブジェクトは、このオブジェクト中に渡され、もし、呼び出しがTrue を返せば、タグはマッチしたとみなされます。

      このコードは属性値をちょうど2つしか持っていないタグを見つけることが出来ます:
      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>]
      

      このコードは1文字の名前を持ち、属性を持たないタグを検索します:
      soup.findAll(lambda tag: len(tag.name) == 1 and not tag.attrs)
      # [<b>one</b>, <b>two</b>] 

  • キーワード引数は、タグの属性を制限します。
    このシンプルな例は "align" 属性に "center" 値を持つ全てのタグを検索します:
    soup.findAll(align="center")
    # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
    

    name引数と同様に、属性ごとに異なる制限を行うため、キーワード引数に、異なる種類のオブジェクトを渡すことが出来ます。
    上で見たように、属性を単一の値に制限するために、文字を渡すことが出来ます。
    また、正規表現や、リスト、ハッシュ、特別な値として、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']
    

    もし、タグ属性に複雑な制限や、連結した制限を課す必要がある場合、上にあるよう、nameに呼び出し可能なオブジェクトを渡し、タグオブジェクトを処理します。

    ここで、あなたは問題に気がつくかもしれません。
    もし、name と言う名前の属性を定義しているタグ付きのドキュメントでは、どうすればよいでしょうか?
    Beautiful Soup のsearchメソッドは、すでに name 引数を定義しているため、 name という名前のキーワード引数を使うことができません。
    また、キーワード引数に for のようなPythonの予約語を使うこともできません。

    Beautiful Soup は、これらのシチュエーションで使用できる attrs と呼ばれる特別な引数を提供します。
    attrs は、ちょうど、キーワード引数のように動作するディクショナリです。
    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>]
    

    名前がclassforimportのような、Pythonの予約語の属性や、namerecursivelimittextattrsなど、Beautiful Soup 検索メソッドのキーワード引数以外の属性に対して、制限を課す必要がある場合、attrs を使うことが出来ます。
    from BeautifulSoup import BeautifulStoneSoup
    xml = '<person name="Bob"><parent rel="mother" name="Alice">'
    xmlSoup = BeautifulStoneSoup(xml)
    
    xmlSoup.findAll(name="Alice")
    # []
    
    xmlSoup.findAll(attrs={"name" : "Alice"})
    # [parent rel="mother" name="Alice"></parent>]
    

    CSSによる検索

    attrs 引数はあるものがなければ、かなり曖昧な機能だったでしょう:それはCSSです。
    特定のCSSクラスを持つタグを探すのに、とても役に立ちます、しかし、CSS属性の class もPythonの予約語なのです。

    soup.find("tagName", { "class" : "cssClass" })
    として検索出来るでしょうが、このような一般的な操作には、たくさんのコードが必要です。
    実際、ディクショナリの代わりに、文字を attrs に渡すことが出来ます。
    渡す文字は、CSSクラスを制限するのに使われます。
    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 は、タグの代わりに、可航文字オブジェクトを検索出来るようにする引数です。
    その値は、文字列、正規表現、リスト、ディクショナリ、True
    None 、引数に可航文字オブジェクトを持つ呼び出し、を取れます:
    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 に渡すあらゆる値と、キーワード引数は無視されます。
  • recursive は、Beautiful Soupに、解析木の全ての経路を下るかどうか、あるいは、タグの直下の子や解析木を見るだけかどうかを伝える(デフォルトではTrueの)ブール値引数です。
    ここに違いを示します:
    [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 引数を設定すると、argument lets you
    stop the search once Beautiful Soup finds a certain number of matches.

    もし、ドキュメント中に千テーブルあるけれども、最初の4つしか必要無いなら、 limit に4を渡せば時間を省略出来ます。デフォルトでは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 を呼び出すのに似ている

小さなショートカットがあります。解析オブジェクトや、タグを関数のように呼び出す場合、 あらゆる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)

O.K. では、他の検索メソッドを見ていきましょう。それらは findAll とほぼ同様の引数を取ります。 find メソッドは、あらゆるマッチングオブジェクトを検索するというを除き、findAll と同じです、こちらは、最初のオブジェクトだけを検索します。 結果の集合に、 limit 1を強制したり、結果の配列から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 引数を取り、結果リストを返します。 ( findfindNextSibling のような)複数形ではない名前の検索メソッドを見ると、そのメソッドは limit 引数を取らず、単一の結果を返します。

firstに何が起こるか?

以前のバージョンの Beautiful Soup は、 firstfetchfetchPrevious などのメソッドを持っていました。 これらのメソッドはまだありますが、それらは推奨されておらず、まもなく削除されるかもしれません。 これらの全ての名前の効果はとてもややこしいです。 新たな名前は一貫性を持っています: 上記で述べたように、名前が複数形や、Allを含んでいたら、複数のオブジェクトを返し、そうでなければ、1つのオブジェクトを返します。

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...