まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:twitter

Node-REDnode-red-node-twitter ノードを使って、ツイッターのリアルタイム検索を行い、その結果を表示するウェブアプリケーションを作ってみました。Node-RED 環境に node-red-node-twitter ノードを追加することで以下の作業が可能になります。或いは IBM Bluemix から Node-RED スターターを使って作成した環境であれば、はじめから同ノードが組み込まれているため利用可能です。

まずは Node-RED のキャンバス内に以下のようにノードを配置します:
balloon_nodered


Twitter のノードにはリアルタイム検索を行うキーワードを指定しておきます。以下の例では "iPhone" というキーワードで Twitter 内をストリーム検索するように指定しています:
2017020501


そして WebSocket 出力ノードでは出力先を /ws/tweets に指定しています。ここは任意の文字列でもいいのですが、後述の HTMLテンプレートの内容が "/ws/tweets" の WebSocket を監視するような内容になっているので、これらの内容を一致させる必要があります:
2017020502


また HTTP 入力ノードでは GET の /tweets を指定しています。つまりウェブブラウザで /tweets というページを参照した時にここで定義するページ内容が表示されるようにしています:
2017020503


その際のページ内容をこちらのテンプレートノードで定義しています。以下のように HTML が指定されており、それがそのまま出力されます:
2017020504


このテンプレートノードの中身は、こちらの template ファイルの内容をそのままコピー&ペーストしてお使いください:
https://github.com/dotnsf/balloon_tweets


※なお、上記で紹介したのとまったく同じノード構成をこちらに用意しておきました。自分でノードを構成しなくても(単に動かしたいという目的だけであれば)この JSON ファイルの内容を Node-RED にインポートして使っていただいてもかまいません:
http://dotnsf.blog.jp/balloon_tweets.json


ノード構成の準備ができたら、Node-RED 画面右上のボタンでデプロイします:
2017020505


デプロイが成功するとここで定義したノードが動き出し、指定したキーワード(今回の場合は "iPhone")で Twitter のリアルタイムツイート検索が行われます。該当するツイートは画面右の debug タブ内に次々と表示されていきます:
2017020506


この様子をもう少し見やすくしたのが HTML テンプレートです。Node-RED と同じホストを指定して、http://(Node-RED のホスト)/tweets をウェブブラウザで開くと、検索されたツイートが画面内に次々と吹き出しを伴って表示されていく様子を確認できます:
2017020500


実際にツイートが次々と追加されていく様子はこちらの動画を御覧ください。"iPhone" くらいに頻度の高いワードで検索すると、こんな感じのスピードでツイートされている、というのが分かる動画になっています:




先日、マンホールマップのログイン機能に障害が発生しました:

通常、マンホールマップ(PC版)のトップページにアクセスすると、画面右上には "Login with Twitter" マークが表示されます(未ログインの場合):
2015122801


で、ここをクリックして、Twitter の OAuth 認可によってマンホールマップにログインして・・・という流れでログインするのですが、ここに問題が発生していると「現在、何らかの障害によってログインできません。」というメッセージが表示されます:
2015122802


一般的には Twitter API の障害が発生していることを示すメッセージなのですが、先日マンホールマップで発生していたものは Twitter API には障害が発生していないにも関わらず、上記のようなメッセージが表示されていたのでした。 ちなみに Twitter API の稼動状態はこちらで確認できます:
API Status | Twitter Developers


さて、上記の原因はなんだったのでしょうか? ちなみにマンホールマップは Java で開発された Web アプリケーションで、この Twitter の OAuth 認可の部分は Twitter4J を使って実装しています(最新版の 4.0.4 でも、スナップショット版の 4.0.5 でも再現)。で、問題部分のコードは以下のようになっていました:
String authorizationURL = null;
try{
  Twitter twitter = new TwitterFactory().getInstance();
  twitter.setOAuthConsumer( TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET );
  RequestToken requestToken = twitter.getOAuthRequestToken();  ←ここで Exception 発生

  String token = requestToken.getToken();
  String tokenSecret = requestToken.getTokenSecret();
    :
    :
}catch( Exception e ){
  e.printStackTrace();
}

