a4lg.com

#njslyr はっくつのためのツイート取得メモ (2014-08-15...2014-10-03)

Twitter 上で連載されている翻訳小説 ニンジャスレイヤー は、徐々に Web を侵食していると言っても過言ではありません。このコンテンツ、そして過去ログをどうやってはっくつし、データベース化するかですが、どうやら複数の方法を併用するのが良さそうです。

小説の話に戻りますが、コンテンツの中身を一切無視したとしても公式の本文ツイートが 5 万近くに及ぶ大作であり、また公式・準公式・ファン向けハッシュタグへの投稿人数を数えるだけで 1 万人以上に波及しています (ここには、単なる RT や "忍殺語; コトダマ" の普及、また ROM 人口を入れていません。となると実際 サンオクニン に近い)。しかし、これらの過去ログの発掘は難しいものです。Twitter の標準検索 API (Web で触れるものではなく search/tweets REST API による) で遡れるのはせいぜい一週間が限度。それ以前の発掘は、別の手段に頼らざるを得ません。

未来の、というより現在のツイートを取得する方法はありますが、これも手法によって良し悪しがあります。このメモでは複数の手段を提案するだけしてみます (一応、全部 "使える" レベルであることまでは確かめています)。

なお、スクレイピングについてはサーバーにやさしいよう気をつけるのは当然として、詳細についてはこのページでは述べません。開発者モードでリクエスト・レスポンスを眺めながら形式が分かるくらいのスキルレベルを想定しています。

主要な更新履歴

2014-08-15
最初のバージョン
2014-08-20...2014-08-21
ハッシュタグ欠けの原因と対処法の提案について追記
2014-09-29
  • Ninjatter のために収集対象を強化した ID リストを公開
  • 2014-08-20...2014-08-21 に追記したハッシュタグの同一視のやり方について新たな情報を含めて再編集
2014-10-03
Togetter の仕様変更に合わせた変更 (続きを読むボタンが全ページではなく最初のページのみに)

目次

はじめに : 注意

それが本当に必要かどうか、よく考えてください。サービスに継続的な負荷をかけかねない行為なので、やらない方が良いし、やるなら人数が少ない方が良いはずです。というか、やらなくても済むよう、ノイズが多い ID リストで良いならダウンロードできるようにしてあります

また Twitter から取得したコンテンツは、(著作者から別途許諾が無い限り) API の下でのみ利用が許可されている点に十分注意してください。

はじめに : Twitter bot を作る

これから紹介するどの手法を使うときも、収集したツイート URL からツイートのメタデータを抜き出すためには Twitter bot か何かのプログラムが必要になります。

特に Twitter ライブラリについては statuses/lookup に対応したものを使用することを推奨します。というのも、この API は 1 API 呼び出しで 100 ツイートの情報が拾え、また 180/15min (720/1h) という制限によって、単一ユーザーですら 1 時間当たり最大 72,000 ツイートを取得することができます。この速度は 1 時間以内にニンジャスレイヤーの本文すべてを収集し、また 1 日でニンジャスレイヤー関連ツイートのほとんどを拾えるレベルです。

私の環境では Arch LinuxCentOSMono 環境で動作する CoreTweet ライブラリを使用して、ツイートの取得と SQLite データベースへのストアを行っています。

これらの bot に食わせるのは、単一ツイートの status/ に続く番号のみで構いません。何故なら最後の番号のみで十分ツイートを識別することができるためです (また Twitter はアカウント名変更等によって生じた齟齬をリダイレクトによって解決します)。

アーカイブ : Twilog のスクレイピング

Twilog は、登録したユーザー (自身) のツイートを保存するサービスです。Twilog によって保存されたツイートは良いアーカイブとなります。特に公式アカウントの幾つかは Twilog に登録しており、過去 3,200 ツイート以上前のツイートを遡る場合にはこれが一番です。

これのスクレイピングは最も簡単です。wget 等のプログラムとシェルスクリプトだけでも十分に組み上げることができます。

アーカイブ : Togetter のスクレイピング

Togetter はアーカイブされたツイートの宝庫です。ツイートは有志によって整理され、必要な情報だけを取得することができます。一方で、今は存在しない、または非公開になっているツイートも含まれているなどの問題があります。

とはいえ、使わない手はありません。……と思いきや、最初から壁にぶち当たります。というのも、ログインしていない状態では "続きを読む" ボタンが毎最初のページに表示されており、しかもこのボタンは JavaScript で続きを実際に読み込む動作を行っているのです。

