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


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つを完全に陳腐化するまで繰り返す演出によって、観ていた私の脳は、良くも悪くも大きなダメージを受けましたダメージを。受けました。

第五世代筆記具


第五世代というと顔をしかめられる業界もあるようですが、文房具業界はどうでしょう。

パーカーから“第五世代”と銘打った筆記具「インジェニュイティ」が発売されたので試してみました。手で書く機会はあまりないにもかかわらず、いつもいい筆記具を探しているので、「書き味」をアピールする筆記具の新製品はだいたい試しています。

allaboutより)

まず、名前にちょっと問題があります。発音しにくく憶えにくい。限定販売の今のうちに、発音しやすい名前に変えておいた方がいいと思います。メーカーは違いますが、ぺんてる「プラマン」の高級版だってことがすぐわかる感じにするのはどうでしょうか。「ハイプマ」とか。

パーカーが勝手に言っていることではありますが、「世代」というのはこんな感じだそうです。

  1. 万年筆
  2. 油性ボールペン
  3. ローラーボール(水性ボールペン)
  4. メカニカルペン(シャープペンシル)
  5. 一般名称不明(製品例:インジェニュイティ)

サインペンが入っていないのも問題です、限定販売の今のうちに、「インジェニュイティ」は“第六世代”ということにして、空いたところにサインペンを入れておいた方がいいと思います。

私のお気に入りはこんな感じです。

万年筆
油性ボールペン
ローラーボール
  • Swift(映画マトリックスの小道具)
サインペン
メカニカルペン
  • クルトガ(すごいと思うが、シャープペンシル自体、あまり使わない)
“第五世代”
  • インジェニュイティ(品質・価格ともに高い)

「1本だけ」と言われればジェットストリームだという人が多いと思いますが、ピュアモルトシリーズ以外は手触りがあまりよくありません。リフィルは同じM66でも、TipoよりもSwiftがいい、これも同じ理由からでしょう。この点に関しては、「インジェニュイティ」はとてもいいと思います。

