2007-02-28

「ニコニコ動画はYouTubeにとって脅威になったのでアクセス拒否された」みたいな論調に話を持って行きたがる人たちについて

割とどうでもいいとは思ってるんだけど書いておくことにする。ここら辺読んで思ったこと。

http://shi3z.cocolog-nifty.com/blog/2007/02/youtubeweb20_0171.html
http://blog.livedoor.jp/lalha/archives/50154713.html
http://mindclip.blog55.fc2.com/blog-entry-121.html

通常の利用頻度でAPI使ってて他は大丈夫なのに自分だけアクセス拒否された!ってことなら、敵視されてるんじゃないか
とかそういう陰謀論が起こるのも理解できるんだけど。

「アクセス拒否=敵視されている」みたいな発想が短絡的だと思う。利用方法に問題があって異常なアクセスがあれば、普通にアクセス拒否すると思うんだけど。敵視してるとかそういうのとは全く関係なしに。

YouTubeの動画を全画面表示したり字幕くっつけたりするのを作るには、動画の元ファイル(flv)のURLを知る必要がある。これはAPI使ってるだけだと出来ないので、スクレイピングというやつをする。具体的には動画の貼られているURLにアクセスして、一定期間有効らしいトークン文字列を抜き出して、get_videoにvideo_idとトークン文字列をわたすとflvのURLにリダイレクトされる、ということらしい。

少なくともニコニコ動画は通常のAPI利用じゃない、はずだ。公開されてるAPIを使ってて蹴られたっていうならケチくさいな、と文句言えばいいけど、スクレイピングしてやってるんだから、いつアクセス拒否されてもおかしくないし、それはYouTube側の当然の権利だろう。flvの直接利用ってのもデリケートな問題だ。ビデオファイルを直接保存されてしまうと、アップロードした権利者のコントロールが及ばなくなる。

この辺り参考になる。
http://saqoosha.net/2007/02/23/466/

ニコニコ動画の担当者のインタビューというのを見つけたんだけど、これは公式な発言と捉えていいのかな。
http://ascii.jp/elem/000/000/020/20245/

Q YouTube側からアナウンスはあったか?
A 特にない。こちらでアクセスが遮断されているのに気づいた。

Q YouTubeの動画をAPIで呼び出す際の約款上、何か問題はなかったか?
A それはなかったと認識している

自分たちのやり方に問題はなくて、一方的にアクセス拒否された、みたいなことを言いたいらしい。
ほんとに?スクレイピングしてないの?flvのURLを取り出す隠しAPIとかあるのかな。何コールまで許されてるの?
そんなのあったらビデオダウンロードされまくりで回線を占有されてしまってYouTubeが全く使い物にならなくなると思うんだけど。

かといって、APIがないから使っちゃダメ、とか、そういうことを言いたいわけでもなくて。

著作権とか二次利用の問題、サーバー負荷の問題とかいろいろな問題を抱えることがあって、とりあえず試しにスクレイピングで作ってみて、それが便利だったり面白かったりしたら、フィードを提供しましょう、APIを作りましょう、とか、そういう友好的で発展的な話になる場合もある。コストがかかるようなら提供先を制限したり、あるいは有料で提供したり、といった話になる。

いぬビームがgreasemonkeyを書いたらはてながAPI作りました、とかね。極端に分かりづらい例だけど。スクレイピングで大量にアクセスされるよりは、負荷の軽いAPIを作ったのでそっち使ってね、みたいなことだってあり得るわけで。

何でそんなに「敵対企業にはビデオを自由に使わせません!」みたいな事にしたがるんだろうか。実際にそういうこともあるかもしれないけど、単にニコニコ動画の側に対話能力が欠けてるだけなんじゃないかと思うんだけど。

2007-02-21

遅延評価を使ってSjaxをAjaxに変換する方法

継続を使ってSjaxをAjaxに簡単に変換する方法
http://d.hatena.ne.jp/llamerada/20070220/1171984586

を見て。こんなのはどうだろう。