リクエストトークンを取得するための Twitter.getOAuthRequestToken() メソッド実行時に Exception が発生していました。それまでに設定しているのは Twitter API の Consumer Key と Consumer Secret だけで、これは正しい値が設定できていました(というか、正しく動いていた時から何も変えてません)。 で、その下の catch 内で以下のようなスタックトレースが出力されていました:
Invalid signature on ECDH server key exchange message
Relevant discussions can be found on the Internet at:
        http://www.google.co.jp/search?q=3cc69290 or
        http://www.google.co.jp/search?q=45a986b4
TwitterException{exceptionCode=[3cc69290-45a986b4 3cc69290-45a9868a], statusCode=-1, message=null, code=-1, retryAfter=-1, rateLimitStatus=null, version=4.0.5-SNAPSHOT(build: 9b7efa6faa540a9defb3e6ba9122a356155986b1)}
        at twitter4j.HttpClientImpl.handleRequest(HttpClientImpl.java:179)
        at twitter4j.HttpClientBase.request(HttpClientBase.java:57)
        at twitter4j.HttpClientBase.post(HttpClientBase.java:86)
        at twitter4j.auth.OAuthAuthorization.getOAuthRequestToken(OAuthAuthorization.java:115)
        at twitter4j.auth.OAuthAuthorization.getOAuthRequestToken(OAuthAuthorization.java:92)
        at twitter4j.TwitterBaseImpl.getOAuthRequestToken(TwitterBaseImpl.java:292)
        at twitter4j.TwitterBaseImpl.getOAuthRequestToken(TwitterBaseImpl.java:287)
        at me.juge.geoimgweb.AppInfo.GetAuthorizationURLTwitter(AppInfo.java:361)
        at me.juge.geoimgweb.AppInfo.GetAuthorizationURL(AppInfo.java:350)
        at org.apache.jsp.stats_jsp._jspService(stats_jsp.java:928)
        at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:377)
        at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
        at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at filters.SetCharacterEncodingFilter.doFilter(SetCharacterEncodingFilter.java:122)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
        at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLKeyException: Invalid signature on ECDH server key exchange message
        at sun.security.ssl.HandshakeMessage$ECDH_ServerKeyExchange.(HandshakeMessage.java:1098)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:278)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:913)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:849)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1035)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1344)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1093)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250)
        at twitter4j.HttpClientImpl.handleRequest(HttpClientImpl.java:137)
        ... 30 more


問題の getRequestOAuthToken() を実行した時に "Invalid signature on ECDH server key exchange message" というエラーメッセージが出力されています。何コレ?"Invalid signature" って言われても、この時点ではこちらは Key と Secret 以外何も指定してないので間違えているとは思えないけど・・・??

しつこいようですが、今までは何の問題もなく動いていたコードが、ある時を境に急にこのような挙動になってしまいました。となると、Java ソースコードに直接の原因があるとは考えにくい・・・


しかもこの問題、何が厄介かというと、デバッグしようとしてローカルの Eclipse 内で動かすと、このエラーも Exception も発生しないのです。ちゃんと動いてしまいます。更に言うと、別のサーバー内に同様の Java + Tomcat 環境を作って、同じ war をデプロイして実行しても発生しない。要はこの本番サーバーだけで発生するエラーだったのです。。 となると環境依存か、原因特定は更に厄介だぞ・・・

とりあえず緊急でサーバーを1つ作り、そちらに Java と Tomcat を導入し、アプリの war をデプロイ、したら動きました。なので、応急処置としてはこれでなんとかなります。この応急処置サーバーで運用を続けながら原因追求と解決を続けます。 ああ、クラウドってこういう時に便利なのね。。。


さて、このエラーメッセージをググってみると分かるのですが、SSL 関連の Exception のようなのです。はて、マンホールマップに SSL 関連モジュールなんか使ってたっけ?? 何よりも仮に使っていたとしても、実行コードは Twitter4J の中だし、指定しているのは Key と Secret だけで変えようがないし、他のサーバー環境だと動いちゃうし・・・ 念のため API Key と Secret を新たに取得し直して見ましたが、状況は変わりませんでした。


