ニシキヘビってかわいいよね、実際みたことないけど。

無職がいよかん国でプログラミングとかの備忘録を書いてます。 一日一食たまごかけごはん。

MongoDB コレクションごとの圧縮方法を変更しなおす

某動画サイトの動画情報を定期的に集める簡単なWebクローラーを作って、 クローリングしたWebページのスクラップ前(ダウンロードしたままのXMLとかHTMLとか)データをそのままと、 情報をスクラップしてjsonにしたものをMongoDBに保存している。

2回目のクロール終了時の容量がこれ

> show dbs
...
crawl_data  0.625GB
...

このままではあっという間にディスク容量を使い切る。 ギリギリまで削りたいなら、クロール直後で未加工のデータはファイルシステムに保存して、スクラップした後tar.xz圧縮でもいいのだが、 MongoDBにそのまま保存していれば、すこしスクラップ内容を変えた時にもさっと呼び出せたり、データの保存先の統一ができたりで楽。

本ケースではsnappyにる圧縮/伸長速度の速さは過剰であるためと、 ディスク容量を富豪的に使えない貧乏性の自身のため、zlib圧縮に切り替える。

まずは全データのダンプ、適当なディレクトリに移動して

mongodump -d crawl_data

続いて、クロールデータを保存しているDBの削除。端末からmongoコマンドでCUIを起動して、利用データベース変更後、db.dropDatabase()を実行する。

> use crawl_data
switched to db crawl_data
> db.dropDatabase()
{ "dropped" : "crawl_data", "ok" : 1 }

次に、圧縮形式を指定してCollectionを作成する。storageEngine.wiredTiger.configStringの値に、 WiredTigerに与えるオプションを<name>=<value>の形式で渡す。

他にもMongoDBが対応しているオプションならいろいろ渡せる模様。 詳細はWiredTiger: WT_SESSION Struct Reference。 lz4やzstdという、snappyやzlibの上位互換のような選択肢もある模様。

> use crawl_data
switched to db crawl_data
> db.createCollection('raw', {storageEngine: {wiredTiger: {configString: 'block_compressor=zlib'}}})
{ "ok" : 1 }

ちゃんと作れているか確認

> db.printCollectionStats()
raw
{
    "ns" : "crawl_data.raw",
    "size" : 0,
    "count" : 0,
    "storageSize" : 4096,
    "capped" : false,
    "wiredTiger" : {
        "metadata" : {
            "formatVersion" : 1
        },
        "creationString" : "access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=1),block_allocation=best,block_compressor=zlib,cache_resident=false,check
# 以下略

いろいろ出力されるが、wiredTiger.metadata.creationStringの値に反映されていればOK。 今回であれば、block_compressor=zlibになっている。

他のコレクションでも作成と確認を同様に行う。

できたらダンプしたデータをレストアする。mongodumpを実行したディレクトリに移動して

mongorestore -d crawl_data ./dump/crawl_data/

容量はどのくらい減ったかな

>show dbs
...
crawl_data  0.367GB
...

半分ぐらい? ごっそり減ってる!

お疲れ様でしてー

TornadoのApplicationRouterで例外が出たら

tornado.testingでの単体テスト中、ドツボにハマったのでメモ。

Failure
Traceback (most recent call last):
  """前略"""   line 62, in create_app
    app = Application(handlers, debug=debug)
  File "/home/nyanmao/python35_vir_env/lib/python3.5/site-packages/tornado/web.py", line 1910, in __init__
    self.wildcard_router = _ApplicationRouter(self, handlers)
  File "/home/nyanmao/python35_vir_env/lib/python3.5/site-packages/tornado/web.py", line 1785, in __init__
    super(_ApplicationRouter, self).__init__(rules)
  File "/home/nyanmao/python35_vir_env/lib/python3.5/site-packages/tornado/routing.py", line 363, in __init__
    super(ReversibleRuleRouter, self).__init__(rules)
  File "/home/nyanmao/python35_vir_env/lib/python3.5/site-packages/tornado/routing.py", line 288, in __init__
    self.add_rules(rules)
  File "/home/nyanmao/python35_vir_env/lib/python3.5/site-packages/tornado/routing.py", line 298, in add_rules
    assert len(rule) in (2, 3, 4)
AssertionError

_ApplicationRouterコンストラクタ呼び出しがトレースバック途中にあり、assert len(rule) in (2, 3, 4)の例外によって動作が止まったら、 Applicationコンストラクタに渡している引数handlerが正しいか確認する。

自分は、ルーティング先の次にRequestHandlerを継承したクラスを格納することを忘れていた。

from tornado.web import Application, RequestHandler

class MyRequestHandler(RequestHandler):

# 中略

app = Application([
    (
        r'/api/v0/spam', 
        MyRequestHandler,  # これ
    )
])

なぜドツボにはまったのかというと、ResourceWarning:でFileやsocketが閉じられてない、ないしは使い尽くしているともでるので、そっちの線をずっと探っていたから。 ああ、情けない。

Google翻訳の翻訳元テキストボックスにコピペした時改行を整形するブックマークレット

書籍や論文などが書かれたPDFから英文をコピペすると、文中に存在するPDFの改行もコピペされるので、 Google翻訳が改行位置を文の区切りと判断し、妙な結果がでることがある。

手直ししてるとめんどくさいので、自動で修正を行うブックマークレットを作ってみた。

javascript:(function(){var textarea_source = document.getElementById('source'); textarea_source.addEventListener('paste', function(){setTimeout(function(){var text = textarea_source.value; var new_text = text.replace(/[\n\r]+/g, ' ').replace(/(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?|!)\s/g, '\n\n'); textarea_source.value = new_text;}, 1000)})}());

ブックマークレット起動後、Google翻訳の翻訳元テキストボックスに英文をペーストして1秒経つと、全改行を空白で置換後、文区切りとなるピリオドっぽいところに改行を2つ挿入する。 待機秒数を早めたい場合は、1000のところを各環境に応じて調節する。

Chromium 62.0.3202.75(Official Build)Built on Ubuntu で動作確認。
正規表現の後戻りを使うので、1年半くらい前から更新していないChromeだと動かないです。

正規表現Python - RegEx for splitting text into sentences (sentence-tokenizing) - Stack Overflowを使わせていただきました。

他に文末扱いしたい記号があれば、最後の肯定後戻りに追加すれば動くかと。 たまにへんてこなとこで切れるが、そのためだけにNLTKのPunktSentenceTokenizerを移植する気にはなれない.......