ElixirでMySQLの株価をUpdate
前回の続き。
今度はMySQLのデータを更新してみる。
基本文法でちょっと時間かかった、やはりまずは基礎を勉強してからやるべきだったかも…。
文字列をintgerやfloatやdateに変換する
String – Elixir v1.4.4
Getting Started – timex v3.1.15
この辺みながらスクレイピングした値をパースする。
To integer
str |> String.to_integer()
** (ArgumentError) argument error (stock_scraping) lib/stock_scraping.ex:18: StockScraping.parse/1 (elixir) lib/kernel/cli.ex:76: anonymous fn/3 in Kernel.CLI.exec_fun/2
はい死亡。
よくよくみると文字列が123,456,000
のように区切り文字が入っている。
他にやり方はあるかもしれないが、何も考えずにリプレイスする。
str |> String.replace(",", "") |> String.to_integer()
無事成功。
このパイプライン演算子(|>
)は慣れると結構気持ちいいかもしれない。
To float
こちらは最初から区切り文字をリプレイス。
str |> String.replace(",", "") |> String.to_float()
** (ArgumentError) argument error (stock_scraping) lib/stock_scraping.ex:18: StockScraping.parse/1 (elixir) lib/kernel/cli.ex:76: anonymous fn/3 in Kernel.CLI.exec_fun/2
はい死亡。
文字列が小数になってないとダメらしい、どないしよ。
Float – Elixir v1.4.4
Float.parse/1
でいけそうだ。
** (ArgumentError) argument error
はい、死亡。
よくみると結果がTuple({123456000.0, ""}
)で返ってきてた。
Tupleへのアクセスはelem/2
で行うらしい。
str |> String.replace(",", "") |> String.to_float() |> elem(0)
これで無事パースできた。
To date
Timex使って特に問題なくパース出来た。
year = Enum.at(list, 0) |> String.to_integer month = Enum.at(list, 1) |> String.to_integer day = Enum.at(list, 2) |> String.to_integer date = Timex.to_date({year, month, day})
MySQLのデータを更新する
更新対象のデータを全件取得する(DBへの通信を1回で済ませるため)
この時気をつけるのは、WHERE句にはピン演算子を使うひつようがあるということ。
理由はよくわからない、後で調べる。
targets = StockScraping.YahooVolume |> where(date: ^date) |> all
上記全データから対象PKデータを取得する
ランキング1位のデータを取得。これは例。
target = targets |> Enum.filter(fn(x) -> x.ranking == 1 end) |> hd
変更したいフィールドと値でmapを作る
変更部分をmapにする感じ。
実際の処理は後述。
params = build_tuple(x, date)
Changesetを作成しUpdate
Ecto.Changeset – Ecto v2.1.4
対象データ(target)と変更map(params)を使ってchangeset
を作りupdate
。
changeset = change(target, params)
update(changeset)
これでMySQLのデータを更新できた。
現時点のまとめ
なアプリケーションが出来上がった(はず)。
次回はAWSを使ってデイリーで実行するよう自動化してみる。
最終的なビジネスロジックが書かれているコードはこちら。
近々GitHubにあげる。
defmodule StockScraping do use Timex import StockScraping.Repo import Ecto.Query import Ecto.Changeset def main(args) do HTTPoison.get!("https://info.finance.yahoo.co.jp/ranking/?kd=33&mk=2&tm=d&vl=b") |> parse end defp parse(%{status_code: 200, body: body}) do volume_elements = Floki.find(body, "tr.rankingTabledata") date_elements = Floki.find(body, "div.dtl") date = parse_date(date_elements) if StockScraping.YahooVolumeDate |> get_by(date: date) do targets = StockScraping.YahooVolume |> where(date: ^date) |> all # UPDATE MySQL update_volume(volume_elements, date, targets) else local = Timex.local() naive_datetime = NaiveDateTime.new(DateTime.to_date(local), DateTime.to_time(local)) |> elem(1) IO.inspect(naive_datetime) %StockScraping.YahooVolumeDate { date: date, add_date: naive_datetime, updt_date: naive_datetime } |> insert() # INSERT MySQL add_volume(volume_elements, date) end end defp parse_date(elements) do date_list = elements |> Enum.at(0) |> elem(2) |> Enum.at(1) |> String.replace(~r/[^0-9]/, ",") |> String.split(",", trim: true) year = Enum.at(date_list, 0) |> String.to_integer month = Enum.at(date_list, 1) |> String.to_integer day = Enum.at(date_list, 2) |> String.to_integer Timex.to_date({year, month, day}) end defp update_volume(elements, date, targets) do elements |> Enum.each(fn(x) -> params = build_map(x, date) ranking = params[:ranking] target = targets |> Enum.filter(fn(x) -> x.ranking == ranking end) |> hd changeset = change(target, params) update(changeset) end) end defp add_volume(elements, date) do entities = elements |> Enum.map(fn(x) -> build_map(x, date) end) insert_all(StockScraping.YahooVolume, entities) end defp build_map(value, date) do element = elem(value, 2) %{ date: date, ranking: get_value(element, 0) |> String.to_integer(), code: get_code(element) |> String.to_integer(), market: get_value(element, 2), name: get_value(element, 3), price: get_value(element, 5) |> String.replace(",", "") |> Float.parse() |> elem(0), volume: get_value(element, 6) |> String.replace(",", "") |> String.to_integer(), volume_average: get_value(element, 7) |> String.replace(",", "") |> String.to_integer(), volume_rate: get_value(element, 8) |> String.replace(",", "") |> Float.parse() |> elem(0), } end defp get_value(element, index) do Enum.at(element, index) |> elem(2) |> hd end defp get_code(element) do Enum.at(element, 1) |> elem(2) |> Enum.at(0) |> elem(2) |> hd end end