次に行ったのは JDK と Tomcat のアンインストール、および再インストールです。環境は CentOS だったので、 JDK のアンインストールは以下の手順で行いました:
# yum list installed | grep jdk  (インストール済み JDK を確認)
java-1.7.0-openjdk.x86_64
java-1.7.0-openjdk-devel.x86_64
jdk.x86_64              2000:1.7.0_79-fcs (候補は3つ、全部削除してもよいと判断)

# yum remove java-1.7.0* (上2つをアンインストール)
# yum remove jdk* (一番下をアンインストール)

同様にして、Tomcat も以下の手順でアンインストールしました:
# yum list installed | grep tomcat (インストール済み tomcat を確認)
tomcat6.x86_64          6.0.24-90.el6   @base
tomcat6-admin-webapps.x86_64
tomcat6-el-2.1-api.x86_64
tomcat6-jsp-2.1-api.x86_64
tomcat6-lib.x86_64      6.0.24-90.el6   @base
tomcat6-servlet-2.5-api.x86_64 (候補は6つ、全部削除してよいと判断)

# yum remove tomcat6* (まとめてアンインストール)

でもってそれぞれを再インストールします:
# yum install java-1.7.0-openjdk-devel
# yum install tomcat6 tomcat6-admin-webapps

これで Java と Java アプリケーションサーバーはまっさらな状態に戻りました。この状態でマンホールマップの JAR をインストールしてアクセスしたところ・・・ 何も変わらない・・・ orz


全く原因が思いつきません。いよいよヤバい。 改めてこの ECDH なるものを調べてみると暗号化のアルゴリズムっぽいようでした:
ECDH アルゴリズムの概要

暗号化アルゴリズムの、鍵の交換の所で(しかも今まで動いていたものが、ある時から)動かなくなった、ということになります。いよいよ OS レベルの中身がぶっ壊れてしまったか?? と思ったのですが、最後の希望を託してインストールした全ライブラリを更新してみることにしました:
# yum update -y
  :
  :
  :

# /etc/init.d/tomcat6 restart

で、Tomcat をリスタートしてアクセスしてみたら・・・ 直りました! 助かった!!
2015122901
 ↑直ったのはついさっき


後は緊急運用サーバーからこちらに切り替えれば対処完了(予定)。 結局「これ」という直接の原因は分からなかったけど、古いライブラリでは対応しない機能を使っていた、ということなんでしょうかね~。

自分が作って運用しているウェブサービスの多くはクラウド上で動いてますが、このマンホールマップに関しては自宅サーバーで(更に言えば ThinkPad で)運用することにまだこだわっています。今回のエラーはその信念を揺るがすものでしたが、とりあえず解決できました。もうしばらく自宅サーバー運用を続けることができそうです。 v(^o^)



久しぶりに Twitter API キーやアクセストークンを新規に取得しようとしたら、それまで知っていた手順と違っていたので、改めて書き残しておきます。

まず以下のサイトへ行きます。ログインしていない場合は右上に "Sign in" というリンクが表示されるので、そこからログインします:
Twitter Application Management


Twitter Application Management サイトにログインすると、自分が過去に作った Twitter アプリの一覧が表示されます(まだ作ったことがない場合は何も表示されないはずです)。新たに API キーを取得するには新しいアプリを1つ定義します。画面右上の "Create New App" と書かれたボタンをクリックします:
2015111101


新規に作成するアプリケーションの概要を入力します。このうち "Name" はアプリケーションの名称ですが、全半角スペースを含むことができません。また、既に誰かが使っている名称を使うこともできません。1語の単語の形でユニークな名称を指定してください。"Description" はアプリケーションの説明文ですが、空にはできません。"WebSite" はアプリケーションのウェブサイト URL でこれも必須ですが、Streaming API などウェブサイトの存在しないアプリを作る場合もあると思います。ここは http://127.0.0.1 で始まるような名前を指定しても動作には支障なさそうでした。Callback URL は OAuth のコールバック先ですが、必須ではありません:
2015111102


