広告

posted by fanblog
「ここにブログの名前を入れます」は更新を終了しました。記事はすべて新ブログ「Big Bang」に移転済みです。記事のタイトルをクリックすると新ブログの該当記事に移動します。そちらでお楽しみください。

JSONPでTwitter API searchメソッド

今回もWebService/Twitter/API - わすWikiを参考にさせていただきました。

先日の記事「Twitter API 愛の劇場のソース」では、Twitterのuser_timelineメソッドをPHPで呼び出し、simplexmlを使って処理しました。

しかし、記事中に書いてあるようにソースに不備があります。

書き直すに当たり、次の変更を行ないました。

  1. searchメソッドを使って検索する
  2. PHPを使わず、JavaScriptとJSONPだけで構成する。

前回のソースは、とりあえずTwitter APIがどんなものか動かしてみるレベルの内容です。
いいコードとはとてもいえないものでした。今回は使う開発言語を減らすことで、コードがスッキリしました。

アクセス制限の問題もあります。1時間に150アクセスまでなので、PHPを置いたサーバーからTwitterのサーバーにアクセスするより、JavaScriptを読み込んだ各ブラウザからTwitteのサーバーにアクセスさせる方がIPを分散できます。

さらに、前回はリレー小説の書き手分user_timelineメソッドを呼び出していました。今回はsearchメソッドで、Twitterのサーバーに問い合わせる回数を減らします。

search API

http://search.twitter.com/search.json

赤字のjsonatomにすると、Atomが返ってきます。

http://search.twitter.com/search.json?q=検索クエリー&callback=コールバック関数名&result_type=recent&rpp=100&since_id=200851367760363520&page=1

参考記事:GET送信とは?

必須パラメーターはqです。
q=には検索クエリーをURLエンコードして送信します。検索クエリーの書き方は後述します。

callback=にはJSONPのコールバック関数を指定します。このオプションはatomの時には適用されません。

result_type=には、欲しい検索結果の種類を指定します。指定できるのは以下の3つ。指定しないとrecentが指定されたものとみなされます。

result_typeに指定できる値
recent   「新しい発言」を優先的に検索結果に含める
popular 「人気の高い発言」を優先的に検索結果に含める
mixed    「新しい発言」と「人気の高い発言」の両方を適度に検索結果に含める

rpp=には、一度に取得する検索結果の数を指定します。指定できる最大値は100、未指定だと15になります。

since_id=は指定したIDより大きなIDのステータスのみ取得するオプションです。検索に指定IDそのものは含まれません。

page=1ページを rpp 件とみなして取得するページを指定します。最初のページは1です。また、rppとpageで遡れるのは、最大1500件までです。

他にもたくさんのオプションがあり、WebService/Twitter/API - わすWikiにまとめられています。


ところが、今回も問題がありました。ある日から1話目が表示されなくなったのです。
どうも、約10日(1.5週)以上前のつぶやきは検索されないようです。

また、利用できる検索インデックスについても日数の制限をかけています。現在は、1.5週となっていますが1日のささやきが増加するとこの数字はもっと短くなります。
Twitter ビジネスユーザー向けブログ » Twitter APIで開発をするために知っておくべきことより

ということで、またスクリプトを修正することになるのですが、それは別の記事にして、この話を続けます。

検索クエリーの書き方

今回の検索クエリはこうなりました。

from:Tom3suteki OR from:welcome2ourshop -RT つづく

from:Tom3suteki で、投稿者がTom3sutekiのつぶやきを検索します。

リレー小説なので、投稿者が2人います。from:Tom3suteki または from:welcome2ourshop という場合は、ORで結びます。

from:Tom3suteki OR from:welcome2ourshop

ORは大文字で、前後に半角スペースを入れます。

検索から除外したい文字列の前には - (ハイフン)をつけます。ハイフンと文字列の間にはスペースを入れずにくっつけます。

-RT とすることでRTを含む文字を除外します。つまり、リツィートの除外を指定したことになります。

つづくなどと、何もつけない文字列を指定すると、その文字列を含んだツィートを探します。

