orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Pythonでクローリング・スクレイピングに使えるライブラリいろいろ

2016-12-09追記

Pythonクローリング&スクレイピング」という本を書きました!


これはクローラー/スクレイピング Advent Calendar 2014の7日目の記事です。

Pythonでクローリング・スクレイピングするにあたって、いろいろなライブラリがあるので一覧でまとめてみます。

以下の4つのカテゴリにわけて紹介します。

  • Webページを取得する
  • Webページからデータを抜き出す
  • Webページの自動操作
  • 総合的なフレームワーク

なんでこれが載ってないの?この説明はおかしい!などありましたらお気軽にお知らせください。なお、この記事はいろいろなライブラリを紹介することを目的にしているので、各ライブラリの細かい説明は他に譲ります。

サンプルで使用するPythonのバージョンは特に断りのない限り3.4で、一部3.xに対応していないライブラリは2.7を使います。

Webページを取得する

urllib.request

21.6. urllib.request — URL を開くための拡張可能なライブラリ — Python 3.4.2 ドキュメント

標準ライブラリです。Python 2.xではurllib2に相当します。

>>> from urllib.request import urlopen
>>> f = urlopen('http://qiita.com/advent-calendar/2014')
>>> f.code
200
>>> f.getheader('content-type')
'text/html; charset=utf-8'
>>> f.info().get_content_charset()
'utf-8'
>>> f.read()
b'<!DOCTYPE html><html xmlns:og="http://ogp.me/ns#"><head><meta charset="UTF-8" /><title>2014\xe5\xb9\xb4\xe3\x81\xaeAdvent Calendar\xe4\xb8\x80\xe8\xa6\xa7 - Qiita</title><meta charset="UTF-8" />...

requests

Requests: HTTP for Humans — Requests 2.5.0 documentation

HTTP for Humans、人間のためのHTTP Clientです。このサンプルではurllibとの違いがわかりにくいですが、Basic認証を使うなどHTTPヘッダーを扱うときには簡単さが際立ちます。

pip install requests
>>> import requests
>>> r = requests.get('http://qiita.com/advent-calendar/2014')
>>> r.status_code
200
>>> r.headers['content-type']
'text/html; charset=utf-8'
>>> r.encoding
'utf-8'
>>> r.text
'<!DOCTYPE html><html xmlns:og="http://ogp.me/ns#"><head><meta charset="UTF-8" /><title>2014年のAdvent Calendar一覧 - Qiita</title><meta charset="UTF-8" />...

aiohttp

KeepSafe/aiohttp

Python 3.3から使用可能なasyncioを使って非同期にページを取得できます。多数のWebサイトを高速にクロールしたいときに力を発揮するでしょう。

pip install aiohttp
>>> import asyncio
>>> import aiohttp
>>> def get_body(url):
...     response = yield from aiohttp.request('GET', url)
...     return (yield from response.text())
...
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(get_body('http://qiita.com/advent-calendar/2014'))
'<!DOCTYPE html><html xmlns:og="http://ogp.me/ns#"><head><meta charset="UTF-8" /><title>2014年のAdvent Calendar一覧 - Qiita</title><meta charset="UTF-8" />...

scrapelib

sunlightlabs/scrapelib

クロール先のサーバーに負荷をかけ過ぎないように、間隔を自動的に調整したりエラー時に自動でリトライしてくれます。内部ではrequestsを使っています。

pip install scrapelib
>>> import scrapelib
>>> s = scrapelib.Scraper(requests_per_minute=10)
>>> r = s.get('http://qiita.com/advent-calendar/2014')
>>> type(r)
<class 'requests.models.Response'>
>>> r.status_code
200
>>> while True:
...     r = s.get('http://qiita.com/advent-calendar/2014')
...     r.status_code
... 

200
200
200
...

Webページからデータを抜き出す

html.parser

20.2. html.parser— HTML および XHTML のシンプルなパーサー — Python 3.4.2 ドキュメント

標準ライブラリです。SAX形式のイベント駆動型のAPIなので、あまり複雑な処理には向いていませんが、開始タグだけが必要な時なら手軽に使えます。

Python 2.xではHTMLParserというモジュール名でした。

>>> from html.parser import HTMLParser
>>> from urllib.request import urlopen
>>> class MyHTMLParser(HTMLParser):
...     def handle_starttag(self, tag, attrs):
...         if tag == 'a':
...             print(dict(attrs).get('href'))
...
>>> f = urlopen('http://qiita.com/advent-calendar/2014')
>>> parser = MyHTMLParser()
>>> parser.feed(f.read().decode('utf-8'))
/
/login?redirect_to=%2Fadvent-calendar%2F2014
/signup?redirect_to=%2Fadvent-calendar%2F2014
/advent-calendar
/advent-calendar/2014
/advent-calendar/2014/new
/advent-calendar/2014/muda/feed
/advent-calendar/2014/muda
/advent-calendar/2014/softlayer2/feed
...

