Showing posts with label Search. Show all posts
Showing posts with label Search. Show all posts

Oct 7, 2007

EstCrawler Plugin 0.01公開

エントリをまとめてHyper Estraierのデータベースに追加したり、エントリの追加・削除に応じてHyper Estraierのデータベースを自動的に更新したりする機能を提供するプラグインを作りました。MT4専用。

EstCrawler - ogawa - Google Code

まあなんですか?「文書ドラフト」形式でエントリ別アーカイブを静的生成するとかダルいでしょ?そう思っちゃう人は使ってみてください。

あとついでに、データベースに登録するときにタグやカテゴリーの情報もアトリビュートとして突っ込んであります。

(何を使って検索しても構いませんが)estseek.cgiを使う場合、下のような問い合わせをすればhogeタグを持つエントリを表示できます、とか。

http://.../estseek.cgi?attr=tags+STRINC+%5Bhoge%5D&perpage=10

Oct 1, 2007

MT4のmt-search.fcgiの性能

Ogawa::Buzz: mt-searchがどうこうという話の続き。

MT4標準のmt-searchの性能をFastCGI環境でもう少し詳細に調べてみた。ベンチマークの条件は、

  • MT 4.01 + FastCGI環境。
  • デフォルト(No Cache)、MemcachedLocalを使った場合(MemcachedLocal)、Memcachedを使った場合(Memcached)の3つを比較。
  • MaxResultsは10,20,40,60,80,100でそれぞれ測定。
  • 検索処理(Search)、レンダリング処理(Render)、それ以外(Misc.)に要する時間をそれぞれ計測。
  • 計測は20回のシーケンシャルリクエストの最良値と最悪値を除いた値を平均化。
  • 対象データはこのブログのある時点でのスナップショット。

まず、デフォルトテンプレートによる検索時間の結果。

MemcachedLocalを用いることで最大約23%の全体処理時間の短縮が実現できている。レンダリング処理に限れば40%以上の時間短縮が期待できる。

Memcachedを用いることで、MemcachedLocalと同様にレンダリング処理の時間短縮が期待できる。ただし、検索処理自体に要する時間が増加しているため、効果が相殺されてしまい、MaxResultsの値が大きい領域でしか高速化できない。Memcachedを用いた場合には、データベースからロードされたデータ本体もキャッシュされるため、そのオーバーヘッドがあるものと思われる。

次に、デフォルトテンプレートからメタ情報(MTEntryCommentCount, MTEntryPingCount)の表示部分を削除したテンプレートによる検索時間の結果。

MemcachedLocalを用いてもほとんど高速化されない。微妙に速くなっているが測定誤差の範囲。Memcachedとの組み合わせで検索処理が遅くなるのは、メタ情報ありの場合と共通している。

数値データコミコミで以下で公開。

Google ドキュメント - MT4: mt-search.fcgi performance

Sep 27, 2007

mt-searchがどうこうという話

「検索結果テンプレート」の修正でMTの検索結果表示にどの程度の効果があるか? (Junnama Online (Mirror))

面白いデータですね。

私が「下手な最適化」と書いたのは、

記事のメタ情報について「タグ」や「カテゴリー」を利用しているけど、エントリーの概要欄やキーワード欄、追記欄を利用していない、という場合はこれらのフィールドをできるだけ活用して他のテーブルにクエリを投げる回数を減らしてやる (MTからデータベースへのクエリの発行回数を減らす (mt-search.cgiを例にとって)。 (Junnama Online (Mirror)))

ようなハックのことです。高速化したければ、外部メタ情報の抽出をO(1)でできるような手段を下位レイヤーで提供するのが筋で、既知のデータベースの構造を元に局所的な最適化を行うのは二次的な方法に過ぎません。「局所的」と言ったのは、データベースの構造が変わった途端に有効でなくなるからです。「MTEntryDateは定数時間で取り出せるが、MTEntryCommentCountはそうではない」という知識は有用ですが、恒真ではありませんし、テンプレート作成者が「最適化」と称して再利用性の低いテンプレートを作り出すことも望ましいことではありません。

実のところ、MT4はそのような、つまり外部メタ情報の抽出を定数時間で取り出すための、手段を提供しています。FastCGI環境かmemcachedを使った環境で試すと分かりますが、エントリのコメント数などの外部メタデータはキャッシュされます。エントリーのコメント数を得る場合を考えてみると、初回はO(N)、2回目以降はO(1)、したがって平均的にはO(1)で得られるはずです。Junnamaさんの環境がどのようなものかは分からないので何とも言えませんが、もしFastCGI環境ならキャッシュハンドリングに改善の余地があるのでしょう。

2007-09-30追記: 仔細にコードを読んでいたらFastCGI環境では外部メタ情報をキャッシュしないことが分かりました。もう少し詳しい話は別のエントリで書く予定です。

あと実験に関して付け加えると、やや公正さに欠けると思います。2.が「ブログ記事のメタデータ」モジュールのみ外した状態になっているのは比較上問題があります。利用するテンプレートの個数が異なりますから。それなら最初からflatteningしたテンプレートを使って比較すべきです。

また、計測時間はMT::Builder::build()メソッドの実行時間を計測したものとほぼ同じとみなせると思いますが、ということは、スクリプトの呼び出しオーバーヘッドや検索本体の処理、テンプレートのコンパイル時間などを含まないということです。これらの時間が全体の実行時間においてdominantなら効果は限定的となりますね。実際のところは分からないわけですが。


ちょっと試しに計ってみました。単位は全部「秒」。

オリジナル
MaxResults レンダリング トータル 差分
10 0.304314 1.528 1.224
20 0.517478 1.868 1.351
40 0.927628 2.364 1.436
60 1.343529 2.865 1.521
80 1.786946 3.451 1.664
100 2.208508 3.996 1.787
メタ情報のうちコメント・トラックバックに関するものを削除したもの
MaxResults レンダリング トータル 差分
10 0.168146 1.406 1.238
20 0.237147 1.543 1.306
40 0.376270 1.795 1.419
60 0.514390 2.058 1.544
80 0.659881 2.309 1.649
100 0.821395 2.606 1.785

