facebook からダウンロードした自分のデータを MongoDB に突っ込んでみるテスト

facebook からダウンロードした自分のデータを MongoDB に突っ込んでみるテスト:


この記事はなに

facebook から自分の投稿などのデータを一括ダウンロードできる機能がいつのまにかアップグレードしていて、(1) 期間を指定して (2) JSON 形式でも、ダウンロードできるようになってました (以前は全期間対象に html 形式でしかダウンロードできなかった)。

で、意気揚々とダウンロードしたのはいいものの、マルチバイトコードのエンコーディングに悩まされました。ググってもよくわからず、どうにかこうにか自力で処理して MongoDB に突っ込むことができたので、それを記録したものです。

「こうやれば一発解決なのに」などの情報をいただければ幸い、という記事です。

あとは、未来の自分のためのメモとして。


実際のコード

npmmongodb パッケージを使ったよ。

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" } 
のようになっていました。文字列は「Windows XP の仕事」を表しています。

最初は無邪気にこのまま MongoDB に突っ込んだのですが、文字化けしました。

JavaScript での Unicode エスケープ表現は、UTF-16 を「\uXXXX」で表したものなので、U+306E である「の」は「\u306e」という表現になるはずです。ですので、期待されるのは次のような文字列です。

{ "description" : "Windows XP \u306e\u4ed5\u4e8b" } 
JavaScript で先の JSON をそのまま読み込むと、「の」に相当する「\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 
UTF-8 では、次の 3 バイトになります。

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 } } 

コメント

このブログの人気の投稿

投稿時間:2021-06-17 22:08:45 RSSフィード2021-06-17 22:00 分まとめ(2089件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)