ユーザーからの入力や、非同期のHTTPリクエストなんかを、具体化されてないオブジェクトとして捉えて、それらを受け取った関数側が遅延オブジェクトを具体化するためのリクエストを投げて再試行する。ネストが深くならないですむ、同期処理で書く場合との変更点が少ない、あるいは完全に差異を無くすことができる。

alert(args)のコメントを外せば、引数が具体化されていく様子が分かるはず。

Function.prototype.receive_lazy = function(){
    var orig = this;
    return function(){
        var thisObj = this;
        var me = arguments.callee;
        var args = Array.prototype.slice.apply(arguments);
        // alert(args);
        function retry(i, as_force){
            return function(arg){
                if(!isLazy(args[i])) args[i] = args[i].value;
                if(as_force && typeof arg == "function"){
                    arg(me.apply(thisObj, args));
                } else {
                    return me.apply(thisObj, args)
                }
            }
        }
        for(var i=0;i<args.length;i++){
            if(isLazy(args[i])){
                args[i].force(retry(i));
                return {isLazy:true, force: retry(i, true)}
            }
        }
        return orig.apply(this, args);
    }
};
function isLazy(obj){
    return typeof obj == "object" && obj.isLazy
}

// ここからが実際に記述する部分
function fetch(path, length, offset, cont){
    var range = ["bytes=" + offset + "-" + (offset + length - 1)].join("");
    var options = {
        method: "get",
        onComplete: function(request){cont(request.responseText);},
        requestHeaders: ["Range", range]
    }
    new Ajax.Request(path, options);
}
fetch = fetch.receive_lazy();
function lazy_fetch(path, length, offset, inflate){
    var lazy_object = {
        isLazy: true,
        force: function(callback){
            fetch(path, length, offset, function(responseText){
                lazy_object.isLazy = false;
                lazy_object.value = 
                    (typeof inflate == "function")
                     ? inflate(responseText) 
                     : responseText;
                callback(lazy_object.value);
            });
        }
    };
    return lazy_object;
};
function fetch_data(){
    var path = "/data.txt";
    var offset = lazy_fetch(path, 4, 0, parseInt);
    var length = lazy_fetch(path, 4, 4, parseInt);
    return lazy_fetch(path, length, offset);
}

// ここでalertも変形させてしまえば同期処理のコードとほとんど同じになる
fetch_data().force(alert)


処理の流れは、
- lazyなオブジェクトを引数に受け取ったら
-- 引数を具体化するためのforceメソッドを呼び出す
-- オブジェクトが具体化されたら、関数を再度実行する
- 全ての引数が具体化されたらオリジナルの関数に処理を引き渡す

Function.prototypeにreceive_lazyメソッドを加えて、あらかじめ遅延評価な変数を受け取れるように関数を変形しておく必要がある。考え方としては、コードを書くときに非同期処理を完全に意識しないで済むようにしたい。非同期に合わせて書くのではなく、同期処理のつもりで書いたコードが、いつのまにやら非同期処理になってる、というのがやりたい。

2007-02-20

まるごとJavaScript売ってます

原稿落としてすいませんでした。


2007-02-17

OPML生成サービスを作りました

livedoor Reader、Bloglines、はてなRSSを公開設定で使っているユーザーのOPMLをマージして、これは読んでおいた方がいい、というフィードのリストを作るサービスを作りました。

サンプルで最初からいくつか入れてありますが、プログラマが読んでおいた方がいい、ってのが出るように適当にピックアップしてみました。
レンタルサーバーなので、あんまり沢山いれると処理時間が長くて表示できません。ソース公開してるので適当に改造して使ってください。

http://la.ma.la/opmlburner/

2007-02-16

JavaScriptの関数の結果を期限付きでキャッシュする

処理に時間がかかるけれども、一定時間は結果が変わらないような関数の結果をキャッシュしたい。

