話題のクローラー・スクレイピング!PythonならScrapyが超優秀な件

Python
スポンサーリンク

WS000216

Rubyの読書会に行ったら、Pythonの面白いお話を聞けたというお話です。

Rubyクローラー本の読書会に参加

先日、Rubyによるクローラー開発技法 読書会 第2回に参加してきました。

[amazonjs asin="4797380357" locale="JP" tmpl="Small" title="Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例"]

こういう会に参加すると、自分の知識の狭さを痛感してもっと勉強しなきゃなぁという気になります。また次回も参加させてもらいたいです。参加者の皆さん、色々ご教示いただき、ありがとうございました。

読書会では本の内容から広がった話がとても面白かったです。個人的には、Rubyのクローラー本の中身を実際に使うということは少ない気がしましたが、他の人がどのようにスクレイピングをしているのかということを知ることができたのはとても有意義でした。

具体的な読書会の内容は西山さんのブログにお任せするとして、そこでcuzicさんから教えていただいたPythonのScrapyのご紹介をします。

フレームワーク Scrapy

ぐぐってみると、真っ先に出てきた orangainさんのブログに詳しい解説が載っています。その記事が優秀すぎるので今更書くことも少ないかもしれませんが、自分なりにScrapyの紹介をしてみますね。

クローリングからスクレイピングまで強力にサポート

クローリングしながら一気にスクレイピングで情報をまとめてしまうということが、フレームワークで簡単にできるようになります。クローラー本来の使い方をしたい人にはとても便利なフレームワークじゃないでしょうか。

クローラーとしての機能

  • 既定サイトから深さを指定してクロール
  • Sitemapからパースしてクロール
  • クッキーやHTTPのキャッシュ等も対応
  • クローラーにクロールする条件だけ書けば動くようになっている
  • Webサーバーとして立ち上げて、botとして動かせる
  • etc…

Scrapyの優秀なところは、このクローリングの設定の簡潔なところです。ざっくり説明すると、クローリングに含めたい条件を正規表現やsitemapの設定をしてあげるだけでクローリングの設定が終わります。この辺りはフレームワークの利点ですね。

スクレイパーとしての機能

  • HTMLやXMLで書かれたデータの展開、取得
  • 結果の出力にはJSON, CSV, XMLが選べ、保存先もFTPからS3, ローカルストレージから選べる
  • スクレイプした結果に関連付けられた画像を自動でダウンロードできるパイプラインが書ける
  • etc…

基本的にはHTMLのやXMLからXPathで指定して情報を抜き出すという使い方になります。保存先の変更も容易なのは使い勝手が良さそうです。

他にもできること

  • インタラクティブなシェルからScrapyを操作できる
  • 各クローラから共通で利用できるデータの整理フィルター

インタラクティブなシェルからScrapyを操作というのは楽しそうです。こちらに紹介があります。実際にxPathでちゃんと情報が抜き出せるかなど、インタラクティブに確認できるのはとても楽です。

scrapy shell <url>

上記コマンドのURLを指定するだけで、インタラクティブモードで対象サイト上のscrapyが立ち上がります。

実際に使ってみよう

yahooファイナンスのこのページより「あ」行で始まる株式会社の名前と銘柄コードをJSONで保存するクローラーを作ります。

インストール方法

pip install scrapy

pipで簡単にインストールできます。ただし、Python 2.7専用なので注意してください。

プロジェクトの作製

scrapy startproject scrapy_sample

railsでよく使っていたような形でフレームワークが入ったプロジェクトを作成します。このような形のファイル構成が自動で作成されます。

scrapy_example/
    scrapy_example.cfg
    scrapy_example/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py

クローリングとスクレイピングだけをしたければ、items.pyにスクレイピング結果を格納するItemクラスを作製して、spidersフォルダの下にクローラーの.pyファイルを作成するだけでクローラーとして使えるようになります。では実際に作ってみましょう。((ただ、今回はJSONを日本語の結果で出力したいため、pipelineを作成しなければならなかったので、一手間かかります。))

item.pyの編集

item.pyでは、取得するItemの内容を予め確保しておく必要があります。dictionaryクラスのキーを予め確保しておくイメージです。

# -*- coding: utf-8 -*-

import scrapy

class StockItem(scrapy.Item):
    name = scrapy.Field()
    stock_id = scrapy.Field()

今回は、会社の名前と銘柄コードを取得したいのでnameとstock_idを確保してみました。

spider(クローラー)の作製

