読者です 読者をやめる 読者になる 読者になる

FutureInsight.info

AI、ビッグデータ、ライフサイエンス、テクノロジービッグプレイヤーの動向、これからの働き方などの「未来」に注目して考察するブログです。

Google Suggestの動作原理 まとめ

パソコン・インターネット

翻訳のまとめです。翻訳してみたのはこのblogのこの記事です。Slashdot、Slashdot-jpで記事になってました。

あと、本文はこんなにファンキーじゃありません。

読み始める前に一度、Google Suggestを試してみると話がわかりやすいと思います。

      • 翻訳はじめ---

解読!!Google Suggest。

結構多くの人が「Google Suggestがどんな風に動くか」ってことには意見を交わしているけど、俺はこのものすげー凝り固まったJava Scriptを解体してやったぜ。
んなわけで、ちょっとしたweb developperならどんな風にGoogle Suggestが動いているか一目瞭然ってわけだ、、、そのソースの最終バージョンはここに置いといた。

俺はMozilla グループがwsdl-enabled SOAP clientをMozillaにぶち込んだ以来のクールなものを見ちまったよ。Google Suggestはあんたがなんか打ち込んだ文字に従ってその後の推測結果を返すんだけど、これがまたものすげーんだ。まぁ、大きく分けるとその理由は二つだ。

1、推測がめちゃくちゃはえーんだ。俺が、相当早く打ってみても、その一文字、一文字ごとにきっちり反応しやがる。

2、インターフェースもクールだ。俺は昔はサーバー側でのweb更新処理に賛成派で、JavaScriptなんてものには見向きもしなかったんだけどよ、gmailといい、Google Suggestといい、そいつらのスゲーインターフェースを見ちまったら、一気にクライアント処理側にお蔵替えしたよ。

おめーらみんなも、きっと、これみたらそうなるよ。俺はこのナイスなインターフェースの奥義を見てさらにぶっ飛んだんだぜ。

1、予測ワード達が検索ワードを入れるフィールドにきっちりおさまりやがる。

2、推測した部分をきっちり強調しやがる。(俺が"fa"っていれたら、こいつは"fast bugtrack"を推測するんだけど、この"st bugtrack"の部分が強調されてるんだ。っで、俺が次のワードを入れたらこいつらは消えやがる。、、すげーよ)

3、キーボード操作に対してもスゲー反応を返すしよ。

4、こいつらは、もし、おめーがバックスケープおしてもわざわざGoogleのサーバに戻らねーんだ、ちゃんとJavaScriptがキャッシュしてるわけよ。

5、あんたが結果をどのくらい早くゲットできるかってことに根ざしたメイン・ドリブン・ループをそのコードは動的に適応するんだぜ。

だからさー、おれはこのインターフェースがどうなってんのかしりたいわけよ。Googleの中のすげー人たちが書いたものを、みんなで今夜調べようぜ、で、俺はまだ書いてないけどよ、あんたと少しずつ理解してーんだ。んで、その結果が俺達みーんなの理解の助けになるってわけだ。

じゃぁ、まず、おれがどういう風に中身を解析したかってことをちょっくらまとめるぜ。

1、まず、おれはGoogle SuggestのhtmlとJavaScriptを保存し、、、で、なんとかそれを俺のコンピュータ上でも動くようにした。次に、わかりにくい変数名とか関数名を改名し、それがちゃんとはじめと同じように動いているかどうかを調べるため、いくつかalartを組み込んだんだ。

2、Google Suggestのコードでは、Googleにコールバックをかけるときに、XMLHttpオブジェクトを使って、結果を取得してるんだけどよ、完全にこのコードを理解するためには、俺はGoogleがコールバックをかけたときに一体何をを送り返してくるのか、理解する必要があったんだ。
だけどだ、おれが、コールバックをかけてくれるはずのurlを直接打ち込んでも、Googleは何にも返してくんねえ、あの404が返ってくるだけだ。(まぁ、これは後で俺が間違ったurlを打ってたってことがわかったんだけど、、、)で、俺は愛用のブラウザーをProxyを通して、アクセスをするように設定しなおした。でも、XMLHttpオブジェクトはプロキシーを通してる場合はつかえーねーこともわかった。(でもこれは、おまえらがプロキシーを通していても、Google Suggestが働かなくなるってことだろ、、、この話、本当にあってんのか?)まぁ、結局、俺はパケットをモニターするなんてことを始める前にurlのスペルミスに気づいたってわけだ。

