facebook からダウンロードした自分のデータを MongoDB に突っ込んでみるテスト
facebook からダウンロードした自分のデータを MongoDB に突っ込んでみるテスト:
facebook から自分の投稿などのデータを一括ダウンロードできる機能がいつのまにかアップグレードしていて、(1) 期間を指定して (2) JSON 形式でも、ダウンロードできるようになってました (以前は全期間対象に html 形式でしかダウンロードできなかった)。
で、意気揚々とダウンロードしたのはいいものの、マルチバイトコードのエンコーディングに悩まされました。ググってもよくわからず、どうにかこうにか自力で処理して MongoDB に突っ込むことができたので、それを記録したものです。
「こうやれば一発解決なのに」などの情報をいただければ幸い、という記事です。
あとは、未来の自分のためのメモとして。
facebook からダウンロードした JSON の文字列データは、「UTF-8 でエンコーディングしたもの」のではなく、『「UTF-8 でエンコーディングしたもの」を ASCII 文字で表現できるよう "\u" エスケープしたもの』になっていました。
具体的には、
のようになっていました。文字列は「Windows XP の仕事」を表しています。
最初は無邪気にこのまま MongoDB に突っ込んだのですが、文字化けしました。
JavaScript での Unicode エスケープ表現は、UTF-16 を「
JavaScript で先の JSON をそのまま読み込むと、「の」に相当する「
「の」が「
UTF-8 では、次の 3 バイトになります。
簡単な方法がないかな、とググったのですが、すぐに見つからなかったので、自分でやってみました。
のところがそれです。正規表現
あとは「16 進数 <-> 数値」変換が必要になりますが、まともにやるのがアレだったので、配列
参考までに。
この記事はなに
facebook から自分の投稿などのデータを一括ダウンロードできる機能がいつのまにかアップグレードしていて、(1) 期間を指定して (2) JSON 形式でも、ダウンロードできるようになってました (以前は全期間対象に html 形式でしかダウンロードできなかった)。で、意気揚々とダウンロードしたのはいいものの、マルチバイトコードのエンコーディングに悩まされました。ググってもよくわからず、どうにかこうにか自力で処理して MongoDB に突っ込むことができたので、それを記録したものです。
「こうやれば一発解決なのに」などの情報をいただければ幸い、という記事です。
あとは、未来の自分のためのメモとして。
実際のコード
npm
の mongodb
パッケージを使ったよ。dunk.js
const fs = require('fs') const mongodb = require('mongodb') const MongoClient = mongodb.MongoClient const client = new MongoClient('mongodb://localhost:27017') client.connect((err) => { const db = client.db('fbtk') insertDocuments(db, () => { client.close() }) }) const insertDocuments = (db, callback) => { const obj = ((s, r, u, v) => { v = ((v) => { u.forEach((c,i) => v[c] = { u: parseInt(i/4), e: (8<=i&&i<12)*(i-8), l: (i%4)*4 }) return v })({}) return JSON.parse(s.replace(r, (m, n11, n21, n22, n31, n32, offset, str) => { return '\\u' + n11 + u[v[n21].e*4+v[n22].u] + u[v[n22].l+v[n31].e] + n32})) })(require('fs').readFileSync('/var/tmp/your_posts.json', 'utf8'), /\\u00[89a-f]([\da-f])\\u00([89ab])([\da-f])\\u00([89ab])([\da-f])/g, '0123456789abcdef'.split('')) db.collection('ownpost').insertMany(obj.status_updates, (err, result) => { callback(result) }) }
問題と解決
JavaScript で読むと文字化け
facebook からダウンロードした JSON の文字列データは、「UTF-8 でエンコーディングしたもの」のではなく、『「UTF-8 でエンコーディングしたもの」を ASCII 文字で表現できるよう "\u" エスケープしたもの』になっていました。具体的には、
{ "description" : "Windows XP \u00e3\u0081\u00ae\u00e4\u00bb\u0095\u00e4\u00ba\u008b" }
最初は無邪気にこのまま MongoDB に突っ込んだのですが、文字化けしました。
JavaScript での Unicode エスケープ表現は、UTF-16 を「
\uXXXX
」で表したものなので、U+306E である「の」は「\u306e
」という表現になるはずです。ですので、期待されるのは次のような文字列です。{ "description" : "Windows XP \u306e\u4ed5\u4e8b" }
\u00e3\u0081\u00ae
」を 「U+00e3、U+0081、U+00ae」の 3 文字として解釈してしまいます。「の」が「
\u00e3\u0081\u00ae
」になっているのは、「の」を UTF-8 で表現すると「e381ae」になるから、でしょう。すなわち、2 バイト文字 (U+0800 〜 U+FFFF) の 2 進数表現が次の場合、upper byte: vvvvxxxx lower byte: yyyyzzzz
first byte: 1110vvvv second byte: 10xxxxyy third byte: 10yyzzzz
解決方法
簡単な方法がないかな、とググったのですが、すぐに見つからなかったので、自分でやってみました。JSON.parse()
するときに、2 バイト文字 (U+0800 〜 U+FFFF) の UTF-8 表現である \u
エスケープされた 3 バイトの連続が現われたら、JavaScript の Unicode エスケープ表現に変換しちゃう、という方法です。先のコードでJSON.parse(s.replace(r, (m, n11, n21, n22, n31, n32, offset, str) => { return '\\u' + n11 + u[v[n21].e*4+v[n22].u] + u[v[n22].l+v[n31].e] + n32}))
r
は、2 バイト文字のところだけにマッチするようになっています。あとは「16 進数 <-> 数値」変換が必要になりますが、まともにやるのがアレだったので、配列
u
や連想配列 v
を用意して基本的に表引きだけで済ますようにしました。配列 u
が数値から 16 進数表現文字への変換テーブル、連想配列 v
が 16 進数表現文字から対応する数値への変換テーブルになっています。参考までに。
u = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' ] v = { '0': { u: 0, e: 0, l: 0 }, '1': { u: 0, e: 0, l: 4 }, '2': { u: 0, e: 0, l: 8 }, '3': { u: 0, e: 0, l: 12 }, '4': { u: 1, e: 0, l: 0 }, '5': { u: 1, e: 0, l: 4 }, '6': { u: 1, e: 0, l: 8 }, '7': { u: 1, e: 0, l: 12 }, '8': { u: 2, e: 0, l: 0 }, '9': { u: 2, e: 1, l: 4 }, a : { u: 2, e: 2, l: 8 }, b : { u: 2, e: 3, l: 12 }, c : { u: 3, e: 0, l: 0 }, d : { u: 3, e: 0, l: 4 }, e : { u: 3, e: 0, l: 8 }, f : { u: 3, e: 0, l: 12 } }
コメント
コメントを投稿