2019

5

2

Pythonでスクレイピングフレームワークのscrapy入門してやんよ!!!

スポンサードリンク


スクレイピングと一言に言っても実際は結構なロジックとモジュールなどを組み合わせて作り上げます。

  • ・requestsモジュールでサイトにhttpでアクセス
  • ・seleniumなどUIテストツールで自動的にwebページ内を操作・遷移
  • ・BeautifulSoupなどのライブラリでソースコードを取得して必要な情報をパース・整形
  • ・MySQLなどのデータベースまたはファイルに書き込んで保存
  • ・cronでコードの自動実行日時を登録
「Scrapy」はこの辺のWEBスクレイピング周りのアーキテクチャをいっぺんにまとめてくれたアプリケーションフレームワークです。こりゃ使うしかない!

Scrapyのインストール

# pipでインストールする場合
$ pip install Scrapy
すでにインストールされているPythonシステムパッケージ(システムツールやスクリプトの中には壊れる可能性がある)と衝突しないようにvirtualenvの仮想環境内にインストールが推奨されています。
その辺は各自で導入してください。

Scrapyで新規プロジェクトを作成

scrapyをインストールできたら以下のコマンド実行でディレクトリ構造が自動生成されます。
# 新規プロジェクトを作成
$ scrapy startproject new_app

生成ファイル一覧

「scrapy.cfg」 ・・・ デプロイ用の構成ファイルです
「new_app」フォルダ
  ┗「__init__.py」 ・・・ 「new_app」フォルダのパッケージ化ファイル
  ┗「items.py」 ・・・ プロジェクト項目の定義ファイルです
 ┗「middlewares.py」 ・・・ プロジェクトのミドルウェア構成ファイル
 ┗「pipelines.py」 ・・・ プロジェクトのパイプライン構成ファイル
 ┗「settings.py」 ・・・ プロジェクトの設定ファイル
「spiders」フォルダ
 ┗「__init__.py」 ・・・ 「spiders」フォルダのパッケージ化ファイル

基本的にはこの構成でファイルが生成されます。

Scrapyでスクレイピングを実行

作成されたディレクトリの「spiders」フォルダの中に「qiitas_spider.py」というファイル名を新規作成して
試しにQiitaの「mysql」タグの一覧ページを2ページ分取得、それぞれのソースコードを「qiitas-mysql_◯.html」名義でファイル保存するコードを作成

スクレイピング実行ファイルを作成

import scrapy

class QiitasSpider(scrapy.Spider):
    name = "qiitas"

    # 巡回URLを指定
    start_urls = [
        'https://qiita.com/tags/mysql/items?page=1',
        'https://qiita.com/tags/mysql/items?page=2',
    ]

    # 取得ソースをページ毎に「qiitas-mysql_◯.html」名義でファイル保存
    def parse(self, response):
        # URLからカテゴリタグ名を分離
        tag = response.url.split("/")[-2]
        # URLからページ数を分離
        page = response.url.split("/items?page=")[1]
        filename = 'qiitas-{0}_{1}.html'.format(tag, page)
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('「%s」を新規作成しました' % filename)

Scrapyをクロールを実行

# 「qiitas_spider.py」を実行
$ scrapy crawl qiitas
これをプロジェクトのトップレベルディレクトリに移動してターミナルで上記のコマンドを実行すると、作成したコードの処理が走り、「new_app」ディレクトリ内に指定したURLの2ページ分のhtmlファイルが生成されます。


Scrapyツールコマンドの種類

scrapy機能を実行できる各ツールコマンドがあります。使用可能な組み込みコマンドのリストとその説明、および使用例が含まれています。次のコマンドを実行すると、各コマンドに関する詳細情報をいつでも入手できます。
※「spider」ディレクトリで実行してください。
# 基本コマンド構文
$ scrapy <コマンド> [オプション] [引数]

# 利用可能なすべてのコマンドを表示
$ scrapy -h

# 各コマンドに関する詳細情報を表示
$ scrapy <command> -h

