この記事はwebスクレイピング Advent Calendar 2017 18日目の記事です。
モチベーション
wgetの記事書こうと思ったけど他所のアドベントカレンダーでネタ被りしたので急遽変更。
気になってたCommon Lispを使ってスクレイピングをやってみようとおもう。
環境はmacOSで。
Common Lisp導入
こちらの記事(いまから始めるCommon Lisp)を参照
mac OSでCommon Lispやるときはroswellを入れればいいらしい
$ brew install roswell
セットアップ
$ ros setup
これで対話型インタプリタ(REPL)が使えるようになる
$ ros run
*印に変わったら成功
HTTPクライアント
pythonでいうとrequestsとかurllibにあたる
どうやらこちらもdexadorとdrakmaの二大流派
common lispのパッケージはql:quickload
でロードできる
- dexador
* (ql:quickload :dexador)
とりあえずこれでHTMLをgetでとってこよう
* (dex:get "https://ja.wikipedia.org/wiki/Common_Lisp")
実行結果
~~~ 略 ~~~
<script>(window.RLQ=window.RLQ||[]).push(function(){mw.config.set({\"wgPageParseReport\":{\"limitreport\":{\"cputime\":\"0.284\",\"walltime\":\"0.327\",\"ppvisitednodes\":{\"value\":5364,\"limit\":1000000},\"ppgeneratednodes\":{\"value\":0,\"limit\":1500000},\"postexpandincludesize\":{\"value\":80208,\"limit\":2097152},\"templateargumentsize\":{\"value\":9196,\"limit\":2097152},\"expansiondepth\":{\"value\":11,\"limit\":40},\"expensivefunctioncount\":{\"value\":2,\"limit\":500},\"entityaccesscount\":{\"value\":1,\"limit\":400},\"timingprofile\":[\"100.00% 209.440 1 -total\",\" 17.69% 37.051 3 Template:Navbox\",\" 16.89% 35.370 1 Template:Infobox_プログラミング言語\",\" 15.10% 31.618 1 Template:Infobox\",\" 13.08% 27.391 319 Template:Lang\",\" 8.88% 18.598 1 Template:Normdaten\",\" 7.89% 16.516 1 Template:プログラミング言語一覧\",\" 7.47% 15.654 1 Template:LISP系言語\",\" 7.07% 14.817 1 Template:Wikibookslang\",\" 5.88% 12.322 1 Template:Sister\"]},\"scribunto\":{\"limitreport-timeusage\":{\"value\":\"0.027\",\"limit\":\"10.000\"},\"limitreport-memusage\":{\"value\":1454412,\"limit\":52428800}},\"cachereport\":{\"origin\":\"mw1299\",\"timestamp\":\"20171211105149\",\"ttl\":1900800,\"transientcontent\":false}}});});</script><script>(window.RLQ=window.RLQ||[]).push(function(){mw.config.set({\"wgBackendResponseTime\":95,\"wgHostname\":\"mw1326\"});});</script>
</body>
</html>
"
200
#<HASH-TABLE :TEST EQUAL :COUNT 25 {1004CC8A53}>
#<QURI.URI.HTTP:URI-HTTPS https://ja.wikipedia.org/wiki/Common_Lisp>
#<CL+SSL::SSL-STREAM for #<FD-STREAM for "socket 192.168.100.104:49730, peer: 198.35.26.96:443" {10040C6563}>>
なぜかダブルクウォートの横にバックスラッシュが入ってる。なんやこれ
- drakma
* (ql:quickload :drakma)
こちらはhttp-requestを使う
* (drakma:http-request "https://ja.wikipedia.org/wiki/Common_Lisp")
実行結果
<script>(window.RLQ=window.RLQ||[]).push(function(){mw.config.set({\"wgPageParseReport\":{\"limitreport\":{\"cputime\":\"0.284\",\"walltime\":\"0.327\",\"ppvisitednodes\":{\"value\":5364,\"limit\":1000000},\"ppgeneratednodes\":{\"value\":0,\"limit\":1500000},\"postexpandincludesize\":{\"value\":80208,\"limit\":2097152},\"templateargumentsize\":{\"value\":9196,\"limit\":2097152},\"expansiondepth\":{\"value\":11,\"limit\":40},\"expensivefunctioncount\":{\"value\":2,\"limit\":500},\"entityaccesscount\":{\"value\":1,\"limit\":400},\"timingprofile\":[\"100.00% 209.440 1 -total\",\" 17.69% 37.051 3 Template:Navbox\",\" 16.89% 35.370 1 Template:Infobox_プログラミング言語\",\" 15.10% 31.618 1 Template:Infobox\",\" 13.08% 27.391 319 Template:Lang\",\" 8.88% 18.598 1 Template:Normdaten\",\" 7.89% 16.516 1 Template:プログラミング言語一覧\",\" 7.47% 15.654 1 Template:LISP系言語\",\" 7.07% 14.817 1 Template:Wikibookslang\",\" 5.88% 12.322 1 Template:Sister\"]},\"scribunto\":{\"limitreport-timeusage\":{\"value\":\"0.027\",\"limit\":\"10.000\"},\"limitreport-memusage\":{\"value\":1454412,\"limit\":52428800}},\"cachereport\":{\"origin\":\"mw1299\",\"timestamp\":\"20171211105149\",\"ttl\":1900800,\"transientcontent\":false}}});});</script><script>(window.RLQ=window.RLQ||[]).push(function(){mw.config.set({\"wgBackendResponseTime\":95,\"wgHostname\":\"mw1326\"});});</script>
</body>
</html>
"
200
((:DATE . "Sun, 17 Dec 2017 18:22:39 GMT")
(:CONTENT-TYPE . "text/html; charset=UTF-8") (:CONTENT-LENGTH . "155338")
(:CONNECTION . "close") (:SERVER . "mw1258.eqiad.wmnet")
(:VARY . "Accept-Encoding,Cookie,Authorization")
(:X-POWERED-BY . "HHVM/3.18.6-dev")
(:P3P
. "CP=\"This is not a P3P policy! See https://ja.wikipedia.org/wiki/%E7%89%B9%E5%88%A5:CentralAutoLogin/P3P for more info.\"")
(:X-CONTENT-TYPE-OPTIONS . "nosniff") (:CONTENT-LANGUAGE . "ja")
(:X-UA-COMPATIBLE . "IE=Edge")
(:LINK
. "</static/images/project-logos/jawiki.png>;rel=preload;as=image;media=not all and (min-resolution: 1.5dppx),</static/images/project-logos/jawiki-1.5x.png>;rel=preload;as=image;media=(min-resolution: 1.5dppx) and (max-resolution: 1.999999dppx),</static/images/project-logos/jawiki-2x.png>;rel=preload;as=image;media=(min-resolution: 2dppx)")
(:LAST-MODIFIED . "Mon, 11 Dec 2017 10:47:05 GMT")
(:BACKEND-TIMING . "D=104736 t=1513272634846516")
(:X-VARNISH
. "946126485 499974105, 450452950, 293262949 167365522, 672744697 669126420")
(:VIA . "1.1 varnish-v4, 1.1 varnish-v4, 1.1 varnish-v4, 1.1 varnish-v4")
(:AGE . "142534")
(:X-CACHE . "cp1052 hit/1, cp2007 pass, cp4032 hit/6, cp4029 hit/3")
(:X-CACHE-STATUS . "hit-front")
(:STRICT-TRANSPORT-SECURITY . "max-age=106384710; includeSubDomains; preload")
(:SET-COOKIE
. "WMF-Last-Access=17-Dec-2017;Path=/;HttpOnly;secure;Expires=Thu, 18 Jan 2018 12:00:00 GMT,WMF-Last-Access-Global=17-Dec-2017;Path=/;Domain=.wikipedia.org;HttpOnly;secure;Expires=Thu, 18 Jan 2018 12:00:00 GMT,GeoIP=JP:::35.69:139.69:v4; Path=/; secure; Domain=.wikipedia.org")
(:X-ANALYTICS . "ns=0;page_id=172200;https=1;nocookies=1")
(:X-CLIENT-IP . "111.239.64.212")
(:CACHE-CONTROL . "private, s-maxage=0, max-age=0, must-revalidate")
(:ACCEPT-RANGES . "bytes"))
#<PURI:URI https://ja.wikipedia.org/wiki/Common_Lisp>
#<FLEXI-STREAMS:FLEXI-IO-STREAM {100254B403}>
T
"OK"
こっちはやたら値が返ってきてるけど、やっぱりダブルクウォートの前にバックスラッシュがある。なんかもやっとするので消したいなあ。
取得したHTMLを変数に入れる
* (defvar *source* (drakma:http-request "https://ja.wikipedia.org/wiki/Common_Lisp"))
dexadorのほうは処理が終わらなかった。原因不明。
HTMLパーサー
plumpとclssを使う
- plump
* (ql:quickload :plump)
* (ql:quickload :clss)
* (defvar *parse* (plump:parse *source*))
* *parse*
#<PLUMP-DOM:ROOT {1004B7FEF3}>
これでbeautifulSoupみたいにHTMLの中を解析できるようになる
- clss
clss:selectを使ってparseからタイトルを抜き出してみよう
* (clss:select "title" *parse*)
#(#<PLUMP-DOM:ELEMENT title {10020211B3}>)
これだとステータスが返ってきてるだけなので、書き方を変えなければいけない
(plump:text (car (coerce (clss:select "title" *parse*) 'list)))
"Common Lisp - Wikipedia"
coerceは値をlistに型変換してる。carはlistの一番目の要素を取得する。
最後にplump:textをかけてやるとタイトルを取得できる。
所感
スクレイピングはpythonでいいんじゃないかな
参考:
http://blog.ryoana.com/entry/2016/12/30/004941
http://d.hatena.ne.jp/masatoi/20170203/1486121334