いっきのblog

技術とか色々

Scrapyのスクレピングが簡単すぎて今更感動した話

僕はPHPスクレイピングする時はGoutteを使っていた。

github.com

サッやりたい時とかは便利だったりするが、robots.txtの中身だったりの確認やページング処理については自分で実装が必要なため手間だなと思っていた。

ふと最近Pythonをよく使ってるし、スクレイピング業界で有名なScrapyを一度使ってみるか・・と思ったのがきっかけである。

Scrapyとは

Python製のスクレイピングフレームワークである。

Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

スクレイピングに特化しているフレームワークなだけあって、スクレイピングでやりたいことは基本的になんでもできる。(JS必須のサイトははまだ試してないが)
また、フレームワークというだけあって、やり方に沿ってプログラミングしていけば楽々できてしまうものである。実際にやってみる。

インストール

いつも通りのpip installを行う。

pip install scrapy

プロジェクトの作成

Scrapyではプロジェクトのテンプレートを作ることができる。

scrapy startproject localhost

今回はローカルのサイトをスクレイピングしてみるため、localhostを名前をつける。中身を確認すると、

$ cd localhost
$ tree 
.
├── localhost
│   ├── __init__.py
│   ├── __pycache__
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── __pycache__
└── scrapy.cfg

データの格納場所を作る

スクレイピングしてきたデータは、Entityのようなデータの入れ物を設定する必要があるためそこを修正する。

$ vim items.py

# -*- coding: utf-8 -*-

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

import scrapy


class LocalhostItem(scrapy.Item):
    # この部分だけ追加
    user_name = scrapy.Field()

今回は、user_nameというデータを取ってくるためその入れ物を追加しておく。

Spiderでスクレイピングの処理を書く

実際にクローリングやスクレイピングを行うのはこのSpiderのお仕事で、その準備をする。
まずはSpiderを作るためにコマンドを叩く。

scrapy genspider local localhost.com

スクレイピングの名前、スクレイピングするホスト名という感じで書く。
実行すると、spiderディレクトリいかにlocal.pyができるので編集する。

$ vim spider/local.py

デフォルトは以下のような感じになる。

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


class LocalSpider(scrapy.Spider):
    name = 'local'
    allowed_domains = ['localhost.com']
    start_urls = ['http://localhost.com/']

    def parse(self, response):
        pass

start_urlsはクロールしたいパスなどを複数記述することができる。
ただし、HTMLの構造が似ていないと難しいので基本的には同じ系統のデータだが、パス名が違う場合のみ書く。異なる場合はまた別にspiderファイルを作る。

start_urls = ['http://localhost.com/path1', 'http://localhost.com/path2']

次に実際のデータを取得するためのロジックをparseに書く。

# 先ほど作った入れ物のインポート
from localhost.items import LocalhostItem

...省略
    def parse(self, response):
        # response.cssでデータの中身をcssみたく取ってこれる
        for sel in response.css('.column > section > .section-accountlist'):
            article = LocalhostItem()
            article['user_name'] = sel.css('.username::text').extract_first()
            yield article

response.csscssを指定するとデータを取得することができる。今回は複数あるため、forで回し、さらにそこからデータ取得し、格納する。

さらに今回はページング処理もあるので、ページングする際の「次へ」のリンクを探し、再帰を行えるようにする。

    def parse(self, response):
        # response.cssでデータの中身をcssみたく取ってこれる
        for sel in response.css('.column > section > .section-accountlist'):
            article = LocalhostItem()
            article['user_name'] = sel.css('.username::text').extract_first()
            yield article

        next_page = response.css('nav .link-next::attr("href")')
        if next_page:
            url = response.urljoin(next_page.extract_first())
            yield scrapy.Request(url, callback=self.parse)

Spiderの準備はこれで終わり。

実際にスクレイピングしてみる

spiderの名前で起動できる。

$ scrapy crawl local -o result.csv

-oオプションをつけると、データの中身をアウトプットできる。
これで、後は勝手にスクレイピングし、データ(今回だとuser_name)を書き込んでくれる。

終わりに

Scrapyはわりと有名だったので知っていたが、ここまで便利だとは思っておらずメモがてら今回は書いた。
実際にはデータベースも絡んだ実装などにもなってくると思うので、次はその辺かきたいなー。