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

この記事は[webスクレイピング Advent Calendar 2017](https://adventar.org/calendars/2105) 18日目の記事です。

# モチベーション
wgetの記事書こうと思ったけど他所のアドベントカレンダーでネタ被りしたので急遽変更。
気になってたCommon Lispを使ってスクレイピングをやってみようとおもう。
環境はmacOSで。

# Common Lisp導入

[こちら](https://qiita.com/t-sin/items/054c2ff315ec3b9d3bdc)の記事（いまから始める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