work.log

エンジニアの備忘録的ブログ

PythonのScrapyでHTML、XML、CSV用のクローラーを作ってみる

投稿:2019-03-03 21:02  更新:

クローラー開発でPython製のクローラーフレームワークScrapyを使ったらめちゃくちゃ便利だったのでメモします。

Scrapyを使うと数行のコードでお目当てのデータを簡単に抽出できるので、これからクローラーを作ろうとする人なら覚えておいて損はないです。

また、Scrapyの読み方は「スクレイピー」と「スクラピー」で二通り分かれていますが、海外の動画を見ているとスクレイピーと言っているように聞こえます。

どっちなんですかね…

スポンサーリンク

Scrapyのインストールと新規プロジェクトの作成

今回はPython 3.7.2 (pyenv) の環境にScrapyをインストールして動かしました。インストールはこのように。

# pip install Scrapy

インストールが終わったら早速、新規プロジェクトを作成します。プロジェクト名は「blogScrapy」にしました。

$ scrapy startproject blogScrapy

treeコマンドとかで確認するとblogScrapy配下にいくつかファイルが作成されますが、settings.py、items.pyを設定後、スパイダーを作成しクローラーとスクレイピング処理を書いていきます。

まずはsettings.pyでクローラーの最低限の設定を行います。

## UAの設定
USER_AGENT = 'blogScrapy (+http://www.yourdomain.com)'

## robots.txtを守る
ROBOTSTXT_OBEY = True

## データ取得時の遅延時間、最低1秒以上は欲しいところ
DOWNLOAD_DELAY = 3

## 取得データのキャッシュ設定。ここでは1時間有効。
# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = []
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'

クローラーを回す許可をサイト管理者に取るなんて中々しないと思うので、勝手にデータを拝借する立場上相手サーバになるべく迷惑はかけないように…

次はitems.pyに取得するデータの出力データフォーマットを設定します。まだこの機能があんまりよくわかってないし、今回は特に無くても大丈夫なのですがこのように書きます。

class BlogscrapyItem(scrapy.Item):
	url = scrapy.Field()
	title = = scrapy.Field()

最後はScrapyのコマンドでクローラーの雛形を作成します。

## scrapy genspider -t <template> <spider_name> <domain>
$ scrapy genspider -t crawl worklog worklog.be

普通のWebサイトをクロールするのに適したcrawlテンプレートを使ってスパイダーを作成しました。

用意されているテンプレートはこんな感じです。

$ scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed

スパイダーはspidersディレクトリに作成されるのでそこで実際の処理を書いていきます。

robots.txtの動作確認

色々とやる前にScrapyがちゃんとrobots.txtを守ってくれるかを確認しておきます。

テスト用にこんなページを用意しました。

このテストサイトにはこのようなrobots.txtを配置します。

User-agent: *
Disallow: /scrapy/crawl-ng-page.html

User-agent: TestSpider
Disallow: /

genspiderでdemo.worklogというスパイダーを新規作成し、このような処理を書きます。

# -*- coding: utf-8 -*-
import scrapy
from blogScrapy.items import BlogscrapyItem
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class DemoWorklogSpider(CrawlSpider):
	name = 'demo.worklog'
	allowed_domains = ['demo.worklog.be']
	start_urls = ['https://demo.worklog.be/scrapy/']

	rules = (
		Rule(LinkExtractor(), callback='parse_item', follow=True),
	)

	def parse_item(self, response):
		item = BlogscrapyItem()
		item['url'] = response.url
		item['title'] = response.css('title::text').extract()
		yield item

follow=Trueでallowed_domainsに設定したドメインのリンクを再帰的に辿るクローラーです。

取得したHTMLはCSSセレクタを使ってスクレイピングしていますが、XPath形式でもこの処理は書けます。

これをこのようにして走らせてみます。

$ scrapy crawl demo.worklog

クローラーのログがこちらです。

2019-03-02 19:55:22 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://demo.worklog.be/robots.txt> (referer: None)
2019-03-02 19:55:26 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://demo.worklog.be/scrapy/> (referer: None)
2019-03-02 19:55:26 [scrapy.spidermiddlewares.offsite] DEBUG: Filtered offsite request to 'worklog.be': <GET https://worklog.be/>
2019-03-02 19:55:26 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://demo.worklog.be/scrapy/crawl-ng-page.html>
2019-03-02 19:55:30 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://demo.worklog.be/scrapy/crawl-ok-page.html> (referer: https://demo.worklog.be/scrapy/)
2019-03-02 19:55:31 [scrapy.core.scraper] DEBUG: Scraped from <200 https://demo.worklog.be/scrapy/crawl-ok-page.html>
{'title': ['Crawl OK'],
 'url': 'https://demo.worklog.be/scrapy/crawl-ok-page.html'}
