金子邦彦研究室情報工学全般Google FirebasePython と Google Firebase で、定期的にデータベースの差分をダウンロード、手元のデータの差分更新

Python と Google Firebase で、定期的にデータベースの差分をダウンロード、手元のデータの差分更新

データベースに「保存」を行った日時を記録するフィールドを使って、差分更新を行う。 データベースの丸ごとのダウンロードではないので、通信量の節約などができる.

前準備

Python, Git のインストール: 別ページ »で説明

git の URL: https://git-scm.com/

以下,Windows でインストール済みであるものとして説明を続ける.

クラウド・ファイアストア(Cloud Firestore)のデータベース準備

  1. 前準備
    • Firebase の Web ページ: https://firebase.google.com/?hl=ja を使い、 プロジェクトの新規作成, 秘密鍵のダウンロードを行う
    • Python の Firebase Admin SDK のインストールを行う
      pip install -U firebase-admin
      

    ※ 以上の手順は、「Python で Google Firebase の Cloud Firestore (クラウド・ファイアストア)を使ってみる」で説明している.

  2. データベースの作成

    datetime 型の created_at フィールドを含むようなドキュメントを作る

    さて、「created_at」は、ドキュメントがデータベースに挿入された日時という意味。 あとから入ったドキュメントは、前に入ったドキュメントの「created_at」よりも新しい日時の値を持つ。

    例えば、次のように。

    [image]
  3. 確認のため、プログラムを実行してみる

    H:/my-project-abcde-firebase-adminsdk-q53ez-5d420dbcf2.json」のところは、先ほどダウンロードした JSON ファイル名にすること。

    u'time', u'>', datetime(2019,4,13,3,10,0)」は、日時(Python の datetime) の比較

    import datetime
    import firebase_admin
    from firebase_admin import credentials
    from firebase_admin import firestore
    
    cred = credentials.Certificate('H:/my-project-abcde-firebase-adminsdk-q53ez-5d420dbcf2.json')
    app = firebase_admin.initialize_app(cred) 
    
    db = firestore.client()
    ref = db.collection(u'docs')
    
    a = ref.where(u'created_at', u'>', datetime.datetime(2019,4,8,9,30,0))
    docs = a.stream()
    for doc in docs:
        print(u'{} => {}'.format(doc.id, doc.to_dict()))
    

    [image]
  4. 前回ダウンロードしたデータ docs を調べ、その中の created_at で最も最新のものを調べる
    import datetime
    import firebase_admin
    from firebase_admin import credentials
    from firebase_admin import firestore
    
    cred = credentials.Certificate('H:/my-project-abcde-firebase-adminsdk-q53ez-5d420dbcf2.json')
    app = firebase_admin.initialize_app(cred) 
    
    db = firestore.client()
    ref = db.collection(u'docs')
    
    # last を設定、「created_at」の値が「last」以降のものをダウンロード
    last = datetime.datetime(2019,4,8,0,30,0, 0,datetime.timezone.utc)
    a = ref.where(u'created_at', u'>', last)
    
    docs = a.stream()
    new_last = last
    for doc in docs:
        print(u'{} => {}'.format(doc.id, doc.to_dict()))
    # 確認のため「last以降のデータであるかを表示」
        print(last < doc.to_dict()['created_at'])
    # その中の created_at で最も最新のものを調べる
        if ( new_last < doc.to_dict()['created_at'] ):
            new_last = doc.to_dict()['created_at']
    

    [image]
  5. タイマーを使って定期的にダウンロードする。

    タイマーは「5」秒に設定。5秒ごとにダウンロードして、表示を繰り返す

    ダウンロードのたびに、まるごとダウンロードはしたくない。差分(新しいデータ)のみをダウンロードして、すでにダウンロード済みのデータに付け加える(更新)の操作としたい。

    そこで、ダウンロードごとに、ダウンロードしたドキュメントの中の「created_at で最も最新のもの」を調べる。 そして、その次のダウンロードでは、「created_at で最も最新のもの」引く10秒以降のものをダウンロードする (「10」と決めているのに、特に根拠はない. create_at を付けるシステムの側で -10秒の変動はありえるかも、と勝手に決めているだけ)

    サイト内の関連ページ一定間隔で処理の繰り返し

    import time
    import threading
    
    import datetime
    import firebase_admin
    from firebase_admin import credentials
    from firebase_admin import firestore
    
    cred = credentials.Certificate('H:/my-project-abcde-firebase-adminsdk-q53ez-5d420dbcf2.json')
    app = firebase_admin.initialize_app(cred) 
    
    db = firestore.client()
    ref = db.collection(u'docs')
    
    # base を設定、「created_at」の値が「base」以降のものをダウンロード
    base = datetime.datetime(2019,4,8,0,30,0, 0,datetime.timezone.utc)
    last = base
    # ダウンロードしたデータを置くためののオブジェクト。最初は空
    cache = {}
    
    def worker():
        global last
        global cache
        a = ref.where(u'created_at', u'>', last)
    
        docs = a.stream()
        new_last = last
        for doc in docs:
    #        print(u'{} => {}'.format(doc.id, doc.to_dict()))
    # 確認のため「last以降のデータであるかを表示」
            print(last < doc.to_dict()['created_at'])
    # ダウンロードしたデータを保存
            cache[doc.id] = doc.to_dict()
    # その中の created_at で最も最新のものを調べる
            if ( new_last < doc.to_dict()['created_at'] ):
                new_last = doc.to_dict()['created_at']
        print("cache data")
        print(cache)
        if ( last < ( new_last - datetime.timedelta(0,10) ) ):
            last = new_last - datetime.timedelta(0,10)
    
    def mainloop(time_interval, f):
        now = time.time()
        while True:
            t = threading.Thread(target=f)
            t.start()
            t.join()
            wait_time = time_interval - ( (time.time() - now) % time_interval )
            time.sleep(wait_time)
    
    # 5秒間隔
    mainloop(5, worker)
    

    [image]