じゃ、まぁ、とりあえず、Google Suggestのメインコードを見てくれ、、、そうすりゃ、このソースコードの最後らへんにあのダイナミックなインターフェースを提供してるJavaScriptの場所がわかるだろう(まぁ、これだよ)。

まったくGoogleのなかの素敵なやつらは、このコードを、それが自分たちの仕事かのようにくそったれに圧縮してくれたので、、、まず、最初にそれを理解するため、俺はこんな感じでインデントを入れたわけだ。そっから、何のためのグローバル変数なのか、それぞれ関数は何をしてんのかを解明するっていう、おもしれー作業を初めて、、、んで、そいつらをもっとわかりやすい名前に変えたわけだ。おれは、なんとかそいつらをプリチーにしてやったぜ。書き換えた、最終バージョンがこれだ。

この課程を通して、俺は以前には知らなかったいくつかのことを学んだよ:

1、俺らはブラウザの自動補完機能をautocomplete="off"こんな感じで、検索キーを打ち込む領域のautocomplete属性をoffにしてやれば、切ることができるんだ、、、まぁ、これをどうやってこの作業する前に知るかって話だけどよ。

2、通信のためのXMLHTTP,XMLHttpRequestオブジェクトは、サーバーに戻って、その後で、新しい情報とかを取得してくるんだけど、これはページを更新しなくてもできるんだぜ、、、まぁ、Web開発の基本だけどよ、、、これに関しては、いくらでも情報はころがってるよ。

3、どうやってこのパワフルなキーハンドリングを実現してるかってこと(keyup/keydownとかeventsとかcursor key eventsの状態変化とかを補足してるんだけどよ。)

4、んで、例のどうやってJavaScriptで検索キー入力箇所の文字を強調するかってこと。

最後にどういう風に動いてるのかをみてみよう。

まずhtmlがInstallAC関数を呼び、システムを初期化する、、、これは興味深いコードだぜ、

var Jb="zh-CN|zh-TW|ja|ko|vi|";

Google SuggestはEnglishしかサポートしないっていってる一方で、しっかりJapanとかKoreaとかChinaとかを認識し、リクエストをハンドルしてるんだ。

installAC関数はもう一つのinstallACPart2って俺が名付けた関数を呼ぶ。この関数は俺たちが使っているブラウザがXMLHttpをサポートしているかを確認し、俺が"_completeDiv"って呼んでる変数を作るんだ。こいつは俺らがgoogleからデータを取得したときの、その推測した結果を持ってるんだけど、こいつは検索キー入力フィールドに完璧に収まる形でそいつらを並べるんだ、最初は表示されないけどな。
installACPart2関数はいくつかのKey downとかresizeとかのイベントハンドラもセットする。こいつが、動的にGooglに対してリクエストを行い、URLが生成されるってわけだ。

俺がmainLoopって呼んでる関数は、繰り返しJavaScriptで書かれたsetTimeout関数を呼ぶんだ、おもしれーことに、設計者はキーボードのイベントをハンドルするよりも、タイムアウトを設定する機構を組み込んだ。これがおせー通信環境でも、はえータイプを何とかするメカニズムってワケよ。(例えばよ、おれがタイムアウトが起きることなく、すばやく3文字を打ち込んだら、その後で、一回だけ、googleからデータを取得するわけよ。)
このmainLoopは検索キー入力フィールドが何か書き込まれたかどうかを常にチェックしてる、んで、もし書き込みがあったらまずそこに同じ文字が書き込まれていないか結果を調べて、そのあと、googleからデータを取得するわけだ。Google Suggestのコードでは、XMLHttpオブジェクトをサポートしていない古いブラウザでもクッキーとフレームレローディングを駆使して、このへんのことをうまくやるらしいぜ。(このへんはおれもまだ試してないけどな、、、)

