BeautifulSoup4 利用ノート

BeautifulSoup4 についてノートをとっておく。

なお、本稿に示すコード片においては例外の存在性をすべて無視する。

目的

既存 HTML 文書のスクレイピングを自在に実現すること。短時間でテキストデータを作成する道具として用いる。

インストールする

省略。公式文書に書いてあること以上のことはない。ただし lxml のインストールも併せるのが望ましい。

オブジェクトを生成する

次のようにして BeautifulSoup オブジェクトを生成する(公式文書からほぼそのまま引用した):

from bs4 import BeautifulSoup

html_doc = # ...

soup = BeautifulSoup(html_doc, 'html.parser')
  • 第一引数は生の HTML 文書だ。str オブジェクトか、生バイナリーオブジェクトを指定する。いちばんの問題はむしろ HTML の入力にあると思われる。上のコード例では html_doc としてあるが、実用的なケースはこうだ:

    • ローカルのディスクに保存済みの HTML ファイルの中身を入力する。

    • インターネットからダウンロードした HTML ファイルの中身を入力する。

    ローカルにある HTML ファイルから読み込むには次のようにする。エンコーディングのことを考えるのは面倒なので、バイナリーで取り扱うことを勧める:

    with open('./example.html', 'rb') as source:
        html_doc = source.read()
    

    インターネット上にある HTML ファイルを読み込む場合には、いったんダウンロードするという手間が入る。このあと HTML の解析に試行錯誤することを想定すると、ローカルにファイルを保存することを勧める。

    from urllib.request import urlopen
    
    with urlopen('http://www.example.com/index.html') as source:
        html_doc = source.read()
    

    もしくは requests が利用可能ならばこうもできる:

    import requests
    
    responce = requests.get('http://www.example.com/index.html')
    html_doc = responce.content # or responce.text
    

    ここまでの説明で html_doc の準備のやり方は理解できたとする。

  • 第二引数は解析エンジンを指定する。lxml が使える場合はそれを指定するのが望ましい。

    soup = BeautifulSoup(html_doc, 'lxml')
    

実践例

以下、私がよく実行するコードを記す。

# 文書内の a 要素(タグ)を全て取得する
soup.find_all('a')

# 文書内の href 属性がある要素全てを取得する
soup.find_all(lambda tag: tag.has_attr('href'))

# 文書内の href 属性がある要素のうち、
# 値が '図書館' で終わるものを全てを取得する
soup.find_all(lambda tag: tag.has_attr('href') and str(tag.string).endswith('図書館'))

# 文書内の a 要素のうち、href 属性の値が指定の正規表現にマッチするものを取得する
# あらかじめ import re が必要
soup.find_all('a', href=re.compile(r'item-cnt-[0-9]+'))

なお soup.find_all(...)soup(...) と同値である。コンソールでの作業では後者を用いるといい。

# <th>所在地</th><td>????</td> というノードから ???? の部分にあるテキストを取得したい
soup.find('th', string='所在地').find_next('td').text

# 所在地の次に来る最初の p 要素のノードを取得する
soup.find(string='所在地').find_next('p')
# <h3 class="shop-casset__ttl"> 要素を全て取得する
soup.select('h3.shop-casset__ttl')

# li 要素の子である a 要素を全て取得する
soup.select('li a')

# li 要素うち、属性 class の値が nav-type01__item であるようなものから
# それらの直下の a 要素を全て取得する
soup.select('li[class="nav-type01__item"]>a')

# a 要素のうち、href の値が .cgi で終わるものをすべて取得する
soup.select('a[href*=".cgi"]')

要素の値としての文字列

ノード <tag>X</tag> から X を文字列として取得したい。それには次に上げる属性のいずれかを用いる:

属性

.string

NavigableString

.strings

NavigableString オブジェクトを yield するジェネレーター

.stripped_strings

空白文字が strip() された str オブジェクトを yield するジェネレーター

NavigableString とは Python 組み込みの str に対して機能強化を施した型だと思っていい。本当に生の文字列が欲しい場合は str() に渡して変換できるようだ。

属性 .string の利用条件は、次のどちらかを満たすことだ: * 要素に子が一つしかなく、かつその小要素が NavigableString である。 * 要素に子が一つしかなく、かつその小要素が上記の条件を満たす。

要するに X が文書木の葉であることが想定されている。一般のノード要素から文字列を得るには、ジェネレーター系のどちらかを採用すればいい。

  • .string を参照するときは get_text() が本当はやりたいことではないか確認すること。

グラフ系属性

文書木を特定のノード、すなわち要素、もう一つ言い換えると HTML タグからアクセスするための属性を表にまとめておく。.next_ 系それぞれに対応する .previous_ 系ももちろん提供されているが、説明は省略する。

属性

コメント

.children

list

直下の子要素全て

.contents

(generator)

直下の子要素全て

.descendants

(generator)

子孫要素全て(深さ優先か)

.parent

Tag

親要素

.parents

(generator)

親要素(祖先に向かって)

.next_sibling

Tag

同じ親を持つ次の要素

.next_siblings

(generator)

同じ親を持つ要素のうち、後に現れるもの全て

.next_element

Tag

解析順で直後の要素

.next_elements

(generator)

解析順で直後の要素全て

表にまとめておいてなんだが、木構造はプログラマーならば体で覚えているデータ構造だ。ここに挙げた属性がノードにあることを期待するのが普通なので、改めて暗記するような情報ではない。しかもスクレイピング作業では、どういうわけかこれらの素直な属性をそれほど利用しないのだ。むしろフィルター機能を重点的に学ぶほうがいい。

検索

検索の二本柱は find() 系メソッドとフィルター指定にある。

フィルター

フィルター機能は find() 系のパラメーターとして実現する。用例は本稿の実践例や公式文書を参照。

  • 指定可能な対象

    • 要素 (a, img, title, td, etc.)

    • 属性 (_class, href, src, etc.)

  • 指定方式

    • 対象の名前を表す文字列 e.g. soup('a')

    • 対象の名前にマッチする正規表現 e.g. soup(re.compile(r'h[1-6]'))

      • 正規表現を使用するには Python 標準モジュール re のインポートが必要であることに注意。

      • 対象を指定する何かのリスト

        • 各項目のいずれかに等しければヒットする。

      • 述語関数(呼び出し可能オブジェクト)

        • 柔軟なフィルターをよく定義できる。

      • True

        • テキスト (CDATA?) 要素を除く全要素を拾う。言ってみれば Tag しか返さない。

習得のコツ

  • メソッド find() 系の利用に慣れる。特に検索フィルターの指定にまず慣れる。

  • メソッド select() 系の利用に慣れる。CSS のセレクターの知識が必要だ。

  • 属性 .string. .strings, .stripped_strings を使い分けられるようにする。

関連ノート

この他のスクレイピングの技法をまとめたノートの一覧。