月報 2020/07

Thu Jul 02 2020 最近作った渾身のコマンド

qj

ターミナル / シェルスクリプトから JSON をダンプする. ずっと欲しかったので今日仕事をサボって作ってた.

シェルスクリプトからJSONを吐くことがある. 例えば Slack の incoming webhook は JSON データをPOSTで送る. Slack じゃなくても今どきの WebAPI ではよくある. シェルスクリプトから雑に curl で投げたいときに JSON データだけどうにかしてダンプする必要がある. 例えば,

cat <<EOM > payload.json
{
    "channel": "#hoge",
    "text": "$TEXT"
}
EOM

みたいにすれば簡単なテンプレート言語として機能してくれる. のだけど, $TEXT に改行が入っていたら, クオーテーション文字が入っていたら, これはおそらく正しくJSONとして機能しない(改行は大丈夫かな?どうだろう?).

これは別に JSON というデータフォーマットに限ったことじゃない. シェルスクリプトに優しいデータフォーマットなんてもはやない. YAML なら安全に吐けるというなら YAML を吐いて JSON に変換してもいいが, それも難しい.

というわけで作った. jq という JSON からデータをフィルタ(射影)するコマンドがあるが, おおよそそれの逆をするという気持ちから qj とした.

README の例だが, こんな感じ:

   qj -e .=3
3

   qj -e .x=1 -e .y=2 -e .z[0]=3
{"x":1,"y":2,"z":[3]}

   qj -e '.hello="world"'
{"hello":"world"}

   qj -e '.persons[1].name="Alice"'
{"persons":[null,{"name":"Alice"}]}

jq 方式の値一個をフィルタする式と, その値のペアを = で区切って指定してく. 値の数だけこれを並べないといけないので, 組み立てる JSON の割にかなり冗長に書いていかないといけない.

だから本当は jq がそうであるようにパイプを使って上手いこと

   qj -e '.x | (.y=2, .z=3)'
{
  "x": {"y": 2, "z": 3}
}

このくらい書けるのがいいんだけど, そういうことまでは書けない.

また, このコマンドが欲しかったモチベーションの全てが, 文字列のエスケープが面倒くさいということだったので, 値は特にパースできなかったときは丸々全て文字列ということにしている.

   qj -e '.=hoge'
"hoge"

   qj -e '.=ho"ge'
"ho\"ge"

   qj -e ".=$(seq 3)"
"1\n2\n3"

クオーテーションが入ってても改行が入ってても出来るだけ文字列として解釈するようにする.

シェルスクリプトから Slack の incoming webhook に POST は次のように出来る

qj -e .channel=#channel .username=bot .text=hello! .icon_emoji=:ghost: |
    curl -XPOST --data @- https://hooks.slack.com/services/XXXXXXXXXXXX

このくらいの単純な JSON なら記述量も減るし, エスケープのこと考えなくていいし楽.

web-grep

これは中の実装に関してはまじでただの tanakh/easy-scraper のラッパーです.

それはともかくめちゃくちゃ便利. web スクレイピングといえば, 私は次の二つを取っていた.

  1. curl して目印を grep してその前後から気合で取ってくる
  2. curl して真面目にHTMLをパース
    • XML の再帰的データ構造になっているので, root から所望のデータが入っているところまで辿る

1 で十分ということもある. これでいいときはこれでいい. 2 を選ぶより楽だし都合がいいこともある. そうでない場合は 2 をする必要がある. 問題は所望のデータの場所を指定するパスを保守するのが面倒だということ. 最初に必死こいてそのパスを探すのに, あるとき簡単に変わってしまう.

easy-scaper は XML に関するパターンマッチを実現してくれる. web-grep ではプレースホルダーとして {} を用いて例えば次のようにパターンを記述する.

<a class="link">{}</a>

これは link というクラス属性を持つ a タグに包まれた中のテキストを表現する. HTML 自体を取得したいことはないからテキストにしか {} はマッチしない.

{} は属性にも使える.

<a class="{}"></a>

これは a タグのクラス名を取得する.

