2016/08/27
から admin
0件のコメント

【Python】Scrapy + Digdag でクローラの定期実行【Workflow】

Scrapy プロジェクトを Digdag でスケジューリングしてみたので導入の忘備録を残しておきます。
環境は MacBook Air (13-inch, Mid 2013), OSX 10.11.6 です。

Scrapy

Scrapy (スクレピー?) は Web Crawling / Scraping Framework で, mechanize や Beautiful Soup といった特定の機能を提供するライブラリと比べると多機能。 基本的な機能に加えて robots.txtポリシー, クロール間隔設定, リトライ処理, 並行処理, scrapydによるデーモン化 などもサポートしている。

基本的には Installation guide 通りで入ると思うが, 自分の環境では pip で上手くインストールできなかったので, conda で試してみる。[1]

$ eval "$(pyenv init -)"
$ pyenv versions
* system (set by /Users/x/.pyenv/version)
  2.7.6
  3.4.0
  anaconda3-4.1.0
$ pyenv global anaconda3-4.1.0

conda で Scrapy をインストールする。

$ conda update conda
$ conda install -c scrapinghub scrapy

プロジェクトを作成する場合は, scrapy startproject コマンドを使う。 また, spider の生成は scrapy genspider コマンドを使う。

今回は Example にある dmoz.org をクローリングしてみる。構成は以下。

$ git clone https://github.com/scrapy/dirbot
$ tree
.
├── README.rst
├── dirbot
│   ├── __init__.py
│   ├── items.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── dmoz.py
├── scrapy.cfg
└── setup.py

最初に, settings.py の中で, ロギングレベル (CRITICAL, ERROR, WARNING, INFO, DEBUG), robots.txtポリシー, クロール間隔, cookieの有効化, UA などの各種設定を書いておく。特に, クロール間隔は対象サーバの負荷を軽くするために忘れないで設定する。また, 見落としがちなのが, ITEM_PIPELINES はデフォルトだとコメントアウトされている点。

DOWNLOAD_DELAY = 3.0
ROBOTSTXT_OBEY = True
LOG_LEVEL = 'INFO'