全て入力したら下にスクロールして、Developer Agreement を確認し、内容に同意できる場合は "Yes, I agree" にチェックを入れて、最後に "Create you Twitter application" ボタンをクリックします:
2015111103


指定したアプリケーションの API キーが作成されます。API キーを確認するには、"Keys and Access Tokens" タブを開くと、その中に API Key や API Secret などが表示されているはずです:
2015111104


一方、アクセストークンはこの段階ではまだ作成されていないはずです。アクセストークンが必要な場合はこのページを下にスクロールして、まだ作成されていないことを確認した上で "Create my access token" ボタンをクリックします:
2015111101


処理が成功すると同ページの内容が書き換わり、アクセストークンとアクセストークンシークレットが確認できるようになります:
2015111102



以前の方法だと、最初は https://dev.twitter.com/ からスタートしたような記憶があるんだけど、今はそこからだと API Key 取得ページへのリンクが見当たらないんだよな、、変わったのかな?


IBM Bluemix から提供されている各種 API の中に Insights for Twitter サービスがあります:
2015102901


Twitter のつぶやきデータに、IBM が付与した感情やユーザーの推定属性をデータベース化して、検索 API として提供しているものです。(Twitter 社が提供している)標準 API ではできないような検索絞り込みが出来たり、標準 API では制限されているような、時間あたり API 利用回数制約もなく使える、という特徴があります。

この Insights for Twitter API には2つの価格プランがあります:
2015102902


1つは Free プランで、もう1つは Entry プランです。上の図を見ると、Free プランは無料で月に500万件までのツイートを取得可能、Entry プランは月額21万円で月に100万件のツイートを取得可能、と書かれているように見えます。。

「え? 逆じゃないの??」

と思うかもしれませんが、実は正しいのです。この価格について、もう少しきちんと説明します。

実はこの2つのプランは取得できるデータに違いがあります。Free プランで取得できるデータ源は decahose と呼ばれているもので、ツイートデータ全体の約10分の1が母集団になっています(つまり、検索データベースの中に含まれていないツイートも多い)。標準の Twitter API を一般の開発者が使う場合もこの decahose を対象にツイートデータを取得できる、というものです。一方 Entry プランで取得できるデータ源は firehose と呼ばれる全ツイートデータです。標準 Twitter API を使う一般の開発者がアクセスできるものではなく、限られた環境下でのみ提供されているものです。Bluemix の Insights for Twitter API の Entry プランを申し込んでいただいた場合は、そこにアクセスできるようになる、ということになります:

Free プラン比較Entry プラン
無料料金月額21万円
500万1ヶ月間に取得できるレコード量100万
decahose
全ツイートの10分の1
データ源firehose
全ツイート


Free プランで取得できるデータは、オリジナルの Twitter API で取得できるデータと比べてデータ源は変わりません。ただ IBM のインサイト情報が付与されており、感情やユーザー属性を指定した便利な検索が可能になっています。
一方、Entry プランを使うと便利な検索機能に加え、一般の開発者権限ではアクセスすることのできない firehose にアクセスすることができる(より実態に近いデータを集めることができる)、という点が大きな違いと言えます。


