orangain flavor

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

2016年を振り返って

2016年を振り返って

今年はやはり書籍「Pythonクローリング&スクレイピング」を出版できたのが大きかったです。お陰様で良い評価をいただけているようで、ありがたいことです。

scraping-book.com

2013年の振り返りの時点で、やりたいことの1つとして「ブログよりも長いまとまった記事を執筆する」を挙げており、なんとなく書籍の執筆とかやってみたいとは思ってました。ですが、こんなにも都合よく機会が来るとは思いもしませんでした。

コミュニティ活動や発表をしてたわけでもなく、ただブログを書いていただけで、面識のない編集者からオファーを頂けたのは、話すより書くほうが好きな自分にとっては幸せでした。2年かかりましたが、自分の実績として残るものができて嬉しいです。

今年からできた神戸Pythonの会にも参加しています。Pythonについてオフラインで話せる機会・人が増えて、毎回楽しいです。

あとは、今年からボードゲームをよくやるようになりました。カタンパンデミックから始めてどんどんハマっていきました。月に1回ほど集まって友人・知人とプレイしています。いつも気付くと夕方になっていて、世の中にある楽しいゲームの多さにただ驚くばかりです。ボードゲームの楽しさをまだうまく言語化できていませんが、結局のところ、自分なんかよりずっと賢いゲームデザイナーの手のひらの上で懸命に頭を使って踊るのが楽しいのではないかと思っています。

各方面で多くの方のお世話になりました。ありがとうございました。

2017年に向けて

長い間本を書いていて、他のことに使える時間が少なかったので、今後はいろいろ作りかけのものを完成させて公開したいです。2015年の振り返りにも同じことが書いてあり、成長していない感じが辛いですが、来年こそは進められるはずです。

書籍の執筆という大きな仕事も落ち着いたので、今後どのように生きていくかじっくりと考えたいと思います。

今後ともよろしくお願いします。

PhantomJSとか使わずに簡単なJavaScriptを処理してスクレイピング

この記事はクローラー/Webスクレイピング Advent Calendar 2016 16日目の記事です。

JavaScriptが使われているWebページからスクレイピングする場合、PhantomJSなどのヘッドレスブラウザーを使うのが一般的です。 ただ、ちょっとしたJavaScriptを解釈できれば十分な場合、オーバーキルなこともあります。

この記事では、PhantomJSとかを使わずに簡単なJavaScriptを処理する方法を解説します。

どんな場合に役立つの?

NHKニュースのWebサイトを題材として取り上げます。

http://www3.nhk.or.jp/news/html/20161215/k10010807361000.html

NHK NEWS WEBのRSSではニュースのタイトルとURL、概要しか提供されていません。画像のURLやニュースの本文を取得したい場合は、スクレイピングが必要です。

普通にスクレイピングしてもいいですが、ソースを見るとJavaScript__DetailProp__という変数が定義されています。この変数の値のオブジェクトをパースできればXPathCSSセレクターでパースするより楽ですし、変更に強くなりそうです。

<script type="text/javascript">
   var __DetailProp__ = {
       cate: '5',
       date: '12月15日 4時04分',
       datetime: '2016-12-15T04:04',
       //id: 'k10010807361000',
       //video: 'k10010807361_201612150440_201612150458.mp4',
       video: 'https:\/\/www3.nhk.or.jp\/news\/html\/20161215\/movie\/k10010807361_201612150440_201612150458.html',
       duration: 87,
       img: 'html\/20161215\/K10010807361_1612150440_1612150458_01_03.jpg',
       title: '\u7C73\uFF26\uFF32\uFF22 \u8FFD\u52A0\u5229\u4E0A\u3052\u6C7A\u5B9A \uFF11\u5E74\u3076\u308A',
       summary: '\u30A2\u30E1\u30EA\u30AB\u306E\u4E2D\u592E\u9280\u884C\u306B\u5F53\u305F\u308B\uFF26\uFF32\uFF22\uFF1D\u9023\u90A6\u6E96\u5099\u5236\u5EA6\u7406\u4E8B\u4F1A\u306F\u3001\uFF11\uFF14\u65E5\u307E\u3067\u958B\u3044\u305F\u91D1\u878D\u653F\u7B56\u3092\u6C7A\u3081\u308B\u4F1A\u5408\u3067\u3001\u30A2\u30E1\u30EA\u30AB\u7D4C\u6E08\u306F\u62E1\u5927\u3057\u3066\u3044...',
       more: '\uFF26\uFF32\uFF22\u306F\uFF11\uFF14\u65E5\u307E\u3067\u306E\uFF12\u65E5\u9593\u3001\u30EF\u30B7\u30F3\u30C8\u30F3\u3067\u91D1\u878D\u653F\u7B56\u3092\u6C7A\u3081\u308B\u516C\u958B\u5E02\u5834\u59D4\u54E1\u4F1A\u3092\u958B\u304D\u307E\u3057\u305F\u3002<br /><br />\u7D42\u4E86\u5F8C\u767A\u8868\u3055\u308C\u305F\u58F0\u660E\u306B\u3088\u308A\u307E\u3059\u3068\u3001\uFF26\uFF32\uFF22\u306F\u3001\u30A2\u30E1\u30EA\u30AB\u7D4C\u6E08\u306B\u3064\u3044\u3066\u3001\u96C7...',
       body: [{
           title: '\u4ECA\u5F8C\u306E\u91D1\u5229\u306E\u898B\u901A\u3057\u306F',
           img: '',
           align: '',
           text: '\uFF26\uFF32\uFF22\u306F\uFF11\uFF14\u65E5\u3001\u30A4\u30A8\u30EC\u30F3\u8B70\u9577\u306A\u3069\u91D1\u878D\u653F\u7B56\u3092\u6C7A\u3081\u308B\u4F1A\u5408\u306E\u53C2\u52A0\u8005\u304C\u4E88\u6E2C\u3057\u305F\u3001\u4ECA\u5F8C\u306E\u91D1\u5229\u306E\u898B\u901A\u3057\u3092\u516C\u8868\u3057\u307E\u3057\u305F\u3002<br /><br />\u305D\u308C\u306B\u3088\u308A\u307E\u3059\u3068\u3001\u6765\u5E74\u306E\u5229\u4E0A\u3052\u306E\u56DE\u6570\u306E\u4E2D\u5FC3\u7684\u306A\u60F3\u5B9A\u306F\uFF13\u56DE\u3068\u3001\uFF19\u6708\u306E\u6BB5\u968E\u306E\uFF12\u56DE\u3088\u308A\u3082\u5897\u3084\u3057\u3066\u3044\u3066\u3001\u30C8\u30E9\u30F3\u30D7\u6C0F\u306E\u7D4C\u6E08\u653F\u7B56\u306B...',
           textPos: ''
       },{
           title: '\u300C\u30BC\u30ED\u91D1\u5229\u653F\u7B56\u89E3\u9664\u300D\u304B\u3089\u306E\uFF11\u5E74\u306F',
           img: '',
           align: '',
           text: '\uFF26\uFF32\uFF22\u306F\u53BB\u5E74\uFF11\uFF12\u6708\u3001\u3044\u308F\u3086\u308B\u30EA\u30FC\u30DE\u30F3\u30B7\u30E7\u30C3\u30AF\u306E\u3042\u3068\u5C0E\u5165\u3057\u3066\u3044\u305F\u300C\u30BC\u30ED\u91D1\u5229\u653F\u7B56\u300D\u3092\u3001\u96C7\u7528\u306E\u6539\u5584\u306A\u3069\u3092\u7406\u7531\u306B\u89E3\u9664\u3059\u308B\u3053\u3068\u3092...',
           textPos: ''
       }]
   };
