LoginSignup
8
6

More than 5 years have passed since last update.

ブックマークレットからFirestoreのデータを読み書きする

Last updated at Posted at 2019-03-17

毎度悩むBookmarkletのjsファイルのホスト先をFirebaseにしてみたら、何かいいことあるかなと試したもの

使ったもの

毎度のことならがWindows環境です
- Windows10
- Cmder
- Node.js (v10.15.3)
- npm (6.4.1)
- javascript

Firebaseを使えるようにするところまで

Firebaseは知っていたが使ったことは無かったので、初めてのFirebaseです。

やったこと

node.jsのインストール

何も考えずデフォルトのC:\Program Files\nodejs にインストール

環境変数の編集

コンピュータ>システムの詳細設定>環境変数で、ユーザの環境変数のPATHにC:\Program Files\nodejsを追加
image.png

コマンドプロンプトでパスが通ったか確認
nodejsをインストールしたディレクトリ以外の場所で叩ければOK。
image.png

大丈夫ですね

Firebaseのツールをインストール

npmのパスが取ってれば、どこで実行してもOKです。実行場所とインストール場所は無関係です。

npm install -g firebase-tools
> @google-cloud/functions-emulator@1.0.0-beta.5 postinstall C:\Users\xxx\AppData\Roaming\npm\node_modules\firebase-tools\node_modules\@google-cloud\functions-emulator

(省略)

+ firebase-tools@6.5.0
added 556 packages from 274 contributors in 114.727s

Windowsのユーザーディレクトリの中にいろいろ保存されます。
ここまでで準備完了。

Firebaseのプロジェクト作成

ブラウザで、Firebase Console へ行き、プロジェクトの追加をする
image.png

プロジェクト名とかは適当につける。アナリティクスに地域を日本に、ロケーションをasia-northeast1(東京)に変更しておきました。なんとなく

Firebaseにアップロードする作業場所を準備

まず、ローカルフォルダを作る。
Windows上に、C:\firebase_sample フォルダを作成。空っぽのまま

次に、コマンドプロンプトで、C:\firebase_sample に行き、firebase init を実行
いくつか質問を聞かれるので、それっぽく答える。

cd C:\firebase_sample
firebase init

Are you ready to proceed? 
YES

Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices
Hostingを選択

Project Setup
先ほどブラウザで作っておいたプロジェクトが出てくるので、選択

What do you want to use as your public directory? 
public

Configure as a single-page app (rewrite all urls to /index.html)?
No

終わるとこんな感じになるはずです。
image.png

ファイルを作ってFirebaseにアップロード

静的なファイルをアップロードする環境が整ったので、アップロードしてみます。
過去に作った単体で動くブックマークレットを持ってきて、C:\firebase_sample\public\sample.jsとして置きました。

public/sample.js
javascript:(
    function(){
      title = document.title;
      href = document.location.href;
      canonical = get_canonical();
      console.log('ページタイトル=%s \n href=%s \n カノニカルURL=%s', title, href, canonical);
      alert('ページタイトル:'+title +"\n現在のURL:"+href + "\nカノニカルURL:" + canonical);

      function get_canonical(){
        links = document.getElementsByTagName("link");
        for ( i in links) {
            if (links[i].rel) {
                if (links[i].rel.toLowerCase() == "canonical") {
                    return links[i].href;
                }
            }
        }
        return "";
      }
    }
  )();

ファイルをアップロードするには、deployコマンドです。

C:\firebase_sample
λ firebase deploy

=== Deploying to 'bookmarklet-25d13'...

i  deploying hosting
i  hosting[bookmarklet-25d13]: beginning deploy...
i  hosting[bookmarklet-25d13]: found 3 files in public
+  hosting[bookmarklet-25d13]: file upload complete
i  hosting[bookmarklet-25d13]: finalizing version...
+  hosting[bookmarklet-25d13]: version finalized
i  hosting[bookmarklet-25d13]: releasing new version...
+  hosting[bookmarklet-25d13]: release complete

+  Deploy complete!

Project Console: https://console.firebase.google.com/project/bookmarklet-25d13/overview
Hosting URL: https://bookmarklet-25d13.firebaseapp.com

終わった。ブラウザでアクセスしてみる。
ファイルのURLは、https://bookmarklet-25d13.firebaseapp.com/sample.js になります。

ちゃんと取れますね。
次は、ブックマークレット経由でsample.jsを取ります。

javascript:(function(d,j,s){s=d.createElement('script');s.src=j;d.body.appendChild(s);})(document,'https://bookmarklet-25d13.firebaseapp.com/sample.js')

当然ながらちゃんと動きました。
ブラウザのブックマークにコードを埋め込まない場合は、closure compilerは不要ですね。そのかわり、ブックマークレットを起動するためにインターネット接続が必要になります。