相変わらずふざけるなってくらい遅いです、特にオリジナルの方。MaxResultsを大きくすればするほど全体時間に占めるレンダリングの割合が増加していて、オリジナルの場合には50%を越えています。やはり10〜20が実用域ですね。私の感覚からすると、0.5秒を切らないと常用する気は起きません。

May 23, 2007

FeedBurnerは何も隠していないと思うよ

FeedBurnerは超重大な事実を隠してる - たねちゃんズ12

それは言いがかり…だけど、NASAばりに○○の存在をひた隠しにしなければならない団体に準えるセンスには脱帽。

自然に考えてこれはFeedBurnerが悪いのではなくて、Yahoo! Japanが悪いのだろう。ブログとフィードのURLの不一致があろうとも正しい形式でRSSを生成しているのであればそれを検索対象に含めるのが、「ブログ検索」のあるべき振る舞いだから。

むしろ問題は、

主に以下のような場合は、検索エンジン用ロボットの巡回対象とならないことがあります。 * RSSのアドレスが、ブログや各記事のドメインと異なる(例:外部サーバ上のRSSアドレスを直接参照している)

みたいに単純に技術的要請から生じた(であろうと想像される)留保条項をいつまで継続しなければならないのか、ということだ。

ブログ検索の中身がどうなっているのかは知らないが、基本的にはフィードに対する検索機能を実現するには、あるブログに(時間的・空間的に)複数のフィードが関連付けられているときにそれらのうちどのフィードを信用するかを決定する必要がある。それにはあるパラメータセットを取る評価関数が必要であって、そのパラメータセットには、フィードURL、ブログURL、フィードの更新頻度や最終更新日時、そのブログのlink要素へのフィードURLの記載の有無と時間的分布などが含まれ得る。で、評価関数はそれらのパラメータを使って各フィードのランク付けを行う。まあそんなとこだろう。

そういう前提があるものとして、ではなぜブログURLとフィードURLのドメインの一致・不一致が問題になるのか(≒「留保条項」が存在するのか)をちょっと考えてみると、まずあるブログに関連付けられるフィードは無限個存在するという事実がある。あなたがあるフィードを提供していたとすると、第三者がそのコピーフィードや若干加工したフィード、あるいはエントリーのURLのみをコピーした中身は出鱈目なフィード、を別のURLで公開することは際限なく実施できるから。で、その無限個存在するフィードのうち、ブログURLとドメインが一致するフィードの信用度はより高いとするだけの蓋然性がある。なぜなら両者のURLの所有者が同一であると考えられるから。これに対して、不一致の場合には、濫造されたフィードか、FeedBurnerのように本来は(暗黙的に)信用に値するフィードかを区別する手立てがなければ信用度は一律により低くするより他ない。

当然この基準に基づいて信用度が低くなったフィードであったとしても他のパラメータセットによって相対的に最も信用度の高いフィードとして選択されることはあり得る。だから、FeedBurnerを使っていてブログ検索に掲載されないサイトもあれば、問題なく掲載されるサイトもある、ということになる。

一方で、こういう疑問もある。「留保条項をいつまで継続しなければならないのか」という疑問に通底するのだが、このようなアルゴリズミックなエフォートと、プロトコル的なエフォート(単純に現時点でのブログのlink要素を信頼してフィードを確定する方法)の、いったいどちらがより優れた結果をもたらすのかという疑問である。現状では前者が後者を上回る結果をもたらすのだろうか。それとも実際にはlink要素を使っていないフィードがあまりに多くて検索対象となるフィードが少なくなり過ぎる(=coverageが下がり過ぎる)のだろうか。

門外漢なりにいろいろ技術的な困難さは想像してはいるのだけれど、日本のネット人口のブログユーザ率は他国に比べてかなり高いそうだから、この問題の解決は是非日本人の手で達成されるといいよねー。

追記:
Typoがあったので修正しました。

Aug 7, 2006

Widget for Google AJAX Search API

Google AJAX Search APIってのがあったんだな。まったくスルーしていた。あまり使われていないのかしら。

Movable Type 3.3のWidget Managerから簡単にプラグインする方法を少し考えてみた。私は3.3に移行していないし、Widget Managerを使う予定もないので(MTIncludeがあるから不必要ではないか、と)、例によって「試してない・やればできるにきまってる」メソッドである。

まず、Google AJAX Search API - Sign UpでサインアップしてAPI Keyを取得する。取得したAPI Keyはちらしの裏にでもメモしておくこと。

次にMovable Type 3.3のテンプレート・モジュールを作る。テンプレート名は「Widget: Google AJAX Search」とし、以下のコードを保存する。冒頭のYour API KeyのところにはさっきのAPI Keyを転記しておくこと。

<MTSetVar name="GoogleAJAXSearchAPIKey" value="Your API Key">
<div class="module-archives module">
  <h2 class="module-header">Google AJAX Search</h2>
  <div class="module-content">
    <script src="http://www.google.com/uds/api?file=uds.js&amp;v=0.1&amp;key=<MTGetVar name="GoogleAJAXSearchAPIKey">" type="text/javascript"></script>
    <style type="text/css">
      @import "http://www.google.com/uds/css/gsearch.css";
      .gsc-control { width: auto; }
    </style>
    <script language="Javascript" type="text/javascript">
    //<![CDATA[
    var gsc_onload = function() {
      var searchControl = new GSearchControl();
      var siteSearch = new GwebSearch();
      siteSearch.setUserDefinedLabel("<$MTBlogHost encode_js="1"$>");
      siteSearch.setUserDefinedClassSuffix("siteSearch");
      siteSearch.setSiteRestriction("<$MTBlogHost encode_js="1"$>");
      searchControl.addSearcher(siteSearch);
      searchControl.addSearcher(new GwebSearch());
      searchControl.draw(document.getElementById("google-ajax-search"));
    };
    if (window.addEventListener) {
      window.addEventListener('load', gsc_onload, false);
    } else if (window.attachEvent) {
      window.attachEvent('onload', gsc_onload);
    }
    //]]>
    </script>
    <div id="google-ajax-search"></div>
  </div>