callGoogle関数の実装は率直で簡単だ。俺でも、その流れを書けるぜ。
(例えば、俺がEnglish圏で、"fast bug"ってタイプしてたらよ)
http://www.google.com/complete/search?hl=en&js=true&qu=fast%20bug
その時、URLはこうなるだろ。で、イベント駆動型のcallback _xmlHttp.onchangeファンクションを呼ばれて、こいつは、googleからデータを取得するために、最初から最後までJavaScriptでかけちまうような単純な処理をする、で、こんな感じのものをよぶわけだ。

sendRPCDone(frameElement, "fast bug", new Array("fast bug track", "fast bugs", "fast bug", "fast bugtrack"), new Array("793,000 results", "2,040,000 results", "6,000,000 results", "7,910 results"), new Array(""));

このsendRPCDoneって関数はac.jsのファイルの中で定義されてるんだけどよ、こいつがmainLoopの中でうまく同期を行い、受け取った結果をキャッシュにぶち込み、_completeDivを作る、、、DIVは結果の配列であり、最終的に表示されるものなわけだ。

displaySuggestedList関数は、その結果を受け取って、表示のためのワンセットのDOM構造のDIVとSPANを動的に生成する、で、こいつが推測結果を表示する最終的な形ってわけだ。さっき場合だと、listの要素やデータ構造はこんな感じになる。(ここでは(x)がコード内での変数な。)