</script>

JavaScript以外の言語でこのオブジェクトをパースするのは少し面倒なので、JavaScriptエンジンを使います。

組み込みJavaScriptエンジン:Duktape

この程度のオブジェクトをパースするのであれば、ブラウザーの機能は不要でJavaScriptエンジンが動けば十分です。 幸い、いろいろな言語から使える組み込みのJavaScriptエンジンはいくつかあり、ここではDuktapeを取り上げます。

Duktape

真面目に比較検討したわけではないですが、ポータビリティの高さとフットプリントの小ささが特徴のようです。 いろいろな言語のバインディングがあります。

PythonでもPyPIduktapeで検索すると、以下のライブラリが見つかります。

ここでは一番よくメンテナンスされている感じのdukpyを使用します*1

動かしてみる

環境の準備

Python 3.5.1を使い、pipでdukpyをインストールします。バージョン0.0.6を使います。

(venv) $ pip install dukpy

ソースコード

以下のようなコードでスクレイピングできます。script要素内のJavaScriptのコードを取得し、dukpy.evaljs()で実行します。

nhknews.py

import urllib
import re

import dukpy

# Webページを取得する。
f = urllib.request.urlopen('http://www3.nhk.or.jp/news/html/20161215/k10010807361000.html')

# HTMLのボディをUnicode文字列にデコードする。
html = f.read().decode('utf-8')

# 正規表現でscript要素の中身を取得する。
# re.DOTALLで正規表現中の.が改行にもマッチするようになる。
m = re.search(r'<script type="text/javascript">(.*?)</script>', html, re.DOTALL)

# 正規表現でキャプチャしたscript要素の中身を取得する。
script = m.group(1)

# dukpyでJavaScriptを実行し、変数__DetailProp__の値を取得する。
js_obj = dukpy.evaljs([script, '__DetailProp__'])

# 取得できた値(タイトル、画像のURL、もっと読むの中身)を表示する。
print(js_obj['title'])  # タイトル
print(js_obj['img'])    # 画像のURL(/news/からの相対URL)
print(js_obj['more'])   # もっと読むの中身

実行

実行すると次のようになり、JavaScriptの変数に格納されていた値を取得できていることがわかります。

(venv) $ python nhknews.py
米FRB 追加利上げ決定 1年ぶり
html/20161215/K10010807361_1612150440_1612150458_01_03.jpg
FRBは14日までの2日間、ワシントンで金融政策を決める公開市場委員会を開きました。<br /><br />終了後発表された声明によりますと、FRBは、アメリカ経済について、雇用の伸びなどを背景に緩やかなペースで拡大しているとして、全会一致で政策金利を引き上げることを決めました。<br /><br />具体的には、0.25%から0.5%の範囲となっている今の政策金利を、0.25%引き上げて、0.5%から0.75%の範囲にします。<br /><br />FRBが利上げに踏み切るのは、去年12月にゼロ金利政策を解除して以来、1年ぶりとなります。...

解説

dukpy.evaljs()関数には、JavaScriptのコードを文字列、または文字列のリストとして渡します。文字列のリストを渡した場合は、同じコンテキストで順に実行されます*2。戻り値は、最後に評価された値です。

今回の場合、script要素の中身はvar文だけなので、そのままだと評価される値はundefinedPythonではNone)となります。このため、リストの2つ目の要素として変数名を与えることで、変数__DetailProp__を評価し、その値を得ます。

JavaScriptのオブジェクトはPythondictにいい感じに変換されます。 dukpyの詳しい使い方はドキュメントを参照してください。

まとめ

Duktapeを使うといろいろな言語から簡単にJavaScriptのコードを実行できます。 Ajaxのようなブラウザーに依存する機能は使えませんが、PlainなJavaScriptを実行できれば十分な場合には便利です。

正直JavaScriptの変数をサーバーサイドで書き出すのはバッドプラクティス*3なので、使える場面は少ないかもしれません。 しかし、こういう手段も知っておくと役に立つ日が来るかもしれません。

宣伝

Pythonクローリング&スクレイピングという本を書きました。紙版・電子書籍版共にちょうど本日発売でした。 この本の中では、オーソドックスにSeleniumとPhantomJSを使ってJavaScriptを使ったページに対応する方法を解説しています。

