初めてのクローリング(Scrapy）


スマホゲーの攻略サイトのキャラクター一覧が検索しずら過ぎたので自分でフィルタかけたくなりました。
一覧情報を引っこ抜くのに初めてクローリングにTryしたのでメモを残します。
手段としてはPythonのScrapyというクローリングフレームワークを利用しました。


# Scrapy
クローリングを行うためのフレームワーク。

1. 取得したい項目をデータクラスで定義
1. パースする部分(Spider処理)をCSSセレクタベースで記載
2. 多重度、インターバル等の考慮はScrapyのフレームワーク側で実現済み

# 導入と環境構築
## 環境
anaconda3.x
python3.7.3
Scrapy 1.6

## 手順
1. Anaconda NavigatorのEnvironmentからScrapyをインストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139625/4f9bf0a0-10e3-04b7-3967-02d165766b78.png)

2. 作業ディレクトリを作成し、その中で以下Scrapyコマンドを実行しScrapyのプロジェクト作成
    
    ```bash
    scrapy startproject sample
    ```
    
3. Itemsを作成
 
    ```items.py
    import scrapy
    
    class sampleItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        part = scrapy.Field()
        name = scrapy.Field()
        # 画像も取得したい場合は以下も追加
        image_urls = scrapy.Field()
        images = scrapy.Field()
    ```
4. Spiderの雛形を作成
    
    ```bash
     cd ./sample
     scrapy genspider sample_spider xxxxxxxxxxx
    ```
4. Spiderを実装（以下は例なのでパース部分は適当に。。。)

    ```sample_spider.py
    # -*- coding: utf-8 -*-
    import scrapy
    from sample.items import sampleItem

    class SampleSpider(scrapy.Spider):
        name = 'sample_spider'
        allowed_domains = ['xxxxxxxxxxx']
        start_urls = ['https://xxxxxxxxxxx']

        # 一覧から詳細のURLを取得
        def parse(self, response):

                # パース処理
                # tableのtr単位でループ
                for record in response.css('table[id^="ui_wikidb_table"] tbody tr'):
                    # 詳細ページのURLを抜いて詳細パース処理を呼ぶ
                    url = response.urljoin(record.css('td[data-col="0"]>a::attr(href)').extract_first())
                    print("★★：" + url)
                    yield scrapy.Request(url, callback=self.parseDetail)

        # 詳細ページをパース
        def parseDetail(self, response):
                
            yield sampleItem (
                part = response.xpath('//*[@id="js_async_main_column_text"]/div/p/span[contains(text(),"部")]/../a/text()').extract_first().strip(),
                name = response.css('#js_wikidb_main_name::text').extract_first().strip(),
                # 画像を取得したい場合はURLを以下に追加、配列である必要があるため注意
                image_urls = [response.xpath('//*[@id="ui_wikidb_main_img_wrap"]/a/img/@src').extract_first().strip()],
            )
```

5. 画像をダウンロードしたい場合は追加でpillowをインストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139625/d316c3d7-0fc6-111e-b697-028b751f80d6.png)

6. 画像取得用のパイプラインクラスをsettings.pyに記載する。
ただファイル取得したいだけならデフォルトで用意されている以下クラスを利用するとxxxにダウンロードしたファイルを置いてくれる。
ファイル名の加工や保存先の切り分けとかやりたい場合は自分でImagesPipelineクラスを継承したクラスを作って以下に設定する

    ```settings.py
    ITEM_PIPELINES = {
        'scrapy.pipelines.images.ImagesPipeline': 1,
    }
    IMAGES_STORE = './xxx'
    ```
    
1. クローリング実行
    
    ```bash
    scrapy crawl sample_spider
    ```
    
    ファイルに吐きたい場合はこう
    
    ```bash
    scrapy crawl sample_spider -o xxx.csv
    ```
    
# トラブルシュート
## yield
クローリングというかPythonの言語使用の話なんだけれども、Spiderのサンプルで使われていたので調査。
メソッドのReturnの代わりに使うと一度処理を中断して呼び元に戻してくれる。
ループ処理の途中でIteratorを逐次返してくれえるようなイメージとのこと。
大量データを纏めて処理するのではなく、部分的に処理することでメモリの使用量を抑える考慮らしい。
なるほどなー。

## Spiderクラスファイルの置き場所
 \<project>/spiders/★ココ★

## CSSセレクタの見つけ方が分からん
ChromeのDeveloperTools使うのが楽。

1. 対象のページで取得したい文字列を右クリックして「検証」を選択
1. Developer Toolsが開いて選択した箇所が選択されるので、右クリックしてCopy->CopySelectorでCSSセレクタのイメージを取得
2. Spiderクラスに書く。

## IDの値が定期的に書き換えられてしまうサイトだった（更新するとID変わるとか）
CSSセレクタには部分一致での指定方法がある。前方一致だとこんな感じ
div[id^="XXXXX"] 

## タグで囲まれていないテキストとかどうすればいいの？
XPATHというものがありまして、これを使うと指定できました。
containsとかparentとか駆使するといける。