2014年4月8日火曜日

oinknoteからzaimへの移行プロジェクト

お小遣い帳としてずっとoinknote使ってきたんですが、たまに検索したいって時があります。でもできないんです。エクスポートしたらできると思ったら、プレミアム料金払わないとエクスポートすらできないらしいです。これは困った。
oinknoteはプレミアムを30日体験できるので、体験でまずは過去のデータすべてダウンロードしました。
その後zaim API。rubyでちゃちゃっとやろうとしたらoauth認証がよくわからん。
あちこち探してようやくoauth認証できました。
そして試行錯誤しながら、途中泣きそうになりながらoinknoteからzaimへの転記スクリプトを作り、昨日(2014/04/19)、無事転記が終わり、各口座の残高もoinknoteと完全に一致という嬉しい結果になりました。

以降、zaimアプリの作成から転記スクリプトについて簡単ではございますがメモしておきます。

zaimアカウントを作る
アカウントないと何もできません。作りましょう。
http://zaim.net/

zaimアプリを作る
zaim apiで何かやろうとするなら、アプリを登録しなければなりません。
ここにアクセスすると「ログインせよ」と言われますので、素直に先ほど作成したアカウントでログインします。

ログインするとこんな画面になります。


「新しいアプリケーションを追加」します。

こんな画面出るので、必要な情報を入れます。

アプリケーション名
アプリの名前ですね。例では適当ですけど、もっとしっかりした名前をつけてあげましょう

サービス種別
画像だと「ブラウザアプリ」になってますけど、今回rubyで作るんですが「クライアントアプリ」にしておかないと、oauth認証がうまくいきませんでした。

組織
ご自分の会社名を入れるなりなんなりと。
あ、法人の場合利用制限があるのかな?個人名をぶち込んでおくのが無難でしょうね。

サービスのURL
ちゃんとURLの形式で入れないとダメみたいです。
"test://de.su" とか "http://test.de.su" とかでもイケます。

アクセスレベル
全部チェックONにしました

画像ファイル
なんの画像か知りませんが、何も設定しません

で、「保存」するとこんな画面になります。
本当は「Consumer Key」と「Consumer Secret」が入ってるんですが、消してあります。ご自分の画面に表示されたものを使ってください。
あ、画面を消す前に、「Consumer Key」と「Consumer Secret」を控えておいてください。後で使います。

ここまでで、zaimの準備は完了ですね。

oauth認証のコードを書く
正直、どうやってやればよいのか分からなかったのでこちらを参考にさせていただきました。
http://d.hatena.ne.jp/ash1taka/20120303/1330739047

んでこんなのを書きました。
require 'oauth'

CONSUMER_KEY='zaimで作ったアプリの値をここに入れる'
CONSUMER_SECRET='zaimで作ったアプリの値をここに入れる'

consumer=OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
        :site=>"https://api.zaim.net",
        :request_token_path=>"/v2/auth/request",
        :authorize_url=>"https://auth.zaim.net/users/auth",
        :access_token_path=>"/v2/auth/access")
request_token = consumer.get_request_token(:oauth_callback=>"http://google.com")
system('open', request_token.authorize_url)
print "Input OAuth Verifier:"
oauth_verifier = gets.chomp.strip
access_token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
p access_token.token
p access_token.secret

上記コードを動かすと、ブラウザが起動します。
DOS窓、あるいはターミナルは "Input OAuth Verifier:" と表示され、待機状態になってます。
ブラウザには「あなたのZaimアカウントを利用することを許可しますか?」
と表示されますんで、ログインします。ログインボタンが2つでてると思うんですけど、下の方のログインボタンを押してくださいね。

んでログインすると、「ログインしました。今しばらくお待ちください...」と表示されますけど、待っても何も起きません(笑)

ここで、ページのソースを表示してください。
この記事を書いてる2014/04/20現在、56行目に oauth_verifier=xxx というのがあります。この xxx 部分をコピます。
で、ターミナルに戻って貼り付けて enter 。

すると、文字列が2つ表示されます。
これがAPIを使うときに必要な access_token とかいうものになります。

今後APIを使う場合は、ここで得た access_token を使えばいいので、毎回 oauth 認証する必要はないみたいです。

API使ってみる
今度はこんなコードを書いてみます。
require 'oauth'
require 'json'

CONSUMER_KEY='zaimで作ったアプリの値をここに入れる'
CONSUMER_SECRET='zaimで作ったアプリの値をここに入れる'
ACCESS_TOKEN='上記で取得した値を入れる'
ACCESS_TOKEN_SECRET='上記で取得した値を入れる'

consumer=OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
                  :site=>"https://api.zaim.net",
                  :request_token_path=>"/v2/auth/request",
                  :authorize_url=>"https://auth.zaim.net/users/auth",
                  :access_token_path=>"/v2/auth/access")
