スクレイピングTweetPythonのWebクローリングとスクレイピングのフレームワークであるScrapyの使い方をサンプルコードとともに説明する。Framework以下の内容について説明する。Japanを対象としている。単独のページをスクレイピングする例リンクを指定してクローリング、スクレイピングする例リンクを抽出してクローリング、スクレイピングする例リンクを抽出して再帰的にクローリング、スクレイピングする例Scrapyのバージョンは1.5.0。バージョン1.1からPython3にも対応している。nkmk/scrapy-exampleスポンサーリンククローリングとスクレイピングクローリングは「Webページのリンクをたどって巡回し、それぞれのページをダウンロードすること」で、クローリングのためのプログラムをクローラーやボット、スパイダーなどと呼ぶ。スクレイピングは「ダウンロードしたWebページ(htmlファイルなど)を解析して必要な情報を抜き出すこと」。ScrapyとBeautifulSoupの違いBeautifulSoupはスクレイピングのためのライブラリで、ダウンロードしたhtmlファイルなどから必要な部分を抽出することができる。スクレイピング以外の処理、例えばhtmlファイルをダウンロードしたりリンクを辿ったりする処理は自分で用意する必要がある。BeautifulSoupの実際の処理の例は以下の記事を参照。Soupでスクレイピング、Yahooのヘッドライン抽出Scrapyはスクレイピングだけでなくクローリングも行う。Webページのリンクを再帰的にたどって巡回(クローリング)し、各ページに対して所定の処理を行って必要な情報を抽出(スクレイピング)することができる。JSONやXML、CSVなどのファイル出力も簡単。複数ページを対象とするのならScrapyのほうが便利。ScrapyのインストールScrapyのインストールの公式説明ページは以下。documentation他のライブラリと同様にpip(環境によってはpip3)でインストールできる。scrapyAnacondaやMinicondaを使っている場合は、condaでインストールできる。scrapy特にWIndowsでゼロから環境構築する場合はAnacondaのインストーラーを使うのが楽だと思う。公式のチュートリアルで使われているサイトを例にScrapyの基本的な使い方を説明する。Scrape以下のような流れ。shellによるデバッグについても述べる。startprojectコマンドでScrapyプロジェクトを作成する。documentation[project_dir]に<project_name>というプロジェクトを生成するコマンドは以下の通り。[project_dir]を省略するとカレントディレクトリにプロジェクトが生成される。[project_dir]ここでは、任意のディレクトリでtutorialという名前のプロジェクトを作成する。tutorial以下のような構造のディレクトリとファイルが生成される。tutorialというプロジェクトのディレクトリの中に同名(tutorial)のサブディレクトリが生成される。startprojectから続けて実行する場合はcdコマンドで移動する。genspiderコマンドでスパイダーの雛形を作成する。スパイダーはボットやクローラーとも呼ばれるクローリングのためのプログラム。documentation<name>という名前で<domain>を対象ドメインとするスパイダーを生成するコマンドは以下の通り。-tオプションでテンプレートを指定することができる。<domain>ここでは上述の名言サイトquotes.toscrape.comを対象とするquotesという名前のスパイダーを生成する。-tは省略してデフォルトのまま。テンプレートを指定する例は後述。quotes.toscrape.comtutorial/tutorial/spiders/quotes.pyが生成される。スパイダーの名称nameや対象ドメインallowed_domains、クロールのスタート地点となるURLstart_urlsなどの変数に適当な値が設定されている。quotes.pystart_urlsのURLからクロールが始まる。start_urlsはリストになっているので、複数のURLを指定することも可能。取得したHTMLソースがparse()メソッドの第二引数responseにscrapy.http.response.html.HtmlResponseオブジェクトとして渡される。documentationこのparse()メソッドに処理を追加していく。genspiderは雛形を生成するだけ。自分でゼロからスクリプトを作成してspidersディレクトリにおいてもOK。itemsを定義しスパイダーに処理を追加items定義サイトから収集したい情報をtutorial/tutorial/items.pyに記述する。プロジェクトを作成した時点でitems.pyには雛形が記述されている。items.py収集したい項目を<name>=scrapy.Field()で追加すればOK。items.pyスパイダーに処理を追加genspiderで作成したスパイダーquotes.pyを以下のように更新する。quotes.pyitems.pyで定義したクラスTutorialItemをインポートして使えるようにし、parse()に処理を追加する。responseのメソッドcss()にCSSセレクタを指定することでDOM要素を絞り込んで取得できる。::textでテキスト部分を選択し、さらにextract_first()メソッドで文字列として取り出している。extract()は選択されたオブジェクトをリストとして取り出すメソッド(要素が一個でもリスト)。tagsは複数個存在するのでextract()を使う。TutorialItemのインスタンスitemを作成し、抽出した値を格納していく。items.pyで定義したクラスは基本的に辞書(dictオブジェクト)のように扱える。CSSセレクタではなくXPathで要素を絞り込むxpath()メソッドもある。shellを使うとどのようなCSSセレクタやXPathを指定すればよいかを試行錯誤しながら確認できる。settings.pyに各種設定を記述settings.pyに各種設定を記述する。プロジェクト作成時点で雛形が記述されているが、ほとんどの設定項目がコメントアウトされている。設定項目についてのドキュメントは以下。documentation注意しておいたほうがいい項目は以下の通り。USER_AGENTユーザーエージェントによってレスポンスが異なるサイトの場合はここで適当な値を設定しておく。Japanでは設定の必要あり。CONCURRENT_REQUESTS同時リクエスト数。対象サイトへの負荷が気になる場合は小さい値にしておく。DOWNLOAD_DELAYリクエストごとの遅延秒数。対象サイトへの負荷が気になる場合は3秒程度にしておく。DEPTH_LIMITリンクを辿る階層の数。1とするとスタートページからリンクを一回しかクロールしない。2とするとスタートページからリンクされているページ、および、そのページからリンクされているページまでクロールする。デフォルトは0で制限なし。settings.pyの雛形にはないので指定する場合は追記する。リンクを再帰的に辿るかどうかはスパイダーのスクリプトの記述で決まる。このチュートリアルの例ではスタートページを処理するのみで、リンクからクロールすることはない。後述の再帰的に辿る場合はDEPTH_LIMITを指定しておかないと延々とリンクを辿っていく可能性があるので注意。crawlコマンドでクローリング、スクレイピングを実行する。documentation<spider_name>という名前のスパイダーを実行するコマンドは以下の通り。<spider_name><spider_name>はスパイダーのファイル名ではなくスパイダークラスのnameで定義された名前。genspiderで生成した場合はファイル名とnameは同じになるが、自分でゼロから作成した場合は注意。ここではquotes.pyのnameであるquotesを指定する。quotesエラーがなければスパイダーが起動しログが表示される。ログを表示させたくない場合は--nologオプションをつける。収集した結果をファイルに保存したい場合は-oオプションをつける。<file_name>とすると、拡張子のフォーマットに自動的に変換され保存される。Lines.pickleが自動的に認識される。stdout:とすると結果が標準出力される。--nologチュートリアルの結果は以下の通り。nkmk/scrapy-exampleCSVの先頭部分を示す。shellコマンドでresponseに対するcss()やxpath()メソッドによるスクレイピング結果を確認することができる。documentationURLを指定して実行する。<url>インタラクティブシェルが起動するので、以下のようにparse()の第二引数responseと同じオブジェクトresponseを使ってコードを記述してその返り値を確認できる。Einstein'終了するときはexitまたはquitと入力する。以下のように<user_agent>にユーザーエージェントの文字列を指定できる。<url>なお、CSSセレクタについては以下のMDNのドキュメントが詳しい。B(A[スペース]B)はA要素の中にあるすべてのB要素ぐらいは覚えておくとよい。Chromeの場合、抽出したい対象の部分を選択して、右クリック→検証でその部分のソースが開発者ツールに表示される。shellで試してみるいいと思う。JAPANを例とする。サイトの構成が変わるとサンプルコードが動かなくなる可能性があるので注意。2018年6月24日時点で動作確認をしている。JapanはユーザーエージェントによってHTMLソースを切り替えている。Soupでスクレイピング、Yahooのヘッドライン抽出settins.pyのUSER_AGENTを設定しておかないとブラウザで見ているソースが取得できない。Chromeのユーザーエージェントを指定している。settings.pyユーザーエージェントの文字列は以下のサイトなどで確認できる。確認したいブラウザでアクセスする。ウィルラボまずは単独のページをスクレイピングする例を示す。startprojectでプロジェクトを作成。yahoo_japanトップページのニュースのヘッドラインを抽出する。genspiderでtopicsという名前のスパイダーを作成。yahoo.co.jpitems.pyを更新。items.pyスパイダーのスクリプトtopics.pyを更新。上のチュートリアルの例とほぼ同じ。::textではなく::attr(属性名)で属性の値を取得できる。リンクのURLを取得したい場合は::attr(href)とする。results/topics.csv出力されたCSVの先頭部分を示す。topics.csvリンクを指定してクローリング、スクレイピングする例コード中でリンクのURLを指定してリンク先の情報を取得する例として、トップページのニュースのヘッドラインのリンクを辿ってニュースタイトル(省略されていない見出し)を取得する。genspiderでtopics_detailという名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。yahoo.co.jpitems.pyにクラスを追加。items.pyスパイダーのスクリプトtopics_detail.pyを更新。topics_detail.pyリンク先を読み込んで処理するにはscrapy.Request()を使う。responseオブジェクトに渡すオブジェクト(辞書形式で指定)ここでは取得した情報を格納するオブジェクトを指定を渡す。metaでオブジェクトを渡すあたりは分かりにくいので注意。公式ドキュメントの説明ページは以下。documentationまた、トップページのヘッドラインのリンクのドメインはrdsig.yahoo.co.jpでnews.yahoo.co.jpにリダイレクトされる。requestとなる)。yahoo.co.jpとしておけばいずれのドメインも許可されるのでOK。results/topics.csv出力されたCSVの先頭部分を示す。上の例はリンクのURLをコードの中で取得しそれを指定してリンク先のページを読み込んだが、Scrapyではページ中のリンクを抽出して自動的にクロールする処理も可能。トップページではなくYahoo!ニュースの各カテゴリページ(経済、スポーツなど)のヘッドラインを抽出する例を示す。この例のようにクロールするURLが固定されている場合はstart_urlsに対象URL(各カテゴリページのURL)をリストで指定しても所望の動作が可能。ScrapyでURLが既知の複数のページをスクレイピング以下のサンプルコードでは説明のためにリンクを自動的にクロールする処理を行っている。items.pyにクラスを追加。genspiderでnews_crawlという名前のスパイダーを作成。プロジェクトディレクトリで以下のコマンドを実行する。crawlとすることでページ内リンクをクロールするスパイダーの雛形が生成される。yahoo.co.jp生成される雛形news_crawl.pyは以下の通り。crawlによりデフォルトとは異なる雛形が生成される。news_crawl.pyこれを以下のように更新する。news_crawl.pyrulesのRuleでクローリングのルールを指定する。documentationどのリンクをクロールするかのルールはRuleの第一引数LinkExtractor()で指定する。documentationLinkExtractor()の引数restrict_cssにCSSセレクタを指定すると選択された要素内のリンクのみがクロール対象となる。ここではYahoo!ニュース上部の各カテゴリページへのリンク部分を選択している。LinkExtractor()にはクロールするリンクのURLを正規表現で指定する引数allowや範囲をXPathで指定する引数restrict_xpathsなどもある。いずれも複数値をリストで指定可能。Ruleの引数callbackにリンク先を処理する関数名を指定する。Falseで指定する。引数callbackに関数を指定した場合はデフォルトでfollow=Falseとなる(それ以上リンクを辿らない)。この例ではデフォルトでいいので引数followは指定していない。引数callbackを指定しない場合はデフォルトでfollow=Trueとなる。この例のようにrulesでRuleを指定した場合はRuleに合致したリンクがcallbackの関数で処理されるのみで、スタートページ(start_urlsのURLのページ)自体は処理対象とならない。スタートページにも何らかの処理を適用したい場合は、parse_start_url()をオーバーライドする。以下のように記述する。self.parse_item(response)この例ではparse_start_url()をオーバーライドしなくてもスタートページのヘッドラインが抽出されるが、それはスタートページに自身へのリンクが存在しているから。rulesで指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。各カテゴリページには各カテゴリページへのリンクがあるが、一度クロールしたページに再びクロールすることはない。そのほか以下の点に注意。start_urlsを雛形の値から変更している。rulesはタプルなので要素のRuleが一つの場合は末尾にコンマ,が必要。Pythonで要素が1個のタプルには末尾にカンマが必要Ruleの引数followなどの設定を誤ると延々とリンクを辿ってしまう可能性があるので、まずはsettings.pyのDEPTH_LIMITを1か2くらいにしておいたほうが安全。results/topics.csv結果は以下の通り。出力されたCSVの先頭部分を示す。news_crawl.csvリンクを抽出して再帰的にクローリング、スクレイピングする例さらに再帰的にリンクをクロールする例を示す。Yahoo!ニュースの各カテゴリページのヘッドラインのリンク先から情報を取得する。items.pyにクラスを追加。genspiderでnews_crawl_detailという名前のスパイダーを作成。yahoo.co.jp生成されたnews_crawl_detail.pyを以下のように更新する。news_crawl_detail.pyrulesに2つのRuleを指定する。1つ目は上の例と同じく上部の各カテゴリページへのリンク部分。これに対しては引数callbackを指定していないため、リンク先のカテゴリページに対しては関数で処理しない(情報を抽出しない)。引数callbackを指定していないと引数followがデフォルトでTrueとなるため、カテゴリページ内のリンクも再帰的にクローリング対象となる。2つ目のRuleの第一引数に各カテゴリページのヘッドライン(個別ニュースページへのリンク)を対象とするCSSセレクタを指定し、引数callbackにリンク先(個別ニュースページ)を処理する関数を指定している。引数followはcallbackを指定した場合のデフォルトFalseなので、それ以上はクロールしない。results/topics.csv出力されたCSVの先頭部分を示す。個別ニュースページのいずれかのリンクを辿る。1とすると下のルートでしかクローリングされないので、主要カテゴリのヘッドラインからリンクされている個別ニュースページの情報しか抽出されない。上述のようにrulesで指定したクローリングでは同一URLのページはデフォルトでスキップされるようになっている。この例ではヘッドラインのリンク先(個別ニュースページ)へのアクセスもrulesで指定しているので、「主要」カテゴリと各カテゴリに同じニュースへのリンクがある場合、どちらか片方はスキップされる。このため、上の例(「主要」カテゴリと各カテゴリが重複してリストアップされる)とは結果の項目数が異なる。