このパターンマッチはHTML中の唯一つにマッチすることを初めから仮定していないのもいい. パスでデータの場所を指定する方法はちょうど一つを指しているが, このパターンで指定する方法はマッチしなければゼロ個だし, 複数にマッチすればその全てを列挙してくれる. 例えば <li> で列挙されたデータを全部取得したいというときに便利だ.

   seq 3 | sed 's#.*#<li>&</li>#' | web-grep '<li>{}</li>'
1
2
3

web-manga-check

Webで連載されてる漫画の更新情報をチェックする. master ブランチには載ってないけど, 更新されてたら Slack に通知するのまで裏で実装されている.

更新チェックするのには, 適当な web ページの適当な箇所を見つければいい. 例えば作品ページがあって, そのどこかに最終更新日が書かれてることがある. その該当の innerText を見ればいい. 作品ごとのページが与えられてない場合は, 最新話へのリンクがどこかに貼られてるのを探してそのリンクを見る, とか. ここにさっき言った web-grep を使ってる.

web ページ丸々の差分を見る, ヘッダにある ETag を見る, とかはたいてい通用しない. 出版社がきちんと運営するような web ページはたいてい毎日, 漫画更新とは関係ない箇所の変更をしているものだ. 別な漫画の新着情報が載ってたりね.

web-manga-check は徹底してシェルスクリプトとして書かれている. そこで Slack への通知にはさっき言った qj を使って組み立てた JSON を curl でポストしている. テキスト中に " が含まれているばっかりに, という経験を何度も味わっているが, これなら安心.

Mon Jul 06 2020 2020/07/05 都知事選投票率

あらすじ

投票率は区の民度(なんだかんだ言っても)

参考

結果

結果は以下の表のとおり. 生データは https://gist.github.com/cympfh/508d88c532b3edc59c4fb42c5b2b6cb7 として置いておきます.

rank 地区 投票率 (%) 参考 URL
1 千代田区 64.65 https://www.city.chiyoda.lg.jp/koho/kurashi/senkyo/tochiji/tohyo.html
2 文京区 62.98 https://www.city.bunkyo.lg.jp/kusejoho/senkyo/r02tochiji/r2tochiji.html
3 練馬区 61.14 https://www.city.nerima.tokyo.jp/kusei/senkyo/kekka/totijisenkyo/tohyo.html
4 台東区 61.07 https://www.city.taito.lg.jp/index/kusei/senkyo/totiji/2chijitouhyo.html
5 世田谷区 58.26 https://www.city.setagaya.lg.jp/mokuji/kusei/007/002/d00186300.html
6 中央区 58.03 https://www.city.chuo.lg.jp/smph/kurasi/senkyo/sokuho/r0207tochijisen/r2tochijisenkyo_tohyosokuho.html
7 北区 57.69 http://www.city.kita.tokyo.jp/senkan/kuse/senkyo/kekka/kekka/190721_sangi-tohyo.html
8 杉並区 57.61 https://www.city.suginami.tokyo.jp/senkyo/r2tochiji/1059073/index.html
9 江東区 56.93 https://www.city.koto.lg.jp/610102/tochiji2020/tohyo.html
10 品川区 56.21 https://www.city.shinagawa.tokyo.jp/PC/kuseizyoho/kuseizyoho-sensei/kuseizyoho-senkyo/tochijisenkyo/hpg000028789.html
11 渋谷区 56.02 https://www.city.shibuya.tokyo.jp/kusei/senkyo/20200705to_tou.html
12 中野区 55.76 https://www.city.tokyo-nakano.lg.jp/dept/711000/d029097.html
13 江戸川区 55.36 https://www.city.edogawa.tokyo.jp/documents/18856/2115.pdf
14 墨田区 55.08 https://www.city.sumida.lg.jp/senkyo_sokuho/tohyo.html
15 豊島区 54.82 https://www.city.toshima.lg.jp/361/kuse/senkyo/2006191019.html
16 目黒区 54.62 https://www.city.meguro.tokyo.jp/smph/gyosei/senkyo/tochiji/sokuhou.html
17 新宿区 54.59 http://www.city.shinjuku.lg.jp/senkyo/tochiji_20ji_output.html
18 荒川区 54.45 https://www.city.arakawa.tokyo.jp/a004/gikaisenkyo/senkyo/tochijisokuho.html
19 板橋区 53.5 https://www.city.itabashi.tokyo.jp/kusei/senkyo/1016101/1023614.html
20 大田区 53.44 https://www.city.ota.tokyo.jp/touhyou/t_r2tochiji_2000.html
21 葛飾区 51.89 http://www.city.katsushika.lg.jp/information/1000080/1020036/1011705/1023858.html
22 足立区 49.58 https://www.city.adachi.tokyo.jp/juyo/200705senkyoall/touhyousaisyu.html
23 港区 49.32 https://www.city.minato.tokyo.jp/senkan/tochijisenkyo/kaihyou.html