access_token = OAuth::AccessToken.new(consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

res = access_token.get('https://api.zaim.net/v2/home/user/verify')

json = JSON.parse(res.body)
pgen = JSON.pretty_generate(json)
puts pgen

これを実行すると、こんな結果が得られます。
自分の情報ですね。この結果がうまく得られれば、後はAPI叩きまくるだけです。
(実際は叩きまくってもうまく動いてくれなかったんですが・・・)
{
  "me": {
    "login": "xxxxxxx",
    "input_count": 1033,
    "day_count": 1,
    "repeat_count": 1,
    "id": 123456,
    "currency_code": "JPY",
    "week": 0,
    "month": 1,
    "active": 1,
    "day": 1,
    "name": "xxxxxxx",
    "profile_image_url": "https://xxxxxx.yy"
    "cover_image_url": "https://xxxxxx.yy"
  },
  "requested": 123123
}

APIは入金、出金、振替、などなどいろいろあります。
口座一覧、カテゴリー一覧、履歴、なんかも取れます。
こちら参照です。
https://dev.zaim.net/home/api

出金してみる
// access_token は、上で verify のAPI叩いた時と同じように生成
access_token.post('https://api.zaim.net/v2/home/money/payment',
        :mapping=1,
        :category_id=1999,
        :genre_id=2999,
        :amount=1080,
        :date="2014-04-20",
        :from_account_id=3999,
        :comment="あそこであれを買った",
        :name="あれ",
        :place="あそこ"

mapping
は1固定だそうです。何に使うのか知りません。

category_id
「食費」とか「エンタメ」とかの大分類になります。
ここには「食費」とかの文字列を入れるんではなく、カテゴリーIDってのを入れる必要があります。
カテゴリーIDは、
res = access_token.get('https://api.zaim.net/v2/home/category')
json = JSON.parse(res.body)
pgen = JSON.pretty_generate(json)
puts pgen
の結果を見ればわかります。
で、ここがハマりどころです。それはまたのちほど。

genre_id
「ゲンレ」ってなんだ?とか思ってましたけど、「ジャンル」のことなんすね(^^;
このゲンレは、「食費」という大分類の中の小分類、例えば「夜ご飯」とかにあたるものです。
これも「夜ご飯」を入れるんではなく、ジャンルIDを入れます。
res = access_token.get('https://api.zaim.net/v2/home/genre')
で取れますね。
ジャンルは、カテゴリーIDとも紐付いてます。なんで、ジャンル名(「夜ご飯」とか)からジャンルIDを探す時は、カテゴリーIDとジャンル名の2つで検索する必要があります。
ジャンルIDもハマりどころなので、のちほど。

amount
出金額です。

date
日付death。

from_account_id
出金元の口座です。
これも「お財布」とかじゃなく、口座IDを入れますね。
res = access_token.get('https://api.zaim.net/v2/home/account')
これもまたまたハマりどころですね。後ほど。

comment
コメント。何を入れてもいいです。

name
商品名とかを入れるようです。
「プレミアムモルツ」とか。

place
お店の名前ですかね。
「スーパータイヨー」とか。

カテゴリーID、ジャンルID、口座IDのハマりどころ
今回、結構悩みました。
ちゃんと存在するカテゴリーID、ジャンルID、口座IDを指定してるのに、何故かうまく記帳されない。
うまく記帳されないってのは、APIで記帳したあと、zaimのWEBアプリ上で「履歴」を確認すると、「カテゴリ」や「出金元」「入金先」が未登録状態になってしまうんです。
これで土曜日の貴重な4時間を潰しましたのでね。

ちなみに、
res = access_token.get('https://api.zaim.net/v2/home/category')
とかで取得した結果の中の、"id" の値を使っていました。だってAPIのマニュアル見るとそんな感じだったし。

おかしいなーと思いつつ、もう一度カテゴリー一覧の結果とか見てたら "local_id" ってのがあるんですよ。
カテゴリーだけじゃなく、ゲンレや口座についても "local_id" があります。
試しに、"id" の値を使っていたところを "local_id" の値に変えてみたんです。
するとどうだろう!!
うまくいくではありませんか。なんなんでしょうね?マニュアルにそんなこと書いてあったかな・・・

一点注意なんですけど、ジャンル名からジャンルIDを探す時に、カテゴリーIDとジャンル名で検索しますよね?ここのカテゴリーIDは、"id" の値で検索します。
ややこしいね。

削除について
削除用のAPIもあるんです。
マニュアル見ると、

DELETE https://api.zaim.net/v2/home/money/payment/:id

って書いてあるんです。

なんで、

https://api.zaim.net/v2/home/money/payment/:12345

とかやってみたんですけど、なんとか 405 とかいうエラーになってしまう。
泣きそうだったんで、問い合わせフォームから問い合わせてしまったんですが、

https://api.zaim.net/v2/home/money/payment/:12345

このコロンがいらなかったんですねぇ・・・

https://api.zaim.net/v2/home/money/payment/12345

とすることで完全無欠のロックンローラーになれました。

こんなところですね。

oinknoteからの移行
さあ、ここまでくればもう安心です。
paymentのAPIがうまく呼べるようになってれば、incomeやtransferも問題ありません。
普通のテキスト処理なんで特にコードは載せませんが、こんな感じで処理しました。

  1. oinknoteから吸いだした履歴を1行ずつ取り出す。
  2. [費目]を見て、payment, income, transfer のどれを使うか決定
  3. 日付やら金額やらカテゴリーやらジャンルやら口座やら設定。カテゴリーやジャンルや口座はoinknoteとzaimで違いますので、oinknote→zaim変換用のテーブルを作りました。んでzaim用のジャンル名や口座名からジャンルIDとか口座IDに変換して、APIに渡す、と。

以上をひたすら繰り返すだけです。

なお、デバッグ中にzaimの記帳データをまっさらにすることが何度もあると思うんですが、上記のDELETEを使わなくても一括削除することができました。

zaimの「設定」ページで、左下の「ログイン関連」というボタンを押して、下の方にスクロールすると「入力データを全削除する」ってのがあります。

大体、こんなところで。