AWSの無料枠内でEC2とMySQLを起動する
昨日飲み過ぎ+寝不足で今日はElixir触れなかった、こんちくしょう。
ということでAWSの設定は終わったのでそれを書く。
IAMの設定
IAM とは - AWS Identity and Access Management
Linuxでrootユーザー使いながら作業しないし、Windowsでadministratorで作業しないよねという話。
admin
グループとadmin
ユーザーを作成し、管理者権限を付与した。
作成完了。
請求アラームの設定
請求アラームの作成 - AWS 請求情報とコスト管理
12ヶ月の無料枠を使って運用するので、1$でも発生したらすぐアラートをメールに送る。
0$ < 過去6時間での請求額を閾値に設定。
EC2
この記事を参考に無料枠ないでインスタンス起動。
sil.hatenablog.com
SSH接続時のIP許可は後ほど設定する。
MySQL
この記事を参考に無料枠ないでry。
qiita.com
EC2からしかアクセス出来ないようにセキュリティグループを組むこと。
AWS CLI のセットアップ
AWS Command Line Interface のインストール - AWS Command Line Interface
pipでインストールするらしい。
sudo apt-get install -y python-pip sudo pip install awscli pip install --upgrade --user awscli
続いてConfig設定。
$ aws configure AWS Access Key ID [None]: *** AWS Secret Access Key [None]: *** Default region name [None]: ap-northeast-1 Default output format [None]: json
aws ec2 describe-instances
載せないけどちゃんと表示された。
SSH 接続
まずはec2サーバーへのSSH接続を許可する。
以下を見ながらローカルのIPアドレスからのSSH接続を許可する。
Linux インスタンス用の受信トラフィックの認可 - Amazon Elastic Compute Cloud
許可したらターミナルからSSH接続できるようになる。
ssh -i "ec2.pem" ec2-user@****
__| __|_ ) _| ( / Amazon Linux AMI ___|\___|___|
こんな出力がされたら成功。
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
ElixirでMySQLに株価をInsert
前回の続き。
今回はO/RマッパーであるEctoを使い、MySQLにデータを保存する。
Ectoをセットアップする
mix.exs
のdepsに下記を追加しライブラリをダウンロードする。
{:ecto, "~> 2.1.4"}, {:mariaex, "~> 0.8.2"},
リポジトリを作成する
rオプションでリポジトリ名をつけてコマンドを実行する。
mix ecto.gen.repo -r StockScraping.Repo
すると、config/config.exs
とlib/stock_scraping/repo.ex
が作成される。
config.exsを設定する
config.exs
の各フィールドにMySQLの接続情報を入れる。
最後の行は自動生成されないので追記する。
use Mix.Config config :stock_scraping, StockScraping.Repo, adapter: Ecto.Adapters.MySQL, database: "stock_scraping", username: "ytoida", password: "", hostname: "localhost", port: 3306 config :stock_scraping, ecto_repos: [StockScraping.Repo]
Supervisorの設定
SupervisorにStockScraping.Repoを監視させる。
Supervisorについてはよくわかってないので、いつかまとめたもの書く。
lib/stock_scraping/application.ex
のchildrenにworkerを追加する。
defmodule StockScraping.Application do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(StockScraping.Repo, []), ] opts = [strategy: :one_for_one, name: StockScraping.Supervisor] Supervisor.start_link(children, opts) end end
これで、StockScraping.Repoプロセスが死んでもすぐに再起動してくれるはず。
ここまでがEctoのテンプレ設定っぽい。
Schemaを作成する
Ecto.Schema – Ecto v2.1.4
Ecto.Schemaを、lib/stock_scraping/[テーブル名].ex
で作成する。
この時@primary_key
を設定することを忘れない。{:id, :id, autogenerate: true}
がデフォルトなので要注意。
defmodule StockScraping.YahooVolumeDate do use Ecto.Schema @primary_key {:date, :naive_datetime, autogenerate: false} schema "yahoo_volume_date" do field :add_date, :naive_datetime field :updt_date, :naive_datetime end end
ここまででMySQLに接続出来るはず。
MySQLからテストデータをSelectしてみる
コマンドラインからMySQLのデータが取得出来るか確認する。
StockScraping.YahooVolumeDate |> StockScraping.Repo.all
とれてる。
MySQLにInsertしてみる
流れとしては、対象サイトの更新日時を取得してDateにコンバート。
そのままPKにしてInsertする、以下抜粋。
if StockScraping.YahooVolumeDate |> get_by(date: date) do # データが存在している場合はUpdate(後日実装) else # データが存在していない場合はInsert naive_datetime = Timex.to_naive_datetime(Timex.local()) row = %StockScraping.YahooVolumeDate { date: date, add_date: naive_datetime, updt_date: naive_datetime } insert(row) end
これで、MySQLへの接続とSelect/Insertは実装出来た。
次回はUpdateの処理と、AWSの無料枠を使いスケジューリングしたい。
Elixirで株価をスクレイピングする
開発ブログに再挑戦。
Elixir習得のため、株価スクレイピングアプリをElixirで書いてAWSで運用するまでを一旦の目標にする。
開発環境を構築する
OS無しノートPCにUbuntu入れた。スペック低いけど、プログラミングかブラウジングしかしないので問題ない。
Elixirをインストールする
公式見ながらコマンド実行。
Installing Elixir - Elixir
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb sudo apt-get update sudo apt-get install esl-erlang sudo apt-get install elixir
これでスッとインストール出来るはず。
Mixを使いプロジェクトを作成する
Mixという便利なプロジェクト管理ツールを使い、Supervisor作成オプション付きでプロジェクトを作成する。
mix new stock_scraping --sup
ライブラリの依存関係を解決する
mix.exs
に使用するライブラリを追記する。
使用するライブラリは以下。
最終的にコマンドラインで実行するためescriptを使う。
escriptの実行モジュール記載を忘れないこと。
実際のmix.exs
はこちら(ファイル名を表示する方法が分からん)。
defmodule StockScraping.Mixfile do use Mix.Project def project do [app: :stock_scraping, version: "0.1.0", elixir: "~> 1.4", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, escript: [main_module: StockScraping], deps: deps()] end def application do [extra_applications: [:logger], mod: {StockScraping.Application, []}] end defp deps do [ {:timex, "~> 3.1"}, {:httpoison, "~> 0.11.2"}, {:floki, "~> 0.17.0"}, {:certifi, "1.1.0", override: true}, {:idna, "4.0.0", override: true}, {:tzdata, "0.1.8", override: true}, ] end end
コマンドを実行しライブラリをダウンロードする。
mix deps.get
これでプロジェクトのセットアップは完了。
ロジックを実装して株価をスクレイピングする
escriptで実行されるStockScrapingモジュールを実装し、実際に株価をとってくる。
関数型言語書いたことないし、Elixirの文法もよくわからない中、気合で書いたので相当汚いはず笑。
現状その汚さすら理解できない状態のstock_scraping.ex
がこちら。
(ここはこう書けド素人!!!みたいな意見貰えるとありがたい)
defmodule StockScraping do use Timex 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 parse_datetime(body) parse_volume(body) end defp parse_datetime(body) do date_list = Floki.find(body, "div.dtl") |> 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 updttime = Timex.to_datetime({year, month, day}, "Asia/Tokyo") end defp parse_volume(body) do Floki.find(body, "tr.rankingTabledata") |> Enum.each(fn(x) -> element = elem(x, 2) rank = get_value(element, 0) code = get_code(element) market = get_value(element, 2) name = get_value(element, 3) price = get_value(element, 5) volume = get_value(element, 6) volume_average = get_value(element, 7) volume_rate = get_value(element, 8) end ) 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
escriptを使ってビルドする
ビルドコマンド実行。
mix escript.build
通ればルートにstock_scraping
というEarlangVM上で実行可能なバイナリファイルが生成される。
escriptを使ってバイナリファイルを実行する
バイナリを実行する。
./stock_scraping
出力された、プログラムがちゃんと動くと嬉しい。
これで一旦値がとれるところまではいけたので、次回はMySQLにこいつを突っ込む。