Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

Deep Learning Acceleration勉強会に参加 #DLAccel

- はじめに -

こちらに行ってきたので簡単に感想など

connpass.com


別に日常ブログでも良い気がしたけど一応技術なのでここに記録しておく


 

- 所感 -

モデルアーキテクチャ観点からの高速化

Yusuke Uchida (@yu4u) | Twitter
https://t.co/4t9EEdI19b

CNNのモデル上での高速化の話
Deep CompressionとかPruningを幅広く浅く紹介

多分本当に上位レイヤ的な工夫等の話をしたのはyu4uだけだった
Deep Compressionに関しては前の人工知能学会以来コツコツ調べてたので大体把握していて、個人的な所感としては本当に浅く広くという感じ(他の登壇者との兼ね合いかな…)

個人的には符号化を上手く進める方法が気になるのでまた調べたい

 

Convolutionの数理とアルゴリズム

Koichi Nakamura (@9_ties) | Twitter
Convolutionの数理とアルゴリズム // Speaker Deck

Nakamura先生のいつものだなあという感じ
後半の同じ界隈の人の登壇のための布石感

ツイートもしたけど、質疑ではWinograd空間でReLUとか処理すれば良くない?みたいなのがあって、ICLRのこれを思い出した。
https://openreview.net/forum?id=r1rqJyHKg


この発表のFast Convolutionの話で数式が増えた辺りから、比例して会場の空気が重くなってTwitterの実況が減ったのがちょっと面白かった。

Keshab大先生のCook-ToomとWinogradのスライドは見ましょう
http://www.win.tue.nl/~wsinmak/Education/2IN35/Parhi/chap8.pdf

あとTwitterでこの辺のwinograd関連情報が得られてよかった
https://www.intelnervana.com/winograd/

 

ウェブブラウザ向け深層学習モデル高速実行フレームワーク「WebDNN」

きくらげ (@Kiikurage) | Twitter
https://t.co/PiG6hjnmBS

MakeGirls.moeでも話題になったWebDNNの中の話
Webassenmblyを使っているらしいので早く、細かい最適化もgraphの最適化から丁寧にやっているなあという印象


質疑でもあったけど、DLは細かな浮動小数点数を切っても最悪精度が出るのでもっと削いで早くするみたいな方向性はありそう
あとやっぱり、この辺のWeb系の開発スピードはDL界隈並に早いので「実装が追いついてない」「WebGL2系に対応できてない」というのは仕方ないよなあという気持ち

個人的にはWebブラウザでユーザの計算リソースを食うよりバックエンド用意するのが好みだけど、今日の話を聞いてフロントで処理するのもまあ良い点が多くなってきたなあと素直に思った。
今後の発展を応援したい

 

Using Raspberry Pi GPU for DNN

Noriyuki OHKAWA (@notogawa) | Twitter
https://www.slideshare.net/notogawa/using-raspberry-pi-gpu-for-dnn

前半はラズパイのアーキテクチャの話
リファレンスの100倍くらい図が分かりやすかった

後半はCNN系の行列計算でim2colやWinogradだと転送コストがすごいのでDirectアルゴリズムをtransposeして使ったという話
そのためのAssembly Golfのコツとか

この発表辺りからだけど完全に上位レイヤな話が無くなって、3割くらいが切り落とされた(空気感として)。

 

TensorFlow XLAの可能性

無限ゲームのなか (@Vengineer) | Twitter
https://www.slideshare.net/ssuser479fa3/tensorflow-xla-78874656

TensorFlow XLAのプラグインで色々なハードウェアへ適応できてすごいんじゃいという話

JITの話はvengineerサンのブログで見たやつってなった
https://blogs.yahoo.co.jp/verification_engineer/71268241.html
(この人だいぶ界隈違うけど自分のTLにも流れてくるので相当過激な人なんだろうと勝手に思っている)

TFやMXNetの中間表現の最適化の現状みたいな話は面白く、Wave ComputingだBrainWaveだといったDPUの話もあって、広くLSIの話を知れて良かった。

 

Googleが開発したニューラルネット専用LSITensor Processing Unit」

Kazunori Sato (@kazunori_279) | Twitter
多分slideはない

大体Googleの記事の要約のようなアレ
Google Cloud Platform Japan 公式ブログ: Google の Tensor Processing Unit (TPU) で機械学習が 30 倍速くなるメカニズム

「まだ、実例できゅうり認識とかすぎゃーん顔識別をアピールしてるんだGoogle…」と思った。

Googleの中の話もあって、「ハードウェアチームずっとあったけど2013年にリソース足りなくなることに気付いて慌てて15ヶ月で作って一発でTPU動かしました」みたいなドヤ話や、TPUをproductionで色々使ってる会社もあるという話もあって「ほえ〜」って感じだった

TPUの第二世台も既に考えていて「学習もできる」らしい
ユーザの用途に合わせて提供していくんだろう

もう全てFPGAでごにょごにょみたいになると、量子化されたNN専用プロセッサと数の暴力でGoogleに勝てる気が全くしなくなったが、一応「Google内でもAssembly直乗せとかじゃなくTensorFlow XLAつかってるよ!」という質疑が見られたので、Googleに乗っかっとけばあるいは…みないな希望がアレした

そしてこの発表で完全にDeep Learning Acceleration勉強会から(NN向け)組み込み最適化勉強会に変貌した

 

ChainerMN による分散深層学習

Takuya Akiba (@iwiwi) | Twitter
多分slideはない

iwiwi「分散深層学習するならGPUだけじゃなくインターコネクトにもお金を使って!」
わかりみが深かった

後スライドが分かりやすくて禿げた。
スライドの中身もそうだけど、一辺倒に「それは○○です」ではなく「○○というのが僕らの主張です」という言い方をしていて、なるほど流石だなあと思った。

TFが分散GPUだと若干遅くて…という話を聞いて「GoogleはもうGPUで〜とかはあんまり注力してないのかもね」と思った

用途と速度と環境を兼ね合いながらChainerもTF使っていきたいですね


 

おわりに

DL系の勉強会にしてはハードの人(というか組み込み屋だと思われる人)の割合が多くて、ハードウェア界隈身内ウケでの笑いみたいなのがちらほらあった
登壇者で明らかだけど、懇親会の感じでもハード屋割合多かったっぽく「これDeep Learning (Hardware) Acceleration勉強会だ…」ってなった

connpass概要の様々な段階とは一体…

Deep Neural Network Modelを用いた訓練や推論の高速化をテーマとした勉強会です。モデリングからハードウェアまで様々な段階での高速化アプローチについて勉強できる場にしたいと思います。


まあでも、入門的な内容はやらないと言い切ってあり、かつ日曜半日使っただけあって知見は多かった
DLは毎日やってるけど、ラズパイとかは高専以来触ってないのでまたやろうかなと思った

あとまとめるとまさにこれ

ハードウェアの気持ちオプティマイザの気持ちコンパイラの気持ちDeepNetモデルの気持ちになってコードを書く時代…できれば来てほしくなかった


開催中にブログを投げるみたいなロックな人も居たのでライブ感はその人のブログの方が良さ
cocodrips.hateblo.jp

あとκeen (@blackenedgold) | TwitterサンがTogetterしてくれたみたいです
togetter.com


最後にかなり個人的な事だけど今この記事を書きながらSIGIR 2017行ったブログが今更下書きから見つかって過去の自分にキレた
あとDeNA今週2回行ったけど、あの会場素敵だしご飯美味しいし良いですね…羨ましい


追記:

Vengineerサンは本人曰く「過激ではない」らしいです!失礼しました!



 

Firefox headlessモードをUbuntuとPythonとSelenium環境で動かす

- はじめに -

headless Chromeが来た頃、Firefoxのheadless対応の噂がありました。


そしてheadlessモードが正式に搭載されました。

developer.mozilla.org


この記事は、PythonSelenium.webdriverを使ってFirefoxのheadlessモードを触ろうという導入記事です。

今までCUIFirefox操作するとなると、xvfbとかVirtual Xを利用してスクリーンを作った上でのFirefox起動が一般的でしたがこれで少し簡単になりますね。

headless Chromeの記事と同じく設定周りについてはいつか追記するかも

環境は以下の通り
//------
Ubuntu 16.04 LTS
Python 3.6.1
pip install selenium (既)
//------

 

- UbuntuFirefoxを入れる -

aptにFirefoxリポジトリを追加

sudo apt-add-repository ppa:mozillateam/firefox-next
sudo apt-get update

途中以下のように聞かれるのでEnter

Press [ENTER] to continue or ctrl-c to cancel adding it

こんな感じの出力が出る

