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
を文字列として取得したい。それには次に上げる属性のいずれかを用いる:
属性 |
型 |
---|---|
|
|
|
|
|
空白文字が |
NavigableString
とは Python 組み込みの str
に対して機能強化を施した型だと思っていい。本当に生の文字列が欲しい場合は str()
に渡して変換できるようだ。
属性 .string
の利用条件は、次のどちらかを満たすことだ:
* 要素に子が一つしかなく、かつその小要素が NavigableString
である。
* 要素に子が一つしかなく、かつその小要素が上記の条件を満たす。
要するに X
が文書木の葉であることが想定されている。一般のノード要素から文字列を得るには、ジェネレーター系のどちらかを採用すればいい。
.string
を参照するときはget_text()
が本当はやりたいことではないか確認すること。
グラフ系属性¶
文書木を特定のノード、すなわち要素、もう一つ言い換えると HTML タグからアクセスするための属性を表にまとめておく。.next_
系それぞれに対応する .previous_
系ももちろん提供されているが、説明は省略する。
属性 |
型 |
コメント |
---|---|---|
|
|
直下の子要素全て |
|
(generator) |
直下の子要素全て |
|
(generator) |
子孫要素全て(深さ優先か) |
|
|
親要素 |
|
(generator) |
親要素(祖先に向かって) |
|
|
同じ親を持つ次の要素 |
|
(generator) |
同じ親を持つ要素のうち、後に現れるもの全て |
|
|
解析順で直後の要素 |
|
(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
を使い分けられるようにする。
関連ノート¶
この他のスクレイピングの技法をまとめたノートの一覧。