つまり「from:Tom3suteki OR from:welcome2ourshop -RT つづく」は、「投稿者がTom3sutekiかwelcome2ourshopでRTを含まず(リツィートを含まず)、つづく という文字列があるツィートを探せ」という意味です。

これを、URLエンコードした上で、q=に続けて書きます。

ソースコード

以上をふまえて、書き直した「Twitterで愛の劇場」のソースコードが以下のものです。

<!-- 表示領域 -->
<div id="twitter_novel_area" style="width:100%"></div>

<script type="text/javascript">
var head=document.getElementsByTagName('head');

//専用スタールシートの読み込み
var css_url='http://api.dwm.me/twitter/twitter_novel_viewer.css';
var css=document.createElement('link');

css.setAttribute('type','text/css');
css.setAttribute('rel','stylesheet');
css.setAttribute('href',css_url);

head[0].appendChild(css);

//JavaScriptの読み込み
var js_url='http://api.dwm.me/twitter/twitter_novel_viewer.js';
var script=document.createElement('script');

script.setAttribute('type','text/javascript');
script.setAttribute('src',js_url);

head[0].appendChild(script);
</script>

読み込むJavaScript

//データ並び替え関数 詳細は「JavaScriptで表の並び替え」参照
var swap=function(id){
	var table=document.getElementById(id);
	if(!table){return;}

	var tr=table.getElementsByTagName('tr');
	var min=0,max=tr.length-1;

	while(min<max){
		var td_min=tr[min].getElementsByTagName('td');
		var td_max=tr[max].getElementsByTagName('td');
		var tmp1=td_min[0].innerHTML;
		var tmp2=td_min[1].innerHTML;
		var tmp3=td_min[2].innerHTML;
		td_min[0].innerHTML=td_max[0].innerHTML;
		td_min[1].innerHTML=td_max[1].innerHTML;
		td_min[2].innerHTML=td_max[2].innerHTML;
		td_max[0].innerHTML=tmp1;
		td_max[1].innerHTML=tmp2;
		td_max[2].innerHTML=tmp3;
		min++;
		max--;
	}
}

var twitter_novel_viewer_properties=null;

//コールバック関数
var twitter_novel_viewer_callback=function(json){
	var results=json.results;
	var table=twitter_novel_viewer_properties.table;
	var url_base="https://twitter.com/#!/";
	var url_search="http://search.twitter.com/search?q=";

	if(!table){return;}

	//テーブルにデータを挿入
	for(var i=0;i<results.length;i++){
		var tr=document.createElement('TR');
		var td0=document.createElement('TD');
		var td1=document.createElement('TD');
		var td2=document.createElement('TD');

		td1.innerHTML='<a href="'
			+url_base+results[i].from_user
			+'" target="_blank"><img src="'
			+results[i].profile_image_url
			+'"/></a>';

		td2.innerHTML='<a href="'
			+url_base+results[i].from_user
			+'" target="_blank" class="name">'
			+decodeURIComponent(results[i].from_user_name)
			+'</a>&nbsp;<a href="'+url_base+results[i].from_user
			+'" target="_blank" class="screen_name">@'
			+results[i].from_user+'</a><br/>'
			+decodeURIComponent(results[i].text)
			.replace(/\\/g,'')
			.replace(/(https?:\/\/\S+)/g,
				'<a href="$1" target="_blank">$1</a>')
			.replace(/(#\S+)/g,function(arguments){
				return '<a href="'+url_search
					+encodeURIComponent(arguments[1])
					+'" target="_blank">'
					+arguments[1]+'</a>';
			})
			.replace(/@(\S+)/g,'<a href="'
			+url_base+'$1" target="_blank">@$1</a>')
			.replace(/つづく$/,'<em>つづく</em>');

		tr.appendChild(td0);
		tr.appendChild(td1);
		tr.appendChild(td2);

		table.appendChild(tr);
	}

	//もし次のページがあったら、
	//そのページを読み込んでページがなくなるまで再帰
	if(json.next_page){
		twitter_novel_viewer_properties.jsonp.parentNode
			.removeChild(twitter_novel_viewer_properties.jsonp);

		twitter_novel_viewer_properties.jsonp
			=document.createElement('script');
		twitter_novel_viewer_properties
			.jsonp.setAttribute('type','text/javascript');
		twitter_novel_viewer_properties
			.jsonp.setAttribute('src',
				twitter_novel_viewer_properties
				.rest_url+json.next_page
				+'&callback=twitter_novel_viewer_callback');

		var head=document.getElementsByTagName('head');
		head[0].appendChild(twitter_novel_viewer_properties.jsonp);
	}else{
		//次のページがなくなったら、
		//テーブルのいちばん左のカラムに番号を割り当て
		var tr=table.getElementsByTagName('tr');

		for(var i=0;i<tr.length;i++){
			tr[i].getElementsByTagName('td')[0]
				.innerHTML=tr.length-i;
		}
	}
}