指先の神経は脳と大変密接に関係していますので、ペンの手触りという面でも満足してもらうというのはとても大切なことだと考えています。(「LAMY」社長が語る、モノ造りの真髄

インジェニュイティ (parkerpen.com)

つぶやきの限界(140文字)への挑戦


4627847327拙著『Webアプリケーション構築入門』のサンプルとして、Twitterクライアントを作りました。

単なるクライアントではつまらないので、つぶやきが140文字未満のときは、文字を追加して139から140文字になるようにしています。

本稿執筆時点でのTwitterの仕様では、ちょっと面白い結果になるみたいです。仕様が変わったら公開を止めます。

Twitterのアカウント情報は保存しませんが、気になる人は https://twitter.com/settings/applications で削除してください。

試してみる

しばらくすると改竄されてしまいました。Twitter社には書き換える権限があるのかもしれませんが、それを頻繁に行使されるとちょっと不安になりますね(ジャスミン革命に使えるのかしら)。

参考書

矢野啓介『プログラマのための文字コード技術入門』(技術評論社, 2012)

4797361190徳丸浩『体系的に学ぶ 安全なWebアプリケーションの作り方』(ソフトバンククリエイティブ, 2011)

スクラップロゴス


ワタナベコメディスクールのライブ「SMILE DAWN」を観てきました。なんというか、常識的な人生の正攻法には絶対入ってこないであろうことを追求する姿を見ていると、ネタがちょっと・・な時でも笑って拍手したくなるから不思議なものです。そんな中でも、「スクラップロゴス」や「サツマカクRPG」、「ひまつぶし」は面白かった(名前をもっとわかりやすくして!)。特に渡辺輝さんの相撲ネタは、たくさん作ってシリーズ化してほしいと思いました。

スクラップロゴス

OAuth認証でTwitterを利用するWebアプリケーション(PHPの場合)


OAuth認証が必要なAPIでは、Twitter APIとFacebook APIが有名ですが、ちょっと仕様が違うので、使い方を簡単に紹介しようとすると、記事を分けなければなりません。さらに、JavaとPHPの両方を学べるというお得な本を書いたこともあって、その読者をサポートするために、JavaとPHPの両方で記事を書かなければなりません。都合、以下の4パターンが必要です。

今回は、「PHPでTwitter API」です。「JavaでFacebook API」はまた別の機会に。

アプリの登録

http://dev.twitter.com/appsでアプリケーションを登録し、Concumer keyとConsumer secretを取得してください。その際、クライアントアプリケーションではなくブラウザウェブアプリケーションとして登録するように注意してください。

OAuthのためのライブラリ

PHPには、標準の「PECL/oauth」や準標準と言える「Pear HTTP_OAuth」、Twitterに特化した「Pear Services_Twitter」がありますが、例によって、PECLはWindowsで使いにくいし、HTTP_OAuthはベータ版、Services_Twitterはアルファ版なので、さっさと見限って「abraham / twitteroauth」を使いましょう。

OAuth.phpとtwitteroauth.phpをダウンロードして、ウェブサーバで配信できる場所に置きます。

cURL

XAMPP

PHPでcURLを使うために、php.iniに以下のように記述して、Apacheを再起動します。

extension=php_curl.dll

Ubuntu

次のコマンドを実行してcURLモジュールをインストールしてからApacheを再起動します。

sudo apt-get install php5-curl

4627847327お約束ですが、このあたりでもうついて行けないという場合は、拙著『Webアプリケーション構築入門』などを参照してください。以下のPHPプログラミングで必要な知識も本書にまとめてあります。

OAuth認証

以下の3画面で認証することにします(oauth-start.phpとoauth-end.phpは一つのファイルにまとめることもできますが、ここではわかりやすいように二つに分けています)。

  1. OAuth認証のためのURIを生成し、それにアクセスするためのリンクを表示するoauth-start.php
  2. アプリケーションを許可するTwitterページ
  3. Twitterからのコールバックを受信し、access tokenとtoken secretを取得するoauth-end.php

実装

oauth-start.php

OAuth認証の入り口となるoauth-start.jspは以下のようになります。「Consumer key」や「Consumer secret」、コールバックURIの部分は、状況に合わせて書き換えてください。

<?php
require_once('twitteroauth.php');

session_start();

//セッションを節約したい場合は別ファイルに書いておく。
$_SESSION['consumer_key'] = Consumer key;
$_SESSION['consumer_secret'] = Consumer secret;
$callbackUri = 'http://localhost/twitter/oauth-end.php';

$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret']);
$request_token = $connection->getRequestToken($callbackUri);
$_SESSION['oauth_token'] = $token = $request_token['oauth_token'];
$_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];

$authUrl = $connection->getAuthorizeURL($token);
?>
<!doctype html>
<html>
  <head>
    <title>OAuth Start</title>
  </head>
  <body>
    <p><a href="<?php echo $authUrl; ?>">Twitter OAuth認証開始</a></p>
    <p><a href="logout.php">logout</a></p>
  </body>
</html>

oauth-end.php

コールバック後の処理を実装するoauth-endは以下のようになります。

<?php
require_once('twitteroauth.php');

session_start();

$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret'],
                $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']);
$access_token = $connection->getAccessToken($_GET['oauth_verifier']);
unset($_SESSION['oauth_token']);
unset($_SESSION['oauth_token_secret']);

//セッションに記録する。データベースなどを使ってもよい。
$_SESSION['access_token'] = $access_token;
?>
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Twitter OAuth認証完了</title>
  </head>
  <body>
    <pre><?php print_r($access_token); ?></pre>
    <p>access_tokenをセッションに保存した</p>
    <p><a href="post.php">送信テスト</a></p>
    <p><a href="logout.php">logout</a></p>
  </body>
</html>

つぶやく

認証が終わったら、以下のようなコードでつぶやけます。

<?php

header('Content-type:text/plain; charset=utf-8');
require_once('twitteroauth.php');

session_start();

$access_token = $_SESSION['access_token'];
$connection = new TwitterOAuth($_SESSION['consumer_key'], $_SESSION['consumer_secret'],
                $access_token['oauth_token'], $access_token['oauth_token_secret']);

$message = "テスト at " . time();
$parameters = array('status' => $message);
$status = $connection->post('statuses/update', $parameters);
print_r($status);

ブラウザを再起動すればはじめに戻りますが、次のようなlogout.phpを作っておいてもいいでしょう。

<?php
session_start();
session_destroy();
?>
<!doctype html>
<html>
  <head>
    <title>Logout</title>
  </head>
  <body>
    <p><a href="oauth-start.php">start</a></p>
  </body>
</html>