サイトアイコン Python Snippets

scrapyでシュッとスクレイピングする

Scrapyはスクレイピング用のフレームワークで、scrapyプロジェクトを作って大規模なクローリング・スクレイピングを作ることができるが、ちょこっと使い捨てなスクレイピングをするだけのコードも簡単に作ることができる。

ここでは、scrapyのプロジェクトを作らずスクレイピングのコードを作成する一連の流れを紹介する。

目的

scrapyのexampleページを対象とする。
https://doc.scrapy.org/en/latest/_static/selectors-sample1.html
上記URLがレスポンスするHTMLは以下のようになる。

<html>
 <head>
  <base href='http://example.com/' />
  <title>Example website</title>
 </head>
 <body>
  <div id='images'>
   <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
   <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
   <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
   <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
   <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
  </div>
 </body>
</html>

上記ページのaタグ内テキストの一覧を取得する。

Name: My image 1 
Name: My image 2 
Name: My image 3 
Name: My image 4 
Name: My image 5 

スクレイピングの動作確認

いきなりコードを書いてから動作確認してもよいが、スクレイピングのセレクターが正しいかどうか確認してみるには対話コンソールで確認するのが楽。まずは目的のデータがどのようにスクレイピングすれば取得できるかを手作業で確認する。

scrapy shellを起動して確認。

$ scrapy shell

>>> fetch('https://doc.scrapy.org/en/latest/_static/selectors-sample1.html')
2017-12-10 11:30:37 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://doc.scrapy.org/en/latest/_static/selectors-sample1.html> (referer: None)
>>> response.css('#images a::text').extract()
['Name: My image 1 ', 'Name: My image 2 ', 'Name: My image 3 ', 'Name: My image 4 ', 'Name: My image 5 ']

fetchコマンドでURLを渡して、ページの情報を取得する。取得結果はresponseという変数に自動的に保存される。
resposen.cssで、CSSセレクタを使ってaタグ内のテキストを取得する。結果、目的のデータが取得できた。

scrapy shellでの作業はこれから書くコードの動きを確認するためのもので、コードの作成には関係しない。「CSSセレクタをこのように記述すれば目的のデータが取れるんだな」ということを試行錯誤しながら試すことができる。

セレクタの記述については、xpathやcssセレクタが使用可能。使い方のチートシートはこちらの記事を参照。
scrapyでよく使うxpath,cssのセレクタ

Spiderの作成

どのようにスクレイピングすればよいかは、scrapy shellで試行錯誤することで確認できた。あとはこれを実行するコードを書けば良い。ScrapyではWebからページをクローリングして、スクレイピングする部分はSpiderというプログラムで記述する。
scrapyのツールでSpiderの雛形が作成できる。

scrapy genspiderコマンドでSpiderの雛形を作成する。Spiderの名前とドメインを指定する。

$ scrapy genspider first_scrapy doc.scrapy.org

生成された雛形のSpiderファイル「first_scrapy.py」を編集して開始URLとレスポンスの処理を修正する。

# -*- coding: utf-8 -*-
#first_scrapy.py

import scrapy

class FirstScrapySpider(scrapy.Spider):
    name = 'first_scrapy'
    allowed_domains = ['doc.scrapy.org']
    start_urls = ['https://doc.scrapy.org/en/latest/_static/selectors-sample1.html']

    def parse(self, response):
        for name in response.css('#images a::text').extract():
            print(name)

Spiderの実行

作成したSpiderを実行するには、scrapy runspiderコマンドを実行する。

$ scrapy runspider --loglevel=WARN first_scrapy.py
Name: My image 1
Name: My image 2
Name: My image 3
Name: My image 4
Name: My image 5

コードで実行しているアンカータグの中身が出力されていることが分かる。--loglevelを指定しないとデバッグの出力が得られる。

データの返却

本来、Spiderはクローリング、スクレイピングした結果をデータとして返却する役割を担う。

先ほどのスクリプトはスクレイピングした結果をそのまま出力することで処理を終えてしまっている。簡単な処理ならばそれで十分だが「データの取得」と「取得したデータを活用する」部分は分けたほうがよいこともある。
Spiderはデータを取得して返すことを目的とした実装にできる。その場合はdict型でデータをyieldで逐次返却するようにする。

スクレイピングの処理を修正し、aタグを走査してhref属性とtext要素を取得するように修正。

# -*- coding: utf-8 -*-
#first_scrapy.py

import scrapy

class FirstScrapySpider(scrapy.Spider):
    name = 'first_scrapy'
    allowed_domains = ['doc.scrapy.org']
    start_urls = ['https://doc.scrapy.org/en/latest/_static/selectors-sample1.html']

    def parse(self, response):
        for a in response.css('#images a'):    #aタグの走査
            yield {
                "href": a.css('::attr(href)').extract_first(),
                "name": a.css('::text').extract_first(),
            }

runspiderを実行する際に、出力先としてhoge.jsonというファイルを指定する。

$ scrapy runspider --loglevel=WARN -o hoge.json first_scrapy.py

hoge.jsonの中身はこのようになる。

[
{"href": "image1.html", "name": "Name: My image 1 "},
{"href": "image2.html", "name": "Name: My image 2 "},
{"href": "image3.html", "name": "Name: My image 3 "},
{"href": "image4.html", "name": "Name: My image 4 "},
{"href": "image5.html", "name": "Name: My image 5 "}
]

jsonファイルを出力先に指定したことで、自動的にjson形式で出力されている。明示的に出力形式を指定する場合は、-t でフォーマットを指定する。(csv, json, jsonlines, xmlなどが指定可能)

また出力先の指定に-を指定すると標準出力に出力できる。

$ scrapy runspider --loglevel=WARN -t csv -o - first_scrapy.py
href,name
image1.html,Name: My image 1
image2.html,Name: My image 2
image3.html,Name: My image 3
image4.html,Name: My image 4
image5.html,Name: My image 5

スクレイピングの注意事項

スクレイピングはブラウザと同じようにHTTPリクエストをサーバーに送り、データを取得するプログラムだが、プログラムの記述方法次第では、短時間に大量のリクエストをサーバーに送りつけ攻撃のようになってしまう事がある。

スクレイピングを実装する作法としては、繰り返しリクエストを送る間隔にsleep処理を入れて(最低でも1秒以上)短時間に大量のリクエストを送らないように注意する必要がある。

関連記事:

モバイルバージョンを終了