Elixirでスクレイピングしてみた

##使うライブラリ

+ [HTTPoison](https://github.com/edgurgel/httpoison)
+ [Floki](https://github.com/philss/floki)

### HTTPoison

HTTPoisonは, HTTPクライアントでリクエストを投げて, レスポンスのHTMLを取得するために使います。

###Floki

Flokiは, シンプルなHtmlパーサでHtmlを解析してデータを取得するために使います。

###プロジェクト作成

> mixは
Elixirプロジェクトの作成や依存解決，コンパイル，テストを行うビルドツール。

mixを使ってElixirプロジェクトの作成をしていきます。

```
$ mix new scraper
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/scraper1.ex
* creating test
* creating test/test_helper.exs
* creating test/scraper1_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd scraper
    mix test

Run "mix help" for more commands.
```

Elixirプロジェクトが作成されたので, プロジェクトに移動し進めていきます

###関連モジュール, ライブラリを取得


```elixir
#mix.exs
defmodule Scraper.Mixfile do
  use Mix.Project

  def project do
    [app: :scraper,
     version: "0.1.0",
     elixir: "~> 1.3",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  def application do
    [applications: [:logger, :floki, :httpoison]]
  end

  defp deps do
    [
      {:floki, "~> 0.10.0"},
      {:httpoison, "~> 0.9.0"}
    ]
  end
end
```

mix.exsの`deps`にimportするモジュールを書き込みます。

ここで

+ floki
+ httpoison

を使うので追加します。

```
{:floki, "~> 0.10.0"},
{:httpoison, "~> 0.9.0"}
```

`mix deps.get` でdepsに書き込んだ関連モジュールを取得します。

```
Takuya@Takuya-no-MacBook-Air:~/scraper1 mix deps.get
Running dependency resolution
Dependency resolution completed
  certifi: 0.7.0
  floki: 0.10.1
  hackney: 1.6.3
  httpoison: 0.9.2
...
```

関連モジュールを取得してスクレイピングをする準備ができました。
次はスクレイピングをしていきます。

###スクレイピング

`iex -S mix`でプロジェクトの環境を読み込み,　依存モジュールを使用しながらインタラクティブにコードを実行していくことができます。

ここでは例としてgithubのプロフィールからfollower数を取ってみたいと思います。
githubのapiで提供されていると思うがやってみます:)

![スクリーンショット 2016-12-20 22.18.36.png](https://qiita-image-store.s3.amazonaws.com/0/130025/08b333fd-2c78-9dd5-e1cd-973be7ed3938.png)


```
iex -S mix
Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.3.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> body = HTTPoison.get!("https://github.com/takpy").body

iex(2)> Floki.find(body, "span.counter") |> Enum.at(2) |> Floki.text() |> String.strip()
```

###body = HTTPoison.get! "https://github.com/takpy"


では"https://github.com/takpy" にhttpリクエストを送ったレスポンスのhtmlをbodyにバインドします。

bodyの Data typeはBitStringになっています

**ちなみに変数のData typeは `i 変数`で確認できます。**

###Floki.find(body, "span.counter") |> Enum.at(2) |> Floki.text() |> String.strip()

パイプライン演算子でつなげていますが一つずつ解説します。

```html
<a href="/takpy?tab=followers" class="underline-nav-item " aria-selected="false" role="tab">
          Followers
          <span class="counter">
取得したい -->            2
          </span>
        </a>
``` 
取得したいfollowerの数を取るためにはこの要素を取らなければならないのですが,
`Floki.find(body, "span.counter")`
をすると

```
Floki.find(body, "span.counter")
[{"span", [{"class", "counter"}], ["\n             3\n           "]},
 {"span", [{"class", "counter"}], ["\n            12\n          "]},
 {"span", [{"class", "counter"}], ["\n            2\n          "]},
 {"span", [{"class", "counter"}], ["\n            2\n          "]}]
```

共通のタグとクラスのデータのレポジトリ数, star数なども取れてしまいます。
ですがリストの2番目(3要素目)に入っていることがわかっているのでlist[2]を指定してデータを取得します。

```
iex(34)> Floki.find(body, "span.counter") |> Enum.at(2)
{"span", [{"class", "counter"}], ["\n            2\n          "]}
```

Enum.at(2)でlistの2番目を取得することができました。
ですが今は{tag, attribute, text}のtuppleが帰って来ています。
textを取得するためには

```
iex(37)> Floki.find(body, "span.counter") |> Enum.at(2) |> Floki.text()
"\n            2\n          "
```

これで取りたいfollowerの数が取れたのですが, 改行コードも付いて来てしまったのでパースしたいと思います。

```
iex(42)> followers = Floki.find(body, "span.counter") |> Enum.at(2) |> Floki.text()
"\n            2\n          "

iex(43)> String.strip(followers)
"2"
```

String.strip(変数)で改行コード, 空白文字をremoveしました。

一応取得したかったfollower数を取得することができました。

振り返ってみると今回のスクレイピングは一行でかけましたね。

`HTTPoison.get!("https://github.com/takpy").body |> Floki.find("span.counter") |> Enum.at(2) |> Floki.text() |> String.strip()`

パイプライン演算子は本当に気持ちがいい...