scrapyの利用可能なコマンド一覧

startproject

# 基本コマンド構文
$ scrapy startproject <プロジェクト名> [ディレクトリ]

# 「scrapy」フォルダ直下にプロジェクト名「yanyo」を新規作成
$ scrapy startproject yanyo scrapy
[ディレクトリ]の直下に<プロジェクト名>の新規scrapyプロジェクトを作成します。
[ディレクトリ]指定が無い場合は<プロジェクト名>と同じ名前のディレクトリが新規作成されます

genspider

# 基本コマンド構文
$ scrapy genspider [-t テンプレート] <スパイダー名> <ドメイン>

# 「tokidoki-web.com」対象の基本スパイダーテンプレート「yanyo」を作成
$ scrapy genspider -t basic yanyo tokidoki-web.com

# 「tokidoki-web.com」対象のクロール用スパイダーテンプレート「yanyo」を作成
$ scrapy genspider -t crawl yanyo tokidoki-web.com

# 「tokidoki-web.com」対象のCSVフィード用のスパイダーテンプレート「yanyo」を作成
$ scrapy genspider -t csvfeed yanyo tokidoki-web.com

# 「tokidoki-web.com」対象のXMLフィード用のスパイダーテンプレート「yanyo」を作成
$ scrapy genspider -t xmlfeed yanyo tokidoki-web.com
・スパイダーファイルの基本テンプレート「basic」、「crawl」、「csvfeed」、「xmlfeed」を指定する事で定義済みのテンプレートに基づいてスパイダーファイルを作成するための便利なショートカットコマンドです。
・「basic」以外のテンプレートを指定すると必要とされるモジュールインポートも記述されたファイルが生成されます

crawl

# 基本コマンド構文
$ scrapy crawl <スパイダー名>

# スパイダー名「yanyo」でクロールを実行
$ scrapy crawl yanyo
・指定した<スパイダー名>使ってクロールを実行します。

check

# 基本コマンド構文
$ scrapy check [-l] <スパイダー名>

# スパイダー名「yanyo」チェックを実行します。
$ scrapy check yanyo
・指定した<スパイダー名>のテストチェックを実行します。
・公式にはファイル内に記述したコントラクトに沿ってテストを実行する様ですが・・・なるほど。わからん!

fetch

# 基本コマンド構文 
$ scrapy fetch <url>

# 「tokidoki-web.com」のペースソースを取得
$ scrapy fetch --nolog http://tokidoki-web.com

# 「tokidoki-web.com」のレスポンスHTTPヘッダ情報を表示
$ scrapy fetch --nolog --headers http://tokidoki-web.com

parse

# 基本コマンド構文 
$ scrapy parse <url> [オプション]

# ブラウザを開いて「http://tokidoki-web.com」にアクセスする
$ scrapy view http://tokidoki-web.com -c yanyo
・指定されたURLを取得し、それを処理するスパイダ。-cオプションで渡されたメソッドを使用して、または指定されparseていない場合はそれを解析します。

その他のコマンド一覧

list spiderディレクトリ内のプロジェクト一覧を表示します。
edit 指定したスパイダーファイルを設定されているエディタを使って編集します。
view ブラウザを開いて指定URLにアクセスを実行します。
settings プロジェクト内のディレクトリで実行されると、プロジェクト設定値が表示されます。
runspider 個別のPythonファイルに自己完結型のspiderを実行します。
version Scrapyバージョン情報を表示します。
bench 簡単なベンチマークテストを実行します。

shell

シェルコマンドにて簡易的にscrapyを実行できるコマンド(※下記にて詳細)

Scrapyシェルの実行

実行ファイルにスクレイピングコードを書き込まずとも、scrapyではそのままコマンドラインからScrapyシェルを実行してデータを抽出することができます。
# qiitaのトップページのソースコードを取得
$ scrapy shell "https://qiita.com/"
このコマンドを実行すると指定URLのページ構成を取得してresponseオブジェクトに格納されます。
抽出したい各データはこのresponseオブジェクトから操作を行います。