ここまでなら、作ったブックマークレットのJSファイルをどこに置くかの問題の解決だけです。無理してFirebaseにしなくても、herokuでもCDNのcloudinaryでも同じです。せっかくFirebaseを使うので、ブックマークレットからFirestoreと連携させてみます。

FireStoreとつないでみる

ブックマークレットのコードからFirestoreに繋げてみます。作るものは、楽天市場の商品検索結果に表示されている商品の情報を刈り取り、Firestoreに放り込むサンプルです。誰トクな感じです。スクレイピングでいいだろ、というのは置いときます。

実行すると、こんな感じで商品の名前、売っている店の名前、各種IDが保存されます。
image.png

Firestoreを使う前準備

Firebase ConsoleのDatabaseへ行きFirestoreを有効にします。Realtime Databaseではないので間違いないように。
Firestore内にrakutenという名前のコレクションを作っておきます。

ブックマークレットのコード

楽天の検索結果ページで実行すると、表示されている商品情報がFirestoreにぶっこまれるブックマークレットです。2019年3月のマークアップに従っていますので、HTMLの構造が変わると動かなくなります。

public/item-to-firestore.js
javascript:(function(f){

    loadFirebase();

    function loadFirebase(){
        console.log('start loading firebase.js');
        s=document.createElement('script');
        s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase.js';
        s.onload=loadFirebaseApp;
        document.body.appendChild(s);
    }
    function loadFirebaseApp(s){
        console.log('start loading app.js');
        s=document.createElement('script');
        s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase-app.js';
        s.onload=loadFireStore;
        document.body.appendChild(s);
    }
    function loadFireStore(s){
        console.log('start loading firebase.js');
        s=document.createElement('script');
        s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase-firestore.js';
        s.onload=loadJquery;
        document.body.appendChild(s);
    }
    function loadJquery(s){
        console.log('start loading jquery');
        s=document.createElement('script');
        s.src='https://code.jquery.com/jquery-3.3.1.slim.min.js';
        s.onload=function(){f(jQuery.noConflict(true));};
        document.body.appendChild(s);
    }
})(
    function($){

        var config = {
            apiKey: "xxx", //change your ID
            authDomain: "bookmarklet-25d13.firebaseapp.com",
            databaseURL: "https://bookmarklet-25d13.firebaseio.com",
            projectId: "bookmarklet-25d13",
            storageBucket: "bookmarklet-25d13.appspot.com",
            messagingSenderId: "000"  //change your ID
        };
        firebase.initializeApp(config);

        var db = firebase.firestore();
        const settings = { timestampsInSnapshots: true };
        db.settings(settings);

        var config=null;

        db.collection("configs").doc("rakuten").get()
        .then((doc)=>{
            config = doc.data();
            console.log(config);
        }).then(()=>{
            showForm();
        });

        console.log('END');


        function showForm(){
            form = 
            '<div>'+
            '<button id="_getitem">情報取得</button>'
            '</div>';
            $('body').append(form);

            $('#_getitem').on('click', function(){
                db.collection("configs").doc("rakuten").get()
                .then((doc)=>{
                    config = doc.data();
                }).then(()=>{
                    list = getItem();
                    console.log(config.max_item, list.length);

                    //ひとつずつ保存
                    for(var i=0; i<config.max_item && i<list.length; i++){
                        console.log(i);
                        saveItemToFirestore(list[i]);
                    }

                    //まとめて保存
                    saveItemsToFirestore(list);
                });

            });
        }
        function getItem(){
            itemlist =[];
            divs = $('div.dui-item.searchresultitem').each(function(i,e){
                item={};
                item.i = i;
                item.id = $(e).data("id");
                item.sid = $(e).data("shop-id");
                item.item_name = $(e).find("div.content div.description.title h2 a").first().attr('title');
                item.shop_name = $(e).find("div.content div.description.merchant a").first().text();
                item.price = $('div.content.description.price span').first().text();
                console.log(item);
                itemlist.push(item);
            });
            return itemlist;
        }

        function saveItemToFirestore(item){
            db.collection('rakuten').add(item).then(function(docRef) {
                console.log("Document written with ID: ", docRef.id);
            });
        }

        function saveItemsToFirestore(itemlist){
            var batch = db.batch();
            for(i=0; i<config.max_item && i<itemlist.length; i++){
                docid = itemlist[i].sid + '-' + itemlist[i].id;
                console.log('(%s) > %s', i, docid);
                var ref = db.collection("rakuten").doc(docid);
                batch.set(ref, itemlist[i]);
            }
            batch.commit().then(function () {
                console.log('success');
            });
        }
    }
)

コードの解説

SDK読み込み部分

firebase.js, firebase-app.js, firebase-firestore.jsの順に読み込む必要があるようで、野暮ったいコードになってます。やっていることは、loadFirebase()で作ったscriptタグのonloadにloadFirebaseApp()を仕掛け、loadFirebaseApp()で作られるscriptタグのonloadにloadFirestore()関数を仕掛けているだけです。
最後にjQueryをロードしています。これはfirebaseやfirestoreと全く関係ありません。