function twitter_novel_viewer(properties){
	if(!properties
		||!properties.id
		||!properties.rest_url
		||!properties.query
		||!properties.callback){return null;}

	var elem=document.getElementById(properties.id);
	var head=document.getElementsByTagName('head');

	if(!elem||!head.length){return null;}

	//検索クエリー作成
	twitter_novel_viewer_properties
		={'page':0,'rpp':15,'rest_url':properties.rest_url};

	twitter_novel_viewer_properties.rest=properties.rest_url
		+'?q='+encodeURIComponent(properties.query)
		+'&callback='+properties.callback;

	if(properties.rpp){
		twitter_novel_viewer_properties.rpp=properties.rpp;
		twitter_novel_viewer_properties.rest
			+='&rpp='+properties.rpp;
	}

	if(properties.result_type
		&&properties.result_type.match(/^(recent|popular|mixed)$/)){
		twitter_novel_viewer_properties.rest
			+='&result_type='+properties.result_type;
	}

	if(properties.sincs_id&&properties.since_id.match(/^[0-9]{18}$/)){
		twitter_novel_viewer_properties.rest
			+='&since_id='+properties.since_id;
	}

	//書き込み用表テーブルの<ベースを作成
	var table=document.createElement('TABLE');
	var tbody=document.createElement('TBODY');
	var caption=document.createElement('CAPTION');

	table.setAttribute('id','twitter_novel_viewer');
	table.setAttribute('style','width:100%');

	caption.setAttribute('style','width:100%;text-align:center');
	caption.innerHTML
		='Twitter愛のリレー小説<br/>'
			+'<span>「セルヴーズの雨傘」</span>';

	table.appendChild(caption);
	table.appendChild(tbody);

	elem.innerHTML='<p style="text-align:right">'
		+'<input type="button" value="古いのを上にする" '
		+'onclick="swap(\'twitter_novel_viewer\');'
		+'this.value=this.value==\'古いのを上にする\''
		+'?\'新しいのを上にする\':\'古いのを上にする\';"/>'
		+'</p>';

	elem.appendChild(table);

	twitter_novel_viewer_properties.table=tbody;

	//JSONPを要求するためヘッダにscriptタグ挿入
	twitter_novel_viewer_properties.jsonp
		=document.createElement('script');
	twitter_novel_viewer_properties
		.jsonp.setAttribute('type','text/javascript');
	twitter_novel_viewer_properties
		.jsonp.setAttribute('src',
			twitter_novel_viewer_properties.rest+'&page=1');

	head[0].appendChild(twitter_novel_viewer_properties.jsonp);
}

//クエリ作成用データ
var properties={
	'id':'twitter_novel_area',
	'rest_url':'http://search.twitter.com/search.json',
	'query':'from:Tom3suteki OR from:welcome2ourshop -RT つづく',
	'callback':'twitter_novel_viewer_callback',
	'rpp':100,
	'result_type':'recent',
	'since_id':'200851367760363520',
	'css_href':'http://in/twitter_novel_viewer.css'
}

twitter_novel_viewer(properties);

上記ソースを実際に適用したのが「Twitterで愛の劇場」です。

新ブログ「Big Bang」で続きを読む