RustでZaimと東京ガスを連携させてみた【1】

最近、昨年の医療費控除を受けるために確定申告の準備などをしているのですが、家計簿ソフトで利用しているZaimにガスや水道代が勝手に計上されてくれると便利だな〜と思い、Zaim APIとSeleniumを使って実現してみようと思います。 Zaim Developers Center 東京ガスのCSVファイルをダウンロードする Rustのheadless-chromeを使って構築します。 とりあえずCargoパッケージ作ってライブラリを追加します。 cargo new sync-zaim cargo add headless-chrome 追加できました。 使い勝手を知るために、まずはGoogleにアクセスしてスクショをとってみます。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use std::fs; fn main() { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); tab.navigate_to("https://www.google.com/").unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); } 実行すると、Googleの画面のスクショが保存されました。 この調子で東京ガスのCSVを取得できるようにしましょう。 コードはpythonですがここを参考にします。 Python(Selenium)で東京ガスのcsvをダウンロード|gadgetking 実行すると少しエラーは出ますが、ファイルダウンロードは可能になりました。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use headless_chrome::protocol::cdp::types::Method; use std::sync::Arc; use std::{env, thread, time, error::Error}; use std::{fs, path}; use serde::Serialize; const TOKYO_GAS_LOGIN_URL: &str = "https://members.tokyo-gas.co.jp/login.html"; const GAS_ID: &str = "東京ガスのログインID"; const GAS_PASSWD: &str = "東京ガスのログインパスワード"; // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/187 #[derive(Serialize, Debug)] struct Command { behavior: &'static str, downloadPath: &'static str, } impl Method for Command { const NAME: &'static str = "Page.setDownloadBehavior"; type ReturnObject = serde_json::Value; } fn main() -> Result<(), Box<dyn Error>> { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/280 tab.enable_request_interception(Arc::new(|transport, session_id, params| { println!("request!"); println!("transport: {:?}", transport); println!("session_id: {:?}", session_id); println!("params: {:?}", params); headless_chrome::browser::tab::RequestPausedDecision::Continue(None) }))?; tab.navigate_to(TOKYO_GAS_LOGIN_URL).unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); tab.wait_for_element("#loginId").unwrap().type_into(GAS_ID).unwrap(); tab.wait_for_element("#password").unwrap().type_into(GAS_PASSWD).unwrap(); tab.wait_for_element("#submit-btn").unwrap().click().unwrap(); // tab.wait_until_navigated().unwrap(); thread::sleep(time::Duration::from_secs(5)); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); let path_string = env::current_dir()?.into_os_string().into_string().unwrap(); // println!("pwd: {}", path_string); let static_path_str: &'static str = Box::leak(path_string.into_boxed_str()); let command = Command { behavior: "allow", downloadPath: static_path_str, }; tab.call_method(command)?; // CSVファイルをダウンロード tab.navigate_to("https://members.tokyo-gas.co.jp/api/mieru/chargeAmountCsv.jsp?no=0&target=total") .unwrap(); thread::sleep(time::Duration::from_secs(30)); Ok(()) } 実行すると以下のようなエラーが出ます。 ...

2023年9月10日 · にあえん

ファンドって何?NISAで扱えるファンドの種類について調べた