*1:これはProjects using Duktape (alphabetical order)からリンクされているkovidgoyal/dukpyとは別物です。kovidgoyal/dukpyはPyPIには登録されていないようです。

*2:リストの要素をセミコロン区切りで接続した文字列を実行するのと同等の結果になります。

*3:参考: HTMLのscriptタグ内に出力されるJavaScriptのエスケープ処理に起因するXSSがとても多い件について - 金利0無利息キャッシング – キャッシングできます - subtech

Pythonクローリング&スクレイピングの電子書籍版も12月16日発売です

Pythonクローリング&スクレイピング電子書籍版も紙版と同じく12月16日発売となりました。

書籍の詳しい中身については以下の記事もご参照ください。

orangain.hatenablog.com

書籍を書きながらOSSに貢献した話

Pythonクローリング&スクレイピングでは、クローリング・スクレイピングやデータ解析のための様々なライブラリを紹介しています。 書籍でOSSのライブラリを紹介すると、そのライブラリに貢献する機会やインセンティブが生まれると考えています。

書籍で紹介すると貢献したくなる

書籍で解説するには、ドキュメントやソースコードを読んでライブラリの正確な挙動を知る必要があります。 ドキュメントが曖昧な場合、そうして得られた知識でドキュメント改善のための指摘や提案ができるようになります。

書籍で紹介するライブラリで変更の大きな新バージョンが公開される場合、Alpha版やBeta版、RC (Release Candidate) 版で検証して、書籍内のコードが正しく動くことを確認する必要があります。動かない場合は自分で直すなり、早めに直るよう報告するなりしたくなるでしょう。

ライブラリの挙動が不自然な場合、「〜については注意が必要です」のように注記するよりも、いっそ改善してしまったほうが解説がシンプルになることもあります。特に1つのライブラリだけが書籍で扱う実行環境から外れてしまうような場合は、そのライブラリに対応してもらえたら解説が楽になります*1

Pythonクローリング&スクレイピングの場合、全編でPython 3を使っていますが、執筆を始めた時点ではPython 2.7のみに対応しているライブラリもありました。Python 3に対応してもらえるようPull Requestを送ったものもあります。

貢献したライブラリなど

せっかくなので、貢献したライブラリをまとめておきます。 並び順はPythonクローリング&スクレイピング(以降で書籍内と言った場合はこの本を指します。)での登場順です。

Beautiful Soup

Beautiful Soupはスクレイピングのための有名なライブラリです。 CSSセレクターで,の挙動がおかしかったのを修正しました。

WikiExtractor

WikiExtractorはWikipediaダンプからテキストだけを抽出するコマンドです。 Python 2.xにしか対応してなかったので、Python 3にも対応させました。

python-amazon-simple-product-api

python-amazon-simple-product-apiAmazonProduct Advertising APIのラッパーです。 インストール時に依存ライブラリが正しくインストールされるようにしました。

PDFMiner.six

PDFMiner.sixはPDFからテキストやオブジェクトを抽出するライブラリです。 インストールが簡単になるようにしたり、コマンドの実行結果がおかしかった箇所を修正しました。

RoboBrowser

RoboBrowserはWebページの自動操作のためのライブラリです。 chardetというライブラリがインストールされていない環境でも文字化けが起きにくくする修正をマージしてほしかったですが、未反応のままです。

MechanicalSoup

MechanicalSoupもRoboBrowserと同様の自動操作のためのライブラリです。書籍内では名前が登場するのみです。 ドキュメントを修正しました。

BigQuery-Python

BigQuery-PythonPythonからGoogle BigQueryを扱うクライアントです。 credentials.jsonというファイルを置いた場合に、コードが簡潔になるようにしました。

Scrapy

Scrapyはクローリング・スクレイピングのためのフレームワークです。 ドキュメントのわかりにくい箇所を修正したり、Python 3に対応したVer. 1.1 RC1が出た時に問題を報告・修正したりしました。 1個目のドキュメントの目次を見やすくしたPull Requestはお気に入りです。

OpenCV

OpenCVはコンピュータービジョンのためのライブラリです。 Debian上でPython 3からOpenCVを使えるようにするpython3-opencvパッケージを実現するパッチを投稿しましたが、特に進展なしでした。

このため、Ubuntu 14.04と16.04向けにpython3-opencvパッケージを含むPPA (Personal Package Archives) を作成しました。

rq

rqはRedisをバックエンドとした軽量なメッセージキューを実現するライブラリです。 ドキュメントが古い箇所やサンプルコードがPython 3で動かない箇所などを修正しました。

ウォッチしてたけど進まなかった問題

以下は特にできることがないのでウォッチしてましたが、残念ながら特に進展のなかった問題です。

MeCabPython 3対応

MeCab公式のPythonバインディングPython 3をサポートするPull Requestがあり、ウォッチしてましたがマージされていません。書籍内では代わりにmecab-python3を紹介しています。

Ubuntu 16.04のVagrant Box

Pythonとは直接関係ないですが、Ubuntu 16.04のVagrant Box (ubuntu/xenial64) がVagrant向けに設定されてなく、まともに使えないという問題があります。 このため、書籍内で使用する環境はUbuntu 14.04のままです*2

まとめ

1つ1つは簡単なものばかりですが、少しでも世界を良くする手伝いができて良かったです。

Beta版やRC版を出しているにも関わらず正式版リリース後にバグ報告が来るという話をたまに聞きます。書籍を執筆することで、正式版リリース前に検証するインセンティブが働くのは非常に良いことだと思います。書籍を書く機会はあまりないので、もう少しカジュアルにこの効果を活用できないかと考えています(小さ目の電子書籍を書くとか?)。

また、個人で開発しているライブラリはドキュメントが不十分だったり、言語のツールチェインとの連携がスムーズでなかったり*3するので、貢献すると喜ばれるでしょう。

GitHubのおかげでOSSに貢献しやすい環境が整っているので、ぜひ気がついたことはどんどん報告・修正していきましょう。

