データベースのお勉強は楽しいのだけれど、実際に何かしらのデータを例に実験をしてみたくなったので、早速やってみる!(=゚ω゚)ノ
とは言っても大きめのデータなんて手元にないので、まずはデータ収集から始めます。
今回のタイトル「MySQLで実験」はデータやハードの環境・DB製品等を徐々に増やしてシリーズ化したいなーと思っています。協力してくれる人募集中 ε-(/・ω・)/
あと、いい感じのデータも募集中 ( ・_・)r 。o O (メモリに載り切らなくて、joinするテーブル3つ以上あるような、、、ね、、、)
今回の記事で書くのは以下2つ
1. 集めてくるデータ・データ元を決める
2. 集めてくる(Seleniumをつかって)
2.1 Seleniumのインストール
2.2 操作の録画
2.3 Xpathに修正
2.4 Pythonのコードに落とす
2.5 必要な処理をPythonで書く
3. download名対策
ということで行ってみよう(=゚ω゚)
1. 集めてくるデータ・データ元を決める
まずデータは、気象庁の過去の天気データに決定しました!
早い、早すぎる。ホントはオープンで大規模なデータを探していたんだけど、いいアイディアが浮かばなかったので、気まずい時に天気の会話をするように天気のデータになりました。
データ元: 気象庁|過去の気象データ・ダウンロード
※この先を読みすすめてもらえればわかるのですが、このデータを持ってくるのは色々と苦労しました。API叩く脳みそくらいはあるので、良いAPI等あれば教えて下さい。。。m( _ _ )m
2. 集めてくる(Seleniumを使って)
まずは上の気象庁のサイトを見てみよう。
APIはないけれど、画面遷移もないし、条件をポチポチクリックして"CSVファイルをダウンロード"ボタンを押せばいい簡単なお仕事♪♪と、誰もが思うでしょう。
しかし、
ここ(赤矢印)のところに"選択済みデータ量"とあるように一度のダウンロードで落とせるCSVファイルの量は思いの外少ないのでした。
「うーむ手動でやるには厳しい。。。」
\\ ドンッ!! //
そんな時のSelenium!!今回はこのSeleniumをつかって(ある程度)自動でcsvファイルを収集してくることが出来ました。
ここで言っているSeleniumとはSelenium IDEのことで、Firefoxのアドオンとして提供されていて、あたかもユーザがブラウザ上で操作をしているかのように任意の処理を実行してくれるツールです。本来はwebページの開発者がweb上での複雑な操作をUI上でテストするためのもので、Seleniumをサーバとして立てて動作させるSelenium Webdriverや、phantom.jsとかcasper.jsといったようなブラウザをエミュレートするヘッドレスブラウザもあります。
ヘッドレスブラウザが、CUI上で動作して、結果を返すのに対して、Seleniumは実行すると実際にブラウザを起動して人間がやる作業をそのまま実行してくれる点です。
結果、HTTPリクエストを操作するだけではなくなる(だけではない?)ので、ブラウザのキャッシュやセッション情報のようなブラウザの機能が使いやすく、例えば社内のプロキシ環境下でもSeleniumさえ入れられてしまえば簡単に動作させることが出来たりします。
バイト先で使おうとするとLinux環境を自由に使ったり出来ない場合も多いので、、、
このあと使っていきますが、以下なんかもわかりやすいと思います。
それではこのSelenium IDEを使ってダウンロードの処理をやってみます。
データとしては、
地点:46都府県 + 北海道の3地点 + 南極観測所の合計50観測所
期間:1995年〜2014年の20年間分
項目:気温・降水量・日照時間・風向・風速・全天日射量・現地気圧・相対湿度・蒸気圧
を収集してこようと思います。
2.1 Seleniumのインストール
Seleniumのインストールはドキュメントをみて簡単に出来ます。
2.2 操作の録画
FirefoxでSeleniumのアドオンをインストールすることができれば、下の図の①のようなIDE画面がでてきます。ここで、②の録画ボタンを押したまま操作すると、その操作を記憶してくれていることがわかると思います。
例として、
・気象庁のサイトをクリック
・過去の天気データのページにアクセス
を記録すると以下の様なコードが生成されました。
New Test | ||
open | about:home | |
type | id=searchText | 気象庁 過去 データ |
clickAndWait | id=searchSubmit | |
click | link=気象庁|過去の気象データ検索 | |
clickAndWait | link=気象庁|過去の気象データ検索 | |
click | css=img[alt="週ごとの値等、気象データをカスタマイズしてダウンロードできます。"] | |
clickAndWait | css=img[alt="週ごとの値等、気象データをカスタマイズしてダウンロードできます。"] |
※ htmlとしてxmlで出力することも出来ます。
③の実行ボタンを押せばここまでの処理を最初から実行してくれることが確認できると思います。これを利用して、「地点」「項目」「期間」を選択し、ダウンロードする流れを録画します。
2.3 Xpathに修正
しかし、実際やってみると同じ処理がやる度に成功したり失敗するじゃないか!という問題にぶつかることがあります。これはレンダリングに時間のかかっていることが問題であることが多いので、適度にsleep処理を挟むことで解決することもありますが、CSSライクにDOM指定している部分をXpathに直したほうが何も考えずにうまくいきます。
特に最初に出した気象庁のページ(日本地図があるやつ)なんかは、都道府県やその後の気象台の指定で指定する部分が複雑かつよく見ると構造化されているので、Xpathでやったほうが繰り返し処理を書きやすかったりもします。
2.4 Pythonのコードに落とす
Seleniumの録画機能を使って、各ページでのクリック処理、formの値の選択、初期化等をひと通りコード化し、DOMの指定をXpathに書き換えることが出来ました。
ここまで来ると後は繰り返し処理やsleepの処理何かを上手くかませてやるだけになります。この後も全てをSeleniumのコマンドを使って書いたり、jsやjQueryを外部ファイルとして書いて実行することも出来ますが、他の言語にexportできる機能が便利なので、pythonにexportして実行してみます。
seleniumのパッケージを入れたりしないと行けないかもしれませんが、すでにやってしまっていたので、省略します。Python2.系でしかexport出来ませんが、dom操作くらいしかしないので、問題無いですよね?
実際にpythonに落としたコードは以下のようになっていました。
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re, timeclass Test(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.base_url = "http://www.data.jma.go.jp/"
self.verificationErrors =
self.accept_next_alert = True
def test_(self):
driver = self.driver
driver.get(self.base_url + "/gmd/risk/obsdl/index.php#")
time.sleep(1)
y = "1994"
m = "-1"
tmp = "0"
driver.find_element_by_id("buttonDelAll").click()
time.sleep(1)
driver.find_element_by_xpath("(//*[@id=\"pr44\"])").click()
time.sleep(1)
driver.find_element_by_xpath("(//*[@id=\"stationMap\"]/div[9]/div)").click()
time.sleep(1)
driver.find_element_by_id("elementButton").click()
driver.find_element_by_xpath("//*[@id='aggrgPeriod']/div/div[1]/div[1]/label/span").click()
time.sleep(1)
driver.find_element_by_id(u"気温").click()
time.sleep(1)
driver.find_element_by_id(u"降水量").click()
time.sleep(1)
driver.find_element_by_id(u"日照時間").click()
time.sleep(1)
driver.find_element_by_id(u"風向・風速").click()
time.sleep(1)
driver.find_element_by_id(u"全天日射量").click()
time.sleep(1)
driver.find_element_by_id(u"現地気圧").click()
driver.find_element_by_id(u"相対湿度").click()
driver.find_element_by_id(u"蒸気圧").click()
driver.find_element_by_id("periodButton").click()
Select(driver.find_element_by_name("iniy")).select_by_visible_text(y)
Select(driver.find_element_by_name("endy")).select_by_visible_text(y)
Select(driver.find_element_by_name("inim")).select_by_visible_text(tmp)
Select(driver.find_element_by_name("endm")).select_by_visible_text("${finishm[{$m}]}")
Select(driver.find_element_by_name("endd")).select_by_visible_text("${finishd[{$m}]}")
driver.find_element_by_css_selector("#csvdl > img.rollover").click()
def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
def is_alert_present(self):
try: self.driver.switch_to_alert()
except NoAlertPresentException, e: return False
return True
def close_alert_and_get_its_text(self):
try:
alert = self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else:
alert.dismiss()
return alert_text
finally: self.accept_next_alert = True
def tearDown(self):
self.driver.quit()
self.assertEqual(, self.verificationErrors)if __name__ == "__main__":
unittest.main()
デバッグを繰り返していたので、各所にsleepが入っていたり、カラースキームつかなくて少々つらいですが、最終盤が僕のgithubにあるので、読む価値があればこちらからどうぞ(https://github.com/tom--bo/selenium_sample)
何はともあれこんな感じでfirefoxをpythonのコードからいじることが可能になりました。
これで終わりが見えてきた気がする ワーイヽ(゚∀゚)メ(゚∀゚)メ(゚∀゚)ノワーイ!!
2.5 必要な処理をPythonで書く
これで、事前にメモしておいた各都道府県と気象台のDOMのidを配列に突っ込んだり、1ヶ月ごとに20年分を持ってくるようにwhile文を回したりする処理をかけば、12,000回 (12ヶ月×20年×50観測所)のダウンロードボタンを押す作業を自動化出来たことになります。ε-(´∀`*)ホッ
最終的なコードは以下。
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import NoAlertPresentException
import unittest, time, re, timeclass Test(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()self.driver.implicitly_wait(30)
self.base_url = "http://www.data.jma.go.jp/"
self.verificationErrors =
def test_(self):
prefectures = [11, 19, 14, 31, 33, 32, 35, 34, 36, 54, 55, 56, 41, 42, 40, 45, 43, 44, 49, 46, 48, 50, 52, 51, 57, 60, 53, 64, 65, 61, 62, 63, 69, 66, 68, 67, 81, 72, 71, 73, 74, 82, 85, 84, 83, 86, 87, 88, 91, 99]
part_of_pre = [1, 19, 9, 7, 37, 21, 31, 25, 26, 9, 11, 19, 21, 17, 11, 21, 1, 9, 7, 5, 37, 19, 49, 7, 1, 7, 27, 1, 21, 19, 7, 39, 5, 9, 9, 29, 19, 3, 5, 9, 9, 13, 9, 21, 13, 11, 29, 25, 15, 1]
start_m_arr = [1,2,3,4,5,6,7,8,9,10,11,12]
start_d_arr = [1,2,2,2,2,2,2,2,2,2,2,2]
end_m_arr = [2,3,4,5,6,7,8,9,10,11,12,12]
end_d_arr = [1,1,1,1,1,1,1,1,1,1,1,31]
t = 0.5driver = self.driver
for p in range(0,len(prefectures)):
print(str(p)+"start")
driver.get(self.base_url + "/gmd/risk/obsdl/index.php#")
time.sleep(t)driver.find_element_by_id("buttonDelAll").click()
time.sleep(t)
driver.find_element_by_xpath("(//*[@id=\"pr" + str(prefectures[p]) + "\"])").click()
time.sleep(t)
driver.find_element_by_xpath("(//*[@id=\"stationMap\"]/div[" + str(part_of_pre[p]) + "]/div)").click()
time.sleep(t)
driver.find_element_by_id("elementButton").click()
driver.find_element_by_xpath("//*[@id='aggrgPeriod']/div/div[1]/div[1]/label/span").click()
time.sleep(t)
driver.find_element_by_id(u"気温").click()
driver.find_element_by_id(u"降水量").click()
driver.find_element_by_id(u"日照時間").click()
driver.find_element_by_id(u"風向・風速").click()
driver.find_element_by_id(u"全天日射量").click()
driver.find_element_by_id(u"現地気圧").click()
driver.find_element_by_id(u"相対湿度").click()
driver.find_element_by_id(u"蒸気圧").click()
time.sleep(t)
driver.find_element_by_id(u"現地気圧").click()
driver.find_element_by_id("periodButton").click()
for y in range (1995, 2015):
Select(driver.find_element_by_name("iniy")).select_by_visible_text(str(y))
Select(driver.find_element_by_name("endy")).select_by_visible_text(str(y))
time.sleep(t)
for i in range(0,12):
Select(driver.find_element_by_name("inim")).select_by_visible_text(str(start_m_arr[i]))
Select(driver.find_element_by_name("inid")).select_by_visible_text(str(start_d_arr[i]))
Select(driver.find_element_by_name("endm")).select_by_visible_text(str(end_m_arr[i]))
Select(driver.find_element_by_name("endd")).select_by_visible_text(str(end_d_arr[i]))
time.sleep(t)
driver.find_element_by_css_selector("#csvdl > img.rollover").click()
time.sleep(t)
time.sleep(t*4)def is_element_present(self, how, what):
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
def is_alert_present(self):
try: self.driver.switch_to_alert()
except NoAlertPresentException, e: return False
return True
def close_alert_and_get_its_text(self):
try:
alert = self.driver.switch_to_alert()
alert_text = alert.text
if self.accept_next_alert:
alert.accept()
else:
alert.dismiss()
return alert_text
finally: self.accept_next_alert = True
def tearDown(self):
self.driver.quit()
self.assertEqual(, self.verificationErrors)if __name__ == "__main__":
unittest.main()
実はTestクラスのsetUPメソッドの中で、driverをChromeに切り替えています。
これは、Firefoxでダウンロードボタンを押した時に"ダウンロードを許可しますか?"と言ったポップアップの警告をSeleniumで回避できなかったためです。
んなもん、ブラウザの設定でできるだろ!と思いましたが、Seleniumで実行した場合、実行ユーザが異なってしまうために、普段使っているときとは設定情報やセッションの情報などが変わってしまうため、上手く行きませんでした。。。
これで後はPCがスリープしないようにして、DOS攻撃にならないように注意してPCに任せるだけ!!
(=゚ω゚)ノ(=゚ω゚)ノ
3. download名対策
処理が終わっていることを期待して翌朝見てみると失敗していました。。。。
その時の様子→ ォオー!!(゚д゚屮)屮 ((((;゚Д゚)))) ウワァァァァァァヽ(`Д´)ノァァァァァァン! o(`ω´*)oプンスカプンスカ!!
ダウンロードしたファイル名はhoge.csvとなり、同名のファイルの場合はファイル名の最後にhoge (1).csv hoge (2).csv....と永遠と続くものと思いきや、どうやら(100)までしかつけてくれないようです。(これもブラウザで設定を変えられるのかもしれませんが、、、)
しかたがないので、シェルで数秒単位で、hoge.csvを探し、hoge(日時).csvと書き換えるように対応しました。
ー これから ー
これで、csvで天気のデータが揃ったので、これからテーブルの作成、集計等のSQL検討をして、indexやpartitionの実験をしてみたいと思います!
MySQLで実験シリーズで続きます。。。
⊂゚U┬───┬~ 終わり