レスポンスオブジェクトを直接操作

# 取得先の実際のページをブラウザに表示
>>> view(response)

レスポンスオブジェクトをCSSセレクターで抽出・操作

# ページtitleのセレクターを取得
>>> response.css('title')
--> [<Selector xpath='descendant-or-self::title' data='<title>Qiita</title>'>]

# ページtitleのセレクターのテキストを抽出
>>> response.css('title::text').get()
--> 'Qiita'

# ページtitleのセレクターのテキストから正規表現で"i"以降の文字列を抽出
>>> response.css('title::text').re(r'i\w+')
--> 'iita'

レスポンスオブジェクトをXPathセレクターで抽出・操作

# ページtitleのセレクターを取得
>>> response.xpath('//title')
--> [<Selector xpath='//title' data='<title>Qiita</title>'>]

# ページtitleのセレクターのテキストを抽出
>>> response.xpath('//title/text()').get()
--> 'Qiita'

ニュース一覧のタイトルを取得

ScrapyシェルコマンドのみでLivedoorのトピック一覧のタイトルを取得してみます
# Livedoorのサイト構成を取得
$ scrapy shell 'http://www.livedoor.com/'
# トピックスの要素タグを取得
>>> topics = response.css("div#newstopicsbox")[0]
# 各ニュースのliタグ内のaタグのテキストをすべて取得
>>> title = topics.css("ol > li > a::text").getall()
# タイトルをリスト出力
>>> title
--> ['裁判官1人で判決「手続き違法」', '「家族殺す」元交際相手連れ回す', '事故の女 衝突音で車に気づいた', '園児事故 相手が止まらずと説明', '男が火のついたタバコをポストに', 'クレカ流出 日本人の情報は高値', '雅子さま1人にせず 陛下の気遣い', '水ダウ SNSでタバコめぐり議論に', '五輪チケット VISAしか使えぬ訳', '木嶋死刑囚と結婚 夫が思い語る']

これだけでも通常のrequestsモジュールでhttpアクセスやBeautifulSoupで取得ソースの抽出などを用いて実装する工程が、かなり短いコードで簡単に再現できました。しゅ・・・しゅごい


クロールした取得データをJSONファイルに保存

だいたい掴めてきたので実践的なコードを作ってみます。
キータの「scrapy」タグページを対象としてエントリーの一覧情報から「タイトル」「筆者名」「記事の登録タグ」「ページURL」を取得するコードを作成します。
※「scrapy」タグ自体はエントリー数がかなり少ないですが、メジャーなタグ一覧ページを指定してしまうと終わらない戦いになるのでやめましょう

スクレイピング実行ファイルを作成

import scrapy

class JsonSpider(scrapy.Spider):
    name = "json"

    # qiita の「scrapy」タグ一覧ページURLを指定
    start_urls = ['https://qiita.com/tags/scrapy/items?page=1']

    def parse(self, response):
        # エントリー一覧から「タイトル」「筆者名」「記事の登録タグ」「ページURL」を取得
        for quote in response.css('article.tsf-Article'):
            yield {
                'title': quote.css('div.tsf-ArticleBody > a::text').get(),
                'author': quote.css('div.tsf-ArticleBody > div > span > a::text').get(),
                'tag': quote.css('div.tsf-ArticleBody > div > div > a::text').getall(),
                'link': quote.css('div.tsf-ArticleBody > a::attr(href)').get(),
            }

        # ページネーションから次のページへのリンクを取得
        for a in response.css('li.st-Pager_next > a'):
            yield response.follow(a, callback=self.parse)

コマンド実行して取得データをJSONファイルに保存する

「json」を実行しつつ、取得データをフィードエクスポート機能を使って「scrapy.json」という名前でJSONファイルに保存することができます。
この機能を使うと「JSON」「JSONライン」「CSV」「XML」形式にエクスポートしてくれます!超便利!
# コマンド実行して「scrapy.json」に保存
$ scrapy crawl json -o scrapy.json




トップへ