例えばgetElementsByTagName("*")なんかを頻繁に呼び出すようなコードがあったとして、結果をキャッシュしたいけれど画面描画が発生すると使えなくなってしまう。setTimeoutで0ミリ秒後にキャッシュを消す処理を入れておいて、画面描画と関係のある処理はタイマーで実行するような制約を付けてコードを書けばDOMが絡む処理の結果もキャッシュすることができる。というようなケースに使えるような気がする。
Function.prototype.timed_memoise = function(ms){
    var self = this;
    ms = ms || 0;
    var memo = {};
    var clear_q = false;
    function clear_cache(){memo={};clear_q=false}
    return function(){
        var args = Array.prototype.slice.apply(arguments);
        if(!clear_q){
            setTimeout(clear_cache, ms);
            clear_q = true;
        }
        var key = "_" + args.join(',');
        return (key in memo) ? memo[key] : (memo[key] = self.apply(this,arguments));
    }
};
some_function = some_function.timed_memoise(expire);

2007-02-15

Functionコンストラクタを使ってJavaScriptネイティブじゃない関数をラッピングする方法

ExternalInterface.addCallback で定義された関数は apply できない
http://d.hatena.ne.jp/nitoyon/20070214/p1

っていう記事に書いてあるコードを手直し。Flashをいじってないんで、上手く動くかどうかよくわかんないですが、こんな感じでいけると思います。
function applySwf(swf, method, args){
    if(swf && typeof swf[method] == "function"){
        var params = [];
        for(var i=0;i<args.length;i++) params[i] = "_"+i;
        Function(
            params.join(','),
            "this("+params.join(',')+")"
        ).apply(swf[method], args);
    }
}

args.lengthが4の場合は
(function anonymouse(_0,_1,_2,_3){this(_0,_1,_2,_3)}).apply(swf[method], args);

というコードが動的に生成されるので、引数がいくつになっても対応できます。

汎用化するとこんな感じに。ただのサンプルコードなので利用はご自由にどうぞ。
Function.toNative = function(obj, method_name){
    return function(){
        var params = [];
        for(var i=0;i<arguments.length;i++) params[i] = "_"+i;
        return Function(
            params.join(','),
            'this["'+method_name+'"]('+params.join(',')+')'
        ).apply(obj, arguments);
    }
}
document.write.apply(document,[1,2,3]); // IEだとエラー
var dw = Function.toNative(document,"write");
dw.apply(null, [1,2,3]); // 123と表示

IEではdocument.writeやwindow.alertなどのブラウザの機能を呼び出す組み込み関数では、JavaScriptのFunction.prototypeを使うことができません。そのような場合に、こういった形でラッピングした関数を作ってやれば、自前で定義したFunction.prototypeが使用でき、かつ、元の関数の機能を損なわない関数に変形してやることができます。

参考
IEでXMLHttpRequestを使えるようにする
http://la.ma.la/blog/diary_200509031529.htm

2007-02-11

最近IE6でWikipedia日本語版の表示が異常に遅いのはKeepAliveのせい

KeepAliveのせいというと誤解があるか。IEのせいなんだけど。
どうもここらへんの問題っぽい。

http://d.hatena.ne.jp/kinneko/20051214/p4
http://otaba.seesaa.net/article/10637205.html

2月初めぐらいからか、キャッシュが空の状態で日本語版のWikipediaを表示すると、IE6が1分間ほど固まる、という不具合があるそうだ。

JavaScriptを切ると正常に表示できるようになるけど、JavaScriptが重い、ということはなかった。JavaScriptが重いならCPUの使用率が高くなるはずだし、なんかおかしいフリーズの仕方をする。で、Proxomitronでレスポンスとか調べてみてたりしたのだけれど、プロキシ経由だと問題なく表示される。

結論としては、なんらかの原因でkeepaliveがタイムアウトするまで(1分間)、ファイルの受信が終わってないと見なされてしまうのが原因。試しに、NEGiESを使ってkeepaliveのコネクションを手動で切ってみたら即座に表示された。

Sleipnirがおかしいとか書いてる人がたまにいるけど、素のIEで再現するので関係ないソフトのせいにするのはやめましょう。あとwikiが重いwikiが重いってどこのwikiだよ(おやくそく)。

IE6のバグ、つーことなんだろうけど、誰か対応してるのかな。
ほっといても直らんと思うんだけど。

追記: 2/11

2月10日の時点でwikipediaの中の人と連絡を取って対応してもらってます。問題点と解決策は特定してるので、近いうちに直るでしょう。なので、この記事を見たあなたがバグ報告をしたりする必要は特にありません。
詳しい原因については、金床さんがコメント欄に書いてくれてます。ありがとうございます。