プロジェクトのspidersフォルダ下にstock.pyファイルを作製します。

# coding: utf-8

from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import Selector

from scrapy_example.items import StockItem

class StockSpider(CrawlSpider):
    name = 'stock'
    allowed_domains = ['stocks.finance.yahoo.co.jp']
    start_urls = [
        'http://stocks.finance.yahoo.co.jp/stocks/qi/?js=%E3%81%82',
    ]
    rules = [
        # xpathに一致する条件でページ遷移
        # followがTrueだと、遷移先のページでもrulesを適応する
        Rule(SgmlLinkExtractor(restrict_xpaths =(u'//a[contains(text(), "次の20")]', )), 'parse_stock', follow=True)
    ]

    def parse_start_url(self, response):
        yield self.parse_stock(response)

    def parse_stock(self, response):
        # HTTPのレスポンスをxPathセレクタにする
        sel = Selector(response)

        # 指定クラスに一致する条件の表の列から、銘柄コードと名前を取得し保存する
        for tr in sel.xpath('//tr[@class="yjM"]'):
            # 保存する情報をインスタンス化
            item = StockItem()
            item['stock_id'] = tr.xpath('td[1]/*/text()').extract()[0]
            item['name'] = tr.xpath('td[3]/strong/a/text()').extract()[0]
            yield item

今回はCrawlSpiderクラスを使いました。このクラスでは、start_urlsにURLを指定すると、Rulesで定義した条件のもと、クローリングしてくれます。Rulesの第二引数で指定したメソッドがクローリングの際に呼び出されます。

SgmlLinkExtractorがこのファイルの肝ですね。どのようにクロールするかの条件を指定するものです。これだけをしっかり指定すれば、クローラーの設定は全て済んでいるといっても言い過ぎではないと思います。

parse_start_urlは開始URLをパースの対象にするためのメソッドです。CrawlSpiderクラスを使うと、通常のparseメソッドのオーバーライドはしてはいけないらしく、このような仕様でしか全てのページをparseする仕様は実現できません。

最後にparse_stockメソッドにて、スクレイピング内容を記述しています。yieldで返るitemクラスがJSONに書き込まれるファイルになります。

pipelines.pyの編集

通常ですとこの作業は不要なのですが、残念ながらJSONの標準出力に日本語が対応していないため、pipeline上でJSON書き込み用のクラスを設定する必要があります。

このクラスは、下記ページより引用しました。

# -*- coding: utf-8 -*-

import json
import codecs

class JsonWithEncodingPipeline(object):

    def __init__(self):
        self.file = codecs.open('stock-code.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        line = json.dumps(dict(item), ensure_ascii=False) + "n"
        self.file.write(line)
        return item

    def spider_closed(self, spider):
        self.file.close()

setting.pyの設定

# -*- coding: utf-8 -*-

BOT_NAME = 'scrapy_example'

SPIDER_MODULES = ['scrapy_example.spiders']
NEWSPIDER_MODULE = 'scrapy_example.spiders'

# ダウンロードする間隔(秒)の指定
DOWNLOAD_DELAY = 3

# pipelineの設定
ITEM_PIPELINES = ['scrapy_example.pipelines.JsonWithEncodingPipeline']

コメントの内容以外に特に説明する必要はないかと思います。

これで、全体の作製が完了しました。

クローラーの起動

scrapy crawl stock

上記コマンドでクローラー(stock.py)を起動できます。

動作が完了すると、stock-code.jsonに「あ」行で始まる株式会社の銘柄コード一覧がJSONで保存されています。

ソースコード GitHub

使ってみた感想

RubyのAnemone等よりはかなり簡潔にクローラーを書けそうです。確かに単にクローラーを作るという観点においては、とても便利なフレームワークです。一般公開されている株価を毎日取得させる処理みたいなのにはとても便利でしょう。ただ、フォームへの入力機能やクリック機能がないため、ログインのページを持ったページに対応しにくいこと、クローリングの途中で情報を取得し、その情報と他を組み合わせてクローリングすること等ができなさそうなので、個人的には使いどころに悩むなぁといったところです。Postのメッセージを送ることはできるんですが、ログインページに可変のログイン情報があった場合はそれを取得して送信という処理の対応が難しそうでした。ともあれ、とても優秀なフレームワークなので、触って見る価値は十分ありますよ!

ご参考になれば幸いです!

コメント

  1. […] 話題のクローラー・スクレイピング!PythonならScrapyが超優秀な件 | Wizard In The Market […]

タイトルとURLをコピーしました