け日記

最近はPythonでいろいろやってます

Scrapyでけ日記をクローリングする (3. parseへ任意の値を渡す方法とエラーハンドリング)

前回・前々回に引き続き、Scrapyを使ってこのブログのクローリングを行います。

github.com

今回は細々としたところで、Spiderクラスのparseメソッドへ値を受け渡す方法と、エラーハンドリングについてです。Spiderの実装は前々回の投稿も参考にしてみてください。

ohke.hateblo.jp

なお、以下のフォルダ構造となってます。

f:id:ohke:20180707152304p:plain

parseへ任意の値を渡す

parseでスクレイピングして得た値を別のページのparse処理でも使い回したい、など、parseメソッドに任意の値を渡して利用したいケースがあります。

そういった場合、metaプロパティを使います。

  • リクエストを作るときにrequest.meta['key'] = valueで値を設定
  • parseメソッド内ではvalue = response.meta['key']で値を取得

2018年のエントリタイトルと投稿日を、1月から順にスクレイピングしていく例を示します。archive_spider.pyに実装しています。

  • http://ohke.hateblo.jp/archive/2018/{月}へ1月から順にアクセスしていきます
  • start_requestsでは、request.meta['month']に初期値1を渡してリクエストしています
  • parseでは、response.meta['month']で今見ている月を取り出し、インクリメントして次のリクエストを作っています
import scrapy
from ..items import PostItem

class ArchiveSpider(scrapy.Spider):
    name = 'archive_spider'

    url_format = 'https://ohke.hateblo.jp/archive/2018/{}'
    filename = '2018.txt'

    def start_requests(self):
        start_month = 1
        url = self.url_format.format(start_month)

        request = scrapy.Request(url=url, callback=self.parse)
        request.meta['month'] = start_month  # 値の設定

        yield request

    def parse(self, response):
        month = response.meta['month']  # 値の取得

        title_list = response.css('a.entry-title-link::text').extract()
        date_list = response.xpath('//time/@datetime').extract()

        with open(self.filename, 'a') as f:
            for t, d in zip(title_list, date_list):
                f.write('{}\t{}\n'.format(d, t))

        next_month = month + 1
        next_url = self.url_format.format(month+1)
        # オプションmetaに辞書形式でも渡せます
        yield scrapy.Request(url=next_url, callback=self.parse, meta={'month': month})

エラーハンドリング

スクレイピングをしていると、リンクが切れていたり、権限のためにアクセスできない、などの問題は避けられません。

Scrapyでは、リトライなどの基本的な設定をsettings.pyでできます。

  • RETRY_TIMES: リトライ回数 (デフォルトでは、2)
  • RETRY_HTTP_CODES: リトライするHTTPステータスコード (デフォルトでは、[500, 502, 503, 504, 408])
  • HTTPERROR_ALLOWED_CODES: エラーとして処理するHTTPステータスコード (デフォルトは [] で全てエラー扱い)

Spiderごとに設定することもでき、例えば400エラーをparse内でハンドリングして正常終了する場合は、以下のような実装になります。
monthが13の場合、存在しない月なので400が返ってきます。それをエラーとせず、parse内でreturnして終了させています。

class ArchiveSpider(scrapy.Spider):
    name = 'archive_spider'
    handle_httpstatus_list = [400]  # ステータス400の場合はエラーにしない

    # 省略

    def parse(self, response):
        # ステータスが400の場合は、クローリングを止める
        if response.status in [400]:
            print(response.status)
            return

        month = response.meta['month']

        title_list = response.css('a.entry-title-link::text').extract()
        date_list = response.xpath('//time/@datetime').extract()

        with open(self.filename, 'a') as f:
            for t, d in zip(title_list, date_list):
                f.write('{}\t{}\n'.format(d, t))

        next_month = month + 1
        next_url = self.url_format.format(month+1)
        yield scrapy.Request(url=next_url, callback=self.parse, meta={'month': month})

まとめ

今回もScrapyの使い方について、parseへのパラメータの渡し方と、エラーハンドリニングについて触れました。

参考文献

Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-

Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-