いまさらながらの PYTHONSTARTUP (+ mongod on docker, and pymongo)

思い込み、ていうかね。

何かしらの WEB サービスがあったとして、そのデータ内容には不満がないもののその UI が使いにくかったり、あるいはまさに「二次利用」のためにスクレイプしてでもデータをお取り寄せたいことがある。ワタシの場合だとアニメ関係にそういうのが多い。それこそ~関連図を作りたい、とかね。

で、データを「お取り寄せた」としたら、そのデータの

  1. 置き場所
  2. フォーマット
  3. そのデータを利用するためのクライアント

で毎度悩むわけだ。初期コストがかかっても RDB にブチ込むのが案外最終的には柔軟だったりもする、としても、「まずはお取り寄せよう」としてスクレイパを書き上げた直後くらいだと、正規化などある程度ちゃんとした設計をしつつ作りこむ決断をするのがかなり億劫で、「ひとまず最初は json で」などとサボるわけだね。

けれども「ひとまず最初は json で」って、サボれてるようで実はサボれてなくて、この json から「柔軟な検索」なんてのをしようと思うと都度都度「検索プログラミング」する必要があるわ、それでの検索は効率悪いわで、まぁいいことないわけね。

して、今更ながらの其の壱。RDB を採用しようとすればスキーマの設計や DDL 記述と投入、などのセットアップまでが大変な上に「ひとまず最初は json」の作業のかなり多くの部分をバラして再構築する必要があるんだけれど、MongoDB ならそれはなさげだよなと。MongoDB はもう10年以上前から存在は知ってたけど手を出してこなかったのよね、初めてやってみようかと。さらにいえば Docker ブームである、ワレ的に。もう「Windows だから大変」なんてことはないのだ、やんややんや。

初体験なのでどれだけ大変なのかわからなかったけれど、30分くらい必要なものを読んで、すぐに「めっさ簡単」であることを知る:

実は MongoDB 本体のチュートリアルから先に読むとちょっと混乱する。MongoDB には専用の出来合いクライアント(PostgreSQL の psql 相当)があるのだが、どこまでがそのクライアント固有の仕様なのかなどの境目がわかりにくい。特に検索条件として使う「$」についてなど。けど docker の説明でのサーバの立て方、pymongo チュートリアルで、ほぼ全部理解出来る。いや、こりゃいいね。

mongod を動かす説明が、docker-library/docs のは「mongod on docker と(Windows)ホスト」という関係のものに対する説明だけ薄いので、docker に疎いと悩むかもしれんけれど、そういう人は先に私の Docker ことはじめを読んでもらえばいい。今回の場合ワタシはこんな感じで起動:

1 [winme@host: ~]$ docker run --name mymongo -d -p 27017:27017 -v `/bin/pwd`://data/db mongo

データの場所はカレントにしたけど、そこはご随意に。ポートのマッピングは必要。で、これによりホスト(Windows)上の pymongo でアクセス出来る:

 1 Python 3.9.10 (tags/v3.9.10:f2f3f53, Jan 17 2022, 15:14:21) [MSC v.1929 64 bit (AMD64)] on win32
 2 Type "help", "copyright", "credits" or "license" for more information.
 3 >>> from pymongo import MongoClient
 4 >>> cli = MongoClient()
 5 >>> db = cli.test_database
 6 >>> mydo = db.mydo
 7 >>> mydo.insert_one({"a": 124})
 8 <pymongo.results.InsertOneResult object at 0x00000221FD6EC8B0>
 9 >>> mydo.find_one({"a": 124})
10 {'_id': ObjectId('62ec70a4f0458be43fa743a3'), 'a': 124}
11 >>> mydo.find_one({"a": 12})
12 >>>