おまけ:英語でのIssue/Pull Requestのコツ

英語のIssueやPull Requestでうまく伝わらないと感じることがあるかもしれません。 コツとしては、一定のフォーマットの小見出し*4に沿って書き、なるべく長い文章を書かないようにすることだと思います。

IssueであればEnvironmentとHow to Reproduceを示し、Expected BehaviorとActual Resultを、実際のコマンドの実行結果やスクリーンショットなどを示してわかりやすく対比させます。文章を読まなくてもわかるようにすると、英語の説明の拙さを補えます。

Pull Requestの場合はまず、変更を小さくして解決したい問題にフォーカスするのが重要です。 Issueと同様に、BeforeとAfterをわかりやすく対比させると良いでしょう。 変更の動機を説明するのが難しい場合は、まず問題を明確にするためのIssueを作成し、それをFixする形でPull Requestを作成するとシンプルに説明できるようになることもあります。

参考: 英語圏の開発者に初めてバグレポートを出す時の5つのポイント【連載:コピペで使えるIT英語tips】 - エンジニアtype

関連

*1:もちろんその実行環境が妥当な場合の話です。

*2:一部Ubuntu 16.04向けの補足もあります。

*3:Pythonであればpipでインストールできないとか。

*4:Issue TemplateやPull Request Templateがあればそれです。

Pythonクローリング&スクレイピングの見本が届きました

昨日Pythonクローリング&スクレイピングの見本が届きました。 表紙はきれいな青色と印象的なタイポグラフィで気に入ってます。

f:id:mi_kattun:20161212213508j:plain

横から見ると、実際のWebサイトを対象としてデータを収集・解析する5章と、Scrapyを扱う6章に多くのページを割いていることがわかります。

f:id:mi_kattun:20161212222515j:plain

どのページを開いても自分の書いた文章がでてくるのは初めての経験で、若干戸惑います。

400ページに渡る原稿はとても1人では作り上げられませんでした。最初にお話をいただいた時から、長きに渡ってサポートしてくださった技術評論社の野田さんのお陰です。作業の見積もりが下手でご迷惑をお掛けすることも多かったですが、無事完成して良かったです。ありがとうございました。

また、いろいろな方にレビューをお願いしました。お会いしたことのない方、昔一緒にコードを書いたけど長らく会ってない方、よく会っている方、最近知り合った方など様々ですが、ぜひレビューしていただきたいと思った方ばかりです。 レビュアーの皆様のお陰で、よりわかりやすく正確な内容になりました。本当にありがとうございました。

レビューをお願いする際は引き受けていただけるか不安もありましたが、お声がけした方には全員快諾していただけてとても幸せでした。いい歳した大人の台詞ではありませんが、ちょっとの勇気を出してお願いすることの大切さを学びました。

発売日は12/16ですが、都内の一部の大型書店では先行販売も始まっています。

電子書籍も着々と準備中です。ぜひ多くの皆様に読んでいただけたら幸いです。

書籍執筆でお世話になったツール 〜Re:VIEW, textlint, prh, goemon, GitHub, CircleCI〜

Pythonクローリング&スクレイピング」という書籍を執筆しました。執筆にあたって色々なツール・サービスのお世話になったので、記録を残しておきます。

大まかな流れは、以前の記事に書いたとおりです。ツールの選定は2015年1月頃(textlintとprhは2016年1月頃)に行ったので、現在では状況が変わっているものもあるかもしれません。ご了承ください。

目次

Re:VIEW

原稿の形式は著者の自由とのことでしたので、Re:VIEWで書きました。

Re:VIEW - Digital Publishing System for Books and eBooks

採用理由

技術評論社さんではWordやMarkdownが多いということでしたが、WordはGitでバージョン管理しづらいし、Markdownは表現力が乏しいのが気になるところでした*1

SphinxやAsciiDocなども検討しましたが、Sphinxは書籍向けを想定したものではなさそうなこと、AsciiDocよりもRe:VIEWのほうが日本の出版社で実績がありそうだったことから、Re:VIEWを採用しました。あとなんか面白そうでした。

良いところ

1つのソースからPDF、ePubを作ることができ、書籍の表現に必要な要素(画像・表・リストの参照、別の章・節・項の参照や、リード文、キーワードなど)が充実しているのが良かったです。あと表の記法がMarkdownより圧倒的に書きやすいのが好きです。

review-ext.rbというファイルにRubyのコードを書くことで、挙動を簡単にカスタマイズできるのも便利でした。

振り返って

結果的にRe:VIEWという選択が正しかったのかよくわかりません。Re:VIEWが生成する成果物は最終的な成果物としては使われていないからです。

紙面のデータはRe:VIEWで出力したPDFとは別にDTPで作成したものが使われています。DTPの際もRe:VIEWで出力したInDesign XMLがうまく処理できないということで、Re:VIEWのフォーマットから無理矢理Markdownに変換してもらいました*2

電子書籍についても、紙版をベースにPDF版を作成する都合上、ePub版だけRe:VIEWで生成したものを使うと確認コストが上がるということで、ePUB版もPDF版をベースに作られています。

結局、Re:VIEWで生成したPDFは紙面のイメージを掴むためと、レビュアーの皆様に読んでいただくためだけに使用しました。PDFで思い通りの見た目にするためにLaTeXとの格闘に時間を費やしましたが、LaTeXで詰まると本当に辛いので、細かい見た目の問題はもっと割り切れば良かったです。

もちろんこの辺はRe:VIEWの経験が豊富な出版社さんであれば違うと思います。書籍制作のフローをよくわかっていない著者が最適なツールを選ぶのは難しいなという感想です。

