少し前に「Node.js+Express+EJSとWordPress REST APIでViewを再構築する実験」と「WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する」を掲載したので、今回はこれらの複合技。昨今、キュレーションサイトが問題になっているが、それどころではなく、複数のWordPressサイトをまるまるパクリ(笑)、一本化するサイトの構築方法となる。
この時肝になるのが、”APIを使って複数サイトのデータをMongoDBへ保存する方法”と、”post、categories、tagsのIDが被る可能性があるので、それを回避する方法”だ。まずPart1では前者から説明したい。
サイトのソースは、自分のブログと、許可を得て、http://techtalk.pcmatic.jpのブログ、この2つを使っているが(画面キャプチャ上のタイトルがtechtalk.pcmatic.jp、下のタイトルがblog.iwh12.jp)、ロジック上はいくつでも対応可能となっている。
単一サイトからAPIを使いMongoDBにpost、categories、tagsのcollectionを保存するのはもう説明済だが、複数サイトを対象とした非同期での書き込みは、全て完全に終了したのを知るにはいろいろ面倒なことになる。
そこで今回はasync(npm install async)モジュールを使うことにした。使い方は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
var async = require('async'); async.series([ //site 1 function (callback) { SetCollection(TAGS, base_url[0], '/wp-json/wp/v2/tags', true, function() { callback(null,null); }); }, function (callback) { SetCollection(CATEGORIES, base_url[0], '/wp-json/wp/v2/categories', true, function() { callback(null,null); }); }, function (callback) { SetCollection(POSTS, base_url[0], '/wp-json/wp/v2/posts', true, function() { callback(null,null); }); }, //site 2(tagsは無い) function (callback) { SetCollection(CATEGORIES, base_url[1], '/wp-json/wp/v2/categories', false, function() { callback(null,null); }); }, function (callback) { SetCollection(POSTS, base_url[1], '/wp-json/wp/v2/posts', false, function() { callback(null,null); }); } ], function (err, results) { if (err) { throw err; } console.log('Series all done.'); // response send res.send('DB Reloaded.'); }); |
上から順にAPIが実行され、callbackが呼ばれるまでは次の処理に進まず、見かけ上、同期で作動する。
SetCollectionの4番目の引数はcollectionをdropするかどうか。以前説明したように、DBは差分ではなく、全件保存しなおしているため、2つめ以降のサイトはdropしないようにする。
そしてAPIを処理する側は、http.getのend部分を一部書き換える。追加したのはcallback()。これで一つのAPI呼出しとDBへの保存が終わったことを先のcallbackで知らせ、次の処理へ進むことが出来る。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
json.on('end', () => { if (max == 0) max = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-TotalPages')) + 1]; var d = JSON.parse(body); db.collection(collection).insertMany(d).then(function(err, r) { if (max == n + 1) { db.close(); callback(); // add return; } getFromAPI(n + 1); }); }); |
以上で理論上はOKなのだが、実際はAPIを使ったデータの取得が(場合によっては)分単位になるため、DB書き換え中にサイトをアクセスすると、対象となるデータが不完全でエラーとなる。
これを回避するには、同じ構造の同期用DBと表示用DB、2つ持ち、APIでの同期が済み次第、表示用DBへ同期用DBをまるごとコピーするのが手っ取り早い。コードは以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// db.copyDatabase("wp0","wp") mongoClient.connect(mongodb_org, function(err, db) { if (err) { console.log(err); } else { var mongoCommand = { copydb: 1, fromhost: "localhost", fromdb: "wp0", todb: "wp" }; var admin = db.admin(); admin.command(mongoCommand, function(commandErr, data) { if (!commandErr) { console.log(data); } else { console.log(commandErr.errmsg); } db.close(); }); } }); |
これはmongoコンソールで、db.copyDatabase(“wp0″,”wp”)とするのと同じ動きとなる。先のAPIでの読み込みをwp0へ行った後、console.log(‘Series all done.’);の後ろにあるこのプログラムでwpへまるまるコピーする。これなら瞬時なので、表示側に影響が出ることは(完全に無いとは言い切れないものの)無いだろう。
但し、表示側(ここではwp)のcollection 3つを先にdropしてからコピーする必要がある。
作動中のサイトはこちら。サイドバーのウィジェットは全てExpressでルーティングしたAPIを使って描画している(Part2で説明予定)。