はやいものでもう9月ですね。 今年の終わりも近いですが、最近つみたてNISAを始めるにあたっていろいろ金融知識を調べているので、雑にまとめてみます。 そもそもファンドって何? ファンドとは、金融用語で資金を募って株や債権を専門家が運用するパッケージのことです。 ファンド | 証券用語集 | 東海東京証券株式会社 ファンドに投資する人は投資家と呼ばれるので、NISAに投資した私は投資家です(どや〜) そもそもファンドは「基金」という意味で、基金は「なにかの目的を達成するために集められたお金」という意味です。 基金 - Wikipedia 今回の場合では、「株や債権を運用して利益を出す」という目的のために集められるお金の入金先のことがファンドとなります。 つみたてNISAで扱うファンドとは めちゃめちゃわかりやすい表があるサイトがあります。 本音が飛び出す! つみたてNISA座談会 これによるとファンドの種類は大きく以下の2種類に分けられます。 インデックスファンド アクティブファンド それぞれの違いですが、その前に指数について理解しておく必要があります。 金融用語において指数とは、市場の値動きを示す数値のことです。 ファンドによって異なりますが、主な指数の一覧は以下のサイトにまとめてあります。 主な指数一覧 | ファンズアイ まずインデックスファンドは、この指数がそのまま利益と連動するファンドのことをいいます。 インデックスファンドは既にある指数を元にしているため、運用にかかるコストが少なく、信託報酬と呼ばれる手数料が抑えられています。 ただし、指数以上の利益を出すことは難しいです。 一方アクティブファンドは指数を上回る利益を目指すファンドのことで、より多くの利益を求める場合やユニークなファンドを選びたい場合はこちらになります。 ただし運用コストはインデックスファンドよりも高く設定されています。 このサイトにわかりやすい表があったので参照します。 会社概要|フィデリティ証券 ファンドの比較 このインデックスファンドの中でも更に投資対象が株式だけかそうでないかで2種類あり、以下のような種類に分けられています。 株式を対象 国内 日本の株のみを対象とし、更に以下のような分類による指数を取る 東証一部に上場する株式のうち代表する225銘柄(日経平均株価を指数とする) 銘柄一覧 東証一部に上場する株式すべての銘柄(約2100銘柄)(TOPIXを指数とする) 全世界 全世界47ヶ国の2800銘柄(MSCI ACWIを指数とする) 全世界47ヶ国の7800銘柄(FTSE Global All Capを指数とする) 海外 米国 米国株式市場に上場する500銘柄(S&P500を指数とする) 米国株式市場に上場するほぼすべての銘柄(約3600銘柄、CRSP U.S.Total Marketを指数とする) 先進国 日本を除く先進国22ヶ国の約1300銘柄(MSCI コクサイを指数とする) 新興国 新興国24ヶ国の約1100銘柄(ESCI Emerging Markets) バランスファンド(株式とそれ以外も対象) 4資産 日本・先進国の株式・債権 6資産 日本・先進国・新興国の株式・債権 8資産 日本・先進国の株式・債権・不動産+新興国の株式・債権 etc.. 以下の様なランキングなど調べると無限にファンドが出てきて迷ってしまいますが、こういったジャンルごとに分けるとなんか分かった気になってきますね。 ...

2023年9月6日 · にあえん

prismaでfactoryboyみたいなやつが作りたい

作りたい。 factoryboyをご存知ですか? factory_boy — Factory Boy stable documentation pythonでテスト実装する際、ダミーデータの実装に役に立つライブラリなのですが、これがまー使いやすい。 ので、prismaでシード作るときにも同じような感じで使いたいなーと思い、実現方法を考えてみました。 factory-botを使ってみる 調べてみると、factory-botというのがありました。 Git これを使ってみたいと思います。 とりあえずディレクトリを作って初期化します。 mkdir prisma-factory-bot cd prisma-factory-bot npm init 次に、typescriptの実行基盤とお目当てのfactory-botをインストールします。 npm i -D typescript ts-node @types/node npm i factory-bot これで準備できました。 最も簡易的な形の実装は以下のとおりです。 import { factory } from "factory-bot"; class User { username: string; score: number; constructor(attrs: { username: string, score: number }) { this.username = attrs.username; this.score = attrs.score; } } factory.define('user', User, { username: 'Bob', score: 50, }); (async () => { const user = await factory.build<User>('user'); console.log(user); })(); とりあえず動く形になりました。 実行すると、生成されたUserが表示されます。 User { username: 'Bob', score: 50 } bulidMany関数を使うと、複数のモデルをいっぺんに作ることができます。 ...

2023年8月23日 · にあえん

OS自作入門【1】

wasmに入門したりprismaにハマったりいろいろありますが、以前から興味のあった自作OSというものに手を出してみます。 この書籍を参考にしています。 ゼロからのOS自作入門 | 内田 公太 |本 | 通販 | Amazon 機械語でHello World! まずはバイナリの編集に使用するエディターをダウンロードします。 sudo apt install okteta エディターがダウンロードできたら、ひたすらHello Worldを実装します。 以下に上がっていますが、本見ながら写経しました。チカレタ。。 mikanos-build/day01/bin/hello.efi at master · uchan-nos/mikanos-build · GitHub 実際はこんな感じです。痺れますね。 これをBOOTX64.efiとして保存します。 ちゃんとコーディングできていれば、sumコマンドでチェックサムが12430となっています。 % sum ../../study/custom_os/BOOTX64.efi 12430 2 ではこれをエミュレーターで実行します。 QEMUをダウンロードしましょう。 Download QEMU - QEMU 公式にダウンロードのためのshがあったので使ってみます。 wget https://download.qemu.org/qemu-8.1.0-rc4.tar.xz tar xvJf qemu-8.1.0-rc4.tar.xz cd qemu-8.1.0-rc4 ./configure make しかし./configureのあたりでエラーになってしまいました。。 % ./configure Using './build' as the directory for build output python determined to be '/home/username/.anyenv/envs/pyenv/shims/python3' python version: Python 3.8.12 mkvenv: Creating non-isolated virtual environment at 'pyvenv' mkvenv: checking for meson>=0.63.0 mkvenv: installing meson>=0.63.0 mkvenv: checking for sphinx>=1.6.0, sphinx-rtd-theme>=0.5.0 'sphinx>=1.6.0' not found: • Python package 'sphinx' was not found nor installed. • mkvenv was configured to operate offline and did not check PyPI. Sphinx not found/usable, disabling docs. ERROR: Cannot find Ninja なんかpython使っているっぽいです。 ...

