Part1では、”APIを使って複数サイトのデータをMongoDBへ保存する方法”を説明したものの、少し腑に落ちないところがあったので該当部分を書き直した。従ってPart1.5となる(笑)そのままコピペすれば、作動するコードも掲載するので参考まで。
Part1で使用したasyncモジュールは、いろいろな作動モードがあり、async.seriesで動かした。この場合、callbackが呼ばれるまでは次の処理に進まず、見かけ上、同期作動となる。
ただ考えてみれば、collectionのdrop、各APIの読み込みは非同期で作動しても問題無く、順番待ちをしている時間が無駄になる。従って流れとしては…。
- 同期用のwp0にあるposts、categories、tagsのdropは非同期だが、全部終わるのを待つ
- 各APIの読み込みは非同期だが、全部終わるのを待つ
- 表示用のwpにあるposts、categories、tagsのdropは非同期だが、全部終わるのを待つ
- 最後にwp0をwpへコピーする
以上の順番さえ守れば、DBの更新に影響なく、加えて、各パートは非同期なので、全てをasync.seriesで行うよりも処理時間が短くなる。このようなケースにはasync.parallelを使用する。
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 31 32 33 |
var mongoClient = require('mongodb').MongoClient; var mongodb_org = 'mongodb://localhost:27017/wp0'; var mongodb_url = 'mongodb://localhost:27017/wp'; var assert = require('assert'); var async = require('async'); var POSTS = 'posts'; var CATEGORIES = 'categories'; var TAGS = 'tags'; var base_url = ['http://blog.iwh12.jp','http://techtalk.pcmatic.jp']; dbReload(); function dbReload() { // drop all collection from wp0 mongoClient.connect(mongodb_org, function(err, db) { assert.equal(null, err); async.parallel([ function (callback) { db.collection(POSTS).drop(function(err) { callback(); }); }, function (callback) { db.collection(TAGS).drop(function(err) { callback(); }); }, function (callback) { db.collection(CATEGORIES).drop(function(err) { callback(); }); } ], function (err, results) { if (err) { throw err; } db.close(); console.log('drop all collection from wp0.'); step1(); }); }); } |
上のvar群は関連するモジュールや変数の宣言で特に説明の必要は無いだろう。ポイントは、async.parallelの部分。wp0の各collectionのdropは非同期で作動し、全部終了した時に、function (err, results)を実行する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// get all API function step1() { var api = { tags:'/wp-json/wp/v2/tags', categories:'/wp-json/wp/v2/categories', posts:'/wp-json/wp/v2/posts' }; async.parallel([ //site 1 function (callback) { SetCollection(TAGS, base_url[0], api.tags, function() { callback(); }); }, function (callback) { SetCollection(CATEGORIES, base_url[0], api.categories, function() { callback(); }); }, function (callback) { SetCollection(POSTS, base_url[0], api.posts, function() { callback(); }); }, //site 2 function (callback) { SetCollection(CATEGORIES, base_url[1], api.categories, function() { callback(); }); }, function (callback) { SetCollection(POSTS, base_url[1], api.posts, function() { callback(); }); } ], function (err, results) { if (err) { throw err; } console.log('get all API done.'); step2(); }); } |
step1()は、各APIで得たデータをmongoDBに保存する部分だ。前回は順番に行っていたが、今回は5つのAPIの読み込みが非同期作動となる。全て終了後、step2()を実行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// drop all collection from wp function step2() { mongoClient.connect(mongodb_url, function(err, db) { assert.equal(null, err); async.parallel([ function (callback) { db.collection(POSTS).drop(function(err) { callback(); }); }, function (callback) { db.collection(TAGS).drop(function(err) { callback(); }); }, function (callback) { db.collection(CATEGORIES).drop(function(err) { callback(); }); } ], function (err, results) { if (err) { throw err; } db.close(); console.log('drop all collection from wp.'); step3(); }); }); } |
step2()は、wp0をwpへコピーする時、コピー先にcollectionがあると失敗するので、先に3つのcollectionをdropする。dbReload()直後と全く同じで対象がwp0かwpかの違いだけだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// db.copyDatabase("wp0","wp") function step3() { 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); console.log('DB Reloaded.'); } else { console.log(commandErr.errmsg); console.log('DB Reloaded error!'); } db.close(); }); } }); } |
step3()はwp0からwpへDBをまるまるコピーする部分となる。これで一連の流れは完了する。
最後に、APIから得たデータをmongoDBへ保存する=SetCollection()を掲載する。前回との違いは、フラグを使い中でcollectionをdropする部分を削除している(APIがhttpかhttpsかの判断も追加)。
と言うのも、非同期で作動する場合、記述した順に実行されるとは限らず、dropせずに追記してしまうケースが発生するからだ。今回はこれを避けるため、一番初めにwp0からdropしているのはご覧の通り。
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// get data from API and set collection function SetCollection(collection, base, url, callback) { var api_url = base + url + '?per_page=' + 10; var max = 0; var db; console.log('API url: ' + api_url); // connect to monogo server mongoClient.connect(mongodb_org, function(err, mongodb) { assert.equal(null, err); db = mongodb; }); // get from API getFromAPI(0); // get JSON with paginate function getFromAPI(n) { if (api_url.slice(0,5) == 'https') var http = require('https'); else var http = require('http'); http.get(api_url + '&page=' + (n + 1), (json) => { var body = ''; json.setEncoding('utf8'); json.on('data', (chunk) => { body += chunk; }); 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) { console.log(collection, 'page: ' + (n + 1) + ' (' + base +')'); if (max == n + 1) { db.close(); callback(); return; } getFromAPI(n + 1); }); }); }); } } |
以前掲載した”WordPress REST APIで得たJSONをNode.jsとMongoDBを使い保存する”に関しても、今回のコードの方がよりロジカルなので、こちらを参考にして欲しい。単一サイトの場合は、step1()のsite 2が不要となる。
Part2で説明するExpress + EJSはmongoDBにwpさえあれば作動するので、今回の部分は別扱いにして、cronを使い一定間隔でDBへリロードするのがいいかも知れない。
実験サイトはこちら。