クライアント側で出来る対策としては
- ブラウザを変える
- ja.wikipediaでJavaScriptを無効にする
- hostsファイルを書き換える

などがあります。

参考リンク: Wikipedia:井戸端#日本語版wikipediaが重い。

2007-02-08

Google Readerの紹介ビデオを作った

今さっきキャプチャしてみた。YouTube。

http://youtube.com/watch?v=DcO4RG3Lx3k

去年の9月終わりぐらいにGoogle Readerがリニューアルしたとき、例に漏れず当時購読していた約2600件のフィードをインポートしてみた。

一度全部既読にしようと思ったのだけれども、All itemsを表示してからのmark all as readが効かない。延々とエラーが出て何も出来ないので、フィードの管理画面を開いてみると、応答のないスクリプトダイアログが何回も表示されて、やっと表示できたと思ったら、画面下半分が真っ黒になっていて、何か悪いことをしたと思い、Select AllってやってUnsubscribeボタンを押した。

しばらくして、TechCrunchなんかがうにゃうにゃ言ってるので、もう少し頑張って使ってみようかと思い、今度は少し減らして、livedoor Readerで公開設定にしている1400件のみインポートした。そしたら最初の画面の表示が完了するまでに約1分間ぐらいFirefoxが固まるが、一応は安定して表示できる状態にはなった。ちなみにIEではフリーズしたまま戻ってこなくなる。記事は一応表示されるものの、記事1件分をスクロールするたびにサーバーにリクエストを送信し、サイドバーの未読件数を再描画しようとするので、まっとうにスクロールすることができない。フォルダをまとめて既読にしようとしたらエラーが出るので、キーボードショートカットでNOANOANOANOAと連打して、何とか全部既読にした。これで普通に表示できるようになるだろうと思って、翌日アクセスしたら、また使い物にならなくなっていた。やっぱり管理画面の画面下半分は真っ黒になっている。

(今さっき試したらAll itemsを表示してmark all as readが正常に動くようになっているようだ、しかし40分後にはやっぱり使い物にならなくなった)

何が言いたいかというと、Googleのプロダクトだから良くできてるなんてことは、無い。

2007-02-06

Google Readerのユーザー名を隠すGreasemonkeyスクリプト

Google Readerのユーザー名の部分をベタ塗りにします。
メールアドレスがばれたくない場合に便利です。
http://la.ma.la/misc/userjs/google_reader_beta.user.js

ソース
GM_addStyle('#global-info b{background:#000}');

ロード完了までの間に表示されてしまうのでUserCSSの方が良かった。
@-moz-document url("http://www.google.com/reader/view/") {
 #global-info b{background:#000}
}

2007-02-05

Operaのメーラーで特定のメールを保護して残りを全部消す方法

Operaのメーラーは10万通とか溜まってくると振り分けが遅くなってもっさりな感じなのですが、不要なメールを消しまくると軽くなります。ただし、間違えて貴重なメールを消してしまわないように最新の注意を払う必要があります。

そんなわけで、たったの10ステップで、特定のメールだけ残して不要なメールをばっさり削除する方法を紹介します。

1. 残したいメールのフィルタを作る。
2. 全部選択して「貴重ラベル」を付ける。(Ctrl+A, L,7)
3. 「貴重ラベル」のメールを表示して、表示オプションで「ごみ箱を表示」にする。
5. 消したいメールをばっさりごみ箱に入れる。(Ctrl+A, Del)
6. ごみ箱のメールを全て既読にして、未読のみ表示にする。(Ctrl+Shift+A)
7. 貴重ラベルのメールを全て選択して、未読にする。(Ctrl+A, Shift+K)
8. ごみ箱の中の未読メールを全て選択して復元する。
9. ごみ箱は貴重ラベルの付いたメール以外が入った状態になる。
10. ごみ箱を空にする。

ポイントは、ごみ箱以外のフィルタからだと復元メニューが使えないようなので、一度未読に戻すという点。
貴重なメールを一度ごみ箱に入れるという発想が常人には浮かびません。