・・・って、軽く書いちゃったけど、firehose にアクセスできる環境が入手できるって凄いことだよね。しかも体験期間中であれば無料ってこと? (O.O ;


IBM Bluemix から提供されている各種サービスについて、開発者視点から実際の使い方を紹介してみようと思います。

ここで紹介するサンプルを開発する時に使う言語は迷いましたが、PHP を使うことにします。
最初にお断りしておくと、IBM Bluemix の API サービスは REST で提供されているものがほとんどで、原則的には(Get や Post などの HTTP アクセスが記述できれば)どの言語でも実現できます。ただ PHP だと(Java でいうところの JAR ファイルとか)拡張ライブラリなどが不要で素のまま HTTP アクセスが使えるということと、取得した結果の JSON テキストも素の PHP のままでデコードして使えるということと、実際にプログラミングとして記述した時に非常にシンプルに書ける、というメリットがあります。 といった観点で PHP を使って紹介しますが、(HTTP アクセスして、結果を取得して、JSON をデコードして、、という)同様の処理を実装すれば他の言語にも応用できるはずです。参考程度ですが、自分が業務用のデモを作る時はほぼ Java で書いてます。


記念すべき第一回目のテーマは IBM Insights for Twitter サービスにします:
https://www.ng.bluemix.net/docs/#services/Twitter/index.html#twitter

IBM が Twitter 社との提携によって decahose と呼ばれるツイート情報を提供されるようになりました。そのツイート情報に IBM のインサイトを加えてデータベース化した上で検索 API を提供しているのが、このサービスです。

このサービスを実際に使って動くデモアプリがこちらに用意されています:
https://cdetestapp.mybluemix.net/
2015060201


テキストフィールドに検索キーワードを入れて "Twitter Search" ボタンをクリックすると、そのキーワードで Twitter の検索(正確には Twitter から提供された情報を元に作られた IBM のデータベース内の検索)結果が表示されます。この例では "IBM" という検索キーワードに対して 333957 件ヒットし、その一部が画面に表示されています。ぶっちゃけ、ここまでは普通の Twitter API でもできることです:
2015060202


次に検索キーワードに "IBM sentiment:positive" と入力してみます。これは「IBM というキーワードを含み、かつ内容がポジティブなもの」を検索しています。結果として 53199 件見つかり、その一部が表示されています:
2015060203


この「内容がポジティブなもの」という検索方法に注目してください。少なくとも Twitter の標準 API ではこのような検索はできません。

IBM が Twitter からツイートの情報を提供されていることは上記で述べましたが、IBM はその内容を自分達のデータベースとして保存する際にストリーミング処理を施し、ツイートの内容やツイートの作者の情報に基づいた情報を独自に付加してデータベース化しています。その付加情報の中に内容のネガ・ポジ情報も含まれているため、このような検索が可能になっています。同様にして「IBM というキーワードを含み、かつ内容がネガティブなもの」という検索("IBM sentiment:negative")も可能です:
2015060204


ネガ・ポジ以外の付加情報も検索キーとして使えます。例えばこの例では「IBM というキーワードを含み、かつ内容がネガティブで、かつ子供がいる人からツイート」という検索("IBM sentiment:negative has:children")をして、その結果を表示しています。なんとなく推測できたかもしれませんが、スペースを付けて複数の条件を指定するとアンド検索になります:
2015060205


なお、検索はここのツイート情報まで表示させることもできますが、単にヒット数だけを調べることもできます。"Twitter Search" ボタンではなく "Twitter Count" ボタンで検索すると、個々のツイート情報は取得せず、数だけを調べることができます。当然ですがこちらの方が軽く動作します:
2015060206



以上、ざっくりとして紹介でしたがなんとなく使い道がわかったでしょうか? 繰り返しますが、IBM Insights for Twitter サービスはこのデモサイトのことではなく、あくまで「このデモサイトの実装に使われている APIです。極端な言い方をすれば、皆さんがこのデモサイトと同じようなサービスを作ることも可能な、そんな REST API が IBM Insights for Twitter サービスです。なお、この API サービスの関数リファレンスについてはこちらを参照ください。ま、要は「一定のルールで書かれた検索文字列を渡せば、その結果を JSON で返してくれる」というウェブ API です:
https://cdeservice.mybluemix.net/rest-api/#!/ibm_twitter_insights


では、実際にこのサービスを PHP から使って、プログラムを作ってみましょう! IBM Bluemix にログインし、(PHP でもそれ以外でもいいのですが)ランタイムを1つ作成します。ここまでの手順がよく分からない場合はこのエントリ等を参考にしてください:
http://dotnsf.blog.jp/archives/1000961115.html

この例では "dotnsf-twitter" という名前の PHP ランタイムを1つ作成しています。ここで「サービスまたは API の追加」をクリックします:
2015060207


サービスの一覧が表示されます。非常に多くのサービスが並んでいますが、「ビッグデータ」カテゴリ内に今回の目的である "Insights for Twitter" サービスが見つかります。これをクリックします:
2015060208



サービス内容を確認します(無料利用の条件などが書かれています)。そして「作成」ボタンをクリックすると、PHP ランタイムにこのサービスを追加します。更に再ステージングについて問われたら「再ステージ」を選択:
2015060209


これで PHP ランタイムに IBM Insights for Twitter サービスがバインドされました。これで API を利用するための情報が確認できます。API 利用情報を確認するには Insights for Twitter サービスアイコンの「資格情報の表示」と書かれた部分をクリックします:
2015060210

 
このような情報が出力されます:
2015060211

 
このような JSON フォーマットのテキストが書かれているはずです。ここに API を実行するために不可欠な情報(特に赤字部分)が書かれています。なお username と password はこの API を実行する際に指定するユーザー名とパスワードで、実際にはランダムに生成された文字列になっています:
{
  "twitterinsights": [
    {
      "name": "Insights for Twitter-8p",
      "label": "twitterinsights",
      "plan": "Free",
      "credentials": {
        "port": "433",
        "username": "(username)",
        "host": "cdeservice.mybluemix.net",
        "password": "(password)",
        "url": "https://(username):(password)@cdeservice.mybluemix.net"
      }
    }
  ]
}

この url の情報と、上記の API リファレンスを組み合わせると API が実行できます。例えば上記デモサイトでも行った「"IBM" というキーワードでツイートを検索」を API で問い合わせる場合を調べてみます。

リファレンスによると "/v1/messages/search" という関数が目的の検索機能であり、ここに "q=IBM" といった具合でパラメータを付与して GET メソッドで実行すればいい、ということが書かれています:
2015060212


これに上記 JSON に書かれたホスト名とユーザー名、パスワードを使って、試しにブラウザでアクセスしてみます。url 値は "https://(username):(password)@cdeservice.mybluemix.net" でした。API は /api パス以下にあるので、ウェブブラウザのアドレス欄に
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM
と入力してみます(username と password は実際に JSON に書かれているものを指定してください):
2015060213

成功すると↑のような JSON テキストが取得できます。これが IBM での検索結果です。もう少し見やすい形に整形するとこのような結果になっていることが確認できます(赤字はコメント):
{
 "search":{
  "results":334805, ヒットしたレコード数
  "current":100
 },
 "tweets":[ 個別のレコード(配列)
  {
   "message":{ ツイッターからの情報は message として取得できる
    "body":"*******", 本文
    "favoritesCount":0, お気に入り登録数
    "link":"http://twitter.com/****", 個別ツイートへのリンク
    "retweetCount":1, リツイート回数
    "twitter_lang":"en",
    "postedTime":"2013-11-15T16:45:53.000Z",
    "provider":{ ***** },
    "actor":{ ***** },
      :
   },
   "cde":{ IBMが付加した情報は cde として取得できる
    "content":{
     "sentiment":{"polarity":"NEUTRAL", "evidence":[] } 感情情報
    },
    "author":{
     "location":{"state":"North Carolina","country":"United States","city":""},
     "parenthood":{"evidence":"","isParent":"unknown"}, 親かどうかの情報
     "gender":"male", 性別
     "marialStatus":{"isMarried":"unknown","evidence":""} 結婚しているかどうかの情報
    }
   }
  },
  {
   "message":{
      :
  }
 ],
 "related":{
"next":{"href":"https://cdeservice.mybluemix.net/api/v1/messages/search?q=IBM&from=100&size=100"}
} }

詳しくはリファレンス内を参照していただきたいのですが、上記のような JSON フォーマットで検索結果が取得できています。

ちなみに検索文字列を "IBM sentiment:positive" にする場合は URL エンコードする必要があるので、
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM%20sentiment%3apositive
を指定することになります:
2015060201


余計な情報かもしれませんが、デフォルトではヒットしたレコードのうち、最初の100件だけを取り出します。この数を変えるには URL オプション size で指定します(例 size=200)。また100件目から150件目までの50件を取り出すには URL オプション from を使って from=100&size=50 のように指定します。

なお、個別のツイートは不要でヒットするレコード数だけを検索したい場合は search ではなく count を実行します:
 https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=IBM
2015060202


この場合の JSON はツイート情報を含まないため、かなりシンプルになります。こちらなら一見して分かりますね:
{
 "search":{"results":334805},
 "related":{"search":{"href":"https://cdeservice.mybluemix.net/api/v1/messages/search?q=IBM"}}
}


さて、ここまで分かれば後は今のオペレーションを PHP で(或いは別の言語で)実装するだけです。例えばこんな感じの内容(test1.php)にしてみます:
<html>
<head>
<title>Test 1</title>
</head>
<body>

<?php
// URLパラメータ q が指定されている場合のみ API を実行する if( isset( $_GET['q'] ) ){ $q = $_GET['q']; if( $q ){ ?> <h1><?php echo( $q ); ?></h1> <table border="1"> <tr> <th>Sentiment</th><th>Body</th><th>DateTime</th> </tr> <?php
// (username) と (password) は実際に有効なものを指定する $url = "https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/search?q=" . $q;
// PHP の file_get_contents 関数で実行
$text = file_get_contents( $url );
// JSON デコード $json = json_decode( $text ); $results = $json->search->results; // ヒット数 $tweets = $json->tweets;
// 各ツイート情報ごとにテーブルの1行を作る foreach( $tweets as $tweet ){ $message = $tweet->message; $cde = $tweet->cde; $body = $message->body; // 本文 $link = $message->link; // リンク $postedTime = $message->postedTime; // 日時 $polarity = $cde->content->sentiment->polarity; // 感情 ?> <tr> <td><?php echo( $polarity ); ?></td> <td><a target="_blank" href="<?php echo( $link ); ?>"><?php echo( $body ); ?></a></td> <td><?php echo( $postedTime ); ?></td> </tr> <?php } ?> </table> (#<?php echo( $results ); ?>) <?php } } ?> </body> </html>

これを動かしてみます。HTTP サーバー上にこのファイルを置いて、test1.php?q=IBM のような感じで、検索ワードを指定してアクセスすると、このような検索結果の画面になります。一列目に検索結果の各ツイートの感情が表示されます:
2015060203


一方、今度は指定したキーワードの感情ごとのヒット数を一覧にしてみます。以下の様な内容の test2.php を用意します:
<html>
<head>
<title>Test 2</title>
</head>
<body>

<?php
if( isset( $_GET['q'] ) ){
  $q = $_GET['q'];
  if( $q ){
?>
<h1><?php echo( $q ); ?></h1>
<table border="1">
 <tr>
  <th>Sentiment</th><th>#</th>
 </tr>
<?php
    $sentiments = array( 'positive', 'neutral', 'ambivalent', 'negative' );
    $url0 = "https://(username):(password)@cdeservice.mybluemix.net/api/v1/messages/count?q=" . $q . "%20sentiment%3a";
    foreach( $sentiments as $sentiment ){
      $url = $url0 . $sentiment;
      $text = file_get_contents( $url );
      $json = json_decode( $text );

      $results = $json->search->results;
?>
 <tr>
  <td><?php echo( $sentiment ); ?></td>
  <td><?php echo( $results ); ?></td>
 </tr>
<?php
    }
?>
</table>
<?php
  }
}
?>

</body>
</html>
これも動かしてみます。HTTP サーバー上にこのファイルを置いて、test2.php?q=IBM のような感じで、検索ワードを指定してアクセスすると、このキーワードを positive(ポジティブ) / neutral(中間) / ambivalent(混合) / negative(ネガティブ) の4つの感情ごとにヒット数を求めて、一覧表にして表示する、という内容になっています:

2015060204


このくらいになると「指定したキーワードが Twitter 上でどういう感情割合で使われているか」を調べるツールとして使えそうです。後はもう少し視覚的に工夫すれば、もう少し実用的になれそうですよね。


今回は PHP でのサンプルを紹介しました。とはいえ、PHP に依存しているのはこのエントリでも最後の部分だけです(PHP は file_get_contents 関数が使えるのですごく楽です)。他の言語でも同様の REST API を実行するだけですので、移植にも是非チャレンジしてみてください。



 

このページのトップヘ