実装を見る機会も多かったですが、実装やドキュメントから意図が読み取れず、疑問に思ったところがいくつかありました。

  • 章への参照は番号や見出しを表示するかどうかに応じて@<chap>{} @<title>{} @<chapref>{}と3種類あるのに、節や項への参照は@<hd>{}しかないのはなぜなんだろう。
  • //tsize//latextsizeはPDFで表の見た目を整えるのに必須なのにマニュアルに載ってないのはなぜなんだろう。
  • //infoとかが一部のBuilderでしか実装されていないのはなぜなんだろう。 他のBuilderでも使えるようになり、ドキュメントにも記載されていました。
  • リスト(ソースコード)は外部ファイルにわけるべきなんだろうか。#@mapfileの使い方がよくわからない。 プリプロセッサのドキュメントが追加されていました。

作ったものとか

書いている途中にvim-reviewのforkとDash用のdocsetを作りました。

textlint

原稿の校正にはtextlintを使用しました。

採用理由

RedPenも検討しましたが、設定をXMLで書いたりプラグインJavaで書いたりするのは辛そうとなことと、(当時調べた限りは)すぐに使える表記ゆれ修正の辞書が見つからなかったことからtextlintを採用しました。あとなんか面白そうでした。

良いところ

textlintはRe:VIEWなどの構文を解析してマークアップを除いた文章だけを対象として校正できるので、誤判定の少なさがポイントです。その分エディタで保存時に実行すると若干時間がかかります。Vimのsyntasticで使えるようになっていて良かったです。

fixableと呼ばれる誤りを自動で修正可能なルールもあり、一括で修正できるのは最高でした。あとは作者のazuさんのレスポンスの速さと開発スピードに驚きました。

振り返って

textlintを導入したのが原稿が一旦書き上がったぐらいのタイミングだったので、十分に効果を発揮することはできなかったように思います。後述のprhを使った表記ゆれのチェックが主で、他のルールは参考程度という感じになってしまいました。プログラムを書く時に開発初期からCIを取り入れたほうがいいのと同じように、執筆初期からtextlintを導入していれば最初からより良い文章を書けたと思います。

textlintはコンテキストによって異なる規則を適用できます。例えば本文は「ですます調」で書くが、箇条書きは「である調」で書くなどです。これは便利ですが、例えば画像のキャプションは何調で書くべきなのか、ソースコード中のコメントは何調で書くべきなのかなど、なかなか難しいです。コンテキストに応じた場合分けがルール内で定義されていると、それを上書きできなくて困ることもありましたが、どこで定義すべきなのかは自分の中でも答えが出ていません。

作ったものとか

Re:VIEWのプラグインがなかったので、自分で作成しました。 パーサーを雑に書けるのはRe:VIEWの設計思想の良い点だと思います*3

prh

表記ゆれを修正するためにprhを使いました。 prhコマンドを直接使用したわけではなく、textlintのルール textlint-rule-prh 経由で使用しました。

採用理由

手軽に表記ゆれのチェック・修正をしたくて使いました。 Re:VIEW形式に対応できそうなツールは他に見つかりませんでした。

良いところ

ルールベースで表記を統一できて非常に便利でした。textlint-rule-prhはfixableなルールなので、Re:VIEWの文法に合わせて必要な箇所だけを自動で修正できました。

振り返って

WEB+DB PRESSのルールを使用しましたが、カタカナ末尾の長音については、基本的に長音をつけるという表記を採用し、元のルールと反対にしたので書き直すのが大変でした。(prhに限らず)この辺をうまくどちらか選べる仕組みがあるといい気がしています。だいたい語尾の形で場合分けすることになるので、カタカナ語を英語表記に変換する辞書があればできそう。

goemon

Re:VIEW形式のファイルをブラウザでリアルタイムプレビューするために、goemonを使いました。 geomonはGoで書かれたLiveReload的なツールです。

採用理由

LiveReload的なツールは色々なものがありますが、原稿を書くのに特定の言語ランタイムに依存するのも変な気がしたのでGoemonを使いました。あとREADMEの画像がカッコよかったです。

良いところ

言語ランタイムがなくても動いて、YAMLの設定ファイルもわかりやすくて便利でした。

振り返って

OS Xだと変更を監視するファイル数が多すぎて困るという問題がありました。.gitignoreを参照して無視するみたいなことをやりたかったですが、あまり綺麗に実装できなかったので適当に手元でごまかして対処しました。最近この問題に対応するプルリクエストがマージされていました。

Re:VIEWのようなマークアップ言語ではありがちですが、慣れるに従ってリアルタイムプレビューは不要になるので、後半はあまり使いませんでした。

GitHub

編集者さんとの原稿のやり取りや課題の管理、情報共有はGitHubのプライベートリポジトリを使いました。

採用理由

筆者・編集者ともに普段から使っていて、自然と使うことになりました。

良いところ

あえて書くまでもない気もしますが、GitとGitHubは普段から使い慣れてて便利でした。GitHubのプルリクエストベースのフローが使えるかは編集者さん個人に依存すると思うので、ありがたかったです。

振り返って

最初に一通りの原稿を書く(脱稿)までは1章ごとにプルリクエストを作成していました。GitHubには1ファイルあたり1,000行以上のdiffを表示できないという制限があり、インラインでコメントできなくて困ることがありました。今見たら制限が緩和されていました

仕方ないのでRe:VIEWのreファイルをch05a.re, ch05b.re, ... のように複数に分けて対応しました。最初に作成した目次に合わせて節または項単位で書いていければ1ファイルが肥大化することもなかったと思いますが、最初は全体像が見えていなかったのでそのような書き方はできませんでした。

GitとGitHubは便利ですが、原稿の執筆に向いているかと言われるとちょっと違うような気がします。Gitは行単位で差分を管理しますが、原稿はソースコードに比べて1行が長くなりがちです。私は1行に1文を書くスタイルで書いていました。

1行が長くなると、修正の際に2つの異なるプルリクエストで同じ行を変更することが起こりえます。 プルリクエストAでは文の最初の方を修正し、プルリクエストBでは文の最後の方を修正するといった具合です。 すると後にマージされるプルリクエストは見事にコンフリクトします。

なので、Wordの校閲ツールのように、1行の中でも一部だけを修正して順次マージしていけるツールがオンラインで使えればそのほうがやりやすかったと思います。もちろん他のサービス・ツールとの連携しやすさもGitやGitHubの重要なポイントなので、総合的に判断する必要はありますが。