2019-03-02 19:55:31 [scrapy.core.engine] INFO: Closing spider (finished)

robots.txtを読んでクロールNGなページはForbiddenとして処理しています。また、許可ドメイン以外のリンクはFiltered offsite requestとして無視していますので、きちんと動いているようです。

次に、settings.pyのUAに禁止クローラー「TestSpider」を設定してもう一度巡回します。

2019-03-03 12:17:02 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://demo.worklog.be/robots.txt> (referer: None)
2019-03-03 12:17:02 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://demo.worklog.be/scrapy/>
2019-03-03 12:17:02 [scrapy.core.engine] INFO: Closing spider (finished)

想定通りに拒否されました。

これで安心してクローラーを回せます。

HTMLのクロールとスクレイピング

一番代表的なクローラーとしてWebサイトのリンクを辿り全ページをクロール&スクレイピングするスパイダーを作成します。

先程実行したCrawlSpiderテンプレートを使うとこの手のクローラーが驚くほど簡単に作れます。

$ scrapy genspider -t crawl worklog worklog.be

試しに、このブログのURLとタイトルを全て抜き出すクローラーがこちらです。

class DemoWorklogSpider(CrawlSpider):
	name = 'worklog'
	allowed_domains = ['worklog.be']
	start_urls = ['https://worklog.be/']

	rules = (
		Rule(LinkExtractor(allow=r'/page/', unique=True), follow=True),
		Rule(LinkExtractor(unique=True), callback='parse_item'),
	)

	def parse_item(self, response):
		item = BlogscrapyItem()
		item['url'] = response.url
		item['title'] = response.css('title::text').extract()
		yield item

1番目のRuleでページネーションを辿り、見つけた内部リンクを2番目のRuleに渡して取得、その後parse_itemに投げてスクレイピングという流れになるようです。

量が多いので完了するまでに時間がかかりますが、-oオプションを付けてCSVに結果を出させるようにしてクローラーを動かしてみます。

$ scrapy crawl worklog -o result.csv

ログからもわかりますがこのように全てのリンク (ページ以外) とそのタイトルが抽出できました。

■ result.csv

title,url
work.log | 元エンジニアの備忘録的ブログ,https://worklog.be/
PyenvでCentOS6にPython3の開発環境を構築する | work.log,https://worklog.be/archives/3402
WordPressに新しい関数を追加する時はfunction_existsを付けた方が無難 | work.log,https://worklog.be/archives/3404
CentOSにSeleniumとGoogle Chrome & ChromeDriverをインストールする | work.log,https://worklog.be/archives/3422
Web経由でFFmpegのエンコード処理を行い進捗状況を表示するメモ | work.log,https://worklog.be/archives/3428
Amazonの偽レビューをscikit-learnで機械学習してみる | work.log,https://worklog.be/archives/3495

この少ないコードでここまで出来るクローラーが書けるScrapyは本当に強力です!

XMLのクロールとスクレイピング

次はXML用のテンプレートXMLFeedSpiderを使ってクローラーを作ってみます。

WordPress何かだとXMLで最新記事を配信しているので、わざわざHTMLをクロールするよりこっちの方が早い場合が多いです。

またgenspiderで雛形を作ります。

$ scrapy genspider -t xmlfeed xml.worklog worklog

クローラーの処理はこのように。

class XmlWorklogSpider(XMLFeedSpider):
	name = 'xml.worklog'
	allowed_domains = ['worklog.be']
	start_urls = ['https://worklog.be/feed']
	iterator = 'iternodes'
	itertag = 'item'

	def parse_node(self, response, selector):
		item = BlogscrapyItem()
		item['url'] = selector.xpath('link/text()').extract()
		item['title'] = selector.xpath('title/text()').extract()
		yield item

抽出処理はXPath形式でき書きます。

そしてクローラー実行。