firebaseの初期化部分


var config = {
    apiKey: "xxx",
    authDomain: "bookmarklet-25d13.firebaseapp.com",
    databaseURL: "https://bookmarklet-25d13.firebaseio.com",
    projectId: "bookmarklet-25d13",
    storageBucket: "bookmarklet-25d13.appspot.com",
    messagingSenderId: "000"
};
firebase.initializeApp(config);

var db = firebase.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);

firebaseのwebアプリケーションを作るときと同じようなコードです。自分のfirestoreに繋ぐためのSDKのコードは、コンソールの「アプリを追加」から左から3番目のボタンをクリックして入手します。configデータをfirebase consoleから貼り付けるだけです。TIMESTAMP系のエラーが出るのでsettingを1つ足しています。

image.png

image.png

firebaseからデータの読み込みをする部分

var config=null;

db.collection("configs").doc("rakuten").get()
.then((doc)=> {
      config = doc.data();
      console.log(config);
}).then(()=>{
      showForm();
});

Firestoreからデータを読み込みしてみます。
ブックマークレットだからといって特別なことはなく、普通のfirebaseのWebアプリケーションと同じ仕組みで行います。今回は、検索結果のうち何件を処理するかをfirebase側に定義をし、その値に従ってスクリプトが動作を変えるようにしてあります。このような仕組みを入れておくとブックマークレット内に定数が入らず、コードの修正をしないで動きを変えることができるようになります。

firebaseに置いてあるconfigの設定内容
image.png

データ取得が終わったら、showForm()を実行します。

formを作る処理

function showForm(){
    form = 
    '<div>'+
    '<button id="_getitem">情報取得</button>'
    '</div>';
    $('body').append(form);

    $('#_getitem').on('click', function(){
        db.collection("configs").doc("rakuten").get()
        .then((doc)=>{
            config = doc.data();
        }).then(()=>{
            list = getItem();
            console.log(config.max_item, list.length);

            //ひとつずつ保存
            for(var i=0; i<config.max_item && i<list.length; i++){
                console.log(i);
                saveItemToFirestore(list[i]);
            }

            //まとめて保存
            saveItemsToFirestore(list);
        });

    });
}

jQueryを使ってボタンを1つ生成しています。作ったボタンにイベントハンドラを仕掛けておきます。
ボタンを押したときにやることは、
1. firebaseのconfigの最新値を読み直す
2. getItem()で画面のHTMLから情報を抜きだす
3. saveItemToFirestore()で保存する

クリックイベントには骨格だけを定義してあります。具体的な処理は、以降の関数に書いてあります。

画面からデータを抜き出す部分

jQueryを駆使して取り出しています。firestoreに入れやすいように、item={};に格納しておきます。

consoleに出力するとこんな感じです。
image.png

firestoreに放り込む部分

function saveItemToFirestore(item){
    db.collection('rakuten').add(item).then(function(docRef) {
        console.log("Document written with ID: ", docRef.id);
    });
}

これもチュートリアルレベルの使い方です。collectionに対してadd()するだけです。javascriptのオブジェクト形式(連想配列形式)でデータをaddに渡せはOKです。firebaseのSDKのおかげでデータの保存は2行で済みます。楽ですね。document(collectionの下の要素)を指定しないで放り込んでいるので、ドキュメントIDは登録時にfirestore側に適当に振られます。

firestoreに放り込む部分(バッチ)

function saveItemsToFirestore(itemlist){
    var batch = db.batch();
    for(i=0; i<config.max_item && i<itemlist.length; i++){
        docid = itemlist[i].sid + '-' + itemlist[i].id;
        console.log('(%s) > %s', i, docid);
        var ref = db.collection("rakuten").doc(docid);
        batch.set(ref, itemlist[i]);
    }
    batch.commit().then(function () {
        console.log('success');
    });
}

こっちは複数のItemを同時に保存する方法です。firestoreのインスタンスからbatchというインスタンスを作って、そこにデータを預けておいて最後にまとめてcommit()する使い方です。HTTP/Sの接続数が少ないので効率がよいです。単一の保存と違いdocument idを定義して保存するようにしています。

実行してみる

firebase consoleでコレクションを削除しておきます。コレクションが無くても、外部から保存すると自動的に作られます。

javascriptのコードをdeployして、ブラウザバーにコードを入れてみます。
カリカリ動いて・・・

image.png

こんな感じでデータが保存されています。とっても簡単。

まとめ

今回はブックマークレットからFirestoreを扱ってみました。
今までもブックマークレットからデータを保存する処理はやっていたのですが、PHPでRESTなAPIを作ってAPI経由でデータを保存していました。それに比べたらAPIを作る手間もなく、SDKが提供されているので保存する処理も簡単にできました。
ブックマークレットとFirebaseを組み合わせるというダレトクな記事ですが、何かのヒントになることを期待しましょう。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6