スクレイピングでこの問題を解決するには、Cookie に対応した Web クロールライブラリを使用しなければなりません。詳しくは述べません (というか開発者モードでリクエストを注意深く観察すれば分かります) が、ページ毎に CSRF トークンを取得し、必要ならこれを用いて "続きを読む" に相当する内容を読み取りましょう。ついでにページ数情報は両 HTML に含まれるため、これらの情報を使って最後のページまで読み取りましょう。

ニンジャスレイヤーの過去ログをサーバーにやさしい頻度でスクレイピングする場合、おそらく 1 週間強かかるでしょう。

アーカイブ : Topsy のスクレイピング

Topsy は過去の多くのツイートの宝庫です。そのため、私にとって重要な情報ソースとなっています。手動での検索方法は補助アプリ "NJRecalls" の作者である rnatori 氏がまとめていますが、より完全なログを取得したい場合、やはりスクレイピングに頼ることになります。

スクレイピングにおそらく Cookie は必要無いはずですが、ブラウザに類似したリクエストを出すために Cookie を保持するライブラリを使用しています。JavaScript リクエストは複雑に見えますが、一部は UNIX タイムから生成されており、また一部は jQuery ライブラリの自動生成部分です。この辺りは無視して、過去から順番にツイートを拾う重要なポイントを見てみましょう。

  • 古いツイートから新しいツイートへ : sort_method=-date
  • 最古のツイートを知りたい場合 (mintime を使わない場合) : window=a (すべての期間で検索する指定)
  • 特定時間からのツイートを知りたい場合 : mintime=1234567890 (取得したい最小の UNIX タイムを指定; maxtime は必要無い)
  • リクエスト数を最小にして Topsy への負荷下げと取得速度の向上 (10 倍) を成し遂げる : offset=0 と perpage=100

このように取得した JSONP または JSON (リクエストを少し変えることで標準の JSONP 以外にプレーン JSON を取得できます) の中にある firstpost_date がポストの日時であり、連続取得の要となります。また trackback_permalink がツイート URL への参照となっています。

この手法、Topsy の Otter API (現在新規登録を停止) を使える方なら多分公式に近い手法なんですが、実は登録していなくても検索ウィンドウ向けの API キーが存在しているため、制限としてはあまり意味が無さそうです。もしかすると Otter API の API キーを持っていても API 廃止などの都合上非公式な方法を使わざるを得ないかもしれません。

サーバーへの負荷を避けるために、15 秒を下回る間隔でリクエストを投げることは絶対に避けてください。おそらく関連ハッシュタグすべてを回収する場合、最悪 2~3 週間かかることを覚悟してください。

これも rnatori 氏が触れていますが、Twitter Web Search の演算子を使った検索は、Twitter REST API のものとは異なる結果を出します。特に、1 週間以上前のツイートを取得できることがアーカイブ目的には最適です。

ただし、検索結果で表示されるツイートはおおよそ 400 程度が最高のようです。いくらツイートが多い日でも最大でこれくらいしか取得できないのです。それでも、Topsy で収録されていないツイートが 4 桁のオーダー存在するようで、補完のためには使い物になると考えて良いでしょう。確か取得用のコードは Web のどっか (多分 GitHub) に転がっていたはずです。

集める : User Stream / Filter Stream を使う

Twitter の Streaming API は強力です。リアルタイムでツイートが流れてくるだけではなく、track パラメータを設定することで、特定ハッシュタグを持つほぼ全てのツイートを取得することができます。詳細は Twitter API のドキュメントを見て学んでください。

ちょっとした欠点は、Streaming API におけるハッシュタグ検索の仕様が Twitter の検索 API と異なるということです。例えば全角ハッシュタグ記号が使われていたり、njslyr の文字が全角 (njslyr) だったとしても通常の Twitter API は検索可能になっていますが、Streaming API ではリアルタイムの追跡対象にならないのです。そのため、Streaming API のみによって同一ハッシュタグとして認識するツイート全てを取得するのは極めて困難です。

一方ではそのようなツイートは非常に少ない (私調べでは 0.5% 未満) ようで、取らないという選択肢も考えられなくはありません。

おそらくリアルタイム投稿を追跡するには、(検索総量の問題を除けば) 一番古典的な手段でしょう。ある程度の遅延を以って配信されるようなツイートは観察した限りでは 1~5 分でおおよそ引っ掛かるようになります。ただし Twitter API の検索はある程度のスパム対策が施されているらしく、実際のスパム以外にも特定パターンのツイートを取得できない問題があります。とはいえ、過去数日、数にして 30,000 ツイート以上を検索結果から抽出できるのは魅力ではあります。

このどちらの API にも言えることですが、API の調子によって検索の結果が変わることは考えられます。特に Search API の場合、使用できる API リクエスト数の枠を考えて検索頻度を増やすというのも、選択肢としては考えられるでしょう。

