Scrapyを使ったスクレイピング

Advent Calendar 2017 クローラー／Webスクレイピング の2日目が空いておりましたので、駆け込みで参加させていただきました！

## 目的
直近のニュースの概要をスクレイピングし1つのファイルにまとめる

## 動作環境
Ubuntu16.04でプログラムを作成しております。

```
NAME="Ubuntu"
VERSION="16.04 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
UBUNTU_CODENAME=xenial
```

## Install方法
今回は `pip` でインストールしました。

```shell-session
pip install Scrapy
```

## Project作成
今回はニュースを収集するので、「news」という命名でプロジェクトを作成します。

任意のフォルダで以下のコマンドを入力し、プロジェクトを作成します。

```shell-session
scrapy startproject news
```

そうすると `news` フォルダが作成されますので、

```shell-session
cd news
```

と入力し、プロジェクトフォルダ内に移動します。
以下の作業はすべてプロジェクトフォルダ内で実施します。

## Project構造
作成したプロジェクトフォルダは下記のような構造になっています。

```
news/ ← 現時点ではココがカレントフォルダ
    scrapy.cfg
    news/
        __init__.py
        items.py
        pipelines.py
        settings.py
        middlewares.py
        spiders/
            __init__.py
```

## クロール設定の変更
`news/settings.py/` というファイルを編集します。

デフォルトだとダウンロード間隔が0になっているため、クロール先に大きな負荷がかかります。
そのためクロール時に5秒間隔を空けるように以下のように設定を変更します。

```
DOWNLOAD_DELAY = 5
```

# itemの設定
itemはスクレイピングしたデータを格納するオブジェクトです。
今回はプログラム実行時にどのようなデータが格納されているかを確認するために設定しています。

`news/items.py/` というファイルを編集します。

```py3
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class NewsItem(scrapy.Item):
    title = scrapy.Field()
    category = scrapy.Field()
    url = scrapy.Field()
    summary = scrapy.Field()
```


# Source
`news/spider/` 配下にスクレイピングのソースファイルを配置します。
今回は `scraping.py` というファイルを作成しました。

ソースは以下です。

```py3

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

# itemを使用するためにはImportが必要
from news.items import NewsItem

class YnewsSpider(scrapy.Spider):
    # Spiderの名前です。プログラム実行時にここで設定した名前を使用します。
    name = "ynews"
    # クローリングの始点URLです。ここからクローリングを開始します。
    start_urls = ['https://news.yahoo.co.jp/list']

    # スクレイピング処理
    def parse(self, response):
        # 欲しい情報が内包されているニュースのリスト表示部分を取得します
        for selectElements in response.css("div.listArea ul.list li"):
            # 設定したアイテムに情報を格納したいので、インスタンスを生成します
            item = NewsItem()
            # 欲しい要素を取得し、対応するフィールドに格納します
            item['title'] = selectElements.css(".ttl::text").extract_first()
            item['category'] = selectElements.css(".cate::text").extract_first()
            item['url'] = selectElements.css("a::attr('href')").extract_first()
            # 別ページに遷移して取得するデータはrequestとして受け取って、itemに格納し遷移後に再度スクレイピングします
            request = scrapy.Request(item['url'], callback=self.getArticle)
            request.meta['item'] = item
            yield request

    # ニュース概要スクレイピング処理
    def getArticle(self, response):
        item = response.meta['item']
        item['summary'] = response.css("div.headlineTxt p.hbody::text").extract_first()
        # 値を格納したitemを返します
        yield item
```

## 実行
以下のコマンドでクローリング・スクレイピングを実行します。

```shell-session
scrapy crawl ynews -o news.json
```

実行完了後、コマンド実行ディレクトリと同フォルダに `news.json` が生成されます。

## 最後に
上記の方法はPython、クローリング、スクレイピングの初心者が、試行錯誤しながら行った方法です。
もっともっと良い方法があると思いますので、おかしい点や改善点などドンドンご指摘いただければと思います！

## 参考サイト
[Scrapy 1.4 documentation](https://docs.scrapy.org/en/latest/index.html)
[ScrapyでWebサイトからデータ収集してみた](https://qiita.com/monisoi/items/b403d4ab67ccb5b94453)