あとは、レビュー時には指摘事項をGitHubにIssueとして作成してもらいました。これはレビュアーの皆様にとってはやや面倒だったかとは思いますが、挙げてもらう側としてはその後の議論や追跡が楽で助かりました。ちょうどIssue Templateが使えるようになって良かったです。

最終的に300のIssueと200のプルリクエストが作られました。

CircleCI

継続的インテグレーションにはCircleCIを使いました。

採用理由

ある程度の使用経験があったことと、プライベートリポジトリでも1並列は無料で使えて助かることが採用理由です。

良いところ

CircleCIではRe:VIEWのビルドと、インタラクティブシェルやソースコードのテストを行いました。クローリングの書籍なので、クロール対象とするWebサイトの構造が変化したときに素早く気づけて良かったです。

振り返って

CircleCIのビルドでDocker Hub上のDockerイメージを使う場合、毎回docker pullを実行することになります。大きなイメージ*4だと時間がかかるので、docker loaddocker saveでキャッシュを作ることもできますが、それでもイメージの復元には時間がかかります。 この辺の時間を短くできて透過的にキャッシュされるような仕組みがあると嬉しいです。

インタラクティブシェルのテストはdoctestで行い、ソースコードのテストはユニットテストを書きました。テストが書きづらいコード(認証が必要、費用がかかる、Python以外のコマンドが関係するなど)は自動テストを書けず、エラーの発見が遅れてしまうこともありました。

成功したり失敗したりするテストケース*5もあり、ビルドの失敗に鈍感になってしまったのは反省点です。Gitのpushをトリガーとして開始するテストしか実行していませんでしたが、原稿の変更がない時期はテストが実行されないのも良くなかったです。ビルドに時間がかかる原因にもなるので、pushをトリガーとするテストは最小限にして、残りはNightly Buildなどで定期的に実行すればよかったです。

textlintは途中から導入したこともあり、textlintの指摘をビルド失敗にするとずっと成功しなさそうだったので、チェックを実行してCircleCI上で結果を見られるようにするのみとしました。

まとめ

改めて振り返ると色々なツール・サービスのお世話になりました。先人の積み上げてきたものに感謝します。

特にRe:VIEW, textlint, prhについては、使っていく中で気づいた点をIssueで報告したりプルリクエストを送ったりと、少しは貢献できたかと思います。今後も発展していくことを陰ながら応援しています。

*1:表現力を補うために独自記法を多用するのも可搬性が気になりました。

*2:Re:VIEWにもMarkdownBuilderはありますが、限定的な実装なので使用できませんでした。

*3:参考: http://www.slideshare.net/kenshimuto/review-41284727

*4:今回使用したイメージは2GB程度です。

*5:ビルドが実行されるインスタンスのIPの違いのせい?

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

2016-12-23更新: 電子書籍書籍版の情報を更新しました。電子書籍版も好評発売中です!

Pythonを使ってクローリング・スクレイピングを行い、データを収集・活用する方法を解説した書籍です。 Pythonの基本から、サードパーティライブラリを使ったスクレイピング、様々なサイトからのデータ収集・活用、フレームワークScrapyの使い方、クローラーの運用までを扱っています。

クローリング・スクレイピングPython

Pythonは言語自体の書きやすさ、ライブラリが充実していること、データ解析との親和性が高いことなどから、クローリング・スクレイピングに向いている言語です。 Pythonの初歩から解説しているので、何らかの言語の経験があればPythonの経験がない方でも読み進められる内容になっています。

また、クローリング・スクレイピングフレームワークScrapyの存在も大きいです。 本書ではScrapyに1つの章を割いて解説しています。日本語でScrapyの使い方をここまで詳しく解説した書籍は初めてではないかと思います。

ちなみに全編Python 3による解説です。Python 3.4で追加されたasyncioによる非同期I/Oの説明もあります。

章の構成

第1章「クローリング・スクレイピングとは何か」では、まずPythonを使わずにUnixコマンドだけで簡単にクローリング・スクレイピングを行います。 Unixコマンドを使うと、Pythonスクリプトを書くほどでないデータ処理が簡単にできるので知っておいて損はないと思います。

第2章「Pythonではじめるクローリング・スクレイピングでは、Pythonの標準ライブラリのみでクローリング・スクレイピングを行います。Pythonを使うと柔軟にスクレイピングできることを体感します。

第3章「強力なライブラリの活用」では、便利なサードパーティライブラリを使ってクローリング・スクレイピングを行います。 Requests、Beautiful Soup、lxmlなどの定番ライブラリを使うことで、標準ライブラリだけでは難しい処理が簡単にできるようになります。

第4章「実用のためのメソッド」では、実際のWebサイトを対象としてクローリング・スクレイピングを行う際に相手のWebサイトに迷惑をかけないための注意点を解説します。

第5章「クローリング・スクレイピングの実践とデータの活用」では実際に様々なWebサイトからデータを収集・活用します。 第5章は1番長く、書籍全体の1/4を占めています。 データセットAPIを使ったデータ収集、CSV/Excelファイルからの時系列データの取得、オープンデータの収集、Webページへのログイン、JavaScriptを使ったページへの対応などのトピックがあります。 データ活用の面では、日本語の自然言語処理、グラフによる可視化、RSSフィードの生成、地図による可視化、BigQueryによる解析などを扱っています。

第6章「フレームワーク Scrapy」では、強力なクローリング・スクレイピングフレームワークのScrapyを使って、効率のよいクローラーを簡単に作成します。 第6章も第5章に次ぐ長さで、ScrapyによるSpiderの作成方法からScrapyの設定やMiddlewareによる拡張方法まで幅広く解説しています。 Elasticsearchを使った検索サイトの作成やOpenCVによる顔画像の抽出などのデータ活用も解説します。