いかがでしたか?

Sat Jul 25 2020 アニメレコメンドを作った

Annictはbooklog, filmarksのアニメ版. APIを充実してユーザーに提供してくれていて, これでユーザーのレビューをアニメ作品に紐付いた状態で取得することが出来る. ユーザー \(i\) がアニメ \(j\) のレビューを書いてるという行列 \(A_i^j\) を作って, ありがちな感じで行列分解して次元圧縮とか推薦が出来る.

できた:

相変わらずUIデザインはなんも分からん.

レコメンドについて

業務でいつもやってることを趣味でやっただけ. 行列分解は implicit がオススメ. 重み付きで行列分解したりロジスティック回帰してくれたりする. データを取得するのにAnnictが提供してるGraphQLを使った. GraphQL生まれてはじめて触るので何もわからんが, グラフでもなんでもないものを強引にグラフとしてるようにしか見えない.

サーバについて

fastAPI 便利

フロントについて

Vue.js 触ってみた. 便利だ.

Sun Jul 26 2020 インドカレーについて

友人が作ってるのに感化されて自分でも試してみた.

私がレシピとして第一に参照してるのはこれ:

自分が組み立てたレシピは以下の通り.

気合をいれた場合

材料

工程

手を抜いたバージョン

いくつかの材料工程は不要だと思ってる. 例えば肉をヨーグルトにつける作業は無駄だと思う. 臭み消しと柔らかくするための操作だと思うのだが, まず日本のスーパーで手に入る肉は普通臭くない. 柔らかさは…よくわかんないです. 柔らかく煮込んだりビーフシチューみたいな作り方するわけでもなし.

材料

玉ねぎ なんでもいい. 具材 お好きに. 肉をヨーグルトに漬ける操作, しなくていい. ただ乳製品を入れたまろやかさが好きなら牛乳入れるといい. 普通の日本のカレーでも牛乳入れたりするしね. トマト 自分で粉砕する工程が面倒だから初めからそのようなものを買えばいい. トマトピューレとかもあるけど, 飲む用のトマトジュースを初めから買うのが一番安価だと思う. ただ適当にトマトジュース買うと塩分とか添加しまくってるのが普通だから何にも添加されてないものを選ぶこと. 探せばそういうのもあります. スパイス ターメリックとガラムマサラだけあれば割とカレー. クミンシードくらいはあったほうがいいとは思うけど, 無くてもちゃんとカレーはカレー.

工程

工程をいくつか省いただけでほぼほぼ同じ.

冷蔵/冷凍保存について

すればいい.

最後まで作ってから保存してもいいけど, トマト(及び牛乳)を入れる手前が一番体積が小さいので, このタイミングで保存しておくのが便利だと思う.

玉ねぎの "細かく刻む" について

本当にペーストくらいになるまで刻むのがインド風らしい, が, 結構扱いに困る. ペーストになった玉ねぎはどんどん油を吸うし粘度が高いと単純にヘラにくっついて炒めにくい. 私は5ミリ角くらいに留めておいてる.

ぶんぶんチョッパー なるものを使ってる. これは要するに手動のフードプロセッサー. 確かに便利だけど包丁で十分困ってないならそれでいい. ただペーストになるまで刻む(もはやミキサーする)ということを試したい場合はこういったなにか道具があると便利.

玉ねぎは3個くらいまとめて売られてることが多い. 予め全部刻んでおいてジップロックに入れて冷凍保存してる. 料理するときにまな板と包丁が要らないように準備しておくことは料理の美徳.