2019-03-03 13:28:33 [scrapy.core.scraper] DEBUG: Scraped from <200 https://worklog.be/feed>
{'title': ['新語に対応したMeCabの辞書mecab-ipadic-NEologdを使ってみる'],
 'url': ['https://worklog.be/archives/3512']}
2019-03-03 13:28:33 [scrapy.core.scraper] DEBUG: Scraped from <200 https://worklog.be/feed>
{'title': ['SFTPでchrootを設定しつつSSHでログインできないユーザーを作成する'],
~ 省略 ~
2019-03-03 13:28:33 [scrapy.core.engine] INFO: Closing spider (finished)

CSVのクロールとスクレイピング

最後はCSV用のテンプレートCSVFeedSpiderも試してみます。

scrapy genspider -t csvfeed csv.worklog demo.worklog.be

クローラーの処理はこのように。headersは日本語もいけます。

class SaisonSpider(CSVFeedSpider):
	name = 'csv.worklog'
	allowed_domains = ['demo.worklog.be']
	start_urls = ['https://demo.worklog.be/scrapy/blog.csv']
	headers = ['タイトル', 'URL']
	delimiter = ','

	def parse_row(self, response, row):
		item = BlogscrapyItem()
		item['title'] = row['タイトル']
		item['url'] = row['URL']
		yield item

クローラーを実行するとこのように情報が取れます。

2019-03-03 20:30:12 [scrapy.core.scraper] DEBUG: Scraped from <200 https://demo.worklog.be/scrapy/blog.csv>
{'title': 'タイトル', 'url': 'URL'}
2019-03-03 20:30:12 [scrapy.core.scraper] DEBUG: Scraped from <200 https://demo.worklog.be/scrapy/blog.csv>
{'title': '新語に対応したMeCabの辞書mecab-ipadic-NEologdを使ってみる',
 'url': 'https://worklog.be/archives/3512'}
2019-03-03 20:30:12 [scrapy.core.scraper] DEBUG: Scraped from <200 https://demo.worklog.be/scrapy/blog.csv>
{'title': 'SFTPでchrootを設定しつつSSHでログインできないユーザーを作成する',
 'url': 'https://worklog.be/archives/3510'}
2019-03-03 20:30:12 [scrapy.core.scraper] DEBUG: Scraped from <200 https://demo.worklog.be/scrapy/blog.csv>
{'title': 'プラグインを有効化するだけで高速化するQuicklink for WordPressを試す',
 'url': 'https://worklog.be/archives/3505'}
~ 省略 ~
2019-03-03 13:28:33 [scrapy.core.engine] INFO: Closing spider (finished)

PythonはPandasがあるからCSVの処理は簡単ですが、更に楽が出来てしまう感じですね。

Scrapyが有能すぎてプログラミングした気が全くしないです。

クローラー運用のリスクについて

Scrapyを使えば誰でも簡単にクローラーが作れる事がわかったと思うのですが、クローラーを回す時はそれなりのリスク (法的) もある事を理解しておきましょう。

クローラーに関する国内の事件と言えば、岡崎市立中央図書館事件 (librahack事件) が有名ですがそもそも「robots.txtに従うだけで大丈夫なのか?」という疑問点もあります。

robots.txtはクローラーの共通のルール的存在だとは思うのですが、全てのクローラーがサイト運営者にとってWelcomeな存在では無いと思います。

実際に自分の元にもクローラー (スクレイピング) を阻止したいという相談があったりするのですが、サイトの動作に迷惑をかけなければOKという技術者達だけの暗黙の了解で情報収集ってのはちょっと乱暴じゃないかなとも思います。

GoogleBot以外のクローラーは招かれざる客と思っているサイト運営者も普通にいます。(そのクローラーが何者なのかもわからないしね)

こういう事情に明るくないサイト運営者も世の中には多く、サイトの何処かに「スクレイピング禁止!」ってテキスト書きしている場合もあります。

自分もそんな情報には気付かずクローラーを回しちゃってる訳ですが、個人でも弁護士と顧問契約を結んでいるという怖い人もいるので、後から訴えられないようにクロール前に対象サイトの規約等は確認しないとですね。

スクレイピングOKとでも書いてない限り絶対は無いんだろうけど。

Scrapyは凄い強力でクローラー開発者にとっては心強い味方ですが、あんまり無茶はせず情報収集しましょう。

スポンサーリンク

コメント

コメントを残す

よく読まれている記事

  • 今日
  • 週間
  • 月間