ソーシャルグラフ最適化の先にある、ユーザー体験とデータ解析の結合

ソーシャルとWeb、あるいは検索エンジンとの関係について、SEO(検索エンジン最適化)、SMO(ソーシャルメディア最適化)をへて、SGO(ソーシャルグラフ最適化)から先につながる流れが、頭の中が整理できたので、新幹線の移動時間を利用してまとめてみた。

SEO (検索エンジン最適化)

Webは検索エンジンと共に発展してきた。検索で上位に表示されるページが、よいコンテンツと見なされ、検索ロボットが見つけやすいように、ページを最適化するSEO (Search Engine Optimization) が重視された。ブログやCMSによって、構造的なHTMLとWebサイト設計が普及し、インターネットであらゆる情報が見つかるという夢が広がった。

seo.png

SMO (ソーシャルメディア最適化)

Twitterの140文字制約という発言のしやすさと、新鮮な情報を早く知りたいというニーズが組み合わさり、リアルタイムストリームが普及した。検索エンジンのパーソナライズではなく、情報の流れを可視化する、という発想の転換。

smo.png

一人のユーザーがリアルタイムで見られる情報量は限られるので、フォローやハッシュタグ(Twitter)、実名の関係性とLike (Facebook)、サークル(Google+)など、新しいフィルタリング手法が開発された。また、情報をシェアしてもらうために、ユーザービリティやレスポンス向上に膨大な開発リソースが投入され、ソーシャルメディア上での関係性の構築 (Social Media Optimization) が重要になった。

スマートフォンによる日常生活のメディア化

スマートフォンとApp Internetの普及により、日常の空間が直接インターネットにつながった。位置情報やカメラ、マイク、センサーなどで集めた情報をスマートフォンが解析し、伝えたい人と共有したり、新たな情報や出会いの機会を提案するようになった。日常生活のあらゆる行動が、メディアとなる可能性を持ち始めた。

everyday-media.jpg

SGO (ソーシャルグラフ最適化)

Facebookは、Open Graph Protocolによって、Web上のコンテンツやユーザーの行動を、リアルタイムにAPIから利用できるようにした。その動きをさらに進め、日常生活の様々な行動をデータとして活用できるように、タイムラインとOpen Graph アプリの連携をリリースした。

facebook-og-app.jpg

すでに数多くのWebサイトやスマートフォンアプリに、ソーシャルプラグインという形で、Facebookがユーザーを追跡可能なビーコンが埋め込まれている。Open Graph APIによって、「料理した」「買った」「訪れた」「泊まった」「学んだ」など、より具体的な行動をリアルタイムに把握できるようになる。ユーザーが行動することでデータが集まり、共有されて、自立的にソーシャルグラフが拡大する。

sgo.png

ユーザー体験とデータ解析の結合

ソーシャルグラフの活用で先行するFacebookに、GoogleとAppleも、AndroidやiOSというプラットフォームを駆使して追従するだろう。ユーザーの関心(Interest Graph)と関係性(Social Graph)に基づいて、いつでも、どこでも(Mobile Graph)、リアルタイムにつながることで、素晴らしいユーザー体験が提供される。

graphs.png

ただし、その逆もまたしかり。あなたが彼らを見つけるように、彼らもあなたを見つけることができる。ユーザー体験とデータ解析は、分ちがたく結びつき、利便性とプライバシーの間で、アイデンティティが試されることになる。法律(コード)が日常生活に大きな影響を与えることが再認識され、群衆と政治の綱引きが繰り返される。

emacs の js2-mode のインデント修正版やjade-mode など

emacsのJavaScript用のメジャーモード、js2-modeをオリジナルのjs2-modeからフォークした、インデントや色々と不便な点を修正したバージョンに変更してみた。

オリジナルだと、こんな感じに間延びしたインデントが、

emacs JS2 mode before

きっちりと詰まってくれます。

emacs JS2 mode after

Macのターミナルから、


% git clone git://github.com/mooz/js2-mode.git
% cd js2-mode
% emacs --batch -f batch-byte-compile js2-mode.el

コンパイルされた、js2-mode.elc を emacsのsite-lispにコピー。emacs.el に以下を追記。