第7章「クローラーの継続的な運用・管理」では、クローラーを継続的に運用していくためのノウハウを解説します。 クラウド上でのサーバーでの運用や、メッセージキューを使ったクローリングとスクレイピングの分離などのトピックがあります。 マルチスレッド・マルチプロセス・非同期I/Oを活用したクローリングの高速化についても紹介します。

出版にあたって

最初にお話を頂いた時はちょっと驚きましたが、書いていたらクローリング・スクレイピングの話は意外と自分に合っていると感じました。 面倒な作業を自動化したり、使いづらいUIを再構成したりするために、クローラーを作成することがよくあったからです。

最初は数百ページも書けるか不安もありましたが、実際に書いてみると書きたいことが多くあり、ページ数を減らすのに苦労したなどいくつかの事情で丸2年もかかってしまいました。ただ、遅くなったお陰でScrapyやその他Python 2のみ対応だったライブラリがPython 3に対応して、わかりやすくなった面もあります。

クローリング・スクレイピングは泥臭い作業ですが、アイデア次第でいろいろな応用が考えられ、プログラミングの楽しさを手軽に味わえる分野だと思います。 ご興味ありましたらお買い求めいただけると幸いです。

目次

第1章 クローリング・スクレイピングとは何か
    1.1.  本書が取り扱う領域
        1.1.1.  クローリングとスクレイピング
        1.1.2.  クローリング・スクレイピングとPython
        1.1.3.  本書が対象とするプラットフォーム
        1.1.4.  本書の構成
    1.2.  Wgetによるクローリング
        1.2.1.  Wgetとは
        1.2.2.  Wgetの使い方
        1.2.3.  実際のサイトのクローリング
    1.3.  Unixコマンドによるスクレイピング
        1.3.1.  Unixコマンドの基礎知識
        1.3.2.  テキスト処理に役立つUnixコマンド
        1.3.3.  正規表現
    1.4.  gihyo.jpのスクレイピング
        1.4.1.  電子書籍の総数を取得する
        [column]   正規表現における欲張り型のマッチ
        1.4.2.  書籍名の一覧を取得する
    1.5.  まとめ
第2章 Pythonではじめるクローリング・スクレイピング
    2.1.  Pythonを使うメリット
        2.1.1.  言語自体の特性
        2.1.2.  強力なサードパーティライブラリの存在
        2.1.3.  スクレイピング後の処理との親和性
    2.2.  Pythonのインストールと実行
        2.2.1.  Python 2とPython 3
        2.2.2.  パッケージマネージャーによるPython 3のインストール
        2.2.3.  仮想環境(venv)の使用
        2.2.4.  インタラクティブシェルの使用
    2.3.  Pythonの基礎知識
        2.3.1.  スクリプトファイルの実行と構成
        2.3.2.  基本的なデータ構造
        2.3.3.  制御構造と関数・クラス定義
        2.3.4.  組み込み関数
        2.3.5.  モジュール
    2.4.  Webページを取得する
        2.4.1.  urllibによるWebページの取得
        2.4.2.  文字コードの扱い
    2.5.  Webページからデータを抜き出す
        2.5.1.  正規表現によるスクレイピング
        [column]   search() と match()
        2.5.2.  XML(RSS)のスクレイピング
    2.6.  データを保存する
        2.6.1.  CSV形式での保存
        2.6.2.  JSON形式での保存
        2.6.3.  データベース(SQLite 3)への保存
    2.7.  Pythonによるスクレイピングの流れ
    2.8.  まとめ
第3章 強力なライブラリの活用
    3.1.  ライブラリのインストール
        3.1.1.  pipによるインストール
    3.2.  Webページを簡単に取得する
    3.3.  HTMLのスクレイピング
        3.3.1.  XPathとCSSセレクター
        3.3.2.  lxmlによるスクレイピング
        3.3.3.  Beautiful Soupによるスクレイピング
        [column]   pyqueryによるスクレイピング
    3.4.  RSSのスクレイピング
    3.5.  データベースに保存する
        3.5.1.  MySQLへのデータの保存
        [column]   Python Database API 2.0
        3.5.2.  MongoDBへのデータの保存
    3.6.  クローラーとURL
        3.6.1.  URLの基礎知識
        3.6.2.  パーマリンクとリンク構造のパターン
        3.6.3.  再実行を考慮したデータの設計
    3.7.  Pythonによるクローラーの作成
        3.7.1.  一覧ページからパーマリンク一覧を抜き出す
        3.7.2.  詳細ページからスクレイピングする
        3.7.3.  詳細ページをクロールする
        3.7.4.  スクレイピングしたデータを保存する
    3.8.  まとめ
第4章 実用のためのメソッド
    4.1.  クローラーの分類
        4.1.1.  状態を持つかどうかによる分類
        4.1.2.  JavaScriptを解釈するかどうかによる分類
        4.1.3.  不特定多数のサイトを対象とするかどうかによる分類
    4.2.  クローラー作成にあたっての注意
        4.2.1.  著作権と利用規約
        4.2.2.  robots.txtによるクローラーへの指示
        4.2.3.  XMLサイトマップ
        4.2.4.  クロール先の負荷
        4.2.5.  連絡先の明示
        4.2.6.  ステータスコードとエラー処理
    4.3.  繰り返しの実行を前提とした設計
        4.3.1.  更新されたデータだけを取得する
        [column]   プロキシサーバーでのキャッシュ
    4.4.  クロール先の変化に対応する
        4.4.1.  変化を検知する
        4.4.2.  変化を通知する
    4.5.  まとめ
