Scrapy の turorial をやってみた

[公式のTutorial](https://doc.scrapy.org/en/latest/intro/tutorial.html#our-first-spider)をやってみました。Scrapyインストール済みの前提。

流れはこんなかんじだそうです。

1. 新しいScrapyプロジェクトを作成
2. websiteをクロールしてデータを抽出するための spider と呼ばれるコードを書く
3. コマンドラインを使ってデータをエキスポートする
4. spiderを再帰的にリンクを辿ってくれるように変更する
5. spider引数を使う

## プロジェクトの作成

```
 $ scrapy startproject tutorial 
```
でプロジェクト作成。すると以下のようなディレクトリ構成が作られる。

```
tutorial/
    scrapy.cfg            # deploy configuration file

    tutorial/             # project's Python module, you'll import your code from here
        __init__.py

        items.py          # project items definition file

        pipelines.py      # project pipelines file

        settings.py       # project settings file

        spiders/          # a directory where you'll later put your spiders
            __init__.py
```

## Spiderを書く
クローラのメインの処理的なものを書きます。Tutorialでは名言の引用サイトをクロール＆スクレイプしています。

```
# -*- coding: utf-8 -*-
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
                 'http://quotes.toscrape.com/page/1/',
                 'http://quotes.toscrape.com/page/2/'
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)
        

```

## Spiderを走らせる
プロジェクトのトップディレクトリに行って、次のコマンドをうつ。

```
$ scrapy crawl quotes
```

## start_requests メソッドを省略する書き方

```
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
```

## 名言とその作者（？）のデータを抽出する

```
$ scrapy shell url
```

で遊んでみると分かるように、quoteの本文と作者は

``` >>> quote = response.css('div.quote')[0] ```
とするならば、それぞれ

```
>>> text = quote.css('div.text::text').extract_first()
>>> author = quote.css('small.author::text').extract_first()
```

のようにして取り出せる。これを繰り返し処理を用いてそれぞれのquote について行うと、

```
 def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }
```
のようになる。

## クロール＆スクレイピングしたデータの保存
最もシンプルな方法は、次のコマンドである。

```
$ scrapy crawl quotes -o quotes.json
```

## リンクを辿る処理
以下のように<a></a>タグで囲われているリンクを辿る。

```
<ul class="pager">
    <li class="next">
        <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a>
    </li>
</ul>
```

extractして、href=のリンクの部分だけがほしいので、次のようにして抜き出す。 aタグのhref attributeを抜き出している。

```
>>> response.css('li.next a::attr(href)').extract_first()
'/page/2/'
```

spiderコードを再帰的にリンクを辿るように変更したものが以下である。

```
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)
```

最後の行の ```callback=self.[method_name]```の部分を任意のメソッド名に変更すれば、parse方法の違うページであっても処理を書ける。たとえば、parse_child()メソッドなどを書けば良い。