はてなブックマークAtomAPIをOAuthで利用する方法(Java, oauth-signpost版)


より簡単な方法:scribe-java版

はてなブックマークAtomAPIの使い方を紹介します。2010年末にOAuthをサポートしたので、ここでもそれを試します。言語はJava、ライブラリはoauth-signpostを使います。

アプリケーションの登録

アプリケーション登録ページでアプリケーションを登録し、Consumer keyとConsumer secretを取得します。

ライブラリの準備

4627847327signpost-core-バージョン番号.jar とcommons-codec-バージョン番号.jarを使えるようにします(このあたりの詳細については、拙著『Webアプリケーション』などを参照してください)。

Access tokenとToken secretの取得

OAuthでユーザの権利を譲り受けるためのAccess tokenとToken secretを取得します。ここで紹介するコンソール上で行う方法の他に、ブラウザ上でリダイレクトを使う方法もありますが、両者の違いはTwitterの場合などと同じなので、Twitterでの方法を見れば、リダイレクトを使う方法も実現できるでしょう。

先に取得したConsumer keyとConsumer secretを下のコードに埋め込んでください。

import java.io.*;
import oauth.signpost.*;
import oauth.signpost.basic.*;

public class HatenaTokenCreator {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");
    //System.setProperty("https.proxyHost", "proxy.example.net");
    //System.setProperty("https.proxyPort", "3128");

    OAuthConsumer consumer = new DefaultOAuthConsumer(
            ***** Consumer key *****,
            ***** Consumer secret *****);

    String scope = "?scope=read_public,write_public,read_private,write_private";
    OAuthProvider provider = new DefaultOAuthProvider(
            "https://www.hatena.com/oauth/initiate" + scope,
            "https://www.hatena.com/oauth/token",
            "https://www.hatena.ne.jp/oauth/authorize");

    String authUrl = provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND);
    System.out.println("このURLにアクセスし、表示されるPINを入力してください。");
    System.out.println(authUrl);
    System.out.print("PIN:");

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String pin = br.readLine();

    provider.retrieveAccessToken(consumer, pin);
    System.out.println("Access token: " + consumer.getToken());
    System.out.println("Token secret: " + consumer.getTokenSecret());
  }
}

TwitterのOAuthと違うのは、どのような権利を求めるかを「score=...」という形で書いておくところです。上のコード実行するとURLが表示されるので、そのURLにブラウザでアクセスしてください。下のようなページが表示されます。scoreの記述によって内容は変わります。この例では、公開・非公開の両方の情報への読み書き権限を要求しています。

OAuthで許可を求められているところ

「許可」をクリックすると下のようなページが表示されるので、表示された文字列を先のコードからのプロンプトで入力してください。Access tokenとToken secretが得られます。

「許可」をクリックした結果

ブックマーク

譲り受けた権利を使って「http://b.hatena.ne.jp/atom/post」にURLをPOSTすれば、そのURLをはてなブックマークに登録できます。例として「http://www.google.com/」を登録するコードは次のようになります。先に取得したConsumer keyとConsumer secret、Access token、Token secretを埋め込んで実行してください。

import java.io.*;
import java.net.*;
import oauth.signpost.*;
import oauth.signpost.basic.*;

public class SignpostBookmark {

  public static void main(String[] args) throws Exception {
    // プロキシサーバの設定
    //System.setProperty("http.proxyHost", "proxy.example.net");
    //System.setProperty("http.proxyPort", "3128");

    // これはユーザによらない
    OAuthConsumer consumer = new DefaultOAuthConsumer(
            ***** Consumer key *****,
            ***** Consumer secret *****);

    // これはユーザごとに異なる
    consumer.setTokenWithSecret(
            ***** Access token *****,
            ***** Token secret *****);

    // ターゲット
    String target = "http://www.google.com/";
    String xml = String.format("<entry xmlns='http://purl.org/atom/ns#'>"
            + "<title>dummy</title>"
            + "<link rel='related' type='text/html' href='%s' />"
            + "<summary type='text/plain'></summary>"
            + "</entry>",
            target);

    // HTTPリクエスト
    URL url = new URL("http://b.hatena.ne.jp/atom/post");
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("POST");
    connection.addRequestProperty("Content-Type", "application/octed-stream");
    consumer.sign(connection);

    // POSTによる送信
    connection.setDoOutput(true);
    OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
    writer.write(xml);
    writer.flush();
    writer.close();

    // 結果の表示
    System.out.printf("%s %s\n", connection.getResponseCode(), connection.getResponseMessage());
    BufferedReader br = new BufferedReader(new InputStreamReader(
            connection.getResponseCode() / 100 == 2
            ? connection.getInputStream()
            : connection.getErrorStream(),
            "UTF-8"));
    String line = null;
    while ((line = br.readLine()) != null) {
      System.out.println(line);
    }
  }
}