items.py の中でデータ構造を定義し, spiders/*.py の start_urls で指定した url をクロールし, parse() で得られた HTML を処理していく。

selector は CSS か Xpath で指定できる。
取りたいデータが CSS selector だけで取得できると楽だけど, XPath での指定方法も知っておくと役立つ。

xpath(), css() 共にマッチした要素が list で返ってくるので, for in の中で extract() で unicode string を取得して itemオブジェクト に追加していく。また, 正規表現を使ってマッチする場合は re() を使う。

from scrapy.spiders import Spider
from scrapy.selector import Selector

from dirbot.items import Website


class DmozSpider(Spider):
    name = "dmoz"
    allowed_domains = ["dmoz.org"]
    start_urls = [
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/",
        "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/",
    ]

    def parse(self, response):
        """
        The lines below is a spider contract. For more info see:
        http://doc.scrapy.org/en/latest/topics/contracts.html

        @url http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/
        @scrapes name
        """
        sel = Selector(response)
        sites = sel.xpath('//ul[@class="directory-url"]/li')
        items = []

        for site in sites:
            item = Website()
            item['name'] = site.xpath('a/text()').extract()
            item['url'] = site.xpath('a/@href').extract()
            item['description'] = site.xpath('text()').re('-\s[^\n]*\\r')
            items.append(item)

        return items

pipelines.py には itemオブジェクト をDBに格納したりする処理を書く。
spider ごとに pipelines の処理を切り替えたい場合は, spider.name で分ける方法がある。

scrapy crawl [spider名] でクローリングが開始される。

$ scrapy list
dmoz

$ scrapy crawl dmoz
2016-08-26 22:44:11 [scrapy] INFO: Scrapy 1.1.1 started (bot: scrapybot)
2016-08-26 22:44:11 [scrapy] INFO: Overridden settings: {'DEFAULT_ITEM_CLASS': 'dirbot.items.Website', 'SPIDER_MODULES': ['dirbot.spiders'], 'NEWSPIDER_MODULE': 'dirbot.spiders'}
2016-08-26 22:44:11 [scrapy] INFO: Enabled extensions:
2016-08-26 22:44:11 [scrapy] INFO: Enabled downloader middlewares:
2016-08-26 22:44:11 [scrapy] INFO: Spider opened
2016-08-26 22:44:11 [scrapy] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-08-26 22:44:12 [scrapy] DEBUG: Crawled (200)  (referer: None)
2016-08-26 22:44:12 [scrapy] DEBUG: Crawled (200)  (referer: None)
2016-08-26 22:44:12 [scrapy] INFO: Closing spider (finished)
2016-08-26 22:44:12 [scrapy] INFO: Dumping Scrapy stats:
...
2016-08-26 22:44:12 [scrapy] INFO: Spider closed (finished)

Scrapy はFWなので手軽にできる一方, JavaScript 実行が必要な場合は, Selenium を使うことになる。[5]

Digdag でスケジューリング

クローラを定期実行する場合 Cron や Jenkins といった選択肢があると思うが, 今回は Distributed Workflow Engine の Digdag を使ったスケジューリングを試してみた。Digdag を使うことで, タスクのステータス管理ができフロー制御が簡単にできそう。また, スケジューラではないが, scrapyd を使うと HTTP JSON API でクローラを動かせる。

インストールは Getting started そのままで jar を配置するだけ。必要な場合は先に JDK8 をインストールしておく。

$ curl -o /usr/local/bin/digdag --create-dirs -L "https://dl.digdag.io/digdag-latest"
$ chmod +x /usr/local/bin/digdag

digdag init で dig ファイルが生成される。

$ digdag init dmoz
$ cd dmoz
$ tree
.
├── dmoz.dig
└── query.sql

0 directories, 2 files

先に作った Scrapy プロジェクトを Digdag でスケジューリングしてみる。適当に下記のような構成とした。

$ tree -I *.pyc -I __pycache__
.
├── dirbot
│   ├── README.rst
│   ├── dirbot
│   │   ├── __init__.py
│   │   ├── items.py
│   │   ├── pipelines.py
│   │   ├── settings.py
│   │   └── spiders
│   │       ├── __init__.py
│   │       └── dmoz.py
│   ├── scrapy.cfg
│   └── setup.py
├── dmoz.dig
└── tasks
    └── scrapy_dirbot.sh

4 directories, 11 files

Scheduling workflow を参考にして, 毎時30分に実行するように設定する。
スケジューリングは他にも cron形式 でも指定できる。dig ファイルは基本的にはYAMLの構文で, operators は shell 以外にも Ruby, Python なども使える。

timezone: "Asia/Tokyo"

schedule:
  hourly>: 30:00

+scrapy_dirbot:
  sh>: tasks/scrapy_dirbot.sh

digdag scheduler コマンドで定期実行する。または digdag run *.dig で一度だけ実行する。

#!/bin/sh

scrapy crawl dmoz

今回は cron を置き換える使い方しかしていないが, Digdag を使うとより柔軟で高度なフローを組み立てることができそう。

終わりに

Scrapyの基本的な使い方や, Webスクレイピングの基礎については下記が参考になります。どちらかと言うと内容は初学者向けな印象です。

また, スクレイピングで得られた Webデータ または Webサービス提供側 が持つデータに対する主なタスク (バースト検出, 評判分類, 意味表現, グラフデータ) やその分析手法について下記が参考になりそうです。

【追記】 Ubuntu 14.04 での Setup

Ubuntu 14.04 での環境構築の例。

# Anaconda インストール
$ wget https://repo.continuum.io/archive/Anaconda3-4.1.1-Linux-x86_64.sh
$ bash Anaconda3-4.1.1-Linux-x86_64.sh

# Scrapy インストール
$ conda install -c scrapinghub scrapy
$ scrapy version
Scrapy 1.1.0

# JDK8 インストール, Digdag は OSX と同様
$ sudo apt-get install software-properties-common python-software-properties
$ sudo apt-add-repository ppa:openjdk-r/ppa
$ sudo apt-get update
$ sudo apt-get install openjdk-8-jdk


[1] Scrapyをインストールするときのエラー対処。 を参考にさせて頂き, Xcode command line tools を最新にしたりしてみたが原因は別のようだった。
[2] pyenvとvirtualenvで環境構築
[3] pyenvでPythonがSystemバージョンから切り替わらない時の対処
[4] PythonとかScrapyとか使ってクローリングやスクレイピングするノウハウを公開してみる!
[5] 【Python】 WebDriverでWebスクレイピング 【Selenium】
[6] Workflow Engine をつくろう! Part 1(Task の依存関係の解決)
[7] Digdag schedulerで定期実行するプロジェクトを作ってみた。
[8] digdag-introduction