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
パーサ指定の文字列を変えればlxml
BeautifulSoupで使うことができます。
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スクレイピングを迅速にかつ確実に行うことを助けます。