スクレイプする目的
先日からRe:dashをいじっててようやくOracleとの接続もクリアしたので、経営層向けの自作ダッシュボードから移行すべくこれが出来ないと移行ができないというものを攻略してみた。
それが外部URLのテーブルからデータをぶっこ抜いてグラフ表示させる機能で、具体的にはアメリカのエネルギー情報局(EIA)のページにある1983年からの原油価格である。
http://tonto.eia.doe.gov/dnav/pet/hist/LeafHandler.ashx?n=PET&s=RCLC3&f=W
このページを知ってから約3年位はスクレイピングさせてもらってるんだけど、HTMLの構造は変わらないし大変ありがたい。
Pythonをデータソースにするには
Redashには任意のPythonスクリプトを実行させてデータソースに使える機能があるので、それを使ってみる。
最終的には このフォーマット のJSONを作れば良いみたいだが、 このソース にもあるように、 add_result_column
と add_result_row
という便利なメソッドが準備されている。
ところが、ぐぐってもなかなかスクリプトの登録方法が見つからない。かといって何も考えずにコードを書けばいいというものでもなく、結構時間が掛かっちゃった。
Redashの設定を変更
まず、デフォルトでは使えない機能なので設定ファイルから有効にする必要がある。やり方はお決まりの /opt/redash/.env
に以下の行を追加するだけ。
export REDASH_ADDITIONAL_QUERY_RUNNERS=redash.query_runner.oracle,redash.query_runner.python
Oracleへの接続も有効化させてるので、カンマ区切りで複数指定されてある。
で、この機能だがどのようなPythonスクリプトを書かれるかわからないので、 マニュアルにもあるように許可するのであれば信用できる環境なのか良く注意したほうが良い。今回はイントラ内だしAPPS認証もつけてるので誰が作成したかわかるので問題ないけど。
この設定を行った後はデータソースの追加ページのタイプにPythonと出てくるので、それを選んでPythonスクリプトを登録する。
Pythoneコードを登録
実際のPythonコードだが、SQLを入力するエディタで登録する。なんだけど、ココがよくわからなかった。
なんて言ってもデータソースタイプをPythonとして登録しているのにSQLの実行を思い起こさせる「Execute」ボタンと、SQLの整形を行う「FormatSQL」ボタンがそのままだから、どっか別にPythonコードを置いてその値に対してSQL投げるのかと思いこんじゃって数時間。
なかなか気持ち悪いでしょ? 思い込みって怖いけど、今時Webに情報が無いってのもなかなかつらい。時間があるときにでもPullReq送ろっかな。
そんなこんなで、ソース見たりしながらなんとか下記のコードでデータ取得できた。
from pyquery import PyQuery as pq
import re
result = {}
url = "http://tonto.eia.doe.gov/dnav/pet/hist/LeafHandler.ashx?n=PET&s=RCLC3&f=W"
dom = pq(url)
pattern = r"^(\d{4})"
for tr_node in dom("tr"):
td_nodes = pq(tr_node)("tr").find("td")
match = re.search(pattern, td_nodes.eq(0).text())
if match:
yyyy = match.group()
if td_nodes.eq(1).text() != "":
add_result_row(result, {"date": (yyyy + "/" + td_nodes.eq(1).text()).replace("/", "-"), "Crude Oil": td_nodes.eq(2).text()})
if td_nodes.eq(3).text() != "":
add_result_row(result, {"date": (yyyy + "/" + td_nodes.eq(3).text()).replace("/", "-"), "Crude Oil": td_nodes.eq(4).text()})
if td_nodes.eq(5).text() != "":
add_result_row(result, {"date": (yyyy + "/" + td_nodes.eq(5).text()).replace("/", "-"), "Crude Oil": td_nodes.eq(6).text()})
if td_nodes.eq(7).text() != "":
add_result_row(result, {"date": (yyyy + "/" + td_nodes.eq(7).text()).replace("/", "-"), "Crude Oil": td_nodes.eq(8).text()})
if td_nodes.eq(9).text() != "":
add_result_row(result, {"date": (yyyy + "/" + td_nodes.eq(9).text()).replace("/", "-"), "Crude Oil": td_nodes.eq(10).text()})
add_result_column(result, "date", "yyyy/mm/dd", "string")
add_result_column(result, "Crude Oil", "Dollars per Barrel", "float")
もとはPerlでスクレイプしてたんだけど、それほど苦労せずに変換できた。
グラフ表示結果
で、最終的に出来たのが以下のグラフ。
うん、悪くない。でも、もうちょっと相場的に安定してもらいたいよね。Redashと関係ないけど。
実はPythonのコードを書くのはこれが初めて。
インデントだけで {}
が無いコードって気持ち悪いと思って敬遠してたんだけど、実際に書いてみるとやっぱり気持ち悪かった。