Elixirでさくっとスクレイピングする
はじめに
- 今回はAPIが提供されていないWebサイトに対して、スクレイピングをして何かしら加工して出力したいと思います
- また、あくまでも私的利用であり数回しか叩かない予定であり、スクレイピングを推奨している訳ではありません
用意したもの
- Elixir1.10
- Visual Studio Code(拡張はよしなに)
実装していく
まず、依存ライブラリを追加していく
defmodule Scrape.MixProject do use Mix.Project def project do [ app: :scrape, version: "0.1.0", elixir: "~> 1.10", start_permanent: Mix.env() == :prod, deps: deps() ] end # Run "mix help compile.app" to learn about applications. def application do [ extra_applications: [:logger] ] end # Run "mix help deps" to learn about dependencies. defp deps do [ {:floki, "~> 0.29.0"}, {:httpoison, "~> 1.2.0"}, {:poison, "~> 3.1"} ] end end
今回追加したものは下記の通り
HTTPリクエストを行うので、そのためのライブラリとしてHTTPoison hexdocs.pm
一旦、DBなどは使わずJsonからmaster情報を取得して、Jsonへ出力するということをしたいのでPoisonを使う hexdocs.pm
API提供されてないサイトへのスクレイピングを行うので、そこら辺はFlokiが頑張ってくれる hexdocs.pm
Jsonをパースしていく
- マスター情報はこんな感じで、とあるショップのショップ名とショップURLを持たせておく
[ { "shopName": "website1", "shopUrl": "https://website1.jp/" }, { "shopName": "website2", "shopUrl": "https://website2.jp/" } ]
defmodule ScrapeSample do # main関数からjsonを取得する関数を呼び出す def main do read_master() end def read_master do "master.json" |> File.read!() |> Poison.decode!() end end
スクレイピングする
defmodule ScrapeSample do def main do read_master() |> Enum.map(& scrape(&1["shopUrl"])) end def read_master do "master.json" |> File.read!() |> Poison.decode!() end def scrape(url) do # 受け取ったURLに対してリクエストして、Floki.find()で取得したい要素を指定する HTTPoison.get!(url).body |> Floki.find("section") |> Floki.find("h2") end end
- ちなみにこの時点で、scrapeした結果を出力すると、下記のような感じになる。
[ {"h2", [], ["aaaaa"]}, {"h2", [], ["bbbbb "]}, {"h2", [], ["ccccc"]} ] [ {"h2", [], ["ddddd"]}, {"h2", [], ["eeeee"]}, ]
- この場合欲しい要素はTupleの3つ目の要素のみなので、修正する
def scrape(url) do HTTPoison.get!(url).body |> Floki.find("section") |> Floki.find("h2") |> Enum.flat_map(&(Tuple.to_list(&1) |> Enum.at(2))) end
- このあと、必要に応じてフィルタの処理を入れてもいいが、今回は割愛する
あとは、jsonに出力する
- 出力するオブジェクトを作成して、それをFile.writeに渡したら終了
def main do read_master() |> Enum.map(& %{shopName: &1["shop_name"], items: scrape(&1["shop_url"]), url: &1["shop_url"]}) |> write_items() end def write_items(items) do File.write!("items.json", Poison.encode!(items)) end
最終的なコード
defmodule ScrapeSample do def main do read_master() |> Enum.map(& %{shopName: &1["shopName"], items: scrape(&1["shopUrl"]), url: &1["shopUrl"]}) |> write_items() end def read_master do "master.json" |> File.read!() |> Poison.decode!() end def scrape(url) do HTTPoison.get!(url).body |> Floki.find("section") |> Floki.find("h2") |> Enum.flat_map(&(Tuple.to_list(&1) |> Enum.at(2))) end def write_items(items) do File.write!("items.json", Poison.encode!(items)) end end
感想
- スクレイピングするだけならめちゃくちゃ簡単でした
- 特に、Flokiのハマりポイントもないので、Elixirの入門として書くコードとしてはまあまあ良さそうという印象です。