lxml

lxml - Processing XML and HTML with Python

libxml2とlibxsltのPythonバインディングです。処理の高速さと機能の豊富さでは他の追随を許しません。

pip install lxml
>>> import lxml.html
>>> root = lxml.html.parse('http://qiita.com/advent-calendar/2014').getroot()
>>> root.cssselect('title')[0]
<Element title at 0x10b391c78>
>>> root.cssselect('title')[0].text
'2014年のAdvent Calendar一覧 - Qiita'
>>> for a in root.xpath('//a'):
...     print(a.get('href'))
...
/
/login?redirect_to=%2Fadvent-calendar%2F2014
/signup?redirect_to=%2Fadvent-calendar%2F2014
/advent-calendar
/advent-calendar/2014
/advent-calendar/2014/new
/advent-calendar/2014/muda/feed
/advent-calendar/2014/muda
/advent-calendar/2014/softlayer2/feed
...

BeautifulSoup4

Beautiful Soup: We called him Tortoise because he taught us.

3まではPure Pythonのため処理の遅さが弱点でしたが、4からはパーサーを選択できるので、lxmlを使えば高速に処理できます。標準ライブラリのパーサーを使えばC拡張を使えない環境でも役立ちます。

日本語ドキュメントが存在するのも、とっつきやすいでしょう。

pip install beautifulsoup4
>>> from urllib.request import urlopen
>>> from bs4 import BeautifulSoup
>>> f = urlopen('http://qiita.com/advent-calendar/2014')
>>> soup = BeautifulSoup(f)
>>> soup.title
<title>2014年のAdvent Calendar一覧 - Qiita</title>
>>> soup.title.string
'2014年のAdvent Calendar一覧 - Qiita'
>>> for a in soup.find_all('a'):
...     print(a.get('href'))
...
/
/login?redirect_to=%2Fadvent-calendar%2F2014
/signup?redirect_to=%2Fadvent-calendar%2F2014
/advent-calendar
/advent-calendar/2014
/advent-calendar/2014/new
/advent-calendar/2014/muda/feed
/advent-calendar/2014/muda
/advent-calendar/2014/softlayer2/feed
...

pyquery

pyquery 1.2.9 : Python Package Index

jQueryライクなAPIを提供するので、jQueryに慣れている人には馴染みやすいでしょう。内部ではlxmlが使われます。

pip install pyquery
>>> from pyquery import PyQuery as pq
>>> d = pq(url='http://qiita.com/advent-calendar/2014')
>>> d('title')
[<title>]
>>> d('title').text()
'2014年のAdvent Calendar一覧 - Qiita'
>>> for a in d('a').items():
...     print(a.attr('href'))
...
/
/login?redirect_to=%2Fadvent-calendar%2F2014
/signup?redirect_to=%2Fadvent-calendar%2F2014
/advent-calendar
/advent-calendar/2014
/advent-calendar/2014/new
/advent-calendar/2014/muda/feed
/advent-calendar/2014/muda
/advent-calendar/2014/softlayer2/feed

feedparser

feedparser 5.1.3 : Python Package Index

RSSなどのフィードをパースするための定番ライブラリです。フィードの種類に依らずに同じ書き方ができるため、標準ライブラリのxml.etree.ElementTreeなんかでパースするより簡単です。

pip install feedparser
>>> import feedparser
>>> d = feedparser.parse('http://qiita.com/advent-calendar/2014/crawler/feed')
>>> d.feed.title
'クローラー/スクレイピング Advent Calendarの投稿 - Qiita'
>>> for entry in d.entries:
...     print(entry.link)
...
http://blog.takuros.net/entry/2014/12/06/235232
http://blog.takuros.net/entry/2014/12/05/061034
http://happyou-info.hatenablog.com/entry/2014/12/04/005504
http://qiita.com/nezuq/items/3cc9772118ad112c18dc
http://blog.takuros.net/entry/2014/12/02/234959

Webページの自動操作

Mechanize

mechanize

PerlのWWW:MechanizeのPython版です。ログインが必要なページのスクレイピングに向いています。最終更新は2011年で、Python 3に対応していません。以下のサンプルはPython 2.7で動かしたものです。6日目のdkfjさんの記事と同様にAmazonアソシエイトから売上を取得するサンプルです。

pip install mechanize
pip install lxml  # lxmlはサンプルで使用しているだけで必須ではありません
>>> import mechanize
>>> import lxml.html
>>> br = mechanize.Browser()
>>> br.addheaders = [('User-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36')]
>>> br.open('https://affiliate.amazon.co.jp')
<response_seek_wrapper at 0x1020b95f0 whose wrapped object = <closeable_response at 0x1020bd878 whose fp = <socket._fileobject object at 0x100d1a350>>>
>>> print(br.title())
Amazonアソシエイト(アフィリエイト)プログラムに参加しよう!
>>> br.select_form(name='sign_in')
>>> br['username'] = 'YOUR_EMAIL'
>>> br['password'] = 'YOUR_PASSWORD'
>>> response = br.submit()
>>> print(br.title())
Amazon アソシエイト(アフィリエイト) - ホーム
>>> root = lxml.html.parse(response).getroot()
>>> print(root.cssselect('#mini-report .line-item-total .data')[0].text)
¥0