2023年8月21日 · にあえん

Rustで100本ノック【1】

ろくにRustに触らないままでwasmをやってしまっていましたが、ちゃんと触っておこうと思って100本ノックに挑みました。 言語処理100本ノック 2015 その際の備忘録を残します。 Q00: 文字列の逆順 逆順にするだけなのに、結局ググってしまった。。 Conversion between String, str, Vec<u8>, Vec<char> in Rust · GitHub fn main() { // 文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ. let s = "stressed"; let mut s = s.chars().collect::<Vec<_>>(); s.reverse(); print!("{}", s.iter().collect::<String>()); } collectの使い方とか、pythonみたいに容易に行かない感じがする。 Q01 さっきよりかはスムーズにできた。 でもイテレータの使い方とかちょっとググった。 fn main() { // 「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. let s = "パタトクカシーー"; for (i, c) in s.chars().enumerate() { if !([1,3,5,7].contains(&i)) { continue; } print!("{}", c); } println!(); } Q02 基本的に先程の2問と解き方は一緒だった。 fn main() { // 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ. let s1 = "パトカー"; let s2 = "タクシー"; let mut s1_vec = s1.chars().collect::<Vec<_>>(); let mut s2_vec = s2.chars().collect::<Vec<_>>(); s1_vec.reverse(); s2_vec.reverse(); let mut result: Vec<char> = vec![]; while s1_vec.len() > 0 && s2_vec.len() > 0 { if s1_vec.len() > 0 { result.push(s1_vec.pop().unwrap()); } if s2_vec.len() > 0 { result.push(s2_vec.pop().unwrap()); } } println!("{}", result.iter().collect::<String>()); } Q03 前の問題に比べると少し楽になりました。 ...

2023年8月20日 · にあえん

hugoのテーマを新しくしました

今のHugoのテーマから、記事の検索機能などもあるいい感じのテンプレートに乗り換えたいなーと思った際の備忘録です。 何個かのテーマをインストールして実際に起動して確認してます。 hugoplateを導入する デモを見る限り、リッチなUIや記事の検索機能などがあり結構いいなと思ったのがこのテーマです。 Hugoplate git submoduleを使用してテーマを追加します。 hugoプロジェクトのthemesディレクトリで以下のコマンドを実行します。 git submodule add https://github.com/zeon-studio/hugoplate.git config.tomlを変更します。 theme = "hugoplate" この状態で一旦ローカルから起動してみます。 hugo serve -D しかしエラーが出てしまいました。。 render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found Rebuild failed: Failed to render pages: render of "page" failed: execute of template failed: template: _default/single.html:8:7: executing "_default/single.html" at <partial "essentials/head.html" .>: error calling partial: "/home/username/Project/personal/hugo-blog/themes/hugoplate/layouts/partials/essentials/head.html:13:3": execute of template failed: template: partials/essentials/head.html:13:3: executing "partials/essentials/head.html" at <partialCached "favicon" .>: error calling partialCached: partial "favicon" not found hugo v0.92.2+extended linux/amd64 BuildDate=2023-01-31T11:11:57Z VendorInfo=ubuntu:0.92.2-1ubuntu0.1 Reload Page なんかファビコンが足りないっぽい? ...

2023年8月18日 · にあえん

wasmに入門してみる【8】

今回で最後になります。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 wasmに入門してみる【7】 wasmのサイズを小さくする いくつかの設定を追記することで、wasmのサイズを小さくすることができます。 Shrinking .wasm Size - Rust and WebAssembly Rustのリリースビルドみたいなものですね。 まず必要なパッケージをインストールします。 cargo install wasm-opt Cargo.tomlに設定を追記します。 [profile.release] lto = true opt-level = "z" 再ビルドする前に、一度どのくらいの大きさなのか見ておきましょう。 % wc -c pkg/wasm_game_of_life_bg.wasm 25136 pkg/wasm_game_of_life_bg.wasm 大体25KBくらいでした。 では先程インストールしたwasm-optで最適化していきます。 wasm-opt -Oz -o output.wasm pkg/wasm_game_of_life_bg.wasm 最適化できました。 % wc -c output.wasm 24955 output.wasm あんまり変わっていないですね。。 wasm-packでもビルドしてみて、wasmファイルに変化があるか確認してみます。 %wasm-pack build [INFO]: 🎯 Checking for the Wasm target... [INFO]: 🌀 Compiling to Wasm... Compiling wasm-game-of-life v0.1.0 (/home/username/Project/personal/wasm-game-of-life) warning: unused macro definition: `log` --> src/lib.rs:25:14 | 25 | macro_rules! log { | ^^^ | = note: `#[warn(unused_macros)]` on by default warning: `wasm-game-of-life` (lib) generated 1 warning Finished release [optimized] target(s) in 0.41s [INFO]: ⬇️ Installing wasm-bindgen... [INFO]: found wasm-opt at "/home/username/.cargo/bin/wasm-opt" [INFO]: Optimizing wasm binaries with `wasm-opt`... [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended [INFO]: ✨ Done in 0.86s [INFO]: 📦 Your wasm pkg is ready to publish at /home/username/Project/personal/wasm-game-of-life/pkg. ビルドできたので確認します。 ...

2023年8月18日 · にあえん

wasmに入門してみる【7】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 今回はタイムプロファイリングの章です。 Time Profiling - Rust and WebAssembly タイムプロファイリングする ライフゲームのパフォーマンス向上の指針として、FPS(Frames Per Second)タイマーの導入を行います。 window.performance.nowという関数を利用して、 www/index.jsにfpsオブジェクトを実装します。 const fps = new class { constructor() { this.fps = document.getElementById("fps"); this.frames = []; this.lastFrameTimeStamp = performance.now(); } render() { // 最後のフレームレンダリングからの差分時間を1秒あたりのフレーム数に変換する。 const now = performance.now(); const delta = now - this.lastFrameTimeStamp; this.lastFrameTimeStamp = now; const fps = 1 / delta * 1000; // 最新の100個のタイミングだけを保存する。 this.frames.push(fps); if (this.frames.length > 100) { this.frames.shift(); } // 最新の100個のタイムの最大値、最小値、平均値を求めます。 let min = Infinity; let max = -Infinity; let sum = 0; for (let i = 0; i < this.frames.length; i++) { sum += this.frames[i]; min = Math.min(this.frames[i], min); max = Math.max(this.frames[i], max); } let mean = sum / this.frames.length; // fpsに描画する this.fps.textContent = ` Frames per Second: latest = ${Math.round(fps)} avg of last 100 = ${Math.round(mean)} min of last 100 = ${Math.round(min)} max of last 100 = ${Math.round(max)} `.trim(); } }; //...中略 const renderLoop = () => { fps.render(); // 追記 universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); }; www/index.htmlにも要素を追加します。 ...

2023年8月17日 · にあえん

prisma client pythonが実行しているNodeはどこにあるのか

前回、prisma-client-pythonに入門しました。 しかし、どうやってNodeが動いているのかちょっとわからなかったので調べてみました。 Nodeの実態は? 現状、グローバルにprismaが使える状態ではないため、普通にprismaコマンドを実行しても実行できません。 % prisma zsh: command not found: prisma しかし、前回作ったパッケージ上であればprismaコマンドは実行できます。 % poetry run prisma This command is only intended to be invoked internally. Please run the following instead: prisma <command> e.g. prisma generate 説明を見るかぎり、ここで実行されているprismaは本家のCLIのラッパーとして実装されているようです。 Prisma Client Python exposes a CLI interface which wraps the Prisma CLI. This works by downloading a Node binary, if you don’t already have Node installed on your machine, installing the CLI with npm and running the CLI using Node. ...

2023年8月15日 · にあえん

prisma-client-pythonでpythonからORMを楽にやる

今、自分の中でORMツールとしてアツいのがprismaです。 Prisma | Instant Postgres plus an ORM for simpler db workflows モデル定義もマイグレーションも、ドキュメント読んだり実装してみたりすればするほどかなり使える書き方ができるので、すごく可能性を感じています。 そんなprismaですが、python用のクライアントがあるということで、勉強がてら触ってみたいと思います。 Prisma Client Python pythonプロジェクトの初期化 まずはpoetryでプロジェクトを初期化します。 poetry new prisma-client-python-playground 作成されたプロジェクトに依存性を追加します。 poetry add -D prisma さて、これでいつものprisma CLIが使えるようになりました。 早速Prismaの初期化をしていきましょう。 今回もsqliteを使用していきます。 % poetry run prisma init --datasource-provider sqlite ✔ Your Prisma schema was created at prisma/schema.prisma You can now open it in your favorite editor. Next steps: 1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started 2. Run prisma db pull to turn your database schema into a Prisma schema. 3. Run prisma generate to generate the Prisma Client. You can then start querying your database. More information in our documentation: https://pris.ly/d/getting-started 初期化できました。 ...

2023年8月13日 · にあえん