処理する : ハッシュタグの取り扱い

ハッシュタグと認識される範囲と、同一視される範囲に注意しましょう。この点に気をつけないと、ツイートの取りこぼしやそれによるデータベースからの誤った削除につながる可能性があります。また個別ツイートを取得すると Twitter からエンティティと呼ばれる補助情報が取得できますが、ここのハッシュタグ欄に入っているのはツイート本文通りの文字列で、検索 API が同一視するものが異なる表現となることがあります。

  • ハッシュタグは、大文字と小文字が同一視されます。 (検索において同一、エンティティと本文において相違: #njslyr と #NJSLYR)
  • ハッシュタグは、全角と半角が同一視されます。 (検索において同一、エンティティと本文において相違: #njslyr と #njslyr [後者はハッシュタグ記号以外全角])
  • ハッシュタグ記号 (シャープ) は、全角と半角の両方が許容されます。 (検索とエンティティにおいて同一、本文で相違: #njslyr と #njslyr [後者はハッシュタグ記号が全角])

ハッシュタグとなる条件は、ハッシュタグ記号の前の文字にも依存しますが、ここで詳しくは解説しません。Twilog で使用されているルールは実際のハッシュタグ検知ルーチンと相似するようですが、同一である保証はありません。一番の保証は Twitter API を用いてツイートのエンティティを取得し、その中にハッシュタグとして求める文字列が入っているか否かを確認することになるでしょう。

エピローグ (to be continued)

私はこうして構築されたデータベースを使って、Togetter に依存しない忍殺過去ログ読書の補助アプリを作ろうとしていますが、ざっと作るだけなら他の人の方が早いと予測しています。まぁ、自分はゆっくりとやるので、データだけ使うなら自由に使ってやってください。

ダウンロード

前述の手法を全て使い、主要な関連タグと公式ハッシュタグ、公式アカウントすべてのツイート ID を収集した結果 (2014-09-29 06:00 JST くらいまで) があります。このリストは ID のみですので、別途実ツイートを取得する必要があります (というより、ツイートの本文等を配信するのは Twitter API の利用規約に反します)。

2014-09-29 版から、Ninjatter の仕様に合わせてツイート収集対象のハッシュタグを増やしました。また現在の主要な収集対象を明記するようにしました (ここに明記されていない収集対象もあるにはあるのですが、それらについては収集の一貫性に欠けていることをお伝えしておきます)。

収集対象 (アカウント)

  • ID 166644783 (NJSLYR)
  • ID 510182439 (njslyr_r)
  • ID 551062834 (現 diehardtales [旧: the_v_njslyr 等])
  • ID 602555550 (the_v_njslyr)
  • ID 438143025 (motor_chibi)
  • ID 575065055 (njslyr_guide)
  • ID 1747167120 (art_of_njslyr)
  • ID 1229454721 (njslyr_ukiyoe)
  • ID 219566976 (shonen_sirius)
  • ID 224153322 (comibi)

収集対象 (ハッシュタグ)

  • #njslyr
  • #njslyr7d
  • #njslyr7k
  • #njslyr7book
  • #njslyr7audio
  • #njslyr7m
  • #njslyr7s
  • #njslyr7r
  • #ニンジャスレイヤー
  • #ウキヨエ
  • #dhtls
  • #diehardtales
  • #ちょっとやめないか (2014-09-29 版で追加)

アーカイブについて

ここからダウンロードできるのは .tar.xz アーカイブで、tweets.list と retweets.list に分かれています。tweets.list はリツイートでないツイート一覧、retweets.list はリツイート一覧です (個人的には収集した中のデータのリツイートは無駄が多いので、圧縮したり別形式でストアすることを推奨します)。またデータについてですが、次の点に注意してください。

  • ノイズが含まれる (様々なキーワードを試している結果、全く無関係のツイートも含まれます。ハッシュタグや user ID で抽出してください)
  • 削除されたツイートや、非公開ツイートがあるかもしれない (全てのツイートがアクセス可能だとは限らない)
  • 仮に ID に対応するツイートが無かったとしても、クロールし直すと現れることがある (アカウントが非公開→公開になったときなど)

本リスト (njmemory-tweets-201409290600.tar.xz) は CC0 1.0 Universal に基づき権利放棄を行います。元々ツイートにある本文等の著作権で保護された内容を含まないため、もし著作権または関連の知的財産権があるとすれば筆者 (大居 司; Tsukasa OI) のデータベース権 (sui generis database rights) のみです。ですが筆者は本リスト利用促進のため、これらの権利をも実質的に放棄します。