[Elixir] Flokiを使ってスクレイピングを行う

ふとHTTPステータスコードの一覧と日本語の説明のデータが欲しくなったので
ElixirのFlokiモジュールを使って [WikipediaのHTTPステータスコード](https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89)からデータを取得してみました。

# ほしいもの

以下のような構造を持ったリストを取得したいと思います

```elixir
[
%{
  status_code: 509,
  summary: "509 Bandwidth Limit Exceeded",
  description: "帯域幅制限超過。そのサーバに設定されている帯域幅（転送量）を使い切った場合に返される。"
},
%{
  status_code: 508,
  summary: "508 Loop Detected",
  description: "ループを検出。WebDAVの拡張ステータスコード。"
},
...
]
```

# コード


```elixir:scraping_wiki_httpstatus.exs
defmodule Parser do
  def parse(nodes), do: parse(nodes, [], %{})
  def parse([], result, _), do: result
  def parse([head | tail], result, tmp) do
    {tag, _, children_nodes} = head
    text = Floki.text(children_nodes)

    {result, tmp} = case tag do
      "dt" ->
        status_code = text |> String.replace(~r/ .*/, "") |> String.to_integer
        {[tmp | result], %{summary: text, status_code: status_code}}
      "dd" ->
        description =
          case tmp[:description] do
            nil -> text
            desc -> "#{desc}\n#{text}"
          end
       {result, tmp |> Map.put(:description, description)}
    end

    parse(tail, result, tmp)
  end

end

%{body: html} = HTTPoison.get! "https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89"

result = html
|> Floki.find("dt, dd")
|> Parser.parse

IO.puts inspect result

```

取得対象のHtmlは以下のようになっているので

```html
<dt>200 OK</dt>
<dd><b>OK</b>。リクエストは成功し、レスポンスとともに要求に応じた情報が返される。</dd>
<dd><a href="/wiki/%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6" title="ブラウザ">ブラウザ</a>でページが正しく表示された場合は、ほとんどがこのステータスコードを返している。</dd>
<dt>201 Created</dt>
<dd><b>作成</b>。リクエストは完了し、新たに作成されたリソースのURIが返される。</dd>
<dd>例: PUTメソッドでリソースを作成するリクエストを行ったとき、そのリクエストが完了した場合に返される。</dd>
```

`Floki.find/2` を用いて `dt` `dd` タグの一覧を取得しています

```elixir
body
|> Foki.find("dt, dd")
#{"dt", [], ["201 Created"]},
# {"dd", [],
#  [{"b", [], ["作成"]},
#   "。リクエストは完了し、新たに作成されたリソースのURIが返される。"]},
# {"dd", [],
#  ["例: PUTメソッドでリソースを作成するリクエストを行ったとき、そのリクエストが完了した場合に返される。"]},
# {"dt", [], ["202 Accepted"]},
# {"dd", [],
#  [{"b", [], ["受理"]},
#   "。リクエストは受理されたが、処理は完了していない。"]},
# {"dd", [],
#  ["例: PUTメソッドでリソースを作成するリクエストを行ったとき、サーバがリクエストを受理したものの、リソースの作成が完了していない場合に返される。",
#   {"a",
#    [{"href", "/wiki/%E3%83%90%E3%83%83%E3%83%81%E5%87%A6%E7%90%86"},
#     {"title", "バッチ処理"}], ["バッチ処理"]}, "向け。"]},

```

このとき　`dt`と`dd` は親子関係になっていません。
なので再帰の中で値を `tmp` に入れておき `dt` タグが来たら
`tmp`の中身を`result`に追加しています。

```elixir
    {result, tmp} = case tag do
      "dt" ->
        status_code = text |> String.replace(~r/ .*/, "") |> String.to_integer
        {[tmp | result], %{summary: text, status_code: status_code}}
      "dd" ->
        description =
          case tmp[:description] do
            nil -> text
            desc -> "{desc}\n#{text}"
          end
       {result, tmp |> Map.put(:description, description)}
    end
```


ソースコードは [gist](https://gist.github.com/tamanugi/6ccececefde54226f5f0b261037dedac) にあげています

実行可能な形式であげていますので以下のように試せます

```shell-session
% git clone https://gist.github.com/tamanugi/6ccececefde54226f5f0b261037dedac
% cd 6ccececefde54226f5f0b261037dedac
% mix deps.get
% mix run scraping_wiki_httpstatus.exs
[%{description: "帯域幅制限超過。そのサーバに設定されている帯域幅（転送量）を使 い切った場合に返される。", status_code: 509, summary: "509 Bandwidth Limit Exceeded"}, %{description: "ループを検出。WebDAVの拡張ステータスコード。", status_code: 508, summary: "508 Loop Detected"}, ...
```

以下の記事を参考にさせていただきました

[GistへElixirのプロジェクトを置き，最小限の自己完結した確認可能なコードとして利用する](http://qiita.com/niku/items/c9ca308d3605be627230)