(autoload 'js2-mode "js2-mode" nil t)
(add-to-list 'auto-mode-alist '("\\.\\(js\\|json\\)$" . js2-mode))

ついでにnode.jsのWAF、Expressで標準採用されているテンプレートエンジン、Jadeのモードも追加してみる。

Githubからクローンして、


% git clone https://github.com/brianc/jade-mode.git        

*.elをsite-lispにコピー。以下を.emacs.el に追記。


(require 'sws-mode)
(require 'jade-mode)    
(add-to-list 'auto-mode-alist '("\\.jade$" . jade-mode))    

jadeファイルを開くとシンタックスハイライトしつつ、タブキーもよしなに動作してくれます。

emacs-jade.png

localhost環境で、Facebook のJavaScript APIでOAuth認証をする

FacebookのJavaScript SDKでOAuthを使ったアプリを、localhost環境で開発するための設定方法です。FacebookのJavascript SDKを利用すると、ログインしているユーザーやウォールの情報を取得できます。ただし、FB.loginを呼ぶ際に、Facebookがページのドメイン名をチェックするので、localhost/test.html のようなURLだと、「The specified URL is not owned by the application」というエラーが発生する。それを防ぐために、localhost.goodpic.com というようなサブドメインを、ローカルのホストに設定します。

まずは、Facebookでの設定。Facebook developers から、アプリを選択して、[Settings] > [Basic] を選択。 App Domain にサブドメイン無しのドメイン名(example.com)を入力する。ただし、その下の Website : Site URL: にhttpあるいはhttps から始まるサイトのURL(http://www.example.com) も忘れずに入力しておかないと、App Domainが正しく保存されません。

Facebookアプリをサブドメインで利用できるように

設定を保存したら、ローカル環境のhostsを書き換える。Macの場合はターミナルから、

% sudo emacs /private/etc/hosts    

以下を指定。

127.0.0.1       localhost local.goodpic.com

ターミナルから、hostsの情報をフラッシュする。

% sudo dscacheutil -flushcache    

例えば、test.htmlなどのHTMLファイルに以下を記入し、

<div id="fb-root"></div>
<script src="./facebook.js"></script>
<div id="fb-page"></div>

facebook.js ファイルでは、Facebook JavaScript ライブラリを非同期に読みこんでログイン処理をする。MAMPなど、ローカルでHTTPサーバを立ち上げて、http://local.goodpic.com/test.html にブラウザからアクセスすると、OAuth 認証が通った authResponse が正しく返ってきます。

window.fbAsyncInit = function() {
  FB.init({
    appId      : 'YOUR_APP_ID',
    channelUrl : 'YOUR_CHANNEL_URL',
    oauth      : true,
    status     : true, // check login status
    cookie     : true, // enable cookies
    xfbml      : true  // parse XFBML
  });
  // Additional initialization code here

  // Fetch facebook page infromartion
  FB.api('/215071268504237', function(res) {
    var widget = ['<h3>Page Information</h3><ul>'];

    for (var key in res) {
      widget.push('<li>',key,' = ',res[key],'</li>');
    }
    widget.push('</ul>');
    $('#fb-page').append(widget.join(''));
  });

  FB.login(function(res) {
    if (res.authResponse) {
      
      FB.api('/me', function(res) {
        console.log('Good to see you, ' + res.name + '.');
      });

      FB.api('/215071268504237/feed', function(res) {
        var widget = ['<h3>Page messages</h3>'];
        console.log(res);

        for (var i = 0, len = res.data.length; i < len; i++) {
          widget.push('<textarea style="width: 500px;" rows="5">', res['data'][i]['message'],'</textarea><br />');
        }
        widget.push('');
        $('#fb-page').append(widget.join(''));
      });

    } else {
      $('#fb-page').append('<p>Login required to browse page messages.</p>');
    }
  });
};

// Load the SDK Asynchronously
(function(d){
  var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
  js = d.createElement('script'); js.id = id; js.async = true;
  js.src = "//connect.facebook.net/ja_JP/all.js";
  d.getElementsByTagName('head')[0].appendChild(js);
}(document));