common-lisp
スクレイピング

Common Lisp入門してwebスクレイピングやろうとしてる

この記事は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