selenium

selenium 2.44.0 : Python Package Index

SeleniumPythonバインディングです。FirefoxChromeなどのブラウザを自動操作したり、PhantomJSのようなヘッドレスブラウザを使うことができます。JavaScriptを使ったページにも対応できる点が強みです。

pip install selenium
>>> from selenium import webdriver
>>> driver = webdriver.Firefox()
>>> driver.get('https://affiliate.amazon.co.jp')
>>> driver.title
'Amazonアソシエイト(アフィリエイト)プログラムに参加しよう!'
>>> driver.find_element_by_name('username').send_keys('YOUR_EMAIL')
>>> driver.find_element_by_name('password').send_keys('YOUR_PASSWORD')
>>> driver.find_element_by_name('password').submit()
>>> driver.title
'Amazon アソシエイト(アフィリエイト) - ホーム'
>>> driver.find_element_by_css_selector('#mini-report .line-item-total .data').text
'¥0'

Splinter

Splinter — Splinter 0.7.0 documentation

SeleniumFirefox, Chrome, Remote, PhantomJSの各種ドライバーに加えて、zope.testbrowserなどもラップしており目的に応じて使い分けられます。シンプルで使いやすいAPIが特徴です。

pip install splinter

デフォルトではFirefox WebDriverが使われますが、以下のサンプルではPhantomJS WebDriverを使います。*1

PhantomJSがインストールされていない場合はインストールします。Mac以外の方はググってください。。

brew install phantomjs
>>> from splinter import Browser
>>> browser = Browser('phantomjs', user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36')
>>> browser.visit('https://affiliate.amazon.co.jp')
>>> browser.title.decode('utf-8')
'Amazonアソシエイト(アフィリエイト)プログラムに参加しよう!'
>>> browser.fill('username', 'YOUR_EMAIL')
>>> browser.fill('password', 'YOUR_PASSWORD')
>>> browser.find_by_value('サインイン').click()
>>> browser.title.decode('utf-8')
'Amazon アソシエイト(アフィリエイト) - ホーム'
>>> browser.find_by_css('#mini-report .line-item-total .data').text
'¥0'

総合的なフレームワーク

Scrapy

Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

Pythonが誇るスクレイピングフレームワークです。Python 3への対応作業は進められていますが、現在はPython 2.7のみ対応です。

pip install scrapy

他のライブラリと違いフレームワークなので、インタラクティブシェルを使わずにファイルを作成します。

advent_spider.py

from scrapy import Spider, Item, Field

class AdventCalendar(Item):
    title = Field()

class AdventCalendarSpider(Spider):
    name = 'advent_spider'
    start_urls = ['http://qiita.com/advent-calendar/2014']

    def parse(self, response):
        return [AdventCalendar(title=e.extract()) for e in response.css('td.adventCalendar_calendarList_calendarTitle a:nth-child(2)::text')]
$ scrapy runspider advent_spider.py
...
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Crawled (200) <GET http://qiita.com/advent-calendar/2014> (referer: None)
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'1\u5186\u306b\u3082\u306a\u3089\u306a\u3044\u7121\u99c4\u306a\u6280\u8853'}
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'1\u5206\u3067\u5b9f\u73fe\u3067\u304d\u308b\u6709\u7528\u306a\u6280\u8853'}
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'2\u679a\u76ee SoftLayer '}
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'Abby'}
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'Adobe'}
2014-12-07 22:47:47+0900 [advent_spider] DEBUG: Scraped from <200 http://qiita.com/advent-calendar/2014>
{'title': u'AngularJS'}
...

このサンプルはフレームワークとしての通常の使い方ではないので、以前書いた記事もどうぞ。

PythonとかScrapyとか使ってクローリングやスクレイピングするノウハウを公開してみる! - orangain flavor

まとめ

いかがでしたでしょうか。いろいろな選択肢を知り、適切なものを選ぶ助けになれば幸いです。

複数のライブラリを紹介した3つのカテゴリにおいて1つを選ぶとしたら、個人的には以下のものが定番かと思います。

  • Webページを取得する:requests
  • Webページからデータを抜き出す:lxml
  • Webページの自動操作:selenium

今回調べて初めて知ったライブラリもありました。他にももっと良いものがありましたらぜひ教えて下さい。

*1:PhantomJSを使うことでFirefoxを使うよりもコードが複雑になり、seleniumのサンプルよりも若干複雑に見えますが、これに他意はありません。本来であればseleniumでもヘッドレスのPhantomJSを使いたかったのですが、seleniumでUser-agentヘッダを変更するのが面倒すぎたので仕方なくFirefoxを使いました。