Twitterの場合との違うのは、リクエストボディにXMLを書かなければならないこと、それを送信するためにContent-Typeをapplication/octed-streamにしておくことです。

ブックマークの編集

scribe-java版で紹介した方法と同様です(手抜き)。

補足

アプリケーションに与えた許可を取り消したいときは、「Myはてな」→「ユーザ設定」→「外部アプリケーション認証」→「外部のアプリケーションから、はてなのサービスを利用する」の順にクリックしてください。「https://www.hatena.ne.jp/はてなID/config/auth/provider」にアクセスしてもいいでしょう。

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)


テキストからキーワードを抽出しようとするとき、形態素解析して単語の頻度を数えるくらいのことではうまくいかないという話をしました。(テキストからキーワードを抽出する方法(非推奨)

自然言語処理の複雑なアルゴリズムを試すのも勉強にはなるのですが、「今すぐ答えがほしい」という状況を想定して、Yahoo!のキーフレーズ抽出APIを使ってみましょう。

4627847327お約束ですが、APIの使い方の基本的なところから学びたいという場合は、拙著『Webアプリケーション構築入門』を参照してください。

準備

Yahoo!のAPIを使うために、アプリケーションIDを登録します。表現は「登録」ですが、実際には「取得」です(参考)。

アプリケーションIDを取得したら、「appid=アプリケーションID」という内容(右辺は適当に置き換える)のファイルyahoo.appidを作っておきます。

練習(APIの使い方の確認)

適当な文章をUTF-8で記述したファイルsentence.txtを用意し、以下のコマンドでAPIを呼び出します。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\

http://jlp.yahooapis.jp/KeyphraseService/V1/extract

結果はJSON形式で返ってくるので、それを処理する簡単なスクリプトyahookeywords.pyを用意します。

import sys, json

for line in sys.stdin:
    keywords = json.loads(line)
    for keyword, score in sorted(keywords.items(), key=lambda x:x[1], reverse=True):
        print score, keyword.encode('utf-8')

先のコマンドとパイプでつなげば完成です。

curl -d @yahoo.appid -d output=json --data-urlencode sentence@sentence.txt\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

日本国憲法の前文をsentence.txtに書いておいた場合の結果は以下の通りです。

100 われら
71 諸国民
52 代表者
51 憲法
47 主権
43 自国
43 他国
42 日本国民
41 わが国全土にわ
41 原理
41 恵沢
38 法則
38 隷従
37 詔勅
34 国家
34 惨禍
34 専制
33 いづれ
33 協和
33 理想

本番

日本語の文章がtweets.txtに書かれていると仮定します。ここでは例によって、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のもの(本文のみ)を使います。

Yahoo! キーフレーズ抽出APIは、1回のリクエストのサイズが100KBまで(URLエンコードした状態で?)に制限されているので、テキストから「( #iwakamiyasumi live at http:…)」と「http…」という文字列を削除しましょう。

cat tweets.txt | sed -e "s/(.*live.at.*)//g" | sed -e "s/http.*[:space]//g" > tmp

補足:Ustream上でつぶやかれたものだけをとりだすなら、「( #iwakamiyasumi live at http:…)」が含まれるものだけを残してください。

方針1:ランダムサンプリング

ここで利用するテキストは、上述の準備をした後でもサイズ制限を超えているので、小さくなるようにランダムサンプリングをしてからAPIを呼び出してみましょう(「3/13」がサンプリングレートです。この値は適当に調整してください)。

cat tmp | awk '{ if (rand() < 3/13) print $0; }' |\
curl -d @yahoo.appid -d output=json --data-urlencode sentence@-\
 http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
 python yahookeywords.py

結果は以下のようになり、以前の結果と比べればずいぶんよくなっています(ランダムサンプリングなので、結果は実行ごとに変わります)。

100 上杉隆
99 岩上
66 uesugitakashi
61 iwakamiyasumi
46 IWJ・UST
46 上杉隆氏ファイナルインタビュー
43 ジャーナリスト休業直前
41 IWJ_UESUGI
38 ジャーナリズム
38 kfuruta777
36 ch1
35 T7OSEDyz
35 インターン
34 journalist
33 岩上安身
32 ホロスコープネイタル
32 自由報道協会
31 Toraji Tachiki
31 プレゼンテーター
31 佐々木俊尚

方針2:テキストの分割

テキストを分割し、APIを複数回呼び出すことも考えられます(実行前にresult.txtやx*を削除してください)。

split --line-bytes 30k tmp

for file in x*
do
  curl -d @yahoo.appid -d output=json --data-urlencode sentence@$file\
   http://jlp.yahooapis.jp/KeyphraseService/V1/extract |\
   python yahookeywords.py >> result.txt
done

cut -d ' ' -f 2 result.txt | sort | uniq

これによって得られるキーフレーズは以下のようになります(APIが変わらなければ、結果はいつも同じになります)。

DEMA, GGB9500, IWJ, IWJ1ch, IWJ_UESUGI, IWJ・UST, Journalist, NHKスペシャル, P8B88dXY, T7OSEDyz, Toraji, U-st, Ust, Web, asd59, ch1, genpatsu, higahayato, iwakamiyasumi, iwakamiyasumi&quot, iwakamiyasumi2, iwakamiyasumi3, iwj_uesugi, kfuruta777, kinoryuichi, komatsunotsuma, ky00950, kyokonoguchi, pkashima, uesugitakashi, w, おしどりマコ, つねつぐ氏, アクセス権, アワード, インターン, インタビュー, ウギリークス, ガサ入れ, ガンバッテ, グリニッジ, ゴルフジャーナリスト, ジャーナリスト, ジャーナリスト休業直前, ジャーナリズム, デマYAROU, デマYallo, デマYaroo, デマyallo, デマ野郎, ニューヨークタイムズ, ハイパーメディアキュリエーター, ヒマナイヌ川井, ピューリッツアー賞, ピューリッツア賞もの, ピュリッツァー, プチ鹿島, プルト君, プレゼンテーター, ホロスコープネイタル, マイク, メディア, メルマガハ, 安川@shinfonic, 炎上ビジネス, 可視化, 岩上, 岩上さん上杉, 岩上安身, 記者クラブ, 恒次, 黒い池上彰, 今西, 佐々木俊尚, 佐藤栄作久, 自由報道協会, 社団法人, 小沢, 小渕, 上杉, 上杉リークス, 上杉隆, 上杉隆ファイナルインタビュー, 上杉隆氏ファイナルインタビュー, 上杉隆氏ラストインタビュー, 神々しい岩神, 世川行介, 孫崎, 孫碕, 大批判, 池田信夫, 朝生, 田中真紀子, 読売ツネツグ, 読売新聞, 日隅, 畠山, 鳩山, 鳩山ゆきよ, 鳩山由紀夫, 福島県民, 立花隆, 鈴木宗男

先の結果よりは冗長ですが、もともとの映像がどういうものだったのか、だいたいわかるようなものになっています。

いちばんよくないのは、無関心だ


4822248763ステファン・エセル『怒れ!憤れ! 』(日経BP, 2011)

私はすべての人に訴えたい。一人ひとりが怒るべき理由を見つけてほしい。怒りは貴重だ。(p.25)

たしかに今日の世界では、怒る理由が昔ほどはっきりしなくなっている。言い換えれば、世界はあまりに複雑になった。(p.43)

だがこの世界にあっても、許しがたいことは存在する。それを見つけるためには、目をよく見開き、探さなければならない。若者よ、探しなさい。そうすれば、きっと見つかる。いちばんよくないのは、無関心だ。「どうせ自分には何もできない。自分の手には負えない」という態度だ。そのような姿勢でいたら、人間を人間たらしめている大切なものを失う。その一つが怒りであり、怒りの対象に自ら挑む意志である。(p.45)

一番いけないのは、おなかが空いていることと、一人でいること

米国製の計算機と日本製の計算機の違い


AppleとSonyの一番の違い part IIという記事を見て、「なるほど」と思ったのですが、こういう「たとえ」を使わなくても、そのままずばりのわかりやすい例がありました。

「高さが3の正三角形の面積は?」と訊かれたら、たいていの人は答えられると思いますが、ウェブで検索するより早く答えられる人は少ないでしょう。実際に、ウェブで解決しようとするとき、米国製の計算機と日本製の計算機、どっちを使いますか?

米国製の計算機

日本製の計算機

米国製の計算機、WolframAlphaの場合

「equilateral triangle height=3」と入力すれば答えが得られます。簡単に答えがわかるのはもちろん、結果のURLが得られるのもいいですね。

日本製の計算機、高精度計算サイトkeisanの場合

  1. 「数学・物理の計算」をクリックする
  2. 「三角形」をクリックする
  3. 「正三角形」をクリックする
  4. 入力指定を「高さ」にする
  5. 高さを「3」にする
  6. 「計算」ボタンをクリックする

これでやっと答えが得られます。初めて行く人は、どこに何があるかがわからないので、「手で計算した方が早い」ということになるでしょう。

keisanがWolframAlphaになる必要はありませんが、「英語がわからなくても大丈夫」以外のメリットを打ち出せるようになるといいですね。

テキストからキーワードを抽出する方法(非推奨)


Streaming APIで大量のつぶやきをリアルタイムに保存する方法(cURL編)で述べたように、Ustreamなどで重要な映像が配信されるときには、Twitterなどでその内容をtsudaってくれる人がいます。そうやって生み出される大量のテキストが、映像のメタ情報としてもっと活用されるとうれしい、という話です。

最初に試したいのはキーワードの抽出です。

テキストファイルからキーワードを取り出そうとするとき、多くの学生はまず「形態素解析」を試みます。自然言語処理についてちゃんと学びたいときは、こういうところからじっくり勉強するといいのでしょうが、単に形態素解析するだけではあまりいい結果は得られません。

実際にやってみましょう。

4873114705形態素解析システムの使い方は、『入門 自然言語処理』(オライリー, 2010)などで紹介されていますが、環境によってはもう少し簡単です。たとえば、Ubuntuの場合、有名な日本語形態素解析システムMeCabをPythonで使うための準備は、次のようにとても簡単です。

sudo apt-get install python-mecab mecab-jumandic-utf8

インストールが終わったら、Pythonのインタラクティブシェルで動作を確認します。

import MeCab
mecab = MeCab.Tagger()
sentence = "すもももももももものうち"
print mecab.parse(sentence)

実行結果は次の通りです。

す	接頭辞,名詞接頭辞,*,*,す,す,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
も	助詞,副助詞,*,*,も,も,*
もも	名詞,普通名詞,*,*,もも,もも,代表表記:股
の	助詞,接続助詞,*,*,の,の,*
うち	名詞,副詞的名詞,*,*,うち,うち,*
EOS

というわけで、うまくいってませんが先に進みましょう。形態素ごとに何らかの処理を行う練習です。

node = mecab.parseToNode(sentence)
node = node.next
while node:
    print node.surface, node.feature
    node = node.next

実行結果は先の場合とほとんど同じになります。形態素がnode.surfaceに、その素性がnode.featureに格納されます。

PythonでMeCabを使う方法がわかったので、形態素の出現回数を数えてみましょう。出現回数た多い単語がキーワードであろう、という発想です。対象テキストは、Streaming APIで取得したつぶやきの処理方法で紹介したもの、つまり「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のものです(本文のみ)。そのテキストが、ファイルtweets.txtに格納されていると仮定します。

次のようなコード(wordcounter.py)は、名詞と見なされたものの出現回数を数え、大きいものから順に出力します(ASCII数字のみからなる単語は除く)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import MeCab, sys

mecab = MeCab.Tagger()
counter = {}

for line in sys.stdin:
    node = mecab.parseToNode(line)
    node = node.next
    while node:
        if node.feature.startswith("名詞,"):
            if not node.surface.isdigit():
                counter[node.surface] = counter.get(node.surface, 0) + 1
        node = node.next

for surface, count in sorted(counter.items(), key=lambda x:x[1], reverse=True):
    print count, surface

cat tweets.txt | python wordcounter.py | head -n 20」などとして、出現回数が多いものから順に20個抽出すると次のようになります。


1509 iwakamiyasumi
1433 t
1406 co
1406 http
1301 live
1301 at
533 上杉
261 隆
157 岩上
121 記者
112 福島
106 ジャーナリスト
81 IWJ
73 クラブ
70 報道
64 uesugitakashi
63 こと
62 w
57 bCljBsCQ
54 ー

というわけで、あまりうまくいっていませんが、形態素解析システムの使い方がわかったので、とりあえずこれでよしとしましょう。

もう少し楽な方法を後で紹介します。

テキストからキーワードを抽出する方法(Yahoo! キーフレーズ抽出API)

IT時代の震災と核被害


4844331140「3.11の震災で、ITは無力だった」と感じた人が、IT関係者の中にもいたそうです。ITを専門にする大学教授の中にすら、そう言う人がいたそうで、「ちょっとかわいそうだなあ」と思っていました。

巨大地震と巨大津波、原発事故。大きな天災と人災を目の当たりにして、そのような無力感を味わった人に、『IT時代の震災と核被害』(インプレスジャパン, 2011)がおすすめです。

ITで地震や津波を防ぐことはできませんし、原発を収束させることもできません(今のところは)。しかし、3.11後の日本において、ITがいかに大きなことを成し遂げてきているか、また成し遂げることが期待されているかがよくわかります。

  • グーグルは地震発生後わずか数時間で消息情報を登録・検索するためのパーソンファインダーを立ち上げました。
  • ヤフーが集約・発信した情報は、日本最大のポータルサイトという名に恥じないものでした。
  • Twitterは安否確認や情報拡散のための最強のツールの一つとして認知されました。
  • Amazonの「ほしい物リスト」は、ふつうの義援金ではカバーしにくい被災地の要求を満たすのに大きな貢献をしました(その実現を支えた運送業者も忘れてはいけません)。公共機関などの情報発信にいち早くクラウドを提供したのもAmazonです(クラウドへの移行をサポートしたボランティアも忘れてはいけません)。
  • 「ふんばろう東日本支援プロジェクト」は、Amazon同様、必要なものを必要な人へ必要なだけ送ることから始まったプロジェクトですが、雇用創出や心のケアなど、物質支援の枠を超えた活動をしています。
  • Twitterは有益な情報と共にデマも拡散しましたが、情報を検証してデマを指摘する「検証屋」という人たちの活動は、「ネットの負の面」の解決を期待させるものでした。
  • Ustreamやニコニコ動画は、テレビを視聴できない地域や海外、消費電力を気にしてテレビを消した人たちに、テレビ放送を再配信して届けました。このサイマル配信は今は行われていませんが、「スカイツリーが無くてもテレビは観られる」ということを多くの人に知らしめた効果は今も残っていることでしょう。
  • 「国民がパニックになるのを恐れた」というパニクった理由で不幸にも公開が遅れたSPEEDIですが、迅速に公開できれば有用な情報源となることが後で実証されました(この知見が役立つことが将来無いことを希望しますが)。
  • 震災当日の夕方にスカイプは、日本人の全ユーザに30分相当の無料通話権を配りました。
  • 東京電力などの会見をノーカットで流し続けたIWJは、映像を流せるのはテレビ局だけではないこと、重要な映像が大手メディアによっていかに編集され切り落とされてきたかを多くの人に知らしめました。(本書では扱われていなかったかも)

「もっと多くのことができたはず」と嘆く人はしょうがないかもしれませんが、「何もできなかった」と嘆く必要はないでしょう。

しかし、こういうことが簡単に成し遂げられたと思うのは間違いです。本書を読むとわかりますが、多くのことは主に海外で発生した過去の大災害の経験を元に計画され、備えられてきたことなのです。そういう意味でも、「ITは無力」と思って生きるのではなく、「ITで何かできるはず」とふだんからそうぞうしていることが大事だと思います。

全3部からなる本書の第3部「複合震災とITの可能性」に収録された記事は、このような内容からは逸脱したものが多いのですが、津田大介さんが、

原発事故に関しては、僕自身もちろん専門家ではないので、政府が発表する情報、それに反対して危険性を訴える情報の双方を流しました。その際、原発関連情報の発信者のバックグラウンドを確認したり、情報のソースの検証には注意を払いました。(中略)「危険だ」と言い切ることはできないけど、「危険であろう」と想像することはできたので、その状況自体をおもんばかった立場から、情報を流し続けたわけです。(p. 262)

と書いているにもかかわらず、宮台信司さんは、

親の多くは内外の情報をランダムにリツイートする僕や津田大介さんのツイッターを見ていました。(p. 364)

と書いていて、「選別したの? ランダムだったの? どっち?」という感じでいきなり情報リテラシーを問われたりするのは面白かったです(「ランダムにリツイートする」は「僕」にしかかからないという解釈もあります)。ちなみに私は二人のうちの一方のツイートしか見ていませんでした。

それでも、「当面は」という条件付きで宮台さんが提唱する、

第一に、空気の支配がもたらす弊害に空気の支配によるカウンターを当てること。原発などもはやありえないとの空気を醸成することがあります。第二に、空気に縛られるがごとき習慣など今後はもはやありえないとの空気を醸成すること。

は、いい指導方針だと思いました。

空気と言えば、「海外メディア報道と日本の情報公開」の中で、著者の坂井信さんは次のように書いています。

福島第一原発事故後、日本のメディアは作業員が身を挺して危険な場所で働くことを、どこか「空気のように当たり前のこと」として報道してきたのだと思う。(中略)戦時中と類似しているのは、国土の被害状況以上に政治的空白の中で根本的な事態の解決を「現場の努力」と「若い作業員の献身」に委ねてしまうような「日本の空気」そのものではないだろうか。(p. 190)

原発の作業員がどのような状況で働いているのか、その現実に多くの人は向き合えるのか、このことについて、今後何十年も口を閉ざすことにならないかは正直不安です。

ITは何かできるでしょうか。

Streaming APIで取得したつぶやきの処理方法


かつて、Streaming APIで大量のつぶやきをリアルタイムに「保存する方法」を紹介しました(cURL編Python編)。つぶやきはJSON形式で保存していましたが、そこから何かを見出すためにはまず、JSON形式のデータを処理できなければなりません。ここではその方法を確認します。

Pythonで簡単に処理する方法を、@nokunoさんが紹介しています(Twitter Streaming APIの使い方)。cURLで取得したつぶやきは、1行に1つずつファイルに格納されるので、次のように1行ずつ取り出してJSONデータをパースすればいいようです(ここではtweet['text']、つまりつぶやきの本文だけを取り出しています)。

#!/usr/bin/env python
import sys, json
for line in sys.stdin:
    tweet = json.loads(line)
    if 'text' in tweet:
        print tweet['text'].encode('utf-8')

cURL編Python編でつぶやきを保存したファイルがresult.datだとすると、「python parse.py < ressult.dat」などとして本文だけを取り出すことができそうです。

Streaming APIのsample.jsonならこれでもいいのですが、filter.jsonを使うときには、条件に合うつぶやきが途絶えることがあります。そういうときには、30秒ごとにCR LFが送信されてきて、それがファイルに書かれることになるので、上のように1行ずつ処理しようとすると、ValueErrorが発生して処理が途中で止まってしまいます(cURLの場合)。ですから、次のようにValueErrorを無視しなければなりません(PythonのコードではJSON形式だけを出力していたので問題ありません)。

#!/usr/bin/env python
import sys, json
#from pprint import pprint

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        #print type(tweet['text'])
        print tweet['text'].encode('utf-8')
    except ValueError:
        pass

入力がstr型でも出力の一部はunicode型になったりするのが紛らわしい。Pythonを使うのはバージョン3が主流になってからにしたいですね、細かいことですが。

つぶやかれた日時はtweet['created_at']で取得できるのですが(このあたりのことはpprint()で確認できます)、この値は「Thu Dec 29 04:27:28 +0000 2011」のように標準時なので、日本ではちょっと使いにくいです。そこで、日本時間の年月日時分秒(20111231000000)のような表現に変換する関数を用意します。

#!/usr/bin/env python
import sys, json, time, calendar
#from pprint import pprint

def YmdHMS(created_at):
    time_utc = time.strptime(created_at, '%a %b %d %H:%M:%S +0000 %Y')
    unix_time = calendar.timegm(time_utc)
    time_local = time.localtime(unix_time)
    return int(time.strftime("%Y%m%d%H%M%S", time_local))

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        print YmdHMS(tweet['created_at']), tweet['text'].encode('utf-8')
    except ValueError:
        pass

リツイートを除外したいときは、次のようにすればいいでしょう。

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        if 'retweeted_status' not in tweet:
            print YmdHMS(tweet['created_at']), tweet['text'].encode('utf-8')
    except ValueError:
        pass

cURL編で書いたように、こういうことをしているのは、Ustreamなどで配信された動画の整理のためなので、保存したつぶやきは、時間を指定してとりだせるようにしておきましょう。

以下のようなスクリプト(parse.py)を使えば、「python parse.py 20111230150000 20111230174600 < result.dat」などとして、日本時間の2011年12月30日15時から2011年12月30日17時46分までのつぶやきだけを取得できます(日時の指定は省略してもかまいません)。

#!/usr/bin/env python
import sys, json, time, calendar
#from pprint import pprint

def YmdHMS(created_at):
    time_utc = time.strptime(created_at, '%a %b %d %H:%M:%S +0000 %Y')
    unix_time = calendar.timegm(time_utc)
    time_local = time.localtime(unix_time)
    return int(time.strftime("%Y%m%d%H%M%S", time_local))

argv = sys.argv
start_time = 0
end_time = 99999999999999
if 1 < len(argv):
    start_time = int(argv[1])
    end_time = int(argv[2])

for line in sys.stdin:
    try:
        tweet = json.loads(line)
        #pprint(tweet)
        if 'retweeted_status' not in tweet:
            tweet_time = YmdHMS(tweet['created_at'])
            if start_time <= tweet_time and tweet_time <= end_time:
                tweet_sec = tweet_time-start_time
                screen_name = tweet['user']['screen_name']
                text = tweet['text'].encode('utf-8')
                url = "https://twitter.com/#!/%s/status/%s"\
                    % (screen_name, tweet['id_str'])
                #print tweet_sec, url, text
                #print text
                t = time.strptime(str(tweet_time), "%Y%m%d%H%M%S")
                print time.strftime("%H:%M:%S", t), text
    except ValueError:
        pass

どのような形式で出力するかは、後で何に使いたいかで決まります。たとえばyouTube上でキャプションを付けたいなら、YouTubeがサポートする形式(参考)にあわせるといいでしょう。

実際に、「#iwakamiyasumi」というハッシュタグで検索し保存したつぶやきのうち、「年末特番 ジャーナリスト休業直前!上杉隆氏ラストインタビュー」の配信時間中(2011年12月30日15:00から17:45)のものを抜き出すと下のようになります(スクロールバーを表示できない環境では閲覧できないかもしれません)。

Streaming APIで大量のつぶやきをリアルタイムに保存する方法(Python編)


なぜこんな技術が必要なのかは、cURL編で書きました。

Streaming APIを使うときは、接続が切れてしまったときにいかに接続し直すかというのがキモなわけですが、cURLで簡単に実現する方法がわからなかったので、240秒ごとに強制的に接続し直すという方法を採用しました。この方法には、(1)再接続のときにつぶやきを取りこぼす危険と重複して取得する可能性があること、(2)接続が切れてから再接続を試みるまでに平均で120秒かかる、という問題がありました。

Streaming APIは、つぶやきが無いときには「CR LF」を30秒おきに送信してくるので、これをチェックすれば切断を検出できるのですが、検出と再接続の機能を自分で実装するのは大変なので、ライブラリに頼りましょう。

ここでは、PythonでTwitterを利用するためのライブラリ、Tweepyを使います。Ubuntuなら次のように簡単にインストールできます。

#sudo apt-get install python-setuptools
#sudo easy_install tweepy

Tweepyは切断の検出と再接続の機能を持っているので、Streaming APIで受け取ったデータを出力するプログラム(stream.py)は次のように簡単に書けます。StreamListenerで、つぶやきに対応するon_status()ではなくon_data()をオーバーライドしているのは、データをそのまま(JSON形式で)出力したいからです。とりあえず全部保存しておいて、必要なものをあとで取り出すというわけです。JSON以外のものがdataとして来る場合もあるので、最初が「{」のものだけをJSONデータと見なして出力するようにしています(ちょっとかっこわるい)。

#!/usr/bin/env python

import tweepy

class StreamListener(tweepy.StreamListener):
    def on_data(self, data):
        if data.startswith("{"):
            print data

username = "Twitterのユーザ名"
password = "Twitterのパスワード"
keyword = "つぶやきを絞り込むためのキーワード"
auth = tweepy.auth.BasicAuthHandler(username, password)
stream = tweepy.Stream(auth, StreamListener())
#stream.sample()
stream.filter(track=[keyword])

Twitterのユーザ名とパスワード、つぶやきを絞り込むためのキーワード(例:「#iwakamiyasumi,#iwakamiyasumi2」。OR条件はカンマ、AND条件はスペースで連結)を入力して、「python stream.py >> result.dat」などとして実行すれば、条件に合うつぶやきがresult.datに書き込まれます。

「せっかくPython使っているんだから、この場でパースして・・・」と思うかもしれませんが、後で何が必要になるかわからないので、とりあえず生データを保存しておくのがいいでしょう(リアルタイムでアウトプットしたいという場合は別です)。ファイルではなくデータベースに保存してもいいのですが、話が複雑になるのでやめておきます(データベースを使う場合でも、後で何が必要になるかわからないので、全データを保存しておくのがいいでしょう)。

再接続できるからといって、通信の不安定な場所での利用はおすすめできません。通信の安定した環境に置いたサーバで動かしっぱなしにするのがいいでしょう。何らかの原因でプロセス自体が落ちたときのために、以下のようなスクリプトを使うといいかもしれません(通常の切断ではプロセスは落ちません)。

#!/usr/bin/env bash

while :
do
  python stream.py >> result.dat
  sleep 240
done

Streaming APIで取得したつぶやきの処理方法

Streaming APIで大量のつぶやきをリアルタイムに保存する方法(cURL編)


つぶやきを大量に取得したいときには、TwitterのふつうのAPIではなく、Streaming APIを使います。ふつうのAPIは1時間あたり350回しか使えないので、対象となるつぶやきが、APIを目一杯使って取得できる数を超えるとどうしようもありません。Streaming APIならこのような心配は無用で、条件を設定して一度接続すれば、設定した条件に合うつぶやきを大量に取得できます。厳密に言えば、これは程度の問題でしかなく、Streaming APIでも、すべてのつぶやきを取得できるわけではないのですが、ここでは、「ふつうのAPIに比べれば遙かにたくさん取得できる」と考えてください。

cURLを使うのが簡単です。次のようなシェルスクリプト(stream.sh)を作成し、「bash stream.sh >> result.dat」などとして実行すれば、指定したキーワードを持つつぶやきを、result.datに保存することができます(「sudo apt-get install curl」などとしてcURLをインストールする必要があるかもしれません)。

キーワードはURLエンコーディングが必要です(ハッシュは「%23」、OR検索のためのカンマは「%2C」、AND検索のためのスペースは「%20」とすればいいのですが、日本語は現時点では難しそうです)。#iwakamiyasumiと#iwakamiyasumi2のどちらかを含むつぶやきを取得したいときは「%23iwakamiyasumi%2C%23iwakamiyasumi2」となります。

#!/usr/bin/env bash

username=Twitterのユーザ名
password=Twitterのパスワード
keyword=つぶやきをしぼりこむためのキーワード(URLエンコーディングが必要)

url=https://stream.twitter.com/1/statuses/filter.json
while :
do
  curl -d track=$keyword -m 240 -u $username:$password $url
done

240秒ごとにcURLの接続をリセットしています。この方法には、途中で接続が切れても最大240秒待てば自動的に再接続されるというメリットがありますが、リセット時につぶやきを取りこぼす危険や、つぶやきを重複して取得する可能性があります。この間隔(240)を小さくすると、Twitterから接続を断られるかもしれないので注意して下さい。この数字は再接続の最大待ち時間です(参考:Streaming API Concepts)。つぶやきがないときは、30秒ごとに「CR LF」が送信されてくるので、これをチェックすれば切断を検出できるのですが、自分で実装するのは大変なので、あとで別の方法を紹介しましょう。(30秒で2バイトなので、cURLの「-y」や「-Y」では対応できないのが残念です。)

取りこぼしを少なくするためにこのスクリプトは、インターネット環境が不安定な出先ではなく、インターネット環境が安定したサーバで動かしたままにしておくといいでしょう。とりあえず全部取っておいて、あとで必要な部分だけ抜き出すというわけです(抜き出し方はあとで紹介しましょう)。

以下の2点については後で書きます。

  • 接続が切れたときにスマートに再接続する方法Python編で紹介しました)
  • 保存したデータから必要な部分だけを取り出す方法書きました

なぜこういう技術が必要なのでしょう。

Ustreamのような動画配信サービスが登場し、それを活用するジャーナリストの活躍を見ると、「記者クラブメディアが諸悪の根源だ」という状況も少しずつ改善するのではないか、と期待してしまいます(最近の面白かった映像は「読売新聞記者vs上杉隆氏と岩上安身さん」)。たとえば、岩上安身(@iwakamiyasumi)さんが率いるIndependent Web Journal (IWJ)のおかげで私たちは、これまでなら記者クラブメディアの能力不足とフィルタリングのせいで人目に触れずに消えていたであろう映像を見ることができます。

しかし、IWJのUstreamスケジュールを見てもわかるように、その量は膨大で、全部見るのはほとんど不可能です。自分にとって重要な映像がすぐに見つかるようになっていればいいのですが、現在の映像検索技術では難しいでしょう。よくやられるのは映像に付与されたメタ情報(タイトルやタグ)などを使って検索することですが、タイトルはともかく、タグを適切に付けるのもかなりの労力だと思います(Togetterもしかり)。ソーシャルメディア上で推薦システムを動かして・・・ということも考えられますが、自分が見た映像の記録がすべて保存され、活用されるのをいやがる人もいるかもしれません(話がそれました)。

幸運なことに、著名なジャーナリストが配信する映像は、一般的な映像とは違います。特に記者会見の映像には、「tsudaる」というボランティア行為による膨大な文字情報が付随します。これを活用するために、まずはその文字情報を保存しよう、というのが今日のお話。

Ustreamには、動画に対する複数のソーシャルメディアからのコメントをまとめて表示する「ソーシャルコメント」という機能があるのですが、とりあえず、Twitterのつぶやきだけを保存することにします。こういうことの大切さが理解され、Ustreamが保存してあるソーシャルコメント()を使いやすい形で提供してくれることを願います。

新・芸術とは…?


楽園王と劇団ING進行形の共同制作、岡本綺堂の「俳諧師」と「近松半二の死」、「修善寺物語」を混ぜ合わせた作品「新・芸術とは…?」を観てきましたかなり訓練された。うまいと思わせる役者がたくさんいましたが文末に。次の文の最初の文節をつなげて読む技法と、大切なことを繰り返す技法、この2つを完全に陳腐化するまで繰り返す演出によって、観ていた私の脳は、良くも悪くも大きなダメージを受けましたダメージを。受けました。