<br /> <DIV (u) - mousedown/mouseover/mouseout class="aAutoComplete"><br /> <SPAN (ka) class="lAutoComplete"><br /> <SPAN (ua) class="cAutoComplete"><br /> bug tracking<br /> </SPAN (ua)><br /> <SPAN (ea) class="dAutoComplete"><br /> 500,000 results<br /> </SPAN (ea)><br /> </SPAN><br /> </DIV (u)><br /> </plaintext></p><p>んで、最後にPa関数(こいつは満足できる名前じゃねーな)は推測結果をうけとったり、なんかキーが押されたりしたら(たぶんマウス関連のイベントも補足してると思うよ)まだ、打たれてねー文字に対して強調をかけるってわけだ。</p><p>いままで書いたものをみたら、あんたらは自分自身で完璧にコードを解読しようと思うかもしんない、、、そん時に、なんか質問でもコメントでもあったら教えてくれ。俺がタイプミスをなおしたり、変数とか関数の名前を変えるいい機会になるからよ。<br /> </p> <ul> <li> <ul> <li> <ul> <li>翻訳終わり---</li> </ul></li> </ul></li> </ul><p>インターフェース的にはおもしろい内容でしたが、リクエストに対してどんな形でGoogleの中の人がデータベースを処理しているかの内部的な実装も知りたいです。でもGoogle Suggestの内部は公開されないだろうなぁ。</p> </div> <footer class="entry-footer"> <p class="entry-footer-section"> <span class="author vcard"><span class="fn" data-load-nickname="1" data-user-name="gamella">gamella</span></span> <span class="entry-footer-time"><a href="http://futureinsight.info/entry/20041227/1104115995"><time data-relative datetime="2004-12-27T02:53:15Z" title="2004-12-27T02:53:15Z" pubdate class="updated">2004-12-27 11:53</time></a></span> </p> <div class="hatena-star-container"> </div> <div class="hatena-star-metadata" style="display: none"> <a class="hatena-star-permalink" href="http://futureinsight.info/entry/20041227/1104115995">Google Suggestの動作原理 まとめ</a> </div> <div class="social-buttons"> <div class="social-button-item"> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/20041227/1104115995" class="hatena-bookmark-button" data-hatena-bookmark-layout="vertical-balloon" data-hatena-bookmark-lang="ja" title="この記事をはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only.gif" alt="この記事をはてなブックマークに追加" width="20" height="20" style="border: none;" /></a> </div> <div class="social-button-item"> <div class="fb-share-button" data-layout="box_count" data-href="http://futureinsight.info/entry/20041227/1104115995"></div> </div> <div class="social-button-item"> <a href="https://twitter.com/search?q=http%3A%2F%2Ffutureinsight.info%2Fentry%2F20041227%2F1104115995" class="social-button-twitter-balloon" target="_blank"> <span class="social-button-twitter-balloon-list">list</span> <i class="social-button-twitter-balloon-arrow-border"></i> <u class="social-button-twitter-balloon-arrow"></u> </a> <a href="https://twitter.com/share" class="twitter-share-button" data-url="http://futureinsight.info/entry/20041227/1104115995" data-count="vertical" data-text="Google Suggestの動作原理 まとめ - FutureInsight.info" data-lang="ja">Tweet</a> </div> <div class="social-button-item"> <div class="g-plusone" data-size="tall" data-href="http://futureinsight.info/entry/20041227/1104115995"></div> </div> <span> <script type="text/javascript" src="//media.line.me/js/line-button.js?v=20140411" ></script> <script type="text/javascript"> new media_line_me.LineButton({"pc":false,"lang":"ja","type":"e"}); </script> </span> <div class="social-button-item"> <a data-pocket-label="pocket" data-save-url="http://futureinsight.info/entry/20041227/1104115995" data-pocket-count="vertical" class="pocket-btn" data-lang="en"></a> </div> </div> <div class="customized-footer"> <div class="entry-footer-html"><script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- hatena-bottom2 --> <ins class="adsbygoogle" style="display:inline-block;width:336px;height:280px" data-ad-client="ca-pub-8316025729440240" data-ad-slot="8180944571"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- hatena-bottom3 --> <ins class="adsbygoogle" style="display:inline-block;width:336px;height:280px" data-ad-client="ca-pub-8316025729440240" data-ad-slot="9657677773"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></div> </div> <!-- enjapan ad --> </footer> </div> </article> <!-- rakuten_ad_target_end --> <!-- google_ad_section_end --> <div class="permalink pager"> <span class="pager-prev"> <a href="http://futureinsight.info/entry/20041228/1104163596" rel="prev"> <span class="pager-arrow">&laquo; </span> 電車男の落としどころ </a> </span> <span class="pager-next"> <a href="http://futureinsight.info/entry/20041227/1104114000" rel="next"> Google Suggestの動作原理 3 <span class="pager-arrow"> &raquo;</span> </a> </span> </div> </div> </div> <aside id="box1"> <div id="box1-inner"> </div> </aside> </div><!-- #wrapper --> <aside id="box2"> <div id="box2-inner"> <div class="hatena-module hatena-module-recent-entries "> <div class="hatena-module-title"> <a href="http://futureinsight.info/archive"> 最新記事 </a> </div> <div class="hatena-module-body"> <ul class="recent-entries hatena-urllist urllist-with-thumbnails"> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="http://futureinsight.info/entry/2016/11/02/192022" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title ">近況報告とAlpacaで機械学習エンジニアを募集のお知らせ </a> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/2016/11/02/192022" class="bookmark-widget-counter"> <img src="https://b.hatena.ne.jp/entry/image/http://futureinsight.info/entry/2016/11/02/192022" alt="はてなブックマーク - 近況報告とAlpacaで機械学習エンジニアを募集のお知らせ" /> </a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="http://futureinsight.info/entry/2016/06/10/224322" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title ">「人工知能ベンチャー企業が打ち出すテクノロジー/未来 Meet up 第1回」で発表します </a> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/2016/06/10/224322" class="bookmark-widget-counter"> <img src="https://b.hatena.ne.jp/entry/image/http://futureinsight.info/entry/2016/06/10/224322" alt="はてなブックマーク - 「人工知能ベンチャー企業が打ち出すテクノロジー/未来 Meet up 第1回」で発表します" /> </a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a href="http://futureinsight.info/entry/2016/05/20/210349" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title ">フィンテック × AIのスタートアップAlpacaにJoinして1年たったので近況報告 </a> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/2016/05/20/210349" class="bookmark-widget-counter"> <img src="https://b.hatena.ne.jp/entry/image/http://futureinsight.info/entry/2016/05/20/210349" alt="はてなブックマーク - フィンテック × AIのスタートアップAlpacaにJoinして1年たったので近況報告" /> </a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="http://futureinsight.info/entry/2016/03/21/081754"> <img alt="AIブームの説明だけではなく、2030年に向けた思考実験の基盤を提供してくれる本「商品の詳細 人工知能は私たちを滅ぼすのか―――計算機が神になる100年の物語」" src="https://cdn.image.st-hatena.com/image/square/be3d0b51f7e88ed749bc7c9268f61329b58b8fcd/backend=imagemagick;height=40;version=1;width=40/http%3A%2F%2Fecx.images-amazon.com%2Fimages%2FI%2F51l7WGk3DGL.jpg" class="urllist-image recent-entries-image" title="AIブームの説明だけではなく、2030年に向けた思考実験の基盤を提供してくれる本「商品の詳細 人工知能は私たちを滅ぼすのか―――計算機が神になる100年の物語」" width="40"> </a> <a href="http://futureinsight.info/entry/2016/03/21/081754" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title ">AIブームの説明だけではなく、2030年に向けた思考実験の基盤を提供してくれる本「商品の詳細 人工知能は私たちを滅ぼすのか―――計算機が神になる100年の物語」 </a> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/2016/03/21/081754" class="bookmark-widget-counter"> <img src="https://b.hatena.ne.jp/entry/image/http://futureinsight.info/entry/2016/03/21/081754" alt="はてなブックマーク - AIブームの説明だけではなく、2030年に向けた思考実験の基盤を提供してくれる本「商品の詳細 人工知能は私たちを滅ぼすのか―――計算機が神になる100年の物語」" /> </a> </div> </li> <li class="urllist-item recent-entries-item"> <div class="urllist-item-inner recent-entries-item-inner"> <a class="urllist-image-link recent-entries-image-link" href="http://futureinsight.info/entry/2016/02/24/230443"> <img alt="リモートワークとAlpacaの新オフィスの話" src="https://cdn.image.st-hatena.com/image/square/72736e35b757fc52b44d23e2fd169df17f3d5a39/backend=imagemagick;height=40;version=1;width=40/http%3A%2F%2Fecx.images-amazon.com%2Fimages%2FI%2F41nDcp8X7fL.jpg" class="urllist-image recent-entries-image" title="リモートワークとAlpacaの新オフィスの話" width="40"> </a> <a href="http://futureinsight.info/entry/2016/02/24/230443" class="urllist-title-link recent-entries-title-link urllist-title recent-entries-title ">リモートワークとAlpacaの新オフィスの話 </a> <a href="http://b.hatena.ne.jp/entry/http://futureinsight.info/entry/2016/02/24/230443" class="bookmark-widget-counter"> <img src="https://b.hatena.ne.jp/entry/image/http://futureinsight.info/entry/2016/02/24/230443" alt="はてなブックマーク - リモートワークとAlpacaの新オフィスの話" /> </a> </div> </li> </ul> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> <!-- --> <ins class="adsbygoogle" style="display:inline-block;width:300px;height:600px" data-ad-client="ca-pub-8316025729440240" data-ad-slot="1151589378"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-title">このブログについて</div> <div class="hatena-module-body"> <p>Tomoya Kitayama(@gamella)が運営している物欲系ブログ。書評からガジェット、テクノロジーからライフハックまでなんでもネタにします。書評・ガジェットレビューなどのご依頼はtkitayama(at)gmail.comまで。ただし、お受けできないこともあります。献本はご連絡のうえ、電子書籍でお願いいたします。</p> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> Facebookページもやってます! <div class="fb-like-box" data-href="https://www.facebook.com/futureinsight.info" data-width="300" data-height="400" data-colorscheme="light" data-show-faces="true" data-header="true" data-stream="false" data-show-border="true"></div> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> <a href="https://plus.google.com/110814829100595115485" rel="publisher">Google+</a>もやってます! <!-- --> <div class="g-page" data-href="//plus.google.com/u/0/110814829100595115485" data-rel="publisher"></div> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-body"> <a class="twitter-timeline" href="https://twitter.com/gamella" data-widget-id="461484675626586112">@gamella からのツイート</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script> </div> </div> <div class="hatena-module hatena-module-entries-access-ranking" data-count="10" data-display_entry_category="0" data-display_entry_image="1" data-display_entry_image_size_width="50" data-display_entry_image_size_height="50" data-display_entry_body_length="0" data-display_entry_date="0" data-display_bookmark_count="1" data-source="access" > <div class="hatena-module-title"> 注目記事 </div> <div class="hatena-module-body"> </div> </div> <div class="hatena-module hatena-module-html"> <div class="hatena-module-title">ブログ内検索</div> <div class="hatena-module-body"> <form action="http://www.google.co.jp/cse" id="cse-search-box"> <div> <input type="hidden" name="cx" value="partner-pub-8316025729440240:5858639779" /> <input type="hidden" name="ie" value="UTF-8" /> <input type="text" name="q" size="30" /> <input type="submit" name="sa" value="検索" /> </div> </form> <script type="text/javascript" src="http://www.google.co.jp/coop/cse/brand?form=cse-search-box&amp;lang=ja"></script> </div> </div> <div class="hatena-module hatena-module-archive" data-archive-type="default" data-archive-url="http://futureinsight.info/archive"> <div class="hatena-module-title"> <a href="http://futureinsight.info/archive">月別アーカイブ</a> </div> <div class="hatena-module-body"> </div> </div> </div> </aside> </div> </div> </div> </div> <script type="text/javascript">!function(d,i){if(!d.getElementById(i)){var j=d.createElement("script");j.id=i;j.src="https://widgets.getpocket.com/v1/j/btn.js?v=1";var w=d.getElementById(i);d.body.appendChild(j);}}(document,"pocket-btn-js");</script> <script src="https://s.hatena.ne.jp/js/HatenaStar.js"></script> <div id="fb-root"></div> <script>(function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) return; js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&appId=719729204785177&version=v2.7"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk'));</script> <script id="hatena-counter-script" type="text/javascript"><!-- hatena_counter_name = "gamella"; hatena_counter_id = "101"; hatena_counter_ref = document.referrer+""; hatena_counter_screen = screen.width + "x" + screen.height+","+screen.colorDepth; //--></script> <script type="text/javascript" src="http://counter.hatena.ne.jp/js/counter.js"></script> <noscript><img src="http://counter.hatena.ne.jp/gamella/101" border="0" alt="counter"></noscript> <script type="text/javascript" src="https://platform.twitter.com/widgets.js"></script> <script src="https://apis.google.com/js/platform.js" async defer> { lang: 'ja', "parsetags": "explicit" } </script> <script type="text/javascript" src="https://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/react-with-addons.min.js?version=0.14.7"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/react-dom.min.js?version=0.14.7"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/jquery.min.js?version=1.12.3"></script> <script type="text/javascript" src="//cdn7.www.st-hatena.com/js/jquery/jquery-ui.1.10.0.custom.min.js"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/jquery.flot.js?version=0.8.3"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/external/jquery.flot.time.js?version=0.8.3"></script> <script id="hatenablog-js" data-env="production" type="text/javascript" src="https://cdn.blog.st-hatena.com/js/hatenablog.js?version=87654a97f0556b482b8ffce533022207" crossorigin="anonymous"></script> <script type="text/javascript" src="https://cdn.blog.st-hatena.com/js/texts-ja.js?version=87654a97f0556b482b8ffce533022207"></script> <script type="text/javascript">Hatena.Diary.GlobalHeader.init()</script> <script src="https://www.google.com/recaptcha/api.js" async defer></script> </body> </html>