</div>

あとはWidget Managerの管理画面から適当なWidget Setに「Google AJAX Search」Widgetをdrag'n dropして保存した後、再構築すればよい。

動作風景。

見ての通り、サイト内検索結果とWeb検索結果が表示されるようにしてある。ローカル検索・ビデオ検索なども追加可能なので、カスタマイズしたい場合にはGoogle AJAX Search API Documentationを参照のこと。

Dec 14, 2005

Yahoo!検索 Webサービスを使った割とありがちなサイト内検索

ありがちですが、このブログのサイト内検索をYahoo!検索(Yahoo!デベロッパーネットワーク - 検索Webサービス)とAjaxを使うようにしてみました。

また、ysearch-json.cgi、ysearch-json.fcgiは単体で使用すると(queryオプションを与えないと)フォームを表示するので動作確認などに使用できます。

注意点

  • YSearch.jsの58行目の'uri'の値は、ysearch-json.{cgi,fcgi}のURLを指定する必要があります。
  • ysearch-json.{cgi,fcgi}の10行目の$YJWS_APPIDにセットするアプリケーションIDはYahoo!デベロッパーネットワーク - アプリケーションIDの登録から適宜取得してください。
  • ysearch.html, ysearch-inc.html中、「YSearch.init('[Your Hostname]',10);」の引数はそれぞれ「自分のサーバーのホスト名」「一度に表示する検索結果の個数」です。適宜変更してください。
  • ysearch-inc.htmlは相当手抜きです。ちゃんと動くようにするためにはインターバルタイマーを真っ当に使う必要があります。ま、簡単なのでいろいろ試してみてください。

Sep 14, 2005

Google Blog Search

Google Blog Search (Find blogs on your favorite topics)

チョー有意義!Technoratiに対するdisadvantageは、今のところCoverageの低さとTag Searchくらい。前者は徐々に改善するだろうし、後者はTechnoratiくらいcoverageがあると使い物にならないという問題がある。

Keyword Searchはもちろん、Cosmos Search相当のことは当然できるし、AtomやRSSも利用できてしまうわけだね。

「後発の強み」の実例を絵に描いて額に入れて飾ったようなもの。

Jun 29, 2005

Technoratiで嵌っている件について。

Technoratiは英語版日本語版でデータを共有しているらしく、英語版で作ったアカウントが日本語版でも有効です。しかし一部のデータはそうでもなかったらしく、英語版でclaimしてある私の2つのブログが日本語版では不完全にしか表示されませんでした。具体的にはclaimしてあるブログが2つあるという情報は表示されるが、その内容は表示されないという現象が観測されました。

しかたがないので一旦unclaimしてからclaimし直そうとしたのですが、今度は逆にclaimできなくなってしまいました。

どうしてこうなったのか少し考えてみると、Technorati英語版の旧インタフェースではclaimする方法として、(1)ブログに認証コードを含むリンクもしくはJavascriptを貼り付ける方法と、(2)XMLRPCインタフェースを使って簡易認証する方法、の2つが用意されており、2つのブログをclaimする際には後者の方法を使いました。一方で、今月になってリリースされた新インタフェースでは前者しか用意されていません。

つまり、(a)日本語版には(2)の方法で登録した情報が正しく引き継がれておらず、かつ(b)そのブログをunclaimしてもTechnoratiには(2)の方法で登録したsubscription情報を正しく削除できないという不具合がある、という現象があるように推測されるわけです。

とりあえずクレームのメールは送ったのでじっと返答待ち状態です。

2005-07-06追記:サポートからというか、Niall Kennedyからメールがやってきてリセットしてくれた模様。再度claimし直して今度は問題なし。

Jun 4, 2005

Google Sitemaps

Google Sitemapsとかっていうサービスが始まったのでこのブログでも対応してみました。以下にテンプレートがあります。

Niall Kennedy's Weblog: Google Sitemaps using Movable Type

このテンプレートをこのまま使う分には問題はないのですが、ただカスタマイズすることを考えるとちょっと注意しなくてはならない点があります。

ひとつはSitemap Protocolを読むと、lastmod要素のタイムスタンプはISO 8601形式(Date and Time Formats)で指定することになっていますが、実際にはISO 8601形式のすべての時刻表現を受け付けてくれるわけではないようです。私の気が付いた限りでは、以下のようにLocal Time + Time Zone offset (2005-06-04T09:24:00+09:00)で指定しようとすると、Google Sitemapsは「Invalid Date」と判定するようです。

<lastmod>
<$MTEntryModifiedDate format="%Y-%m-%dT%H:%M:%S"$><$MTBlogTimezone$>
</lastmod>

KennedyのテンプレートにあるようにUTC時間を指定していれば問題ありませんし、Googleが早々にこの問題を解決する可能性があります。

もうひとつは、MTBlogURLに対する更新時間(lastmod)も表示させようと思って以下のように書くと、現在時刻の9時間前の時刻を返してしまう点です。

<url>
<loc><$MTBlogURL encode_xml="1"$></loc>
<lastmod><$MTDate utc="1" format="%Y-%m-%dT%H:%M:%SZ"$></lastmod>
<priority>1.0</priority>
</url>

これはMTDateにutc="1"オプションを与えた場合に2回オフセット補正されてしまう(正確にはgmtimeに対してlocaltimeからUTCへの補正を行ってしまう)ために起きます。Local Time Zoneが+09:00の場合には9時間前の時刻になります。例えば時刻が「2005-06-04T21:09:00+09:00」であったとするとそのUTC時刻は「2005-06-04T12:09:00Z」ですが、これを誤って9時間補正して「2005-06-04T03:09:00Z」を返してしまうということです。

この問題はMovable Type日本語版3.17以降では修正されています。

このバグを修正するには以下のパッチを当てる必要があるでしょう。

