ウェブスクレイピングは重要な技術であり、特にデータサイエンスやデータマイニングなど、多くの異なる文脈で頻繁に採用されています。
Pythonはウェブスクレイピングに適した言語と考えられており、その理由はPythonのバッテリーがインクルードされているためです。
Pythonを使えば、簡単なスクレイピングスクリプトを15分程度で、100行以下のコードで作成することができます。
そのため、用途に関係なく、ウェブスクレイピングはすべてのPythonプログラマーが身につけておかなければならないスキルなのです。
実践に入る前に、ウェブスクレイピングとは何か、いつ使うべきか、いつ使わないべきかを考えてみる必要があります。
ご存知の通り、ウェブスクレイピングはウェブサイトから自動的にデータを抽出するために採用される技術です。
ここで重要なのは、ウェブスクレイピングは、さまざまなソース(通常はウェブページ)からデータを抽出する、やや粗雑な技術であるということです。
もし、ウェブサイトの開発者がデータを抽出するためのAPIを提供してくれるなら、その方がより安定的で堅牢な方法でデータにアクセスできるだろう。
したがって、経験則として、ウェブサイトがデータをプログラムで取得するためのAPIを提供している場合は、それを使用する。
APIが利用できない場合は、ウェブスクレイピングを使用する。
ただし、Webスクレイピングを許可していないWebサイトもあるので、利用するWebサイトごとのルールや制限を遵守する必要があります。
それでは、チュートリアルに入りましょう。
このチュートリアルでは、有名な作家の名言を掲載しているサイト、http://quotes.toscrape.com/
をスクレイピングしてみます。
ウェブスクレイピングパイプライン
ウェブスクレイピングは3つの要素からなるパイプラインとして理解することができる。
- ダウンロード。HTMLのウェブページをダウンロードする
- パース。HTMLを解析し、目的のデータを取得する。
- 保存する。取得したデータを特定のフォーマットでローカルマシンに保存する
HTMLのダウンロード
ウェブページからデータを取り出すには、まずそれをダウンロードしなければならないのは当然のことです。
これを行うには、2つの方法があります。
- ブラウザ・オートメーション・ライブラリを使う
Seleniumのようなブラウザオートメーションライブラリを使って、ウェブページからHTMLをダウンロードすることができます。
Seleniumを使えば、例えばChromeのようなブラウザを開いて、好きなように操作することができます。
ブラウザでウェブページを開き、そのページのHTMLコードを取得することができ、すべてSeleniumを使って自動化することができます。
しかし、この方法には大きな欠点があります – それは著しく遅いことです。
理由は、ブラウザを実行し、ブラウザでHTMLをレンダリングするオーバーヘッドがかかるからです。
この方法は例外的な場合にのみ使用されるべきです。
スクレイピングしたいコンテンツがブラウザでJavaScriptコードを使用している場合、またはデータにアクセスするためにボタンやリンクをクリックする必要がある場合、Seleniumが私たちのために行うことができます。
- HTTP ライブラリの使用
Requests モジュールや Urllib などの HTTP ライブラリを使用すると、最初の方法とは異なり、ブラウザを開く必要をまったく回避して、HTTP リクエストを送信することができます。
この方法は、Seleniumよりもはるかに高速であるため、常に優先されるべきです。
それでは、Selenium と Requests ライブラリを使用して、パイプラインのこのコンポーネントを実現する方法をお見せします。
Requests を使う
Requests` モジュールを以下のようにインストールします。
$ pip install requests
そして、あなたのコードでこのように使うことができます。
import requests
result = requests.get('http://quotes.toscrape.com/')
page = result.text
ここでは、URLに対してHTTP GETリクエストを行っていますが、これはウェブページをダウンロードするのとほぼ同義です。
そして、requests.get()
メソッドが返す結果オブジェクトにアクセスすることで、ページのHTMLソースを取得することができるのです。
Seleniumの使用
pipを使って、
selenium` モジュールをインストールすることができる。
$ pip install selenium
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('http://quotes.toscrape.com/')
page = driver.page_source
ここでは、まず、ブラウザを表す webdriver
オブジェクトを作成することから始めます。
これにより、コードを実行しているコンピュータで Chrome ブラウザが起動されます。
次に、webdriver
オブジェクトの get
メソッドを呼び出すことで、URLを開くことができます。
そして最後に、webdriver
オブジェクトの page_source
プロパティにアクセスして、ソースコードを取得します。
どちらの場合も、URL の HTML ソースは文字列として page 変数に格納されます。
HTMLのパースとデータの抽出
理論的なコンピュータサイエンスに立ち入ることなく、解析とは文字列を分析し、その内容を理解し、その中のデータに簡単にアクセスできるようにすることだと定義することができます。
Pythonには、HTMLを解析するのに役立つ2つのライブラリがあります。
BeautifulSoupとLxmlです。
LxmlはBeautifulSoupよりも低レベルのフレームワークで、BeautifulSoupのバックエンドとしてLxmlを使用することができるので、単純なHTMLパース目的であればBeautifulSoupが好ましいライブラリでしょう。
しかし、解析に入る前に、ウェブページのHTMLを解析し、スクレイピングしたいデータがどのように構成され、どこにあるかを確認する必要があります。
その情報によって武装しているときだけ、解析されたHTMLから欲しい情報を得ることができるのです。
しかし、ありがたいことに、エディターでソースコードを開き、手作業で各HTML要素とレンダリングされたページの対応するデータを理解し関連付ける必要はありません。
ほとんどのブラウザには、Chrome のデベロッパー ツールのようなインスペクターがあり、要素をクリックするだけでその HTML コードをすばやく確認できます。
Chromeでこれを行うには、Chromeでウェブページを開き、スクレイピングしたいデータの上で右クリックし、「Inspect」を選択する。
Firefoxでは、このオプションはInspect Elementと呼ばれ、同じものですが名前が違うだけです。
Chrome ウィンドウの下部に、クリックした要素のソース コードを含むペインが開いていることに気づくでしょう。
ソースコードを少し見て、スクレイピングしたいデータがHTMLコードの中でどのように構成されているかを知ることができます。
少し見ればわかるように、http://quotes.toscrape.com/
の各引用語は class="quote"
属性を持つ div
に含まれています。
その div
の中で、引用文のテキストは span
に class="text"
で、著者の名前は small
タグに class="author"
で記述されています。
この情報は、実際にHTMLを解析してデータを抽出する際に必要になります。
では、BeautifulSoupを使ってHTMLページのパースを始めてみましょう。
その前に、インストールする必要があります。
$ pip install beautifulsoup4
インストールしたら、次のようにコード内で呼び出すことができます。
from bs4 import BeautifulSoup
soup = BeautifulSoup(page, 'html.parser')
まず、解析されたページを作成するために、BeautifulSoup
クラスのコンストラクタにページを渡します。
見ての通り、コンストラクタには第2引数として html.parser
も渡しています。
これは、Beautiful Soup が渡された文字列をパースするために使用するパーサーの名前です。
Lxml ライブラリがインストールされていれば、以前に説明した lxml
というパーサーを使用することもできます。
quotes = soup.find_all('div', class_='quote')
次に、ページ内の class="quote"
を含む div
タグをすべて抽出します。
これを行うために、Beautiful Soup 4 には find_all
という関数が用意されています。
タグ名とクラス名を find_all
関数に渡すと、条件を満たすすべてのタグ、つまり引用符を含むタグが返されます。
ここで注意すべき重要なことは、ここでは木構造を扱っているということです。
変数 soup
と quotes
の各要素はツリー構造になっています。
ある意味では、 quotes
の要素はより大きな soup
ツリーの一部と言えます。
ともかく、別の議論に移らないように、話を続けましょう。
scraped = []
for quote in quotes:
text = quote.find('span', class_='text').text
author = quote.find('small', class_='author').text
scraped.append([text, author])
引用のテキストは span
タグに class="text"
で、著者は small
タグに class="author"
で入っていることが分かっています。
引用の要素からそれらを抽出するために、再び同様の関数である find
を使用します。
find関数は
find_all関数と同じ引数を取ります。
唯一の違いは、find_allがタグのリストを返すのに対して、条件を満たす最初のタグを返すことです。
また、返されたオブジェクトのtext` プロパティにアクセスしたいのですが、このプロパティにはそのタグで囲まれたテキストが含まれています。
そこで、コードにあるように、リスト quotes
のすべての要素をループして、引用文と著者名を取り出し、リスト名 scraped
として保存します。
scraped` のリストは、コンソールで出力されると次のようになる。
[['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”',
'Albert Einstein'],
['“It is our choices, Harry, that show what we truly are, far more than our abilities.”',
'J.K. Rowling'],
['“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”',
'Albert Einstein'],
['“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”',
'Jane Austen'],
["“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”",
'Marilyn Monroe'],
['“Try not to become a man of success. Rather become a man of value.”',
'Albert Einstein'],
['“It is better to be hated for what you are than to be loved for what you are not.”',
'André Gide'],
["“I have not failed. I've just found 10,000 ways that won't work.”",
'Thomas A. Edison'],
["“A woman is like a tea bag; you never know how strong it is until it's in hot water.”",
'Eleanor Roosevelt'],
['“A day without sunshine is like, you know, night.”',
'Steve Martin']]
取得したデータの保存
データを取得したら、CSVファイル、SQLデータベース、NoSQLデータベースなど、好きな形式で保存することができます。
厳密には、このステップはスクレイピングプロセスの一部としてカウントされるべきではありませんが、それでも念のため簡単に説明します。
スクレイピングしたデータを保存する最も一般的な方法は、CSVスプレッドシートとして保存することだと思うので、ごく簡単にその方法を紹介することにします。
詳細はPythonの公式ドキュメントを参照してください。
では、早速コードを見てみましょう。
import csv
with open('quotes.csv', 'w') as csv_file:
writer = csv.writer(csv_file, delimiter=',')
for quote in scraped:
writer.writerow(quote)
見てわかるように、このコードはとてもわかりやすいものです。
開いているquotes.csvファイルからCSVのwriter
オブジェクトを作成し、writerow
関数を使って引用文を1つずつ書き込んでいます。
明らかなように、writerow
関数は入力としてリストを受け取り、それをCSVに行として書き込みます。
結論と次のステップ
このチュートリアルは、あなた自身が簡単なスクレイパーを実装することを学びながら、スクレイピングが基本的に何であるかを理解するのに役立つはずです。
この種のスクレイパーは、単純な自動化や小規模なデータ取得には十分でしょう。
しかし、大量のデータを効率的に抽出したいのであれば、スクレイピングフレームワーク、特にScrapyに目を向けるべきでしょう。
Scrapyを使えば、数行のコードで非常に高速かつ効率的なスクレイパーを書くことができる。
このチュートリアルを理解することは、あなたのスクレイピングの冒険のための基礎知識を構築するのに役立つはずです。