第5章 クローリング・スクレイピングの実践とデータの活用
    5.1.  データセットの取得と活用
        5.1.1.  Wikipediaのデータセットのダウンロード
        5.1.2.  自然言語処理技術を用いた頻出単語の抽出
    5.2.  APIによるデータの収集と活用
        5.2.1.  Twitterからのデータの収集
        5.2.2.  Amazonの商品情報の収集
        5.2.3.  YouTubeからの動画情報の収集
    5.3.  時系列データの収集と活用
        5.3.1.  為替などの時系列データの収集
        5.3.2.  CSV/Excelファイルの読み込み
        5.3.3.  グラフによる可視化
        [column]   pandasとmatplotlib
        [column]   科学技術計算やデータ分析のための便利なツール: IPython・Jupyter・Anaconda
    5.4.  オープンデータの収集と活用
        5.4.1.  オープンデータとは
        5.4.2.  PDFからのデータの抽出
        5.4.3.  Linked Open Dataからのデータの収集
        [column]   オープンデータとシビックテック
    5.5.  Webページの自動操作
        5.5.1.  自動操作の実現方法
        5.5.2.  Amazon.co.jpの注文履歴を取得する
    5.6.  JavaScriptを使ったページのスクレイピング
        5.6.1.  JavaScriptを使ったページヘの対応方法
        5.6.2.  noteのおすすめコンテンツを取得する
        5.6.3.  RSSフィードを生成する
    5.7.  取得したデータの活用
        5.7.1.  地図による可視化
        [column] JSONに対してクエリを実行するjqコマンド
        5.7.2.  BigQueryによる解析
    5.8.  まとめ
第6章 フレームワーク Scrapy
    6.1.  Scrapyの概要
        6.1.1.  Scrapyのインストール
        6.1.2.  Spiderの実行
    6.2.  Spiderの作成と実行
        6.2.1.  Scrapyプロジェクトの開始
        6.2.2.  Itemの作成
        6.2.3.  Spiderの作成
        6.2.4.  Scrapy Shellによるインタラクティブなスクレイピング
        [column]   ScrapyのスクレイピングAPIの特徴
        6.2.5.  作成したSpiderの実行
        [column]   FTPサーバーやAmazon S3などにデータを保存する
    6.3.  実践的なクローリング
        6.3.1.  クローリングでリンクをたどる
        6.3.2.  XMLサイトマップを使ったクローリング
    6.4.  抜き出したデータの処理
        6.4.1.  Item Pipelineの概要
        6.4.2.  データの検証
        6.4.3.  MongoDBへのデータの保存
        6.4.4.  MySQLへのデータの保存
    6.5.  Scrapyの設定
        6.5.1.  設定の方法
        6.5.2.  クロール先に迷惑をかけないための設定項目
        6.5.3.  並行処理に関する設定項目
        6.5.4.  HTTPリクエストに関する設定項目
        6.5.5.  HTTPキャッシュの設定項目
        6.5.6.  エラー処理に関する設定
        6.5.7.  プロキシを使用する
    6.6.  Scrapyの拡張
        6.6.1.  ダウンロード処理を拡張する
        6.6.2.  Spiderの挙動を拡張する
        [column]   ScrapyでJavaScriptを使ったページに対応する:Splash
    6.7.  クローリングによるデータの収集と活用
        6.7.1.  レストラン情報の収集
        6.7.2.  不特定多数のWebサイトのクローリング
        6.7.3.  Elasticsearchによる全文検索
    6.8.  画像の収集と活用
        6.8.1.  Flickrからの画像の収集
        6.8.2.  OpenCVによる顔画像の抽出
        [column]   UbuntuでのOpenCV 3のビルド
    6.9.  まとめ
第7章 クローラーの継続的な運用・管理
    7.1.  クローラーをサーバーで動かす
        7.1.1.  仮想サーバーの立ち上げ
        [column]   Windowsから公開鍵認証を使ってSSH接続する
        [column]   AWS利用におけるセキュリティの注意点
        [column]   Windowsでサーバーにファイルを転送する
        7.1.2.  サーバーへのデプロイ
    7.2.  クローラーの定期的な実行
        7.2.1.  Cronの設定
        7.2.2.  エラーの通知
    7.3.  クローリングとスクレイピングの分離
        7.3.1.  メッセージキューRQの使い方
        [column]   Redisのデータの永続化に関する設定
        7.3.2.  メッセージキューによる連携
        [column]   ScrapyからRQにジョブを投入する
        7.3.3.  メッセージキューの運用
    7.4.  クローリングの高速化・非同期化
        7.4.1.  マルチスレッド化・マルチプロセス化
        7.4.2.  非同期I/Oを使った効率的なクローリング
        [column]   Python 3.4でasyncioを使う
        [column]   複数のマシンによる分散クローリング
    7.5.  クラウドを活用する
        7.5.1.  クラウドを使うメリット
        7.5.2.  AWSのSDKを使う
        7.5.3.  クラウドストレージを使う
    7.6.  まとめ
        [column]   外部サービスを活用したスクレイピング
Appendix Vagrantによる開発環境の構築
    A.1.  VirtualBoxとVagrant
        A.1.1.  VirtualBoxとは
        A.1.2.  Vagrantとは
    A.2.  CPUの仮想化支援機能を有効にする
        A.2.1.  Windows 10の場合
        A.2.2.  Windows 7の場合
        A.2.3.  ファームウェアの設定で仮想化支援機能を有効にする
    A.3.  VirtualBoxのインストール
    A.4.  Vagrantのインストール
    A.5.  仮想マシンを起動する
    A.6.  ゲストOSにSSH接続する
        A.6.1.  Tera Termのインストール
        A.6.2.  Tera TermでゲストOSにSSH接続する
    A.7.  Linuxの基本操作
        A.7.1.  ソフトウェアをインストールする
    A.8.  Vagrantで仮想マシンを操作するコマンド
        A.8.1.  仮想マシンを起動する(vagrant up)
        A.8.2.  仮想マシンを終了・再起動する(vagrant halt/reload)
        A.8.3.  仮想マシンを削除する(vagrant destroy)
        A.8.4.  仮想マシンの状態を表示する(vagrant status)
        A.8.5.  仮想マシンにSSH接続する(vagrant ssh)
        A.8.6.  仮想マシンをエクスポートする(vagrant package)

電子書籍版の発売日は未確定ですが、電子書籍版も発売予定です。 電子書籍版もPDF/EPUB版Kindle版共に紙版と同時発売です。

関連

orangain.hatenablog.com