--- lib/MT/Template/Context.pm.bak	2005-01-13 15:47:37.000000000 +0900
+++ lib/MT/Template/Context.pm	2005-06-04 22:56:13.000000000 +0900
@@ -1444,6 +1444,7 @@
     my @ts;
     if ($args->{utc}) {
 	@ts = gmtime $t;
+	delete $args->{utc};
     } else {
 	@ts = offset_time_list($t, $_[0]->stash('blog_id'));
     }

May 28, 2005

Google Searchとの最後の戦い

ややっ!! Googlebotが来ている気配です。今度こそ本当にインデクシングされますか。そうですか。

Googlebotごときで何を大騒ぎしているのであるかーっ!!

と思う方も多いかとは思いますが、10か月にも及ぶGooglebotと私との戦いが今、終焉を迎えつつあるからなのです。

Ogawa::Buzz: Google Searchとの戦いの日々
Ogawa::Buzz: Google Searchとの最近の戦い

思いっ切りペナルティを食らっていたのか、「as」とか「is」とか除外キーワードっぽいドメイン名だったからなのかは定かではありませんが…。ともあれインデクシングを待ちましょう。

2005-06-23追記:
その後再びクロールに来なくなり、今日またやってきました。月一回のビッグウェーブなのかもしれません。

2005-06-28追記:
モーレツにクロールされ続けている模様です。Googlebotって激しかったんだなあ。しかしまだインデックスには追加されていません。

2005-07-04追記:
ついにインデックス入りしました。長かったなー、ここまで。地を這うようなPagerankから頑張っていきたい所存です。

2005-10-01追記:
related:as-is.net/blog/ - Google 検索が使えるようになっていました。また、ogawa - Google 検索で6番目に表示されるくらいに順位が向上したみたいです。

Mar 27, 2005

AllKeywords Plugin とMT-XSearchの連携

AllKeywords PluginはTagwire Pluginに開発が引き継がれました。下記のエントリーをご参照ください。

AllKeywords Pluginは、エントリーのキーワード部分をハンドリングするための機能を追加するだけでなく、MT-XSearchと連携して軽量なキーワードサーチを行うための機能も持っている。キーワードサーチはMovable Typeに付属しているmt-search.cgiを改造することもでも実現できるが、改造してしまうと他の用途に利用できないというデメリットもある。一方、MT-XSearch*1はさまざまな検索サービスを追加できるフレームワークで、どのような検索を実現するかはプラグインとして分離することができる。AllKeywords Pluginはその一例となっている。

このエントリーではAllKeywords PluginとMT-XSearchとの連携方法とその高速化手法について述べる。

*1 Tim Appnelの作。昨年のDevelopper's Contestの受賞作の一部で、Movable Typeに拡張性のある検索サービスを追加するためのフレームワーク。運悪くmovabletype.org : Developer's Contest Plugin Pack: 2004のページからplugin packへのリンクがなくなっており、Appnelも(パッチは公開しているが)公開していないので現在Webでは入手不能。公開してくれよとは言っているのだが...。そういうわけで残念ながらAllKeywords Pluginの連携機能の効果は限定的なものとなる。

2005-06-11追記: MT-XSearch自体はDownload mt-plus | Appnel Internet Solutionsから入手できる。

連携方法

すでにAllKeywords Pluginはインストール済みとする。

  1. MT-XSearchをインストールする。具体的にはプラグインパッケージに含まれるMT-XSearch0.3.zipmt-plus-1.01.zipをアンパックしてその中のmt-xsearch.cgi、plugins/mt-xsearch.pl、extlib/MT/XSearch.pmをそれぞれ適切なディレクトリにアップロードする。mt-xsearch.cgiはCGIとして実行できるように実行権限を与える必要がある。
  2. mt-xsearch.cgiはそのままでは日本語がうまくハンドリングできないので、以下のパッチを当てる。
    --- mt-xsearch.cgi.bak Fri Aug 27 12:06:24 2004
    +++ mt-xsearch.cgi Sat Jun 11 02:58:33 2005
    @@ -41,7 +41,8 @@
         $ctx->stash('CGI',$q);
         my $out = $tmpl->build($ctx)
             or die "Building search template failed: ".$tmpl->errstr;
    -    print $q->header.$out;
    +    my $charset = $mt->{cfg}->PublishCharset;
    +    print $q->header(-charset=>$charset).$out;
     };
     if ($@) {
         print "Content-Type: text/html\n\n";
    
  3. 2005-06-11追記: plugins/mt-xsearch.plはそのままではdivision by zeroエラーが起きるので、以下のパッチを当てる。
    --- plugins/mt-xsearch.pl.bak Sat May 14 06:01:19 2005
    +++ plugins/mt-xsearch.pl Sat Jun 11 00:31:39 2005
    @@ -63,7 +63,7 @@
         my $pages = $limit ? ($count-($count % $limit)) / $limit : 1;
         $pages += ($limit && $count % $limit) ? 1 : 0;
         my $offset = $xsearch->args->{offset} || 0;
    -    my $current = $offset / $limit + 1;
    +    my $current = $limit ? ($offset / $limit + 1) : 1;
         $ctx->stash('MT::XSearch::current_page',$current);
         $ctx->stash('MT::XSearch::pages',$pages);
         my $builder = $ctx->stash('builder');
    
  4. 「XSearch AllKeywords」という名前のテンプレートモジュールを作り、例えば以下のように記述する。
    <html>
    <body>
    <form method="get" action="<$MTCGIPath$>mt-xsearch.cgi">
    <input type="hidden" name="blog_id" value="<$MTBlogID$>" />
    <input type="hidden" name="search_key" value="AllKeywords" />
    <label for="search" accesskey="4">Search this site:</label>
    <input id="search" name="search" size="20" value="<$MTSearchString decode_url="1" encode_html="1"$>" />
    <input type="submit" value="Search" />
    </form>
     
    <MTSearchResults>
    <MTSearchHeader>
    Results found: <$MTSearchResultCount$> 
    <ol>
    </MTSearchHeader>
    <li><a href="<$MTEntryLink$>"><$MTEntryTitle$></a></li>
    <MTSearchFooter>
    </ol>
    <p>Searched on: <em><$MTSearchString decode_url="1"$></em></p>
    </MTSearchFooter>
    </MTSearchResults>
    <MTNoSearch><p>No search performed.</p></MTNoSearch>
    <MTNoSearchResults><p>Nothing found.</p></MTNoSearchResults>
     
    </body>
    </html>
    
    テンプレートモジュールは好きなようにカスタマイズできる。

使い方

インデックステンプレートやアーカイブテンプレートに以下のように追加すれば、キーワード検索用のフォームを作ることができる。

<form method="get" action="<$MTCGIPath$>mt-xsearch.cgi">
<input type="hidden" name="blog_id" value="<$MTBlogID$>" />
<input type="hidden" name="search_key" value="AllKeywords" />
<label for="search" accesskey="4">Search this site:</label>
<input id="search" name="search" size="20" value="<$MTSearchString decode_url="1" encode_html="1"$>" />
<input type="submit" value="Search" />
</form>

また、以下のようにフォームを生成せずに特定のキーワードの検索結果へのリンクを作ることもできる。

<$MTCGIPath$>mt-xsearch.cgi?blog_id=<$MTBlogID$>&search_key=AllKeywords&search=Blog

.htaccessに以下のように記述すれば、「http://blog.your.domain/tag/Keyword」のように簡便なURLで特定のキーワードの検索結果を得られる。

RewriteEngine on
RewriteRule ^tag/(.*)$ /mt/mt-xsearch.cgi?blog_id=1&search_key=AllKeywords&search=$1 [QSA,L]

以下は2005-06-11に追記したものです。

高速化の方法

上記で説明した方法ではリクエストのたびにCGIを起動し、検索結果を取得して表示しているため、動作が遅いことは否定できない。しかしタグには滅多に変更がないことを前提にするなら、一定期間検索結果をキャッシュしたり、データの転送を抑制したりすることで大幅な高速化が可能になる。

ここでは手っ取り早くCGI::Cacheを使って高速化を実現する方法を紹介する。具体的には、検索結果を一定時間(例では1時間)キャッシュし、なおかつHTTP HeaderのLast-Modifiedにキャッシュした時刻を格納して返す。つまり、以下の二つの効果が期待される。

  • キャッシュを作成してから一定時間内は再検索しないことによる負荷の軽減
  • この期間内に二回以上アクセスするブラウザには304 Not Modifiedを返すことによる、データ転送の削減

方法は単純で、CPANからCGI::Cacheモジュールをインストールし、「連携方法」の2.で示したパッチの代わりに以下のパッチをmt-xsearch.cgiに適用するだけでよい。赤字で示した部分はそれぞれ「キャッシュの作成場所」「キャッシュの生存期間」を表す。下記の例ではそれぞれ「mt-xsearch.cgiのあるディレクトリの直下のcacheディレクトリ」「1時間(=3600秒)」を指定してあるので必要に応じて変更するとよい。

--- mt-xsearch.cgi.bak Fri Aug 27 12:06:24 2004
+++ mt-xsearch.cgi Sat Jun 11 03:44:32 2005
@@ -17,6 +17,7 @@
 }
 
 use CGI;
