Scrapyで発生した例外をキャッチする方法

最近、Heroku上で定期実行し、Scrapyで取得した新刊情報をSlackに通知するプログラムを作成しました。

その際、スクレイピングで発生した例外についても手動で確認せずに済むよう、Slack通知を設定したためその方法を記載します。
(Heroku上で動作させ、Slack通知する方法については参考記事参照)

↓作成したプログラムはこちら↓

github.com

Scrapyとは

ScrapyはPythonでクローリング・スクレイピングを行うためのフレームワークです。

クローリング・スクレイピングを行う際には

  • robot.txtで拒否されているページのクロールを行わない
  • あるページのリンク(ニュース一覧に記載されている各ニュースヘのURL等)を辿り、遷移先のページから情報を取得する
  • サイト内のリンクを辿る際にはサイトに負荷を掛けないよう、アクセス間隔を空ける

等、毎回行う処理や注意しなければいけない点があります。

Scrapyにはこういった点を考慮した機能が備わっており、少ないコードでクローリング・スクレイピングを行うプログラムを記述できます。

例外をキャッチする方法

Scrapyは

  • WEBページを取得するDownloader
  • 取得したWEBページをパースし、情報を取得したり次のページに遷移するためのリクエストを発生させたりするSpider
  • 取得した情報に対する処理を行うItem Pipeline
  • これらを管理するScrapy Engine

といったアーキテクチャで動作します。
(詳しくは下記ページをご覧下さい)

doc-ja-scrapy.readthedocs.io

そして、Scrapy EngineとSpiderの間で入出力をフックするSpider Middlewareを使う事でSpiderで発生した例外をキャッチできます。

doc-ja-scrapy.readthedocs.io

Spiderで発生した例外をキャッチするためにはprocess_spider_exceptionを実装したクラスをミドルウェアとして追加すればOKです。

setting.py

SPIDER_MIDDLEWARES = {
    'notification.middlewares.NotificationErrorMiddleware': 543,
}

独自定義したミドルウェア

class NotificationErrorMiddleware(object):
    def process_spider_exception(self, response, exception, spider):
        webhook_url = environ.get('SINKAN_TUUTI_SLACK_URL')
        if not webhook_url:
            raise DropItem('webhook url is not defined.')

        message = 'url: ' + response.url + '\n'
        message += traceback.format_exc()

        # 例外が発生したページのURL・スタックトレースをSlackに通知
        slack = slackweb.Slack(webhook_url)
        slack.notify(text=message)

        return None

これでいちいちログを目視で確認しなくて済みますね!

まとめ

失敗するクローリング・スクレイピングのプログラムをそのままにしておくとクロール先のサイトに迷惑を掛けてしまう恐れがあります。

発生した例外を自動通知し、スクレイピングの失敗にいち早く気付く事でプログラムを改善していきましょう!

参考記事

Scrapyでイベントサイトの更新情報を定期ツイート on Heroku
Python3でslackに投稿する