scrapy を用いてデータを収集し、mongoDB に投入する

Googleはサーチエンジンの情報収集に[Googlebot](https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%BC%E3%82%B0%E3%83%AB%E3%83%9C%E3%83%83%E3%83%88)を使っています。あるウェブサイトを起点に、そのサイトのリンクを自動で辿り、情報を収集します。

pythonの [Scrapy](http://scrapy.org/) モジュールを使えば、同じようなことを実現できます。
Scrapy を用いてサイトの情報を収集してみます。

#準備
Scrapyをpipでインストールします。
`$ pip install scrapy

#使い方
Scrapyは、プロジェクト単位で管理します。プロジェクトを生成した後、そこで自動生成された下記ファイルを編集していきます。
1. items.py : 抽出データを定義する
2. spiders/以下のスパイダー(クローラー)ファイル：巡回、データ抽出条件
3. pipelines.py　：　抽出データの出力先。今回はmongoDB
4. settings.py　：　データ巡回の条件 (頻度や、階層など)
##プロジェクトの作成
まずはプロジェクトを作成します。
`$ scrapy startproject tutorial`
すると、このようなフォルダが生成されます。

```Python: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 file

        pipelines.py      # project pipelines file

        settings.py       # project settings file

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

##抽出データの定義
何を得るかを定義します。データベースで言う、フィールドの定義です。

```items.py
import scrapy

class WebItem(scrapy.Item):
    title = scrapy.Field()
    link = scrapy.Field()
    date = scrapy.Field()
```

##スパイダーの作成
ここがウェブを巡回しデータを抽出する、花形ファイルです。巡回開始のアドレスと、巡回条件、そしてデータ抽出条件を指定します。
######スパイダーの生成
スパイダーを作ります。構文は `$ scrapy genspider [options] <name> <domain(巡回するサイトドメイン)>`です。

```py:commandline
$ scrapy genspider webspider exsample.com
  Created spider 'webspider' using template 'basic' in module:
  tutorial.spiders.webspider
```

生成されたファイルは

```py:tutorial/spiders/webspider.py
# -*- coding: utf-8 -*-
import scrapy

class WebspiderSpider(scrapy.Spider):
    name = "webspider"   # プロジェクト内での名前。動かすときのスパイダー指定で使われる
    allowed_domains = ["exsample.com"] # 巡回OKのドメイン指定
    start_urls = (
        'http://www.exsample.com/', # ここを起点にする。リストで複数指定できる。
    )

    def parse(self, response):  # ここが抽出条件
        pass
```

このようなファイルが生成されます。
これを、自分好みに変えます。

```py:tutorial/spiders/webspider.py
# -*- coding: utf-8 -*-
import scrapy
from tutorial.items import WebItem
import re
import datetime
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class WebspiderSpider(CrawlSpider):  #クラス名にたいした意味はない
    name = 'WebspiderSpider'  # これは重要。この名前を指定してスパイダー(クローラー)を動かす
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com']

    xpath = {
        'title' : "//title/text()",
    }

    list_allow = [r'(正規表現)'] #この条件に合うリンクは巡回
    list_deny = [
                r'/exsample/hogehoge/hoge/', # こちらは巡回しないリンクの指定例。リスト表記も可能
            ]
    list_allow_parse = [r'(正規表現)']  #データ抽出するリンク指定
    list_deny_parse = [                #データ抽出しないリンク指定
                r'(正規表現)',
                r'(正規表現)',
                ]

    rules = (
        # 巡回ルール。
        Rule(LinkExtractor(
            allow=list_allow,
            deny=list_deny,
            ),
            follow=True # そのリンクへ入っていく
        ),
        # データ抽出ルール
        Rule(LinkExtractor(
            allow=list_allow_parse,
            deny=list_deny_parse,
            unique=True # おなじリンク先ではデータ抽出しない
            ),
            callback='parse_items' # 条件に合えば、ここで指定したデータ抽出実行関数を実行する。
        ),
    )

   #データ抽出関数定義
   def parse_items(self, response): # response に、ウェブサイトの情報が入っている
        item = WebItem()  # items.pyで指定したクラス
        item['title'] = response.xpath(self.xpath['title']).extract()[0]
        item['link'] = response.url
        item['date'] = datetime.datetime.utcnow() + datetime.timedelta(hours=9) # 現在時間。日本時間にして突っ込む。

        yield item
```

書いている内容はコメントを参考にしてください。

##pipeline.pyの編集
上記で作成したスパイダーから `yield item` を、mongoDB に突っ込みます。

```py:pipelines.py
from pymongo import MongoClient  # mongoDB との接続
import datetime

class TutorialPipeline(object):

    def __init__(self, mongo_uri, mongo_db, mongolab_user, mongolab_pass):
        # インスタンス生成時に渡された引数で、変数初期化
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        self.mongolab_user = mongolab_user
        self.mongolab_pass = mongolab_pass

    @classmethod  # 引数にクラスがあるので、クラス変数にアクセスできる
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'), # settings.py て定義した変数にアクセスする
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items'),
            mongolab_user=crawler.settings.get('MONGOLAB_USER'),
            mongolab_pass=crawler.settings.get('MONGOLAB_PASS')
        ) # def __init__ の引数になる

    def open_spider(self, spider): # スパイダー開始時に実行される。データベース接続
        self.client = MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]
        self.db.authenticate(self.mongolab_user, self.mongolab_pass)

    def close_spider(self, spider): # スパイダー終了時に実行される。データベース接続を閉じる
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].update(
            {u'link': item['link']},
            {"$set": dict(item)},
            upsert = True
        ) # linkを検索して、なければ新規作成、あればアップデートする

        return item
```

いろいろ書いていますが、要はデータベースを開けて、データ突っ込んで、終わったら閉じているだけです。

##settings.py
まずは、pipelines.py で呼び出している各種変数を定義します。

```py:settings.py
MONGO_URI = 'hogehoge.mongolab.com:(port番号)'
MONGO_DATABASE = 'database_name'
MONGOLAB_USER = 'user_name'
MONGOLAB_PASS = 'password'
```

これはmongolabの例です。
settings.pyでは他に、挙動を指定します。

```py:settings.py
REDIRECT_MAX_TIMES = 6
RETRY_ENABLED = False
DOWNLOAD_DELAY=10
COOKIES_ENABLED=False
```

ここでは、リダイレクトの最大回数を6回に、リトライを実行しないように、ウェブサイトへのアクセスは10秒ごとに、クッキーは保存しないように、という条件を設定しています。
`DOWNLOAD_DELAY`を指定しないと、バシバシ全力でアクセスしまくるので、巡回先のサイトに大きな負荷がかかります。やめましょう。

##実行
実行してみましょう。

```py:commandline
$ scrapy crawl WebspiderSpider
```

どんどんリンクを辿っていき、条件にあうリンクからデータが抽出されていきます。