はじめに
こんにちは、データ分析部の久保 (@beatinaniwa) です。
今日は義務教育で教えても良いんじゃないかとよく思うWebクロールとスクレイピングの話です。
私自身、日頃は社内に蓄積されるニュース記事データや行動ログをSQLやPythonを使って取得・分析することが多いですが、Web上にある外部データを使って分析に役立てたいというシーンはままあります。
単独のページをガリガリスクレイピングしたいときなどは、下の1年半ぐらい前の会社アドベントカレンダーに書いたような方法でやっていけば良いんですが、いくつもの階層にわかれたニュースポータルサイトやグルメポータルサイトを効率よくクロール+スクレイピングするためには、それに適したツールを使うのがすごく便利です。
そこでPython用スクレイピングフレームワークScrapyの登場です。
Scrapy | A Fast and Powerful Scraping and Web Crawling Framework
Scrapyとは
Scrapyを使えばweb spiderと呼ばれる、サイトをクロール+スクレイピングする処理を簡潔に記述することができ、さらに後述するようにそれをScrapy Cloudにデプロイすることによって、自分がほしいサイトの情報を自動的に(それも最低限であれば無料で!)、取得することができます。
今年の5月にリリースされた1.1.0(最新バージョンはこの記事を書いている時点で1.1.1)からはついにPython3系がサポートされ(まだベータサポートということでWindowsではPython2系のみの対応ですが...)、心を落ち着かせて使うことができます。
Scrapyの使い方
インストール
まずインストールはいつもどおりpipで
$ pip install scrapy
前準備
今回は実際のクロール+スクレイピングを行うニュースポータルサイトとしてWeb版のグノシーを題材にしたいと思います。
グノシーのトップページはこのようになっていて、大きくトップ、エンタメ、スポーツ、... グルメのように分かれています。
今回はエンタメからグルメまでの各カテゴリの記事をScrapyを使って集めてみることにします。各記事のタイトル、URL、サブカテゴリ名を取得するターゲットとします。
プロジェクト作成
まずはScrapyのプロジェクトを作ります。
$ scrapy startproject gunosynews $ cd gunosynews
そうするとこんなファイルがディレクトリ以下に作られます。
. ├── gunosynews │ ├── __init__.py │ ├── __pycache__ │ ├── items.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ └── __pycache__ └── scrapy.cfg
gunosynews/items.py
を編集して
# -*- 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 GunosynewsItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() subcategory = scrapy.Field()
次に要のweb spider部分を作ります、scrapyで用意されているコマンドを実行することで雛形を作成してれます。
$ scrapy genspider gunosy gunosy.com
これはgunosy.comドメインをクロールするgunosyという名前のspider(クローラ)を作成する、という意味になります。 早速できた雛形を編集します。
gunosynews/spiders/gunosy.py
# -*- coding: utf-8 -*- import scrapy from gunosynews.items import GunosynewsItem class GunosynewsSpider(scrapy.Spider): name = "gunosy" allowed_domains = ["gunosy.com"] start_urls = ( 'https://gunosy.com/categories/1', # エンタメ 'https://gunosy.com/categories/2', # スポーツ 'https://gunosy.com/categories/3', # おもしろ 'https://gunosy.com/categories/4', # 国内 'https://gunosy.com/categories/5', # 海外 'https://gunosy.com/categories/6', # コラム 'https://gunosy.com/categories/7', # IT・科学 'https://gunosy.com/categories/8', # グルメ ) def parse(self, response): for sel in response.css("div.list_content"): article = GunosynewsItem() article['title'] = sel.css("div.list_title > a::text").extract_first() article['url'] = sel.css("div.list_title > a::attr('href')").extract_first() article['subcategory'] = sel.css("div.list_text > a::text").extract_first() yield article next_page = response.css("div.page-link-option > a::attr('href')") if next_page: url = response.urljoin(next_page[0].extract()) yield scrapy.Request(url, callback=self.parse)
順番に説明します。
start_urls
にはクロールの起点となるURLを記述していきます。今回はエンタメ~グルメ各カテゴリの記事を集めたかったので、各カテゴリのトップをクロールの起点としました。parse()
メソッドが今回のクローラの中心となるメソッドです。先ほど指定したstart_urlがダウンロードされるとそのレスポンスが引数となってこのメソッドに渡されることになります。parse()
メソッドの中では与えられたレスポンスから取り出したい要素の箇所をCSSセレクタで指定しています。記事数の分だけfor文で処理を繰り返しています。next_page~
以降の箇所に注目してください。ここでは記事一覧ページの下部にある「次のページをみる」リンクの要素をCSSセレクタによって選択し、もし「次のページをみる」リンクが存在すればそのURLをscrapy.Request
オブジェクトに渡して、callback引数に先ほどのparse()
メソッドを指定することによって再帰的に次のページをたどることが可能になっています。こうすることでScrapyは自動的に次のページがなくなるまで、各カテゴリの記事リストをたどってくれます。
ここまでで一通りクロール+スクレイピングする処理ができました。いよいよ実行してみるところですが、その前に設定ファイルを見てみます。設定の中身は gunosynews/settings.py
に記述されています。DOWNLOAD_DELAY = 3
と書かれているところに注目してください。ここではクローラが各ページをダウンロードする間隔(秒)を指定できます。デフォルトではコメントアウトされていますがクロール先に迷惑をかけないためにもコメントアウトを外しておきましょう。
その他細かい設定などについては本家ドキュメントを参照してください。
Settings — Scrapy 1.1.1 documentation
実行&結果
おつかれさまでした、実行してみます。
$ scrapy crawl gunosy
するとクロール+スクレイピングが始まりたとえば下のような実行結果がつらつらと出てくると思います。
2016-08-17 11:46:55 [scrapy] DEBUG: Scraped from <200 https://gunosy.com/categories/8> {'subcategory': 'お店', 'title': '「刀削麺って何?」そんなアナタに『冷やし野菜刀削麺』はいかがでしょう? @『唐家』', 'url': 'https://gunosy.com/articles/R0MAi'} 2016-08-17 11:46:55 [scrapy] DEBUG: Scraped from <200 https://gunosy.com/categories/8> {'subcategory': 'グルメ総合', 'title': 'ベルギーで1年に2回だけの味!お祭りと共にやってくる絶品スイーツ「スマウトボゥ」', 'url': 'https://gunosy.com/articles/RWeO2'} 2016-08-17 11:46:55 [scrapy] DEBUG: Scraped from <200 https://gunosy.com/categories/8> {'subcategory': 'お店', 'title': '「空間と時間」日本と違うアプローチ/記者ルポ', 'url': 'https://gunosy.com/articles/Ri3TP'}
目的の要素が正しく取得できていることがわかります。
Scrapy Cloudで簡単クローラ管理
導入
さて目的の情報を取得するところまでできましたが、重要なのはこれらのデータをどうやって保存しておくのか、またどうやってクローラの実行を管理するかということです。そこでScrapy Cloudの登場です。
Scrapy Cloudでは先ほど作成したクローラをデプロイし、ブラウザの画面から簡単に管理することができます。 クローラを一つ作って動かすのなら無料で使うことができ、クレジットカードの登録なども必要ありません(データの保持期限など無料ならではの制限は当然ながらあります)。 サインアップしてOrganizationを適当に設定すると下のような画面になると思います。
上のメニューバーから Scrapy Cloud > Create Project
を選択し、プロジェクトを作成します。
無事作成されました。
すると左のサイドバーに Code & Deploys
という項目があるのでそれをクリックして、APIキーとプロジェクトIDをメモっておきます。
デプロイ
いよいよデプロイです。デプロイツール用のライブラリが用意されているのでそれを利用します。
$ pip install shub
Scrapy Cloud — Scrapinghub documentation
ログインします。
$ shub login
ログイン時にAPIキーを聞かれるのでさきほどメモったAPIキーを入力すると ~/.scrapiinghub.yml
にAPIキーが保存され、次回以降ログインするときには自動的にこれが使われるようになります。
実際のデプロイです。ここでは対象プロジェクトIDが聞かれるので、同様にさきほどメモったプロジェクトIDを入力します。
$ shub deploy
デプロイが完了するといろいろなディレクトリやファイルが追加されているのがわかります。
. ├── build │ ├── bdist.macosx-10.10-x86_64 │ └── lib │ └── gunosynews │ ├── __init__.py │ ├── items.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ └── gunosy.py ├── gunosynews │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-35.pyc │ │ ├── items.cpython-35.pyc │ │ └── settings.cpython-35.pyc │ ├── items.py │ ├── pipelines.py │ ├── settings.py │ └── spiders │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-35.pyc │ │ └── gunosy.cpython-35.pyc │ └── gunosy.py ├── project.egg-info │ ├── PKG-INFO │ ├── SOURCES.txt │ ├── dependency_links.txt │ ├── entry_points.txt │ └── top_level.txt ├── scrapinghub.yml ├── scrapy.cfg └── setup.py
実行&結果
実際にScrapy Cloudの左サイドバーから Spiders > Dashboard
からデプロイしたクローラ一覧を見ることができ、クローラ名をクリックすることでクローラ詳細画面に行くことができます。
右上のRun Spiderボタンを押して、もう一度開いたウィンドウでRun Spiderボタンを押すことで実際のクローラの実行が始まります、押してみましょう!
するとRunning Jobsのところにみるみるうちにアイテムが溜まっていきます。
アイテム数のところをクリックすることで、実際にスクレイピングした項目をチェックすることができ、さらにはCSVなどでダウンロードすることができます。 超便利!!
最後に
いかがでしたでしょうか、クローラ+スクレイピングは自分で一から書くとなると、コードのメンテナンスや実際のクローラの監視、データの保存などいろいろな箇所がネックとなってきますが、Scrapy + Scrapy Cloudを使うとかなり負担が少なくなることがわかると思います。 Scrapyには今回紹介しきれなかった便利機能がたくさんあり、ドキュメントもしっかりしているので詳しく知りたい方はぜひ読んでみてください。
Scrapy 1.1 documentation — Scrapy 1.1.1 documentation
それでは良いスクレイピングライフを。