スクレイパでお取り寄せて…のお取り寄せたデータをこの MongoDB に突っ込むのは、ワタシのケースだと例えばこんな:

 1 # ...
 2 def all_animes():
 3     result = {}
 4     # ...スクレイプ...
 5 
 6     #with io.open("hhs_animedb.json", "w", encoding="utf-8") as fo:
 7     #    json.dump(result, fo, indent=2, ensure_ascii=False)
 8     from pymongo import MongoClient
 9     cli = MongoClient()
10     db = cli.test_database
11     hhs_animedb = db.hhs_animedb
12     for k, v in result.items():
13         d = dict(v)
14         d["titid"] = k
15         hhs_animedb.insert_one(d)    

バルクインサートのインターフェイスがあるのは認識してるけど、先に insert_one でやってみた。これでも json.dump してるのとほぼ同じ「まんま」を突っ込めてる、てことね。そしてだ、json として永続化するだけで終わらせるのと違って、「検索が簡単」なわけよね:

1 Python 3.9.10 (tags/v3.9.10:f2f3f53, Jan 17 2022, 15:14:21) [MSC v.1929 64 bit (AMD64)] on win32
2 Type "help", "copyright", "credits" or "license" for more information.
3 >>> from pymongo import MongoClient
4 >>> cli = MongoClient()
5 >>> db = cli.test_database
6 >>> hhs_animedb = db.hhs_animedb
7 >>> hhs_animedb.find_one({"tit": "宝石の国"})
8 {'_id': ObjectId('62ec8812f2fb82cadda49fd9'), 'tit': '宝石の国', ...}

さて、今回の本題はここから。

「とりあえずデータお取り寄せて簡単に取り出せるようにしときたい」までは出来たけれど、その「簡単にかつ自由に検索」なる専用クライアントの話。特にそれを作るまでもない、今のところ、て場合にどうしとくか、てハナシ。凝ったことをしたければ専用の便利道具を作るけれど、ひとまずは「たとえば特定声優が関係したアニメをささっと検索出来ればええやん」程度に留まる場合に、ならばと毎度「from pymongo import MongoClient云々かんぬん」と「コーディング」するのか、したいのか、てことね。

この発想ってあんましワタシはしたことなかったんだけどさ、PYTHONSTARTUP を活用するつもりなら、「専用クライアントに少し近いもの」を実現出来るよなと:

 1 [me@host: ~]$ cat .hhs_animedbcli.py
 2 # -*- coding: utf-8 -*-
 3 from pymongo import MongoClient
 4 cli = MongoClient()
 5 db = cli.test_database
 6 hhs_animedb = db.hhs_animedb
 7 [me@host: ~]$ PYTHONSTARTUP=.hhs_animedbcli.py py -3
 8 Python 3.9.10 (tags/v3.9.10:f2f3f53, Jan 17 2022, 15:14:21) [MSC v.1929 64 bit (AMD64)] on win32
 9 Type "help", "copyright", "credits" or "license" for more information.
10 >>> hhs_animedb.find_one({"tit": "宝石の国"})
11 {'_id': ObjectId('62ec8812f2fb82cadda49fd9'), 'tit': '宝石の国', ...} 

例によって「py」は Windows 専用のランチャなので、Windows 以外のユーザはここは読み替えとくれ。典型的には python3 だのだね。

たとえば graphviz とかで可視化するだのといったことや、もっと「毎日便利に使えるもの」からは遠いものの、この程度のニーズってのも割とあって、今のケースだとたとえば「2017年夏クールってどんなアニメあったけか?」みたいなのをささっと調べるみたいなね、その程度なら、これだけで十分なこともあるってわけよね。

なかなか python の「インタラクティブ」って、「テスト」「検証」「実験」以外で使おうという発想に至らないことが多いわけね。つまり「Python をシェルがわりに使う」とはあんまり思わない。まぁそれがなぜなのかは Python ユーザには結構自明かなと思うわけよ、それこそまさしく「インデントがウザい」んだよ、対話的にはね。そういうマインドだもんで、長らく PYTHONSTARTUP をこういう発想で使おうと思うに至らなかった、って話でしたとさ。をしまい。



Related Posts