取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial InRelease [17.6 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main amd64 Packages [12.5 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main i386 Packages [12.5 kB]
取得: http://ppa.launchpad.net/mozillateam/firefox-next/ubuntu xenial/main Translation-en [3,716 B]


 
インストールする

sudo apt-get install firefox

バージョンは56.0が入る

firefox -v
 Mozilla Firefox 56.0

試しにheadlessモードを使ってみると以下のような表記が出る。ctrl+cで抜ける。

firefox -headless
*** You are running in headless mode.


 

- PythonSeleniumから使う -

Seleniumから使うにはFirefox用のgeckdriverをインストールしておく必要がある。
geckdriverのバージョンはよしなに。

wget https://github.com/mozilla/geckodriver/releases/download/v0.18.0/geckodriver-v0.18.0-linux64.tar.gz
tar -zxvf geckodriver-v0.18.0-linux64.tar.gz
sudo cp ./geckodriver /usr/local/bin

geckdriverをインストールしてないと以下のようなエラーが出る。

~~~
raise child_exception_type(errno_num, err_msg)
FileNotFoundError: [Errno 2] No such file or directory: 'geckodriver'
During handling of the above exception, another exception occurred:
~~~
os.path.basename(self.path), self.start_error_message)
selenium.common.exceptions.WebDriverException: Message: 'geckodriver' executable needs to be in PATH.


 
wbdriverの引数に "-headless" を付けて起動させる。
特に何もしてないと以下のように "/usr/bin/firefox" に配置されているはず。

which firefox
/usr/bin/firefox

FirefoxBinaryにpathを指定して、 "add_command_line_options" でheadlessを指定する。

# -*- coding: utf_8 -*-
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

binary = FirefoxBinary('/usr/bin/firefox')
binary.add_command_line_options('-headless')
driver = webdriver.Firefox(firefox_binary=binary)
driver.get('http://www.google.com')
print(driver.page_source)
driver.quit()


HTMLが表示されたら優勝。

ちなみに "-headless" をつけないで "webdriver.Firefox()" で起動させようとすると以下のようになる

~~~
raise WebDriverException(
   "The browser appears to have exited "
   "before we could connect. If you specified a log_file in "
   "the FirefoxBinary constructor, check it for details.")
WebDriverException: Message: The browser appears to have exited before we could connect. If you specified a log_file in the FirefoxBinary constructor, check it for details.


 
上手くSeleniumが起動しない場合の参考は以下
参考:Selenium 3.4.0 Unable to find matching capabilities · Issue #3884 · SeleniumHQ/selenium · GitHub
参考:How to specify Firefox command line options using Selenium WebDriver in Python? - Stack Overflow


 

- command line argumentとFirefoxProfileについて -

コマンドラインからのオプションとして設定できる項目はMozilla公式の以下にまとまっている。

コマンドラインオプション - Mozilla | MDN

"-sage-mode" すれば拡張を全て無効化してくれるので便利といえば便利そうである。
引数だけで設定できる項目は少なめで、後述するProfileを使ってゴニョる。


 
Seleniumのwebdriverには "FirefoxProfile" なるclassがあり、そちらでも設定を記述できる。
一例で書くと以下

# -*- coding: utf_8 -*-
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

# profileの記述
profile = webdriver.FirefoxProfile()
# useragent指定
profile.set_preference("general.useragent.override", "hogehoge")
# 画像を読み込まない
profile.set_preference("permissions.default.image", 2)
# CSSを使わない
profile.set_preference('permissions.default.stylesheet', 2)
# Flashを使わない
profile.set_preference('dom.ipc.plugins.enabled.libflashplayer.so', 'false')

# Firefoxの起動
binary = FirefoxBinary('/usr/bin/firefox')
binary.add_command_line_options('-headless')
driver = webdriver.Firefox(firefox_binary=binary, firefox_profile=profile)

# スクリーンショットを撮って終了
browser.get("hogehoge")
browser.save_screenshot("test.jpg")
browser.quit()


 
一応Firefoxのconfig entriesが一番まとまっているのはこの辺
About:config entries - MozillaZine Knowledge Base
Category:Preferences - MozillaZine Knowledge Base

あと公式サポートも参考に
Profiles - Where Firefox stores your bookmarks, passwords and other user data | Firefox Help

一部使えない(?)ものもあるけど参考にはなる
On Firefox in Selenium WebDriver tests with Python do not want images to load and CSS to render – Selenium Webdriver Trainings

 
Seleniumのオプション周りは以下がちょっと参考になる
selenium.webdriver.firefox.options — Selenium 3.6 documentation
7. WebDriver API — Selenium Python Bindings 2 documentation

 
加えて"webdriver.FirefoxProfile" で既存の設定ファイルを読み込む事もできる。
またProfileにaddExtensionする事で拡張も使える。Proxyを使う方法もある。


 
使っていて分かった事として、消化不良でquit()するとプロセスがゾンビ化して、次以降の起動で以下のようなエラーが出るっぽい。

WebDriverException: Message: connection refused

う〜んと悩んで実際にpsコマンドで見てみるとこんな感じ。

$ ps aux | grep “firefox”
master    3158  0.0  0.0      0     0 ?        Z    16:33   0:00 [firefox] <defunct>
master   12661  0.0  0.0      0     0 ?        Z    16:53   0:00 [firefox] <defunct>
master   13005  0.0  0.0  12948  1088 pts/3    S+   16:54   0:00 grep --color=auto firefox
master   80330  0.0  0.0      0     0 ?        Z    15:08   0:00 [firefox] <defunct>
master   84301  0.0  0.0      0     0 ?        Z    15:12   0:00 [firefox] <defunct>
master  101809  0.0  0.0      0     0 ?        Z    15:34   0:00 [firefox] <defunct>
master  103666  0.0  0.0      0     0 ?        Z    15:37   0:00 [firefox] <defunct>

まあUbuntuFirefoxGUIで使ってた時もこんな感じだったような気がするから許容範囲か…


 

- おわりに -

TODO:
ChromeFirefoxのheadlessを比べるやつを書く
画面操作してみてissue確認する

今後もheadlessで色々できたら良いなと思います。


 

  • 追記 

後輩曰く「Firefoxは.lockみたいなファイルができて〜」という話。
何気なくツイートしていると下記のような事がわかった。


ChromeやPhontomJSと違い、Firefoxはparent.lockをゾンビ化したプロセスが握っている限り次のFirefoxを起動できないのでこのファイルを開放してやる必要があるらしい。う〜ん…


 

MacへのJupyter導入からextensionと設定メモ

- はじめに -

業務PCがWinからMacになりまして、Jupyter notebookしたいので自分の設定とextensionの導入までやったメモ。

あとChrome拡張使ってCSSを書き換えている話とか。


最初に参考資料を示しておくと、どのネット記事よりも以下extensionのGithubリポジトリのREADMEが分かりやすい。


Pythonやpip、バージョン管理環境に合わせて適宜読み替えて。

この記事書いた時点

Mac OSX Sierra 10.12
pyenv上でPython3環境構築済


 

- JupyterをVivaldiで起動するまで -

jupytera 本体の導入はpip

sudo pip install jupyter

インストールが終わったらconfigファイルを作る

jupyter notebook --generate-config
sudo vim ~/.jupyter/jupyter_notebook_config.py

Windowsだと、マシンのデフォルトブラウザで開いてくれてた気がしたけど、MacだとSafariでnotebookが開いてしまう。
Vivaldiなるブラウザを常用しているので以下設定。

c.NotebookApp.browser =u'/Applications/Vivaldi.app/Contents/MacOS/Vivaldi %s'

Chromeだと多分こう。

c.NotebookApp.browser = u'/Applications/Google/ Chrome.app/Contents/MacOS/Google\ Chrome %s'

ブラウザへのパスは適宜書き換える。

python - Launch IPython notebook with selected browser - Stack Overflow
Jupyterをブラウザ指定して新しいウィンドウで開く【なれない日記20160709】 - けつあご日記

jupyter常用者なのでbashrcにはaliasの指定をしている。

alias ju='jupyter notebook'


 

- jupyter extension -

extension導入は前述の通りREADMEを見ながら。
GitHub - ipython-contrib/jupyter_contrib_nbextensions: A collection of various notebook extensions for Jupyter

sudo pip install -e jupyter_contrib_nbextensions
jupyter contrib nbextension install --user

上のコマンドだけとか、gitからcloneしてsetup.py動かせば導入終わりみたいなネット記事が多い理由は謎。(多分上だけだと、nbextensions見に行っても404 Not Foundだと思うんだけど…)

2つめのnbextension installでブラウザ向けのJSやCSSが入るのだけど、highlight_selected_wordが上手く導入できてないっぽくて死んだ。

NotADirectoryError: [Errno 20] Not a directory: '/usr/local/lib/python3.6/site-packages/jupyter_highlight_selected_word-0.0.11-py3.6.egg/jupyter_highlight_selected_word/static/highlight_selected_word'

highlight_selected_wordだけpipで再インストー

sudo pip uninstall jupyter_highlight_selected_word
sudo pip install jupyter_highlight_selected_word
jupyter contrib nbextension install --user

http://localhost:8888/nbextensions を見に行けばextensionのオン・オフができる。

エディタ内で色々開閉できる「Codefolding in Editor」「Codefolding」、メニューに拡張ショートカットを追加する「Nbextensions edit menu item」「Nbextensions dashboard tab」、Vimキーバインド使う用の「Select CodeMirror Keymap」、PEPの下僕として78文字超えたくないので「Ruler」を使っているのでONにしておわり。


 

- StylishでMonokai風にする -

JupyterはそもそもThemeを使う機能を備えているが、Jupyterを動かすサーバ等環境が変わったら一々設定しないといけないのと、コーディング中にも設定見直したいのでChrome拡張を使ってCSSを書き換えている。

以前までStylishを使っていたのだけど、情報送信の話があったでちょっと考えもの。
Webブラウザアドオン「Styish」、ユーザーデータの収集を始めて騒動に | スラド IT

今はCSSとJSのシンプルな拡張は全て自前。
Stylish以外で簡易なのだとStylus、Stylistあたりか。

一応StylishのユーザグループにJupyter用のCSSが公開されてたりして、これを土台に一部書き換えて使っている。

 

書き換えてMonokaiっぽくする文字周りはこんな感じ。白文字がfffなので適宜。

div.output_stderr {background-color: #050505;}
div.output_stderr pre {
color: #509050; 
font-size: 12px;}
.cm-s-ipython .CodeMirror-matchingbracket { text-decoration: underline; color: #c3c3c3 !important; }
.CodeMirror { color: #c3c3c3 !important; }
.cm-s-default .cm-link {color: #3974dd;}
.cm-s-default .cm-string {color: #de846c;}
.cm-s-default .cm-header {color: #1090f0;}
.cm-s-ipython div.CodeMirror-selected {background: #3C4555 !important;}
.cm-s-ipython .CodeMirror-gutters {background: #39414F; border: 0px; border-radius:0px;}
.cm-s-ipython .CodeMirror-linenumber {color: #5A647B !important; font-size: 11pt;}
.cm-s-ipython .CodeMirror-cursor {border-left: 2px solid #0095ff !important;}
.cm-s-ipython span.cm-comment {color: #6E7C95; font-style: normal !important;}
.cm-s-ipython span.cm-atom {color: #CAA6EC;}
.cm-s-ipython span.cm-number {color: #ae81ff;}
.cm-s-ipython span.cm-property {color: #fff;}
.cm-s-ipython span.cm-attribute {color: #E39194;}
.cm-s-ipython span.cm-keyword {color: #f92672; font-weight: normal;}
.cm-s-ipython span.cm-string {color: #e6db74; font-weight: normal;}
.cm-s-ipython span.cm-operator {color: #f92672; font-weight: normal;}
.cm-s-ipython span.cm-builtin {color: #66d9ef; font-weight: normal;}
.cm-s-ipython span.cm-boolean {color: #E39194;}
.cm-s-ipython span.cm-variable {color: #fff;}
.cm-s-ipython span.cm-variable-2 {color: #fd971f;}
.cm-s-ipython span.cm-error {background: rgba(191, 97, 106, .3) !important;}
.cm-s-ipython span.cm-tag {color: #CAA6EC;}
.cm-s-ipython span.cm-link {color: #E39194;}
.cm-s-ipython span.cm-storage {color: #CAA6EC;}
.cm-s-ipython span.cm-entity {color: #E39194;}
.cm-s-ipython span.cm-class {color: #E5DEA5;}
.cm-s-ipython span.cm-support {color: #77ABE7;}
.cm-s-ipython span.cm-qualifier {color: #77ABE7;}
.cm-s-ipython span.cm-property {color: #fff;}

あとextensionで導入したCodefoldingの三角のアレの位置とかを修正

.CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { margin-left: -4px; }

跡は幅を100% にしたりしてこんな感じ

f:id:vaaaaaanquish:20170715184749p:plain

拡張なのでCSSで指定しにくいところの書き換えはできないけどまあ概ね満足。
コードもグラフ出力も黒背景の方が僕は好きです。


 

- おわりに -

extensionは導入はpyenvやAnaconda等のベース環境によってたまに失敗してるイメージがあるんだけど、なんかもっとこう絶対優勝できるようになりたい。

あと便利なextensionあったら知りたいところです。
はてブコメントでよしなに。

Jupyter生活は快適でサイコー。

PythonでWebスクレイピングする時の知見をまとめておく

- はじめに -

最近はWebスクレイピングにお熱である。

趣味の機械学習のデータセット集めに利用したり、自身のカードの情報や各アカウントの支払い状況をスクレイピングしてスプレッドシートで管理したりしている。

最近この手の記事は多くあるものの「~してみた」から抜けた記事が見当たらないので、大規模に処理する場合も含めた大きめの記事として知見をまとめておく。


追記 2018/03/05:
大きな内容なのでここに追記します。
github.com
phantomJSについての記載が記事内でありますが、phantomJSのメンテナが止めたニュースが記憶に新しいですが、上記issueにて正式にこれ以上バージョンアップされないとの通達。
記事内でも推奨していますがheadless Chrome等を使う方が良さそうです。


- 知見まとめ -

最初に言うまでもないが、Pythonのバージョンは3系を選択すべきである。

2系ではユニコードの問題に悩まされ、小学生にもバカにされる。

とにかくencodingに悩まされる事を防ぎたければ3.x系。


 

requests

pip install requests

標準的なHTTPライブラリ。
HTTPを使うならrequestsにするのが吉。

Python 2.x系では標準としてurllibとurllib2があり、urllibはPython 3.xでは廃止され、urllib2もurllib.requestとなった。そのurllibをメインページ(http://requests-docs-ja.readthedocs.io/en/latest/)で「APIがまともに使えません」「Python的ではない」とまで言うのがrequestsというライブラリである。

それら以外にもurllib3(requests内部でも使われている)やhttplib、http.clientなど多数HTTPライブラリがあるのがPythonの現状。

PythonのHTTPライブラリ urllib, urllib2, urllib3, httplib, httplib2 … | スラド
日本語リファレンスには書いてない話:urllibとurllib2の違いってなんだ « DailyHckr

メインページで自ら「人が使いやすいように作られた、エレガントでシンプルなPythonのHTTPライブラリ」と言い切るクールなやつ。

スクレイピングに関してのみ挙げておくと以下が簡易に行えるのがメリット。

HTTPリクエストかける時は、timeoutとheaderをかけて実行するのが吉。
timeoutに関しては30secを設定する等のネット記事が多々見られる。
以下によればtimeoutはページのロード時間を含まないので、ガッツリWebスクレイピングをかけるならボトルネックにならないよう短くしておいて大丈夫。ちなみにデフォルトでは60sec。
http://requests-docs-ja.readthedocs.io/en/latest/user/quickstart/#timeouts
headerには自身の正しいブラウザとOSをUser-Agentとして設定しておきましょう。
UserAgentString.com - unknown version

import requests
headers = {"User-Agent": "hoge"}
resp = requests.get(URL, timeout=1, headers=headers)
# textでunicode, contentでstr
print(resp.text)

requestsではオレオレ証明書等を利用したWebサービスやお固めのWebページを取得しようとすると以下のようなエラーがでる場合がある。

Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7fe861db6908>: Failed to establish a new connection: [Errno 111] Connection refused',))
[CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:749)
UNKNOWN_PROTOCOL

SSL関連についてはverifyなるフラグが設定できるので以下のように。

requests.get(URL, timeout=1, headers=headers, verify=False)

その他post, deleteなどのメソッドの他、oauth認証やストリーム接続も用意されている。

このやり方でHTMLを取得した場合文字化けする可能性があるが、そちらについては後述する。

Python for iOSなどではデフォルトでrequestsがインテグレーションされている。良い。

あとはHTTP処理をガツガツ回す前にDNSの設定をしていた方が良い。

理由としては以下


BeautifulSoup

pip install beautifulsoup4

「pip install beautifulsoup」だとbeautifulsoup3が入ってしまう。
所々module名が変わっていたりいる。違いはこの辺りを参照。
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.html#porting-code-to-bs4

HTMLの構造解析をした上で、HTMLを綺麗に整形、問題のある点の修正等を行ってくれる。
簡易な所では、headタグがbodyタグの中にあったら丁寧に外出ししてくれるとか。
XML, RSS等にも対応している。

名前の元ネタは「Alice in Wonderland」に出てくる美味しいスープの歌らしい。
Alice in Wonderland: 1999 Professor Tortoise - YouTube

一番シンプルなところだと以下。

import requests
from bs4 import BeautifulSoup
resp = requests.get(URL)
soup = BeautifulSoup(resp.text)
# aタグの取得
print(soup.get("a"))
# 整形後のhtml表示
print(soup.prettify())

ガッツリスクリプトにして回す時は、解析器を選択しておくと吉。

  • html.parser
    • デフォルトで付いてくるやつ
    • 本当に些細なミスでも落ちるし中身がPythonなので遅い
    • Pythonのバージョンに依存して中身も違う
  • lxml
    • C言語の高速実装な解析器
    • 最近のWebの複雑な構造や動的な物に少し弱い
    • apt-get install libxml2-dev libxslt1-dev python-dev
    • apt-get install python-lxml
  • html5lib
    • html5の規則に対応
    • ブラウザで表示するのとほぼ同じメソッド
    • かなり重い
    • pip install html5lib

Beautiful Soup 4.x では parser を明示指定しよう - AWS / PHP / Python ちょいメモ http://hideharaaws.hatenablog.com/entry/2016/05/06/175056
Beautiful Soup Documentation — Beautiful Soup 4.4.0 documentation

パーサの良し悪しを考えるとlxmlでチャレンジしてダメならhtml5libを試すのが良さそう。

try:
    soup = BeautifulSoup(html, "lxml")
except:
    soup = BeautifulSoup(html, "html5lib")


parseして情報を得るだけならlxml単体でも可能である(lxmlはそもそもそういうパッケージ)。
lxml単体でやるメリットとしてlxml.html.fromstringでDOMのツリー構造を得られるとか、make_links_absolute()、rewrite_links()によって全てのリンクを相対パスに書き換えられたりとかが標準で実装されている事がある。
単体であればかなり、高速なので簡易なスクレイピング(特定のページ内からの取得など)ならBeautifulSoupを使わずlxmlで十分である。


Mechanize

上記のrequestsとBeautifulSoupくらいの機能ならMechanizeで十分間に合ったりする。
Mechanizeは元々PerlによるWebスクレイピングツールで、ブラウザをエミュレートして操作する事ができる。

最低限「何かにログインしてフォームを入力して情報を出してスクレイピング」ならMechanizeでよい。
(JavaScriptは動かないっぽい)

mechanize http://wwwsearch.sourceforge.net/mechanize/

pip install mechanize
import mechanize

# ブラウザをエミュレート
browser = mechanize.Browser()
response = browser.open(URL)
#HTMLを表示
print(response.read())

urllib2ベースな使い方もできる。

response = mechanize.urlopen("http://www.example.com/")
print(response.read())

デフォルトでrobots.txtスクレイピング禁止事項を読み込んでエラーを返してくれるのもありがたい。
(外す時はbrowser.set_handle_robots(False)とする)
BrowserのaddheadersでHeaderの追加、その他cacheの設定もできる。

標準的な要素指定だけでなく、form()やsubmit()ができるのがエミュレートしてるmechanizeの利点でもある。

pythonモジュールmechanizeでWeb上の作業を自動化する | TRIVIAL TECHNOLOGIES 4 @ats のイクメン日記
Python/mechanize - kuro-tech http://wiki.kurokobo.com/index.php?Python%2Fmechanize


PyQuery

mechanizeに似た、BeautifulSoup等より簡単にWebから情報を取得、かつjQueryライクなAPIを提供するパッケージとしてPyQueryがある。

導入はpipでできる。

pip install pyquery
from pyquery import PyQuery

pq = PyQuery(url='hogehoge.com')
print(pq)

引数にhtml形式のテキストを投げても良いし、urlパラメータにurlを投げればWebサイトを取得する事ができる。

# リンク(aタグ)の所得
for elem in pq.find('a'):
    q = PyQuery(elem)
    print(q.text())

かなり簡単。
普段からjQuery書いてる人はこれが親しみやすいかも。

中身で使われているパーサはlxmlなのでlxmlのインストールも忘れずに。

【python】ウェブスクレイピングで社内ツールを作る | 技術広場

ライブラリ:PyQuery - Life with Python
pyqueryでHTMLからデータを抽出 - 唯物是真 @Scaled_Wurm

 

Robobrowser

mechanize、PyQueryと似たパッケージとしてRobobrowserが存在する。

導入はpipでできる

pip install Robobrowser

RoboBrowserの特徴として「Browserを操作するようにコードが書ける」点がある。

from robobrowser import RoboBrowser

# インスタンス(ブラウザを開いてURLへ)
browser = RoboBrowser(parser=html5lib)
broswer.open('hogehoge.com')

# フォームがあるページのリンクを取得
link = browser.get_link('form_link')

# フォームを投げる
browser.follow_link(link)
form = browser.get_form(action='hogehoge.com/forms')
form['email'] = 'hogehoge@mail.com'
browser.submit_form(form, headers={
    "Referer": browser.url,
    "Accept-Language": "utf-8"})

順序よく書けるだけでなく、フォーム入力やスライドバーの操作などが直感的に書ける。
すでに決まった操作を自動化する場合などに良い。

GitHub - jmcarp/robobrowser
Welcome to robobrowser’s documentation! — robobrowser 0.1 documentation


 

Selenium.webdriverとPhantomJS

requests、MechanizeだけではJavaScript等動的な表示が進むタイプのWebページは上手く取得できないので、ブラウザ経由でhtmlを取得する。
そのためにSeleniumとブラウザを直接Pythonから触る方法利用する。

Selenium

Selenium with PythonSelenium Python Bindings 2 documentation http://selenium-python.readthedocs.io/

Seleniumはブラウザオートメーションツールで、そもそもWebサービスのテスト用の統合ツール。
Webスクレイピング用ではなく、その為のメソッドが豊富な訳ではないが、今のところこれしかないのでみんな使っているという感じはある。
Pythonバインディングもあってwebdriver経由でブラウザを操作することができる。

Selenium何とかっていうツールがやたら色々あるのはどういうわけなのか | 品質向上ブログ http://blog.trident-qa.com/2013/05/so-many-seleniums/


導入はpipでできる

pip install selenium

 

PhantomJS

FireFox等の既存ブラウザも操作できなくないが、いちいちGUI開くと重いとかいうアレがあるのでPhantomJSなるWebkitベースのヘッドレスブラウザを使う。
PhantomJS | PhantomJS

ヘッドレスブラウザは別に他にもあるのだけれど、正直これしかないからみんな使っているというアレ (かなりバギーである)。
使った感想だと次点でCasperJSが良い(Seleniumからの操作もできるが中身がPhantomJSなのであまり変わりはないが)。

※記事最後記述:今はChromeFirefoxがheadless対応しておりベストはChromeかと


headless browserメモ - なっく日報 http://yukidarake.hateblo.jp/entry/2014/09/12/204813

各headlessブラウザをまとめたRepositoryも
github.com

Seleniumから利用できる、各ブラウザの関係性等は今のところ以下の記事がわかりやすくまとまっている。
qiita.com

最近jsによる動的なWebページがめちゃくちゃ多い事もあって、スクレイピングでのJS実行環境は作るべきではある。
(専用の色々一緒に作りたい人居ないかな…)


PhantomJSはWindows環境下だと少し面倒(PATHとかexeプロセスの処理とか)。
Linux環境なら以下で導入できる。

wget -O /tmp/phantomjs-2.1.1-linux-x86_64.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
cd /tmp
bzip2 -dc /tmp/phantomjs-2.1.1-linux-x86_64.tar.bz2 | tar xvf -
sudo mv /tmp/phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/
phantomjs --version

Win環境ならexeを落としてきてPATHを通せばある程度は動く。
Thread環境で動かすとゾンビプロセスになりやすいので注意が必要。

 

SeleniumとPhontomJSを利用した実装

Selenium経由でPhantomJSを利用する最もベターな実装は以下

from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get(url)
# htmlの表示
print(driver.page_source)

前述のrequestsと同様timeoutを設定する必要がある。
細かくセットするなら以下のようにセットできる。
デフォルトは全て30秒となっている。

# PhantomJS側のtimeoutを直接設定する
driver = webdriver.PhantomJS(
             desired_capabilities={
                 'phantomjs.page.settings.resourceTimeout': str(30)})
# 非同期なJSが動き続ける最大秒
driver.set_script_timeout = 30
# リクエストに使う最大秒
driver.timeout = 30
# 要素が見つかるまでの待機時間
driver.implicitly_wait = 30
# HTMLファイル群を読み込むためのtimeout
driver.set_page_load_timeout = 30

色々なWeb記事を見ると「ページを読み込むまでtime.sleep()を設定しよう!」というものがあるが、selenium.webdriver.supportを使う事で、ページのロード待機や操作の最大TIMEOUTを設定できるので、上記詳細設定が不要な場合はこちらを利用する。

from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver
driver = webdriver.PhantomJS()

# ページロードのタイムアウトを設定
driver.set_page_load_timeout(10)
# 操作した時のtimeoutする時間をdriverに設定
driver_wait = WebDriverWait(driver, 10)
# Webページの取得
driver = driver.get(url)
# 作業が終わるまで待つ
driver_wait.until(ec.presence_of_all_elements_located)

Exceptionが発生するのでcatchすればよい。

 
ページのロードが終わると、Webページがリダイレクトされ別のサイトに飛んでいる場合がある。
urlを適宜確認して、リダイレクトが発生してないか確認する実装が必要である。

driver.current_url


ここまでで「じゃあrequests要ら無くない?」となるが、seleniumのwebdriverはstatus_code取得が実装されていない
スクレイピングだと問題なのだが「本来Seleniumはテストツールであり、幾回かHTTP通信を行っている。そのため完全なstatus_codeの取得およびそれに伴った改修は難しい」といった議論が既にある。
WebDriver lacks HTTP response header and status code methods · Issue #141 · SeleniumHQ/selenium-google-code-issue-archive · GitHub
How to get status code by using selenium.py (python code) - Stack Overflow

そこで後述するようにstatus_codeの取得やencodingの取得をrequestsで別途行ったりするのが良い。

また、PhantomJSを実行する際には、requests同様User-Agentの設定等を行っておくと吉。
desired_capabilitiesにブラウザ設定を、service_argsで引数を設定できる。
スクレイピングで重要そうな設定は以下のような感じ。

# Mozillaを指定, WebDriverにデフォルトのGhostDriverではなくFirefox後継のmarionetteを利用する
dcap = {
    "phantomjs.page.settings.userAgent": "hoge",
    "marionette": true}
# 引数の設定
args = ["--ignore-ssl-errors=true",   # sslエラーの許容
        "--ssl-protocol=any",         # sslプロトコルはよしなに
        "--proxy=hogehoge:0000",      # Proxyサーバがあるなら指定
        "--disk-cache=false",         # キャッシュを残さない
        "--load-images=false",        # 画像をロードしない(文書のスクレイピング等画像不要の場合に)
        "--script-encoding=utf-8",    # 既に特定済みの文字エンコードがあるなら(デフォutf-8)
        "--output-encoding=utf-8"     # 出力する際の文字エンコード(デフォutf-8)
]

driver = webdriver.PhantomJS(desired_capabilities=dcap, service_args=args)

中でも引数に利用する--script-encoding辺りが厄介で、scriptさえencodingできずWebスクレイピングできないといった場合を見越して事前にセットする必要がある。
ここのencodingの取得の為に、別途requestsで投げるのが良いのではないかなと思う。

ちなみに利用できる引数のリストはここ。
Command Line Interface | PhantomJS
他にもログ出力の設定やweb-securityの指定ができる。


あとPhantomJSにはdriver.close()なるメソッドがあり、これによってdriverのProcessを殺す事ができる。
クローズしないとメモリをガバガバ食っていくのでちゃんとクローズしましょう。

driver.close()

PhantomJSを利用していると多々あるのが、driver.close()の失敗、driver.get()が全てErrorに、といったProcessのゾンビ化の問題である。
普通にコマンドでProcess killを定期的に実行するだけだが、まあまあ面倒。

pgrep -f 'phantom' | xargs kill


呼応してheadless Chromeなるものもあって、開発が停止されたもんだと思ったら最近日本語記事が更新されてビビる。
ヘッドレス Chrome ことはじめ | Web | Google Developers https://developers.google.com/web/updates/2017/04/headless-chrome?hl=ja

そして油断している間にPhantomJSのメンテナが辞める。
www.infoq.com


そしてheadless Chromeを使う記事も自分で書きました。
(PhantomJSよりこっちが良いです...)
vaaaaaanquish.hatenablog.com

今ならこのあたりが分かりやすいです。
Headless Chrome をさわってみた | CYOKODOG

またFirefoxも。
vaaaaaanquish.hatenablog.com


記事最後にFirefoxもheadlessに対応した旨等の経緯を書いていますが、Chromeが現状最高です。

urllib.parse

本記事の最初の方でrequests辺りにけちょんけちょんに書いたurlibだが、Python3以降、urlib.parseにurlparse, urljoinなるWebスクレイピングに必要な関数が統合されたので一応書いておく。

21.8. urllib.parse — URL を解析して構成要素にする — Python 3.6.5 ドキュメント

urlparse, urljoinはよしなにURLの操作をしてくれる。
例えば、URLのドメイン部が欲しい場合は以下のように書ける。

from urllib.parse import urlparse
# httpを先頭に付与しないと上手くいかない場合がある
if not url.startswith("http"):
    url = "http://" + url

parsed_url = urlparse(url)
domain = parsed_url.netloc

paramsを切り離さないurlsplitもあるので適宜活用すると吉。

他にもURLをよしなに接続したい場合
http://hogehoge.com/main/と/main/index.htmlをよしなに繋ぎたいなど)

url = urljoin("http://hogehoge.com/main/", "/main/index.html")
print(url)
# $ http://hogehoge.com/main/index.html


この辺りurlib以外でもっと便利なメソッドが多いのでチェックしておくと良い。

 

chardet, cchardetによるエンコーディング検出

Webスクレイピングをしている際に非常に厄介なのが多岐に渡る「エンコーディングの種類」である。
utf-8以外のWebサイトみんな地獄で呪ってやるという気持ちになる時もある。
cp932なMicrosoftIBMも絶対許さんという気持ちになる時もある。


ちなみにrequestsはデフォルトで一応、encoding推定が動作する。
デフォルトではHTTPヘッダに基いて推定されるようだが、ヘッダに文字が入っている場合に、Content-Typeのエンコーディング、タグのエンコーディングを無視してエンコーディングをISO-8859-1としてしまう(RFC 2616のデフォルト値である)。
Quickstart — Requests 2.18.4 documentation
http://d.tpdn.kim/2014/10/11/python-requests-mojibake

requestsでは上記代替としてapparent_encodingなるメソッドも用意されている。

import requests

response = requests.get(URL)
print(response.encoding)                       # Content-Typeに文字が入っていた場合ISO-8859-1
response.encoding = response.apparent_encoding #apparent_encodingに置き換え
print(response.text)                           #文字化けしない

apparent_encodingの中身はchardetで実装された文字エンコーディング推定。
requests/models.py at bd3cf95e34aa49c8d764c899672048df107e0d70 · requests/requests · GitHub
GitHub - chardet/chardet: Python 2/3 compatible character encoding detector.


chardetはよくできたエンコーディング推定器であるのだが、中身がPythonで処理が重い。
Webスクレイピングの速度に耐えきれない程重い場合があるので、C実装のcChardetなるライブラリを利用すると吉。
github.com

import cchardet

resp = requests.get(URL)
ccencoding = cchardet.detect(resp.content)["encoding"]
if len(ccencoding):
    # 小文字じゃないと適応できない場合あり
    resp.encoding = ccencoding.lower()
    encoding = ccencoding
# 文字化けしない
print(resp.text, encoding)


以下にもあるように、Python2系の頃はエンコーディングに関して色々なパッケージがあったようだが、ユニコード問題も解決し今のところはcchardet一強かなという感じ。
Python でエンコーディングを判定する | 傀儡師の館.Python - 楽天ブログ
ppkf で日本語の文字コード判別 | 傀儡師の館.Python - 楽天ブログ

上記記事内の中であれば「easy_install pykf」「pip install cssutils」は成功する。
pykf プロジェクト日本語トップページ - OSDN
encutils - cTHEdot

どちらも以下のように文字コードが判定できる

import encutils
import pykf

encutils.tryEncodings(html)
pykf.guess(html)

pykfは文字コード変換ツールであるので、利用の仕方によっては強い面があるが、若干重いのが欠点である。
機種依存文字などに対応してるのは良い。
機種依存文字対策でpykfをインストールしてみた

encutilsはgetEncodingInfoがCSSの中身を見に行っているため、Webサイトならかなり高い確率で正答する。
parseString等のメソッドがあり、CSS特化のパッケージではあるものの場合によっては採用できる。
 

これまでの事も含めて、requestsでstatus_codeの確認とcchardetによるencodingの検出(ISO-8859-1以外のContent-Typeを利用しつつ)をして、WebDriverに渡しましょうというコード。

import requests
import cchardet
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from selenium import webdriver

URL = "http://hogehoge.com"

# requests
headers = {"User-Agent": "hoge"}
resp = requests.get(URL, timeout=1, headers=headers, verify=False)

# status_codeの確認
if resp.status_code != 200:
    print("200じゃない時の処理")

# encodingの検出
encoding = resp.encoding
ccencoding = cchardet.detect(resp.content)["encoding"]
if len(ccencoding) > 0 and encoding != "ISO-8859-1":
    resp.encoding = ccencoding.lower()
    encoding = ccencoding

# WebDriver
dcap = {
    "phantomjs.page.settings.userAgent": "hoge",
    "marionette": true}
args = ["--ignore-ssl-errors=true", "--ssl-protocol=any", "--proxy=hogehoge:0000",
        "--disk-cache=false", "--load-images=false", "--output-encoding=utf-8",
        "--script-encoding={}".format(encoding)]
driver = webdriver.PhantomJS(desired_capabilities=dcap, service_args=args)
# GET
driver.set_page_load_timeout(10)
driver_wait = WebDriverWait(driver, 10)
driver = driver.get(URL)
driver_wait.until(ec.presence_of_all_elements_located)

# 引数でoutput-encodingを指定していなければ > html = driver.page_source.decode(encoding)
# 大体文字化けせずJSをうまいこと回して取得できる
html = driver.page_source


# 解析に使いたい時は
from bs4 import BeautifulSoup
try:
    soup = BeautifulSoup(html, "lxml")
except:
    soup = BeautifulSoup(html, "html5lib")
print(soup.get)

多分これが一番つよいと思います。


timeout_decoratorによるタイムアウト

上記のような設定をしても、複数のWebスクレイピングを回している時にはSocketの不足やHTTPメソッドが返ってこない等によるPythonのフリーズが発生する。
PhantomJSにせよHTTP通信を使っているメソッドは、通信状態によって設定したtimeoutを無視して周り続けたりゾンビプロセスになってしまう場合がある。
デコレータで関数にタイムアウトを実装できるtimeout-decorator使う。

導入はpip

pip install timeout-decorator


デコレータでtimeout_decoratorを付けてあげると良さげ。

@timeout_decorator.timeout(5, timeout_exception=StopIteration)
def print_while():
    while(1):
        print(".")

try:
    print_while()
except StopIteration as es:
    print(es, "Stopped")

Windowsなる不自由なOSだと(多分bash.exe経由したりすると) module 'signal' has no attribute 'SIGALRM' で使えそうにないが、概ね良さそう。


 

retryingデコレータによるリトライ

GitHub - rholder/retrying: Retrying is an Apache 2.0 licensed general-purpose retrying library, written in Python, to simplify the task of adding retry behavior to just about anything.

スクレイピングではネットワーク環境などによって、サーバへのアクセスが失敗する可能性がある。

そこでretryingデコレータを利用する。
導入はpipで。

pip install retrying

関数内のExceptionをcatchして、最大10回リトライするような実装は以下。

from retrying import retry
import requests

@retry(stop_max_attempt_number=10)
def scraping(url):
    return requests.get(url)

print scraping("http://hogehoge.com")

リトライ時のdelayもwait_fixedで指定できるので有用。

 

joblib, multiprocess, asyncio, threadingで並列処理

PythonでWebスクレイピングするなら並列、もしくは非同期処理が必要になる事がある。
一般的に想像される"並列処理"ならjoblib, multiprosessing、"非同期処理"ならasyncioを使うと大体良い(正確にはどちらもできる)。
それぞれの違い、及びasyncioの手引はicoxfog417氏の下記記事が最も分かりやすいと思います。
Pythonにおける非同期処理: asyncio逆引きリファレンス - Qiita http://qiita.com/icoxfog417/items/07cbf5110ca82629aca0

asyncioはPython3.4から標準化されたパッケージで、最初こそ不安定さでissueだらけだったのだが大分安定的に並列処理できるようになった模様。
ただ、未だ若干の記述のしにくさ、拡張のしにくさがある。

同様にPythonの標準モジュールにthreadingがあるが、1つ1つ継承したクラスを作ったりと若干記述が面倒である。
マルチプロセッシング(multiprocessing) - Python | Welcome to underground

asyncio, threadingのような非同期処理によるWebスクレイピングするのであれば、その辺りを担ったパッケージaiohttp, grequests, requests-futuresを使うのが吉(後述)。

multiprocessing

既に作成済みの関数群を並列化するならjoblibかmultiprocessingが良さそう
Python並列処理(multiprocessingとJoblib) - Qiita

multiprocessingであれば標準module
ごく簡単な Python multiprocessing の使い方 - Librabuch

以下のようによしなに

from multiprocessing import Pool, cpu_count
import requests

def main(x):
    """スクレイピング処理."""
    return requests.get(x)

c = cpu_count()
pool = Pool(c)
l = [p.get() for p in [pool.apply_async(main, args=(x,)) for x in URL]]
c.close

もしくは

from multiprocessing import Pool
import multiprocessing as multi

p = Pool(multi.cpu_count())
p.map(main, url_list)
p.close()

この場合引数がリストになってないといけないので、リストのリストのような状態になる場合がある。

より実践的にPoolを作ってQueue,Pipeで処理するなら以下が参考になる。
マルチプロセッシング(multiprocessing) - Python | Welcome to underground
multiprocessingのあれこれ - Monthly Hacker's Blog

multiprocessingはclose()しないとガバガバメモリを食い続けるので気をつける必要がある。

joblib

joblibはpipで導入

pip install joblib

こちらの方が記述が簡単であり、子プロセスの自動削除など使いやすさがある。
n_jobsに-1を指定すれば使えるだけプロセスを使ってくれるし、1にすれば1Threadで普通に処理できるのでテストもしやすい。

from joblib import Parallel, delayed
Parallel(n_jobs=-1)(delayed(main)(x) for x in url_list)


ただjoblibは反応しなくなる事がまれにあるかなと使っていて感じた。
(多分各プロセスのマージ処理がかなり重たい)


以下記事ではmultiprocessingの方が高速という話もある。(真偽不明)
Python並列処理(multiprocessingとJoblib) - Qiita


 

aiohttp, grequests, requests-futures

非同期なHTTP通信を扱うライブラリ群は多々ある。

aiohttp

aiohttpはasyncioを用いながらrequestsに似たAPIを使って情報を取得するライブラリ。
記述が簡単で、基本的なrequestsらしい操作に対してデコレータの設置とyieldを書いてやればよい。

リクエストの時間をコントロールするにはsemaphoreを使う。
Synchronizationで同期中にあるコルーチンが動く数を制限する事ができる。

GitHub - aio-libs/aiohttp: HTTP client/server framework for asyncio
http://aiohttp.readthedocs.io/en/latest/migration.html

最もシンプルな例を書くと

import aiohttp
import syncio

@asyncio.coroutine
def get_url(url):
    session = aiohttp.ClientSession()
    response = yield from session.request('GET', url)
    html = yield from response.content.read()
    # htmlを表示
    print(html)
    # セッションはちゃんとcloseする
    response.close()
    yield from session.close()

url_list= ['hoge', 'piyo', 'koko']
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([curl(url) for url in url_list]))

記述面で少し制限される部分があるが、適切にsyncioを振り分けられるので良い。
パラメータが少し少ないのがネックではあるが、asyncioの中でPhantomJSの起動と停止を書いてやるだけで、よしなにやってくれる非同期処理が可能である。

以下gitのサンプルはめちゃくちゃ充実している。
aiohttp/examples at master · aio-libs/aiohttp · GitHub

日本語の記事は以下の記事くらい。
(もしかしたら今後ちゃんと書くかもしれない)
asyncioを用いたpythonの高速なスクレイピング | POSTD

 

grequests

grequestsは非同期ネットワーク処理が可能なgeventにrequestsを乗せたライブラリ。
GitHub - kennethreitz/grequests: Requests + Gevent = <3

pip install grequests

かなり簡易に非同期なrequestsを扱える

import grequests

url_list = ["hoge", "piyo", ...]
header = {}
response = (grequests.get(url, headers=header) for url in url_list)
for x in grequests.map(response, size=3):
    print(x.text)

はあ簡単。
sizeで処理数の指定もできるし、基本的にrequestsと同様に扱える。
JSの実行が不要ならこれで十分そうですね。
返るリストの失敗した箇所はNONEな注意すれば、requestsと使い方が同じで慣れやすい。

まだまだ全然開発途上という感じなのと、日本語の記事はキツいのしかないので今後また別で記事書くかも。


 

requests-futures

requests-futuresはgrequestsと違って標準moduleの中の並列処理のconcurrent.futuresに乗っかっているライブラリの1つ。
GitHub - ross/requests-futures: Asynchronous Python HTTP Requests for Humans using Futures
requests-futures 0.9.7 : Python Package Index


pipで導入できる。

pip install requests_futures

例えば以下のように書くだけで非同期処理が実行できる。

from requests_futures.sessions import FuturesSession

session = FuturesSession()
header = {}
one = session.get("hogehoge.com", headers=header)
two = session.get("hogehoge.com", headers=header)

response_one = one.result()
print(response_one.content)
response_two = two.result()
print(response_two.content)

人によってはgrequestsより直感的かもしれない。
grequests同様、中身がrequestsなので引数の管理もしやすい。
例えばだがresponse_twoの取得に失敗した場合、response_twoが「NameError: name 'response_two' is not defined」となってしまうのでそこだけ注意したい。


 

scrapy

色々書いたが、同じサービスで形式がほぼ決まっている(例えば特定のニュースサイトの記事をグルグルスクレイピングしたい, 機械学習向けの画像データを収集したい)ような場合は、既にScrapyなるフレームワークがあるのでそちらを利用した方が早い。

多分このフレームワーク1つで本が書ける程なので、ここでは紹介程度にしておきたい。

Scrapyの思想があまり好きになれないというのもありますが(HTMLを保存せず解析するため負荷が大きい, JSの実行は結局独Spiderを自前, あーだこーだ...)。

Scrapy CloudなるScrapy設定をdeployすれば勝手に回してくれるサービスがあったり、ScrapyだけでQiita連載書いてる人が居たりする。

シンプルにScrapy入門は良いと思うので読むと良いと思います。
Scrapy入門(1) - Qiita

2016年5月にPython3に対応したのですが、それまで2系でのネット記事や書籍出版が多く行われているので、注意が必要。
Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

アーキテクチャが図としてあるので概念と紹介だけ。
https://doc.scrapy.org/en/latest/_images/scrapy_architecture_02.png
Architecture overview — Scrapy 1.5.0 documentation

大きくは「Engine」「Scheduler」「Downloader」「Spiders」「Item Pipeline」の5つが絡んで動作している。

Engineが各コンポーネントのフロー制御を行い、Schedulerがスケジューリングとキューイングを行う。
DownloaderとSpidersが主にスクレイピングを行う部分で、Custom Classを適宜設定する事が可能。
Item Pipelineに流し込んでDBやtext、画像で保存するアーキテクチャ

別々のコンポーネントをそれぞれ動かす事で高速な処理を可能にしており、Spidersをちょろっと書けばすぐスクレイピングを始められる事も含め非常に良くできている。

しかし、パラメータの数はゆうに100を超えるので調整がなかなか難しい。
また。サーバへの負荷が主にSleepでの調整だったり(別途記述しようと思えばできるが)と、JSを動かすにはwebdriver周りを結局自前で書くことになるので、大きめのフレームワークらしい欠点は備えている。
(便利かつデフォルト設定でよければすぐ回せるので自分も機械学習用の画像集めとかには使っています)


 

SimpleHTTPServerとThreadを使ったunittest

Webスクレイピングはコードが肥大化しやすく、かつ複雑化しやすいです。
なのでテストコードを書いておくのが良いでしょう。

内容については、以下に別途記事を書きました。

vaaaaaanquish.hatenablog.com


 

法律の話

重要なのは著作権法と動産不法侵入。
以下にまとめておく。

基本的に押さえておくべき法律

  • 著作権法
    • 47条の7 情報解析のための複製等
      • 情報解析のためならデータを複製していいがマナーを守れ
    • 47条の6 送信可能化された情報の送信元識別符号の検索のための複製等
    • 施行令第7条の5 送信可能化された情報の収集、整理及び提供の基準
      • プログラムにより自動的に収集、整理して他の規定にかかってたらちゃんと削除
    • 基本的にWeb上で公開されているものは著作者のものであり知的財産
    • データ分析に使用する場合は違法ダウンロード系でなければOK
    • データベースにするにしても最小限でスキーマが分からないレベルで
    • スクレイピングした情報をそのまま公開するのはほぼアウトだと思ったほうが良さそう
    • セーフハーバー保護やDMCAの下利用できるものもある
    • 画像は商標などもあるので注意
  • 動産不法侵入
    • サーバへのアクセスで抵触しかねない基準と対策
      • 同意の欠如 -> Webページが提示する条項を守ろう
      • 実害 -> サーバへの負荷等を考えよう
      • 意図性 -> プログラムを書いたなら意図がある
    • 1秒に1リクエストあれば良いんじゃないかという資料がある

 

判例と倫理

一応個人情報保護士なる資格を取るにあたって勉強したが、なかなかこの辺は難しいと感じる。
難しいのは判断基準。
分析に使う情報取得は権利的にOKだが「機械学習データこんな感じですと集めたデータをアップロード」辺りはアウトっぽい。
言うなれば「節度と倫理を守ろう」だ…


Serverの負荷は「1秒に1回リクエスト論」に対してよく「岡崎市立中央図書館事件」が議題に上がる。
2010年に図書館のDBに1秒Sleep入れてアクセスしたが(図書館のDBのバグが問題で)負荷になってしまい警察に呼ばれた人の話。
控えめに言って、この時のインターネットの盛り上がりはすごくてTwitter2chも以下のブログの虜だったので実際一回は全体眺めるのを勧めたい。
(不謹慎ながら端から見てる分にはクソ面白かった…)
librahack.jp


後企業だとeBayとBidder's Edgeの話とか。
1997年にメタオークションサイトを作るためebayサイトを1日100,000回叩いたら動産不法侵入になった話(Wikiがある)。
eBay v. Bidder's Edge - Wikipedia https://en.wikipedia.org/wiki/EBay_v._Bidder%27s_Edge
最終的に2001年に金で和解したらしいけど、その時の金額は個人で払えるレベルのアレじゃないっぽい。


正直コーディングする前に設計上で他の作業と並行させるとか、出来る工夫は沢山あると思うのですべき。
スクレイピングだけ切り離すと突然家に警察が来てもおかしくないなというグレーなやつ。

 

技術的なルールと各規約の読み込み

技術的なルールもあるのでまとめておく。これは遵守したいところ。

  • 技術的なスクレイピングルール
    • robots.txtやrobots metaタグを守る
      • クローラへの指示が示されている(アクセス禁止のパスとか)
      • 見つけたら守る必要がある
      • 読まなければ営業妨害になる可能性
    • HTMLにPragma:No-cacheとなっていたらデータ収集しない
    • aタグにrel="nofollow"が設定されていたら辿らない
    • User-agentなどをきちんと設定しIP偽装等をやめよう


追記で規約周りの読み込み記事を書きました。
vaaaaaanquish.hatenablog.com
スクレイピングの間隔はどうすればいい?」「規約は?」「違法でないの?」という人のために法律等もまとめています。


書籍

関連して読んだ書籍とか

日本の書籍

Pythonクローリング&スクレイピング -データ収集・解析のための実践開発ガイド-


正直基本的な事だけならこれ一冊で良くねと思えるバイブルっぽい良書。
Pythonの基本的な事もそうだが、Unixコマンドも多々解説しており、基本的な事が出来るようになる流れで書いてある。
(まずはwgetしてgrepしてjqで~とか、Pythonならやりたい事別にこうしてとか~)

robotsに対する処理とか、負荷のかけかたも実践的なコードがある。

一応解析のやり方としてAPIの利用や、自然言語処理技術の紹介、SPARQLのBigQueryの利用方法等が1章分ある。

この本の良い所はScrapyを1章まるまる使って解説している所にあると思う。
あのクソ多いパラメータを重要な所だけ抜き取って書いてくれてるので、Reference読む前に読んでおくと楽。
Scrapyの中がどう動いてるか理解できたのはこの本のおかげ。

クローラの継続実行に関する解説もあり、Linuxへの理解もちょっと深まる。


 

Pythonによるクローラー&スクレイピング入門

これも日本語の書籍の中ではオススメできる本です。

一番新しい書籍なので当たり前ですが、Python3.6への対応や、実在するWebページに対するスクレイピングの例、Sampleソースコードのダウンロードができます。
curl等のコマンドやgit、正規表現MySQLの話、プログラミングの基礎みたいな話が3章まで続き、やや蛇足感がありますが、4章以降はしっかり例が示されており初心者に分かりやすい構成になっていると思います。

取ってきたデータのXMLCSVへの変換やテキスト情報の抜き出し(w3lib)、webdriver、並列処理、loggingの記載、長めのScrapyの話があります。
DjangoでWeb API化して運用する構成の話など、やや蛇足かなと思う部分も多いですが、著者はGunosyの方だそうで、実務で得られた経験や知見を活かしながら書かれた本だと思います。

実務で突然スクレイピング始めろって言われたら買っておくといいです。


 

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

実践 Webスクレイピング&クローリング-オープンデータ時代の収集・整形テクニック

2章まで、Pyhonの使い方(インストールからprint関数の利用、四則演算とかそういうレベルから)が書いてあって、かつ2章までで書籍の4割くらいを使っている。

スクレイピングに関する話は中の数章で、WebAPIの利用、SPARQL、スクレイピングサービスのkimonoの紹介という感じ。
加えて法律とrobot.txtに関する話がちょこっと程度。

最後4割はExcelAWKコマンド、Pandasの使い方が載ってる。

あまりに知見が少なく、説明も痒いので正直かなり厳しい。
著者の情報を見るにそれなりのエンジニアっぽいコミュニティや受賞を持っているようだが果たしてこれは…という感想。


Pythonによるスクレイピング&機械学習 開発テクニック

「絶対これ機械学習エンジニアが書いたでしょ…」ってなる。

2章までが主にスクレイピングの話で、確かにrequests+BS4+Selenium+PhantomJSを使った実践的なコードと、cronの操作、WebAPIの話が書かれている。
(本当に基本的な所までが連ねてある感じで書いてある)

残りの6~7割程度が本当に機械学習の話しか載っていない。
sklearnからTensorFlow、MecabとCabochaによる自然言語処理ベイズマルコフ連鎖の実装、OCRやCNNでの画像認識…とかなりの実践機械学習本。

スクレイピングの知見含めて正直Qiita等で間に合うレベル感の内容なので、「PythonでのWebスクレイピングから機械学習を使った分析まで全体感を見ながら通しで勉強したい!」という人には向いているかも。

スクレイピングとは機械学習とはなんぞやという人が読めば、確かに世界が広がりそうな流れの書籍にはなっている。
あと、サンプルコードがダウンロードできるようになってるのも良い点。


 

PythonによるWebスクレイピング

PythonによるWebスクレイピング

PythonによるWebスクレイピング

Web Scraping with Python: Collecting Data from the Modern Web

Web Scraping with Python: Collecting Data from the Modern Web

Pythonを勉強した後、スクレイピングをとりあえず試したいという人向けなレベル感の書籍。

PythonでのCSVJSONの扱い方とか、GoogleTwitterAPIの使い方、PythonMySQLの解説、NLTKによる自然言語処理による分析、Tesseract OCRによる画像から文字抽出する部分辺りはスクレイピング本としては余計に感じた。
requestsとBeautifulSoupを扱っているが、めちゃくちゃ有益な知見とまではいかなかった(Qiitaやブログ記事でカバーできそう)。

順を追って色々勉強したいという人向けである。

良いところを挙げると、本記事には乗せていないPySocksを用いた、Torによるスクレイピングの匿名化の話等が書いてある。
GitHub - Anorov/PySocks: A SOCKS proxy client and wrapper for Python.
また、ハニーポットをどういう風に避けるかみたいな話もある。

付録の事案紹介や倫理の部分は丁寧で、実際に弁護士に伺った部分があるとの事。
事例が国内外解説してあって「日本法とアメリカ法は違うのでおおよそでも良いから読んだほうが良い」とのこと。

英語版も似たような内容。
英語版筆者が元々Webスクレイピングより分析の仕事をしていたようで、まあそこに寄ったのかなって。


 

クローリングハック あらゆるWebサイトをクロールするための実践テクニック

クローリングハック あらゆるWebサイトをクロールするための実践テクニック

クローリングハック あらゆるWebサイトをクロールするための実践テクニック

Javaの本なので基本的にこの記事の趣向とは離れるが、HTTPや各認証の仕組み等について触れられて居るので良い。

クローリングとしては、JavaSeleniumを用いた基本を抑えつつ、その仕組みを重点的に抑えている書籍。
ビズリーチ社の社員が複数人で書いているため、章によって情報のバラつきが激しいのがちょっとネックだが、一歩踏み入った勉強ができる。


 

ウエブデータの機械学習

ウェブデータの機械学習 (機械学習プロフェッショナルシリーズ)

ウェブデータの機械学習 (機械学習プロフェッショナルシリーズ)

スクレイピングとしての書籍ではないが、「スクレイピングした後に機械学習をやりたい!」という人には絶対オススメ。

データのクレンジングや、様々な自然言語処理機械学習モデルの使い方が丁寧に書かれており、「Pythonによるスクレイピング&機械学習」を単体で買うよりは、この書籍とスクレイピング専門の書籍を1冊ずつ買うほうがお得。

話題のバースト検出やウェブのリンク解析等、幅広く業務に応用できる内容が書かれていると思う。

洋書

Web Scraping with Python

Web Scraping with Python (Community Experience Distilled)

Web Scraping with Python (Community Experience Distilled)

実践というより、Webに関する知見な書籍。
ただPythonがある程度書ける前提を持った人向けの書籍で、その分レベルは実践に近い(しかしPython2系)。

cacheを使って帯域幅を削減する方法や、スクレイピングの並列化、リンク走査の話辺りはかなり良い。
また、クッキーやブラウザレンダラの話は知見として有益な部分が多い。
それらのコードも載っているのだがPython2系なのが辛いところ。

あと「Pythonクローリング&スクレイピング」と同様、6章まるまるScrapyの話をふんだんに書いてくれているので、Scrapyの設計を理解するには使えそう(しかしPython2系)。

Pythonコードは少なめだが丁寧ではあると思う(しかしPython2系)。
以外と知見が散りばめられているので、値段にしては有益な書。

 

Python Web Scraping - Second Edition

Python Web Scraping - Second Edition: Hands-on data scraping and crawling using PyQT, Selnium, HTML and Python

Python Web Scraping - Second Edition: Hands-on data scraping and crawling using PyQT, Selnium, HTML and Python

多分邦書洋書含めた中でも最も実践的で丁寧な良書。

基本的なrequestからBS4, PhanotomJSとScrapyの解説が載っているだけでなく、キャッシュの利用や発生しがちなExceptionの解説、SeleniumPyQtによるGUI操作にまで及んでいる。
また、robot.txtのparseや、python-wohoisで取得先の情報見ましょうとか、「普段みんなスクレイピング周辺でどんな事してるの?」という疑問に対して痒い所まで手が届いた良い書籍。

またコードがGithubにあるのも良い。
GitHub - PacktPublishing/Python-Web-Scraping-Second-Edition: Python Web Scraping Second Edition, published by Packt

 

Create a web crawler in Python

http://amzn.to/2gHR3sy

URLだけ一応貼っておく。
文書の前半がPythonのインストール、エラー処理、四則演算といった内容。後半は本当にQiita以下の内容。

筆者を見ると、Qiita以下のクソ電子書籍を適当に出しまくってるオッサンらしいので本当クソ。
こういう本全部Amazonに報告するスクリプト書きたい。


 

おわりに

なんか色々書いてるうちに本が書けるくらいの量になってしまった…

Referenceも結構読んだので、それぞれのライブラリ毎にまた記事が書けると良いなと思っている気がします。

あと最後ですがこれ大事です。気軽にはじめたい人はScrapy。

Seleniumに変わるスクレイピング向けのPythonモジュール作りたい人居ないかな~。

他で簡素なまとめは以下が分かりやすいかも

Python3でクロールしようと思って調べたこと | mwSoft


あと他にこの書籍とかツールマジいいよ!ってやつあれば教えてくださいな。
 
 
追記 : User-Agentについて「UA偽装をやめよう」「よしなにUA書こう」という背反した記述があると指摘がフレンズからありました。

「確かに」以外の感想が得られなかったので修正しました。
User-Agentに自身の正しい情報を乗せるだけでなく、自身の連絡先等乗せてスクレイピングするくらいが紳士的ですね。

以下のような話も読んでおくとよさそう。

また以下のようにChrome headlessのアクセス情報の偽装に関する記事も

It is *not* possible to detect and block Chrome headless


 
追記 : !!

Firefoxのheadlessも使いました
vaaaaaanquish.hatenablog.com



追記 : 結構Python2系が小学生にバカにされる話に関するコメントが多いので元ネタを…

togetter.com
 

追記:環境構築や他headlessブラウザについて書きました


使ってみた感じChromeがゾンビ化しないし高速だしで現時点優勝候補です

 
追記:HTMLのエンコードですがUTF-8しか利用できなくなるので期待ですね

 

Pythonのhttp.serverを利用してWebスクレイピングのunittestを書く

- はじめに -

「Webスクレイピングで情報を収集する」という内容は多い。

しかし、Webスクレイピングのコードは肥大化しやすいだけでなく、細かな変更が多くなる。
テストを書いて変更の影響をちゃんと見ておく必要性が高い。

unittestとhttp.serverを使ったテストの実装についてメモしておく。


参考:python - How to stop BaseHTTPServer.serve_forever() in a BaseHTTPRequestHandler subclass? - Stack Overflow


- http.server -

http.serverはPython 2.xではSimpleHTTPServerと呼ばれていたもの。
(http.serverよりSimpleHTTPServerの方がググラビリティ高いかも)

Webサービス等の開発用にローカルサーバとして利用している人も多い。

21.22. http.server — HTTP サーバ — Python 3.6.1 ドキュメント


コマンドでの実行も可能だが、Pythonからだとハンドラを指定したHTTPServerを以下のように記述し、簡易にサーバを立てる事ができる。

from http.server import HTTPServer, SimpleHTTPRequestHandler

httpd = HTTPServer(("localhost", 8888), SimpleHTTPRequestHandler)
httpd.serve_forever()

これによって http://localhost:8888 に、ローカルでのServer環境が整う。


 

- unittestにかませる -

serve_foreverによって起動されるサーバ(SocketServer.py)は以下のようにWhileで実行されている。

def serve_forever(self):
    """Handle one request at a time until doomsday."""
    while 1:
        self.handle_request()

なのでwrapperとなるclassを書いてやって、server.shutdown及びserver.server_closeを実行できるようによしなに書いておけば良い。

また、HTTPServerが動いているThreadでPythonのコードを動かすのは少し困難なので、別のThreadで動かしてやる。


別ThreadでHTTPServerを実行し、requestを使ってアクセスするunittestは以下のような感じ。

from http.server import HTTPServer, SimpleHTTPRequestHandler
import threading
import unittest

HOST = "localhost"
PORT = 8888

class StoppableHTTPServer(HTTPServer):
    """
    ThreadでSimpleHTTPServerを動かすためのwrapper class.
    Ctrl + Cで終了されるとThreadだけが死んで残る.
    KeyboardInterruptはpassする.
    """
    def run(self):
        try:
            self.serve_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()

class TestWebScraping(unittest.TestCase):
    def setUp(self):
        """Use unittest setUp method."""
        self.server = StoppableHTTPServer((HOST, PORT), SimpleHTTPRequestHandler)
        self.url = "http://{}:{}/test_case/index.html".format(HOST, PORT)
        self.thread = threading.Thread(None, self.server.run)
        self.thread.start()

    def test_requests_get(self):
        """
        requestsモジュールでURLを叩くunittest.
        contentが返ってくるか.
        """
        r = requests.get(self.url)
        self.assertEqual(type(resp.content), str)

    def tearDown(self):
        """Use unittest tearDown method."""
        self.server.shutdown()
        self.thread.join()

unittestのコードと同ディレクトリにから./test_case/index.htmlが設置されていれば良い。

最後はServerを終了してthreadをjoinするようになってる。


後はテスト内容に合わせてhtmlを修正したり複数ファイルを付与してく。

JSやPHPの実行もできるのでよしなにテストができる。


 

- おわりに -

BaseHTTPServerとかTCPServerとかTCP扱える枠組みなら多分似たような事ができると思います。

他にも良さげなやり方があったら知りたい。


 

実践 Python 3

実践 Python 3

headless chromeをPythonのseleniumから動かして引数を考えた (Ubuntu 16.04)

- はじめに -

Chrome 59が正式版となりheadless版も正式に動き始めました。めでたい。

New in Chrome 59  |  Web  |  Google Developers

headless chromeUbuntuに導入してPythonから触ったという記事です。

Ubuntuへの導入から、実行時の引数となるargsの考察などを含みます。



スクレイピング関連記事です。

vaaaaaanquish.hatenablog.com



- インストール -

まずPythonからの起動に必要なchromedriverを取得しておきます。
apt-getでも入りますが最新版が欲しいので以下のように。
(記事書いた当時で最新版は2.29)

wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo mv chromedriver /usr/local/bin/


依存ライブラリとなるlibappindicator1をインストールしておきます。
事前にやらないとハマるっぽいので、先走ったら後述で入れたChromeをアンインストールしてやり直すのが良いです。

sudo apt-get install libappindicator1


Chromedebパッケージを取得してきます。
公式ページに行くと、小さな文字で「Google Chrome をインストールすると Google レポジトリが追加され、Google Chrome がシステムで自動更新されます。Google のレポジトリを追加したくない場合は、パッケージをインストールする前に「sudo touch /etc/default/google-chrome」を実行してください。」と書かれています。
スクレイピング等ガンガン回したい場合などはやっておくべきでしょう。(やらなくてもOK)

sudo touch /etc/default/google-chrome
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb


以下でインストールします。

sudo dpkg -i google-chrome-stable_current_amd64.deb


この時以下のようなエラーが出るかもしれません。

Errors were encountered while processing:
google-chrome-stable


その時はapt-getでこうしてこう。

sudo apt-get install -f
sudo dpkg -i google-chrome-stable_current_amd64.deb


インストールおわりです。


- Pythonで実行 -

Python3.xを使います。2系は小学生にバカにされるからです。
seleniumのwebdriver配下にchrome.optionsというのがあるのでそれを利用して、--headlessを指定します。

「--disable-gpu」は以下Google曰く現状暫定必須だそうです。
ヘッドレス Chrome ことはじめ  |  Web  |  Google Developers


from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(chrome_options=options)
driver.get('hogehoge.com')
print(driver.page_source)

後はPhontomJS等と同じように使えます。


driver.save_screenshotしてみたら以下のように

f:id:vaaaaaanquish:20170606194222p:plain


多分デフォルトだとUbuntuのフォントが足りず文字化けする。
私は導入済み。以下参考。


 

- パラメータに関する考察 -

Google Chromeはとにかく実行時の引数が多い。

List of Chrome Driver command line arguments – Assert Selenium

ここではスクレイピングに関係しそうなパラメータだけ抜粋して考える。

「--headless」「--disable-gpu」は必須。
SSL周りのエラーを許容するために「--ignore-certificate-errors」を付ける。
実行不可なコンテンツやセキュリティの許容範囲を広げるなら「--allow-running-insecure-content」「'--disable-web-security'」すると良さそうだけど、常用はできそうにない。
デスクトップ通知やExtensionの利用も基本不要だと思われるので「--disable-desktop-notifications」「--disable-extensions」する。
ユーザエージェントの指定は「--user-agent=hogehoge」でダブルクオーテーションで囲まなくても大丈夫。

かつては「--disable-images」なるフラグで画像を読み込まない設定が可能だったけど、バージョン5.0.335.0より使用不能に。
画像をオフにするにはprefsに対してprofileを設定する方法があるよ〜との記述もちらほらある。
こんなやつ

prefs = {"profile.managed_default_content_settings.images":2}
options.add_experimental_option("prefs",prefs)

でもこれではダメで、正解は「--blink-settings=imagesEnabled=false」っぽい。
設定画面でimagesを切りに行くと以下を参考にしたらできた。
https://groups.google.com/a/chromium.org/forum/#!topic/headless-dev/0zD4nAyVoCY


あとは「--lang」で言語の指定。
必要に応じて「--proxy-server」などを利用する。

例えばこんな感じ

from selenium import webdriver

options = webdriver.ChromeOptions()
# 必須
options.add_argument('--headless')
options.add_argument('--disable-gpu')
# エラーの許容
options.add_argument('--ignore-certificate-errors')
options.add_argument('--allow-running-insecure-content')
options.add_argument('--disable-web-security')
# headlessでは不要そうな機能
options.add_argument('--disable-desktop-notifications')
options.add_argument("--disable-extensions")
# UA
options.add_argument('--user-agent=hogehoge')
# 言語
options.add_argument('--lang=ja')
# 画像を読み込まないで軽くする
options.add_argument('--blink-settings=imagesEnabled=false')
driver = webdriver.Chrome(chrome_options=options)
driver.get('hogehoge.com')
print(driver.page_source)


以下も参考に
起動オプション - Google Chrome まとめWiki
 

 

- おわりに -

今までPythonでWebスクレイピングというとPhontomJSなるブラウザが圧倒的シェアでした。

しかしPhontomJSもなかなかバギーで、プロセスがゾンビ化するなどの問題を抱えていました。

headless chromeに関する日本語ページが更新されるなど、期待が高まっていたここ半年です。

こんな記事も。

www.infoq.com

このままheadless chromeが希望の星となる事を期待しています。


人工知能学会全国大会に参加した #jsai2017

- はじめに -

JSAI2017 – 2017年度 人工知能学会全国大会(第31回) に参加した。

今まで学会への参加は学生発表、個人の聴講参加だけだったが、今回は企業ブースでの参加となった。

あまり聴講への参加は出来てないがメモ程度に気になったものをまとめておく。


- 聴講 -

企業ブース出展者での参加では1企業につき2名分のみ聴講証が渡されていたため、6割は会社の犬として自社の説明やステッカー配りなどをしていた。

以下には会社の犬時間以外で聴講し気になった所のみまとめておく。

会社の犬なので、基礎研究より産業応用の部分が多いかもしれない。

05月23日

DNNによるRDF上の単語間の関係の予測を見たかったのだが、部屋に着いた時には質疑だった。

Deep Learning系やチュートリアルセッションはどこも激混みで、大変であった。


DNN圧縮時のパラメータと圧縮後の精度, 大きさの関係

DNN圧縮時のパラメータと圧縮後の精度, 大きさの関係

Deep CompressionなるDeep Neural Netの圧縮手法に関する発表。
元論文にあるK-meansではなくX-meansを使っていた。
そうしたら、パラメータを設定する時の基準の知見が得られたよという内容。
クラスタ数を自動推定するX-means法を調べてみた - Qiita


「DNNの圧縮に枝刈りを使う」というのは恥ずかしながら初めて知った手法で、Deep Neural Networksの重み量子化と共有, ハフマン符号化をやれば上手くいくらしい。前提となる論文は以下。

Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding

先に上記Deep Compressionの話を書いておくと、所謂「枝刈り」と同じ概念で、ニューロン間を接続しているノードを狩ってパラメータ数削減、計算量圧縮を図るというもの。論文ではVGGモデルが49分の1にまで圧縮され、高速化されながら同等の精度を保っている。

まずは閾値を取って重み行列を疎行列化してしまう。
疎行列となった重み行列をK-meansでまとめて クラスタのインデックスとcentroid行列に分けて扱いやすくする(量子化)。
そうした場合、追加の学習をしないとDNN全体の精度が落ちた状態になるが、K-meansで求めたクラスタを使い、クラスタ毎の勾配を使いながら追加で学習を回す事で精度が担保される(Fine-Turning)。
その上で重みをハフマン符号化してやってコンピュータ最適にしてやる事で、速度と電力消費が圧倒的に改善されている。


本題の発表では、ハフマン符号化を使わず、圧縮後に再学習させない事を制約条件としていた。
(「まあそこまでやるのどう考えても実装と設計が面倒だよね…」と正直思った…)

本題はK-meansのパラメータ設定に関して基準が欲しいとの事で、パラメータを変更したいくつかのモデルとX-meansで試していた。

結果としては、量子化する際のクラスタ数が少ないと精度が下がるが、大きすぎるとモデルが大きくなるのであんまり意味なく、そうするとX-meansを使った場合に「小さすぎ」「大きくなりすぎ」問題が発生してあんまり良くなかったッスという感じだった。
ただ、大まかな基準は示されていたのと、再学習等の余地は大きそうだなと思った。
あと、圧縮方法について幾つか調べてDeep Learning with Limited Numerical Precision等を知れて良かった。

日本語だと以下を見つけたので気になれば。
ニューラルネットのメモリ消費を小さくする類の手法 - うどん記
introduction-to-deep-compression (SlideShare)

追記:


ニューラルネットワークの量子化についての最近の研究の進展と、その重要性 - SmartNews 開発者ブログ 良いです。




CNNへの競合学習の統合による表現学習の強化

CNNへの競合学習の統合による表現学習の強化

DNNの強みは入力データから認識に必要な情報の基底を獲得する表現学習(Representation Learning)にあるけど、今のCNNってあんまり表現学習に適してはいないよね。
じゃあSOMなんかに使われる競合学習みたいな教師なし学習と共存した学習手法があれば、表現学習可能なCNNって作れるよねという内容。

実は学生時代かなり似た研究をしていた時期があって、SOMとNNを接続して上手く学習させようとするCounter PropagationとかSOMを多層化して教師あり学習に応用するSOINNなんかを熱心に調べていた。
(結論から言うとこれはあまり上手くいかなかった...)

発表では、SOMで言うところの勝利ノードの出力(ユークリッド距離)とBack Propagationをシームレスに組み合わせ、SOMフィルターをいくつか作りその数が十分な場合において通常のCNNより良くなったというものだった。
f:id:vaaaaaanquish:20170527231824p:plain

教師なし学習が前提にあれば学習データが少ない状態からでも応用できる可能性があると示唆はしていたが、指摘の通り学習データが少ない場合にSOMのフィルター数を十分に確保できないので難しそう。

SOMって結構安定しないので、数確保したりSOMの研究みたいになりはじめてう~んってなっちゃうんだよね…としみじみ思った。
SOM自体がそれなりの表現学習能力を持っていたと仮定するなら、後方に接続するネットワークもPre-trainingを十分に行ったものを使うとかすると良さそう。


05月24日

お仕事でも自然言語処理にお熱で、時間的にちょうど医療応用の部屋で自然言語処理系のセッションがあったので聞いた。

あとはせっかくなのでオーガナイズドセッションでLinked Dataの話を聞いた。


深層学習による医療テキストからの固有表現抽出器の開発とその性能評価

深層学習による医療テキストからの固有表現抽出器の開発とその性能評価

医療文書って非文法的で断片化した表現が多いので難しい。海外だと語彙サーバーのようなものがあったり標準的なツールがあるが、日本にはないので作りたい。
発表では、よくある双方向LSTM(Bi-LSTM)とCRFをつかった手法を適応し、既存システムに多いCPFベースより精度良く出せたという事だった。
課題は2つあり、疾患識別(ER)と疾患と医師の初見の分類(P/N分類)を1つのEnd-to-Endなモデルで行いたいらしい。

参考にしたのは多分以下(?)。

Bidirectional LSTM-CRF Models for Sequence Tagging

Bi-directional LSTMは文脈を過去(past)と未来(future)に分けて、過去->未来(未来->過去)と双方向に接続するLSTMの拡張モデル。
それらを系列ラベリングに適応するために確率的条件場(CRF)に適応するというのは最近の流れの一つである。

発表内ではBi-LSTMに対して、表層文字のみ入力する場合や辞書の活用等と比較していたが、あまり有意差があるようには見られなかった。
(なかなか分野が違うので、結果を見ても分からず…)

あと普遍的な課題として「コーパス量がもっとあれば」という話をしていて、やっぱり自然言語処理機械学習組み合わせるとどこもそうなるよなあ…と思った。

ついでにBi-LSTMについて調べてた時に見た把握しやすいURLを書いておく。
pythonでBidirectional LSTMを使った分類問題 - ゆるふわめも
ニューラルネットワークで時系列データの予測を行う - Qiita
Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs – WildML


Sleep Pattern Visualization via Clustering on Sound Data

Sleep Pattern Visualization via Clustering on Sound Data

睡眠の改善のための分析をSequenced-based kernel SOMを使って視覚的に行ってみようという発表。
利用したのは、睡眠中の音声データで、スコアリングしていくと睡眠改善において音データは非常に重要だという事がわかる。

Sequenced-based kernel SOMは、KLSOM(Kullback-Leibler情報量を使ったSOM)を組み込んだKernel SOM(カーネル関数を使うSOM)、SbSOM(クラスタの時系列変化を可視化するためのモデル)を組み合わせたモデル。
これで見た。


結果を見れば分かるが、音声データが睡眠の情報と並列している事が視覚的にわかる(歯軋りの時間と悪い睡眠の時間が同じ等)。
f:id:vaaaaaanquish:20170527224123p:plain


SOMの時系列可視化はかなり有用そうなので自分でも何か使いたいなと思う。

英語の発表で、プレゼンを見ながらギリギリ追い付いたくらいなのでこの程度で。
質疑では、スマフォで録音した音声じゃなくてマイクにすれば~みたいな話がなされたが全く追い付けなかった(英語力が皆無)。


オーガナイズドセッション OS-7 意味と理解のコンピューティング (2)

オーガナイズドセッション OS-7 意味と理解のコンピューティング (2)

Linked DataやSPARQL、セマンティックWebの話を産業科学研究所の古崎先生がセッションしていた。
なかなか歴史のある話で面白かった。

後は宇宙航空研究開発機構宇宙科学研究所の人の宇宙研究で役立つ事はない話とか、雑誌取材の裏側の話とか。

後はおざき先生が人工知能学会、無限に楽しかったという話をしていて楽しそうだった。
DeepNetは夢があっていいですね。


05月25日

学生のポスターセッションがあったので、同じ部屋内にあった企業ブースは人が混み混みだった。

PFNの岡野原さんの公演を聞きに行きたかったが、こちらも激混みでダメだった…流石岡野原さん…


RDFデータベース構築によるユーザの気分に応じた観光スポット推薦システムの提案

RDFデータベース構築によるユーザの気分に応じた観光スポット推薦システムの提案

観光スポットの推薦システムをSPARQLとAndroid OS上で作成したよという内容。
推薦方法としては観光地データをTF-IDFに投げて、感情(喜べる, 泣ける,…,etc)で分けてユーザに推薦するという事だった。

指摘でもあったが、評価に利用したのがGoogle API等に比べた「新規性」なる軸であり、元となるデータベースが違うので怪しい部分があるが、感情によって推薦するシステムはかなり有意であるとは感じた。


価値観に基づく行列演算ベース情報推薦システムの提案

価値観に基づく行列演算ベース情報推薦システムの提案

商品推薦システムでよく使われる、行列計算を利用したMatrix Factorizationに価値観モデルを付与してやろうという話。

価値観モデルはある商品の属性に対するこだわりを定義するモデルで、簡易な数式ながら少ない評価数で安定したモデル化が可能となる事が知られている。
https://kaigi.org/jsai/webprogram/2017/pdf/310.pdf

Matrix Factorizationは予測評価値をユーザベクトルとアイテムベクトルとに分解し、行列計算と捉える事で、二乗誤差を使ったSGD等で最適化していく方法。
Matrix Factorization: A Simple Tutorial and Implementation in Python @ quuxlabs
MatrixFacorization を使った評価予測 ―アルゴリズムシリーズ 3―|サイバーエージェント 公式エンジニアブログ
Matrix Factorizationとは - Qiita

価値観モデルとなるベクトルを別途用意して、Matrix Factorizationしようという話。
従来の行列ベースのものと、手動でアイテムベクトルを設定したもの、SGDで最適化したものを比較していた。
SGDを回していくと精度が下がるなど少し疑念の残るグラフが示されていたが、価値観モデルを加えると初期の精度が良くなり、少ない評価で良い結果が出せる事が分かった。

指摘にもあった通りFctorization Machinesのような手法を使うともっと良くなりそうである。
Factorization Machinesについて調べてみた - Qiita

あと会場で調べてたらこんなんとか


行動経済学的な知見を用いた消費者の情報探索行動の予測とレコメンデーション法の開発

行動経済学的な知見を用いた消費者の情報探索行動の予測とレコメンデーション法の開発

あるWebサイト(中古車販売)のコンバージョンをRFやXGBoostを使って分析してみましたという内容。

価格帯や車種によって、特徴的なVCがあるという結果が見られた。

Google Analyticsの内容しか利用できなかったらしく、なかなか厳しそうだった。
分析データはやはり多い方が良い。


情報量に注目した推薦のための商品間関係性の分類法

情報量に注目した推薦のための商品間関係性の分類法

牛乳を買う理由にはクリームシチューを作る等の要因があり、同時に適した肉や野菜を買う。
つまり、商品関連性を見つければ次にユーザが買い物カゴに入れる商品が推定出来るのではという話。

条件付きエントロピー相互情報量を使い、「同時性・代替性(ある牛乳を買ったら他の牛乳を買わない)」「相補性(焼肉のタレを買ったら肉を買う)」「独立性(無関係な商品購入)」を定義し、次のネットショップにおける次の買い物商品を推定する。

結果を見ると、それなりに推定出来ながら、ネットショップなのでカップラーメンを複数種類まとめ買いされていたりとの課題は多かった。
加えて、ネットショップではUIによる差異が大きい事があると指摘されていた。確かにと思った。


- 聴講以外 -

はじめて企業ブースの中の人として参加してみて、自社説明や配布物配り、名刺交換をやった。
自社Tシャツを着た。完全に会社の犬だ。

学生時代は「なんで学会に企業ブースなんか出すんだろう」と思ってたけど、社会人になって企業ブースに企業側として参加してみて「なんで学会に企業ブースなんか出すんだろう」ってなった。

まあ冗談だが、ブランディング等の効果はあったと思う。
人工知能というか、機械学習にかなり注力しつつあってかぐるますたーガンガン雇ったりしてるので、機会としては良かった。
研究界隈の大ボスみたいな人が来て、クッソビビリながら自社説明するのとかスリリングで良かったです。
これからも弊社をよろしくお願いします。


個人としては幾人か昔から知っている人や、Twitterで知り合った人と挨拶出来たのが良かった。
挨拶してくれた皆さんありがとうございます。
挨拶できなかった人もまたいつか。


MicrosoftとPFNのMeetupではChainerの良い話が沢山聞けた。
海野さん相変わらず面白かったし、PaintChainerの心暖まる話も良かった。
MSの発表はde:codeで見たのと大体同じだったが、新しいSur◯aceをプレゼントする企画があってビビった。
色んな人に挨拶できたし、shi3zの激しい発言を聞けて良かった。


名古屋メシは味噌煮込みうどんが舌に合わなかった。
(有名な山本なんとか行ったんですがアレ美味いって言うんですか…)

でも、手羽先はめっちゃうまかったし会場近くに飲み屋があったのも良かった。

会場ちょっと狭くて廊下と階段なかなか辛かったけど、場所はめちゃ良かったよね。
美味しいご飯情報もネットで得られて良かった。


- おわりに -

知見が多く得られて、多くの人に会えて良かった。

ネットでは色々あったけどそっちはTwitterで。

あとこれ名古屋で一番ウケました。