Python – BeautifulSoupでWebサイトをScraping(スクレイピング)
Web Scraping (スクレイピング)とは、
理論的には、WebスクレイピングとはAPIを使って直接使うプログラム(または、Webブラウザを使った人間)以外の手段で、データを収集する作業を指します。これは通常、Webサーバにクエリを出し、(HTMLやWebページを作る他のファイル形式で)データを要求し、そのデータをパースして必要な情報を抽出する自動プログラムを書くことによって達成されます。
今回は、WebサーバにGETリクエストを送って特定のページを入手し、そのページのHTML出力を読み込み、探している内容だけを取り出すために単純なデータ抽出を行っていきます。
crawlが必要な場合は、以下Python – Javascriptページをseleniumでスクレイピングページをご参照ください。
1. Scraping library install (BeautifulSoupのインストール)
BeautifulSoupライブラリはデフォルトのPythonライブラリではないので、インストールする必要があります。BeautifulSoup 4ライブラリ(BS4とも言う)を今回使用します。ただし、AnacondaでPythonをインストールしているなら、BeautifulSoupは既にインストール済みです。最新のPython 3をインストールしていれば、以下でインストールできます。
pip install beautifulsoup4
2. BeautifulSoupを実行する
BeautifulSoupライブラリで普通に最もよく使われるのが、BeautifulSoupオブジェクトです。早速、その動作を見てみましょう。
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
bs = BeautifulSoup(html.read(), 'html.parser')
print(bs.h1)
出力は、次のようになります。
<h1>An Interesting Title</h1>
ページで最初に見つかったh1タグしか返さないことに注意してください。普通は、1つのページには、1つのh1タグしかないはずですが、このような原則はWebでは守られないことが多く、これでは最初のタグしか取り出さないので、探しているタグとは限らないことに注意が必要です。
html.parserはPython 3に組み込まれているパーサで、使うためにインストールする必要がありません。特別な場合を除き、今回はこのパーサを使います。
よく使われる別のパーサは、lxml(http://lxml.de/parsing.html)です。これはpipでインストールできます。
$ pip install lxml
パーサ指定の文字列を変えればlxmlBeautifulSoupで使うことができます。
bs = BeautifulSoup(html.read(), 'lxml')
html.parserと比較すると、lxmlは整っていないHTMLコードでも一般にうまくパースできるという利点があります。規則にきちんと従っていない、閉じていないタグや、ネストのおかしいタグ、ヘッダや本体のタグが欠けている場合を扱えます。
別のよく使われるHTMLパーサはhtml5libです。html5libは、lxmlよりもさらに、HTML構文に問題があってもそれを修正して読み込めるパーサです。外部ライブラリに依存もしていて、lxmlやhtml.parserよりも遅いです。それでも、問題のありそうなHTMLサイトを扱う場合には、使うとよいかもしれません。
bs = BeautifulSoup(html.read(), 'html5lib')
3. 例外を処理してScrapingの精度を上げる
Webスクレイピングで最も苛立つことの1つは、処理を実行させて、寝床に行き、明日はすべてのデータがデータベースに揃っていると夢見ていたのに、起きてみたら、予期せぬデータフォーマットでスクレイパーがエラーを起こし、画面を閉じたすぐ後で実行を停止していたとわかることです。このような状況下では、Webサイトを作成した開発者の名前を(それからおかしなフォーマットも)呪いたくもなるでしょうが、本当に腹が立つのは、そもそもそのような例外的なことを予想していなかった自分自身に対してです。
import文の後のスクレイパーの最初の行を見てみましょう。起こり得る例外をどう扱えばよいか考えてみましょう。
html = urlopen('http://www.pythonscraping.com/pages/page1.html')
この行は、2つの大きな問題をはらんでいます。
- ページがサーバ上で見つからない(または、取り出すときにエラー)
- サーバが見つからない
前者では、HTTPエラーが返ります。このHTTPエラーは、「404 Page Not Found」、「500 Internal Server Error」などです。このとき、urlopenは、ジェネリック例外HTTPErrorを投げます。これは、次のようにして扱うことができます。
from urllib.request import urlopen
from urllib.error import HTTPError
from urllib.error import URLError
try:
html = urlopen('https://pythonscrapingthisurldoesnotexist.com')
except HTTPError as e:
print(e)
except URLError as e:
print('The server could not be found!')
else:
print('It Worked!')
もちろん、ページがうまく取り出せても、ページのコンテンツが期待していたものとまったく異なっていたという問題があり得ます。BeautifulSoupオブジェクトのタグにアクセスするたびに、タグが実際に存在しているかどうかチェックして確認するのが賢い方法です。存在しないタグにアクセスしようとすると、BeautifulSoupはNoneオブジェクトを返します。問題は、Noneオブジェクトのタグにアクセスしようとする試みそのものがAttributeErrorを投げられる結果になることです。
try:
badContent = bs.nonExistingTag.anotherTag
except AttributeError as e:
print('Tag was not found')
else:
if badContent == None:
print ('Tag was not found')
else:
print(badContent)
このようにすべてのエラーをチェックして処理するのは手間がかかると、最初は思えますが、このコードを少し整理するだけで、書きやすく(より重要なことは、はるかに読みやすく)なります。このコードは前に書いたのとは少し異なる書き方のスクレイパーです。
from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
try:
html = urlopen(url)
except HTTPError as e:
return None
try:
bs = BeautifulSoup(html.read(), 'html.parser')
title = bs.body.h1
except AttributeError as e:
return None
return title
title = getTitle('http://www.pythonscraping.com/pages/page1.html')
if title == None:
print('Title could not be found')
else:
print(title)
この例では、関数getTitleを作りました。ページのタイトルを返しますが、取り出すときに何か問題があったらNoneを返します。getTitleの内部では、前の例での場合と同様に、HTTPErrorをチェックし、try文で、BeautifulSoupの2行をカプセル化します。この2行からは、AttributeErrorが投げられるかもしれません(サーバが存在しないとhtmlはNoneオブジェクトになり、html.read()がAttributeErrorを投げます)。実際、必要なだけ多くの行をtry文1つにまとめるか、あるいは、別の関数を呼び出して、任意の時点でAttributeErrorを投げることができます。
まとめ
今回は、Python – BeautifulSoupでWebサイトをScraping(スクレイピング)についてでした。
スクレイパーを書くときには、例外を扱うと同時に読みやすさのために、コードの全体としてのパターンについて考えることが重要です。コードの再利用も考えましょう。getSiteHTMLやgetTitleのような(例外の処理が完全な)ジェネリックな関数は、Webスクレイピングを迅速にかつ確実に行うことを助けます。