+use CGI::Cache;
 use MT;
 use MT::ConfigMgr;
 use MT::Template;
@@ -27,10 +28,13 @@
     my $mt = MT->new( Config => $MT_DIR . 'mt.cfg', Directory => $MT_DIR )
         or die MT->errstr;
     my $q = new CGI;
+    CGI::Cache::setup({ cache_options => { cache_root => './cache', default_expires_in => 3600 } });
     my $blog_id = $q->param('blog_id') or
         die "Missing parameter blog_id";
     my $key = $q->param('search_key') or
         die "Missing parameter key";
+    CGI::Cache::set_key($q->Vars);
+    CGI::Cache::start() or exit;
     my $search = MT::XSearch->execute($q);
     my $tmpl = MT::Template->load( { 
                         name=>'XSearch '.$key, 
@@ -41,7 +45,14 @@
     $ctx->stash('CGI',$q);
     my $out = $tmpl->build($ctx)
         or die "Building search template failed: ".$tmpl->errstr;
-    print $q->header.$out;
+    my $charset = $mt->{cfg}->PublishCharset;
+    my @m = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
+    my @w = qw(Sun Mon Tue Wed Thu Fri Sat);
+    my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime(time);
+    my $now = sprintf("%3s, %02d %3s %04d %02d:%02d:%02d GMT",
+        $w[$wday], $mday, $m[$mon], $year+1900, $hour, $min, $sec);
+    print $q->header(-charset=>$charset,-Last_Modified=>$now).$out;
+    CGI::Cache::stop();
 };
 if ($@) {
     print "Content-Type: text/html\n\n";

Mar 10, 2005

Google Searchとの最近の戦い

このサイトにはGooglebotがクロールに来てくれません。クロールに来てくれるまで私とGoogle Searchとの戦いは続きます。これまでの戦歴は下記を参照のこと。

Ogawa::Buzz: Google Searchとの戦いの日々

簡単に経緯を説明すると、一年ほど前に某サーバーからロリポップ(hassaku.main.jp)に、半年以上前にhassaku.main.jpからas-is.netにドメインを変更し、変更のたびにリダイレクトしていたのですが、これがスパミング行為と取られたか、いろいろ手を尽くしてもas-is.netの方にはGooglebotがクロールに来てくれません。リダイレクトしていても一向に変化がないので旧ドメインへのアクセスはフェイルするようにしていますが状況は変わらず、旧ドメインの古いデータがキャッシュされている状態が続いていて結構不愉快です。

2月27日

404エラーなどでロリポップのエラーページに誘導されるのを停止させてみました。Yahooなどにこのエラーページがキャッシュされている例が見られたためです。徹底的にシンプルにするために以下のように.htaccessに設定してみました。

ErrorDocument 400 "400 Bad Request
ErrorDocument 401 "401 Unauthorized
ErrorDocument 403 "403 Forbidden
ErrorDocument 404 "404 Not Found
ErrorDocument 410 "410 Gone

さらに、旧URL(hassaku.main.jp)に関して残っているキャッシュ情報を削除するためにrobots.txtを作ってすべてのページのクロールを禁止しました。ただし、このrobots.txtは旧URLからアクセスしたときには読め、現URLからアクセスしたときには読めないように.htaccessには以下のように記述しました。

RewriteEngine on
RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule !^robots\.txt$ - [G,L]
RewriteCond %{HTTP_HOST} ^(as-is\.net)(:80)? [NC]
RewriteRule ^robots\.txt$ - [G,L]

これで旧URLに関して残っているキャッシュ情報はいずれ削除されるはずということになります。

3月4日

いずれというのも心許ないので、Googleの自動URL削除システムを使ってみることにしました。このシステムでは(1)robots.txtファイルを使用して個々のページ、サブディレクトリ、画像を削除する、(2)メタタグを使って1ページだけを削除する、(3)無効なリンクを削除する、のいずれかの方法が採れます。ひとまず、(1)の方法で旧URLのキャッシュを削除するように申請してみました。

翌日には削除されました。…だからってクロールに来てくれるわけではありませんが。しかも旧旧URLでのキャッシュが10件分残っているようでこれも消す必要があるのでしょう。しかもGoogle 検索: site:as-is.netとかしてみると、旧旧URLでのキャッシュが10件分見られますが1560件ヒットしているという訳の分からなさ加減です。インデックスの再計算が必要なのかしら。

3月10日

キャッシュされている旧旧URLのデータを削除するべく、(3)の方法で一個ずつURLを指定してやりました。本当に10件だけだとよいのですけど。1560件は無理です。

あと2月27日にやった方法だとas-is.netでrobots.txtを設定できなくなってしまうことに気が付いてしまいました。なので、robots.txtには以下のように記述。

<?php
header('Content-type: text/plain');
if (!strcasecmp($_SERVER['SERVER_NAME'], 'hassaku.main.jp')) {
 echo <<<EOD
User-agent: *
Disallow: /
EOD;
} else {
 echo <<<EOD
User-agent: *
Disallow: /mt/
EOD;
}
?>

.htaccessは↓のように設定。

<Files robots.txt>
AddType application/x-httpd-php txt
</Files>

3月14日

やっぱり(1)の方法でざっくり消してやることにしました。即日反映されました。Google 検索: site:as-is.netとしても何も表示されなくなりました。が、Google 検索: ogawa site:as-is.netすると、blogpeopleのキャッシュとともに「約1,620件」と表示されます。…増えとるがね。

でも勝利は目前のような気がしてきました。


4月18日

目前のような気がしていましたがまだまだでした。今日、Google 検索: ogawa site:as-is.netしてみると何も表示されなくなりました。インデックスが更新されたことは確かですね。しかし…。

ちなみにこのブログ、Inktomi SlurpやMSN Searchbotにはえらく気に入られているらしいのです。

Jan 19, 2005

No Follow, No Life

たかがIndexingのクオリティを上げるトリック(しかも実質的にユーザーやアプリケーションにオフロードしただけ)を10年来の技術革新と呼んでしまうのはどうかと思いますね。まあアプリケーションベンダーがYesと言っているのだからいいのだという言い方もできますが、この調子でやっていればMachine writableだけどHuman writableでないHTMLの拡張仕様が出来上がってしまいます。

ITmediaニュース:Googleなど、リンクスパム対策に乗り出す――「ノーフォロー」タグを導入へ

最近のネタだとTechnorati Tagsあたりも結構怪しいと思っています。Technorati TagsではA要素のrel属性の名前空間を実際「無制限に」「混沌と」使ってしまいます。A要素やLINK要素のrel属性というのはそもそもリンク先の当該文書に対する「関係」を記述するものであって、一般的な「アノテーション」を記述するものではなかったはずです。スタイルシートを示すLINK要素に対して「rel="stylesheet"」と付記するように「ごく秩序立って」利用されてきたはずなのですがね。

そういうわけで将来この属性を使ったまともな拡張ができなくなる可能性もあるでしょう。Google他が「rel="nofollow"」とか言い出したのは、この名前空間を自分たちの都合のいいように早いうちに予約してしまった方がいいんじゃないか、という判断が働いているように思えます。

同じ仕組みを使って、Webブラウザがrel="nofollow"のリンクにアクセスするときにはRefer(r)erを渡さないようにすることも可能ですが、そちらの方が技術的にも社会的にも重要な意味があります。と言うよりもですね、こういう仕様を標準化してWebブラウザにあまねく採用していただき(いやまあそれは無理だとして仮に採用されなかったとしても提案・標準化したことには意義がある)、その仕様を(不肖Google、Yahooも)Indexingに流用させていただきます、という話の方が筋が良いと思います。話としてアサマシクなく、むしろスガスガシイです。

と書きながら自分でもアサマシクも"nofollow"を指定したりしているわけですけれど、私が対象にしているのはPing URLへのリンクだけで、それ以外のところに指定するとリンクの意義を損ないかねないと考えています。ですが、そういう思惑とは逆にと言うか無関係に、安直に指定してしまう人が増える気もしています。特にプラグインはその傾向を加速しがちですよね。つまるところ、お互いにリンクの効果を弱め合うチキンレース(?)の始まりだとも言えます。ブログはリンクの効果が強すぎたのは確かで、だからこそスパマーにも付け狙われやすくもあり、この時点で多少是正される方がよいのかもしれません。が、残念ながらこれはみんなで不幸になるレース、あるいは胴元だけが儲かる博打です。

つまりですね、サーチエンジンやポータルは、それを支える技術やマネーの規模がいつの間にやら増大し、淘汰が進み、ほとんど寡占が完成しつつある状況です。しかもそれらは広告料を主収入としているわけです。いずれこのシステムはテレビや新聞同然(ビジネスモデルはすでに同然です)となるでしょう。あなたが自分のブログにAdSenseを付ける時、それは二次広告収入というメリットをもたらすかもしれませんが、広告の製品やサービスに対してネガティブなことを書けば広告が表示されないように「技術的には」できますし、サーチエンジンのインデックスから削除することも「技術的には」できます。それが嫌なら製品やサービスの宣伝をするしかありません。つまりあなたは『Googleテレビ』の出演者・制作者になったのです。そしてまた今、あなたはスパムコメントが減るという餌に釣られていますが、これは一義的には検索結果の質を上げる(=広告の効果を上げる)ための意味しかありません。「立ち位置をもう少し右にすると画面映りが良くなるよ」という甘言の裏に「そこに立たれたままだと後ろのスポンサーの文字がよく見えない、スポンサーが煩いから何とかしろ」という意図があることに出演者はなかなか気が付かないものなのでしょうか。

さて、もう一度身近な経験を元に自問してみるとよいでしょう、

『スパムフィルタリングを施したからと言って、
 自分のところに送られるスパムメールが本当に減ったのか?
 減ってないとしたら誰が儲かったのか?』

おまけですが、Movable TypeのNofollow pluginを作ろうとしていたのですが、先越されてしまいましたー。しかし、ダイナミック・パブリッシング用のプラグインが私のところでは正常に動きません。

Movable Type Publishing Platform: Movable Type 'nofollow' plugin

コメント欄にも書いてありますが、MTPingsコンテナタグ内部に対する処理が怪しいです。これはちょっと考えたら理由が分かりました。

なぜなら、Nofollow pluginの処理ではまずサニタイズを行い、その結果生成物に対してrel属性を追加しているからです。この前処理のサニタイズでは、Sanitize Specで示されたルールに従ったタグおよび属性しか通過させません。

サニタイス: 入力された文字を安全に

ですから、MTPingsコンテナの内部にこのルールにマッチしない要素が含まれれば、そのタグ部分は除去されます。デフォルトでは、A要素に対してはhref属性だけを通過させることにしていますから、そのままではname属性やrel属性、title属性を通過させることができません。また、やはりデフォルトでは<br />は通過しますが、<hr />は通過させません。そういうわけでユーザーに自分の要求にミートするようなSanitize Specを定義しなくてはならないわけです。

基本的にはMTPingsコンテナ内に何を書くかはユーザーの自由ですし、それにミートするようなSanitize Specを指定したとすれば、それは結局「サニタイズ」として意味があるのかしら、と思います。もう少し説明を加えると、下の赤い部分の方が青い部分よりハードな制約が必要なはずですが、実際には同じSanitize Specが適用されるということです。つまり、赤の部分に合わせれば青の部分の記述力が制約されますし、青に合わせれば赤への制約としては意味がないでしょう。

追記1: よく考えたら、MTPingTitle、MTPingURL、MTPingBlogName、MTPingExcerptには予めサニタイズがかかっているじゃないか。だから上の図で赤の部分に危険なコードは含まれない。ということは、危険があるとしたら青い部分であってMTPingsをまるごとサニタイズするのはまったく意味がない。

したがって、nofollow.plの27行目あたりにあるtags_to_filterのPingsの行をコメントアウトした上で、テンプレートのMTPingsの部分に以下のどちらかの修正を加えればよい。

<MTPings nofollowfy="1">
<p id="p<$MTPingID$>">
&raquo; <a href="<$MTPingURL$>"><$MTPingTitle$></a> from <$MTPingBlogName$><br />
<$MTPingExcerpt$> <a href="<$MTPingURL$>">[Read More]</a>
</p>
<p class="posted">Tracked on <$MTPingDate$></p>
</MTPings>
<MTPings>
<p id="p<$MTPingID$>">
&raquo; <a href="<$MTPingURL$>" rel="nofollow"><$MTPingTitle$></a> from <$MTPingBlogName$><br />
<$MTPingExcerpt$> <a href="<$MTPingURL$>" rel="nofollow">[Read More]</a>
</p>
<p class="posted">Tracked on <$MTPingDate$></p>
</MTPings>

追記2: 一応、こんなBookmarkletも作っておきました。"nofollow"なA要素を見つけてcolorをlimeにします。

Show "nofollow" (Drag this and drop your brower's menu bar)

Jan 13, 2005

MSN Search RSS Feeds

宮川さんのところ(MSN Search Results as RSS: blog.bulknews.net)で知りましたが、MSN Search betaがRSSを生成するようになっていました。

RSS Feeds for Search Results

早速、Ogawa::Buzz: mt-search.cgiを捨てて簡単メタサーチにしてみようでやっていた、サイト内検索にGoogle Web APIsを使う方法を放棄しました(待てど暮らせどうちのサイトにはGoogleがクロールに来てくれないので役に立たないのデス)。で、こっちに乗り換えました。

どういうことかと言うと、例えば、「小川」をサイト内(as-is.net内)検索したい場合、

http://beta.search.msn.co.jp/results.aspx?q=%E5%B0%8F%E5%B7%9D+site:as-is.net&format=rss

というURLで検索結果のRSSが得られます(「%E5%B0%8F%E5%B7%9D」は「小川」をUTF-8に変換してURLエンコーディングしたもの)。したがって、このRSSをMagie RSS(Magpie RSS - PHP RSS Parser)などを使って読み込んで表示するだけで、真っ当なサイト内検索として機能してくれるというわけです。

ざっくり書くとこんな感じ。

<?php
define('CHARSET', '<$MTPublishCharset$>');
define('BLOG_HOST', '<$MTBlogHost$>');
define('BLOG_SITE_PATH', '<$MTBlogSitePath$>');

$search = isset($_GET['search']) ? htmlspecialchars(trim($_GET['search'])) : "";

echo <<<EOD
<form method="get" action="{$_SERVER['PHP_SELF']}">
<h3>サイト内の検索</h3>
<p><input name="search" size="30" value="{$search}" /><input type="submit" value="Search" /></p>
</form>

EOD;

if ($search) {
  $qstring = urlencode(mb_convert_encoding($search, 'utf-8', CHARSET));
  $cond_site = urlencode("site:" . BLOG_HOST);

  echo "<h2>MSN Search betaのサイト内検索結果:</h2>\n\n";

  require_once BLOG_SITE_PATH . 'rss_fetch.inc';
  define('MAGPIE_CACHE_DIR', BLOG_SITE_PATH . 'cache');
  define('MAGPIE_OUTPUT_ENCODING', CHARSET);
  $url = "http://beta.search.msn.co.jp/results.aspx?q={$qstring}+{$cond_site}&format=rss";
  $rss = fetch_rss($url);
  if ($cnt = count($rss->items)) {
    echo "<p>{$cnt}件見つかりました。</p>\n\n";
    echo "<ul>\n";
    foreach ($rss->items as $item) {
      $title = htmlspecialchars($item['title']);
      $url = htmlspecialchars($item['link']);
      $snippet = htmlspecialchars($item['description']);
      echo "<li><a href=\"$url\">$title</a>\n<div class=\"note\">$snippet</div></li>\n\n";
    }
    echo "</ul>\n";
  } else {
    echo "<p>見つかりませんでした。</p>\n\n";
  }
  echo "<p><small>[Powered by <a href=\"http://blogs.msdn.com/msnsearch/archive/2005/01/11/351064.aspx\">RSS Feeds for MSN Search Results</a>]</small></p>\n\n";
}
?>

上のコードはMovable Typeのテンプレートに張り込むことを考慮して、一部MTタグを使って書かれていますが、先頭で定義しているCHARSET、BLOG_HOST、BLOG_SITE_PATHという三つの定数を適当に変更するだけで、大抵のサイトで利用できます。

Feed2jsやprocfeedのように貼り付け用のJavascriptを生成してくれるサービスを利用してもよいのですが、こうしたサービスは総じて負荷が集中しやすく動作速度も遅いため、ページ全体のレスポンスを悪化させるので、私は使っていません。

Nov 16, 2004

Google Searchとの戦いの日々

MSNサーチ(beta)なんですが、今のところメンテ作業がたびたびあるものの、実際のところ悪くないんじゃないかと思います。「ogawa」「memoranda」で検索するとこのページがトップに表示されるという意味ではむしろ良いのかもしれません。うちのサイトにも熱心にクロールに来てくれますので、全文検索をMSNサーチ(beta)に任せてもよいくらいにキャッシュされています。

正直Google Searchはもう飽きました。もう何万回検索したか分かりませんが、日常的に使い続けているせいで「もしかして」機能のようなサプライズがないとトキメキを感じられません。インデックスが多いというメリットより、デッドリンクが多いというデメリットを最近では強く感じてしまいます。80億ページとか喜んでいる場合じゃないですよ(>中の人)。何らかのロジックでデッドリンクを排除しているはずですが、有効に機能していない気配があります。

しかも(実はここからが本文)、どういうロジックなのだか、7月末(Ogawa::Buzz: 独自ドメイン取ってみました)から3ヶ月以上が経とうというのにas-is.netにはクロールに来てくれず、hassaku.main.jpに相変わらずクロールに来ます。前者へのリンク数の方が今では遥かに多いと思うのですが。また、ときどきはサイトの登録 / 削除から登録してみたりもしています。ひょっとして「as」と「is」が検索除外語だからとか変なロジックが入ってはいまいかと要らぬ憶測をしてしまうほどです。

8月初旬

当初私は「.htaccess」に↓のように書いて素朴にRedirectさせていました。

RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule ^(.*)$ http://as-is.net/$1 [L,R=permanent]

この方法でRedirectするとURLのユニフィケーションが機能してしまって新URLへのクロールが起きないらしい話も(真偽のほどは怪しいものの)ちらほら見かけます。でもそれってロジックが逆では? とも思うのですが、でも確かに待てど暮らせどクロールに来てはくれないのでした。

9月頃

次に試してみたのは、↓みたいな感じでリダイレクト用のCGIに投げるようにするというものです。リダイレクト先ではNOFOLLOW, NOINDEXを指定したり、titleを工夫したりしてさまざまなバリエーションを試みましたが、これも駄目でした。短期間にいろいろ試しすぎたかもしれません。

RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule ^(.*)$ http://as-is.net/redirect.cgi?$1 [L,R]

10月

そろそろ業を煮やしてきたので、↓のようにしてみました。つまり、旧URLにアクセスしてきたのは全部「403 Forbidden」です。新しいURLへの誘導もしてやりません。

RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule ^.* - [L,F]

うちにGoogle経由で来てくれる人というのは、「検索して旧URLが引っかかり、一旦アクセスしてみたものの403 Forbiddenを食らい、さらに検索ページに戻ってキャッシュ内にあるリンクをクリックしてくれた人」というとてもとても奇特な人、ひょっとしてファンですか? ということになります。それから一月、別にas-is.netにクロールに来るでもなく、hassaku.main.jpがキャッシュから消えるでもなくという状態です。

11月16日

この状態もナニかなーと思うので、最終手段として「410 Gone」を発動してみることにしました。ただ今回は少し誘導しましょうということでErrorDocumentも設定してあります。「403 Forbidden」のときに誘導しなかったのは「アクセス禁止にも拘らず誘導する」のに意味的な矛盾を感じたからで、今回の「ここのはなくなったので誘導する」というのはむしろ自然に感じられます。

RewriteCond %{HTTP_HOST} ^(hassaku\.main\.jp)(:80)? [NC]
RewriteRule ^.* - [L,G]
 
ErrorDocument 410 /error/410.shtml

で、/error/410.shtmlの中身は↓のような感じですね。これでしばらく(今度こそ)放置しようと思います。

<html>
<head>
<title>410 Gone</title>
<meta name="ROBOTS" content="NOINDEX,NOFOLLOW">
</head>
<body>
<h1>410 Gone</h1>
<p>The requested URL was gone from this server.
The link on the referring page seems to be wrong or outdated.</p>
<p>The requested page will be found on: 
<a href="http://as-is.net<!--#echo var="REQUEST_URI" -->">
http://as-is.net<!--#echo var="REQUEST_URI" --></a></p>
<hr>
<address>as-is.net</address>
</body>
</html>

12月23日

とうとう、ErrorDocument 410 /error/410.shtmlを削除しました。hassaku.main.jp以下にアクセスするとすべて410 Goneとなり、しかも移動先も示されません。