2007年10月アーカイブ

言っておくか。

検索エンジンからは、そのリンクが有料リンクかその他のリンクかは分からない。少なくともロボットがHTMLを見て分かるわけがない。

分かるとしたら、関連性の低いページから不自然に大量にリンクされたサイト/ページ/ドメイン、であるとか「価値の高いページが自然に得るリンク」とは何か違う力によって貼られた「であろう」リンクくらいか。

だから、「有料リンクにペナルティ」とGoogleは言っているかもしれないが、「単なる検索エンジン側の検索結果の是正・補正」に過ぎない。

ランクの低下、ペナルティに人の手が入っているにしても、不自然な上位表示とか目立つものは機械的に見つけるんだろうよ。

* 人の手入れるんなら広告の審査をして欲しいものだけどね。

そこに働いているのは「検索結果」と「リンク先」と「検索順位」のバランスを取ろうという行為だけで、その中に人為的なマイナス評価(例えば特定のドメインや特定のパーツをマイナス評価するなど)が加わったとしても、それはペナルティとはいわず「是正をはかった結果、ランクが変化した(本来あるべきランクに戻った) ということだろう。

まぁ一時BMなんとかっていう自動車の件が話題になったわけだけれども、『『「BMなんとか」ってキーワードで検索した人がどんなページが上位に表示されるかという期待』と『検索結果』が一致していること』ってのが検索エンジンの検索結果に対する前提だろうしそんなことは昔から言われていたことだろうよ(だから、ユーザーが求める情報をきちんと掲載して、他のメディアも含めて自ブランドの価値を高めて行くなんていう普通のことが検索エンジンの検索結果に反映するようになってるんじゃないのか)。

だからGoogleを欺くってのもちょっとニュアンスが違っていて、「ユーザー」を欺いて利益を上げようとする行為をやらない、ということだ(Googleが利益をくれるわけじゃなくて、ユーザーが利益をもたらすのだから)。

しかし毎度毎度何を大騒ぎしているのか良くわからん。もちろん、それが利益 (あくまでも短期的な) に直結するから騒いでいるということくらいは分かるけどさ。

さて、余談の余談。

検索エンジンからはそれが動的生成によって吐かれたページなのか静的生成によって吐かれたページか、なんてことはわからない。

検索エンジンが判断するとしたら、リクエストに対するレスポンスの速さ、HTTPヘッダによって推測する、URLによって推測する、この位なもんだろう。

HTTPヘッダなんて好きなようにコントロールできる。URLだって mod_rerwrite を使わないまでも何とでもなる。クライアントのリクエストに対してHTTPヘッダ、ボディをどう返すかなんていかようにもコントロールできる。何なら自分でHTTPサーバーを作ったっていい。

だから、「静的生成はSEOに有利」ということを真面目な顔で謳っているSEO業者はガセ。言うのは自由だけど。

言えるのは静的生成されたページが複雑なパラメタの付いていない、1ページ1URL であることがインデクシングされない(にくい)リスクが少し減る、ってのと非力なサーバーでも高速なレスポンスが得られやすい、という点くらいか。

と、いうことでウチの会社では SEO はやりません。

あぁ、すっきりした。

こんなこと書くとまた SEOな方に怒られるかもしれんけど、誤用? されてる言葉、イメージの低下した言葉を使い続けることによる不利益ってものを考えたら、やらないってのも潔いかなと思ってさ。もちろん「クライアントの利益を最大化」するのが仕事ですよ。そのためには「検索エンジンの先にあるユーザー」へ最適化しないといけない。「User's Value Optimizatin」とか「User Experience Optimizatin」とか、まぁ横文字はもういいか。

某所でまたHTTPステータスコードの話が盛り上がってるみたいだけどちゃんと見解というか言ってなかったな。というか今日あげたエントリーがBlogのサーバー側のキャッシュの話だったので (検索結果のフィードも吐けるようにしてあるし) このブログのMTのダイナミックパブリッシングのHTTPヘッダをチェックしてみた。

あらあら、404だわ。mod_rewriteが無効だからNotFound扱いで動いてるのね。このままじゃ怒られちゃう(謎)から修正。

一応MTが吐く.htaccessはこんな風になってる。


## %%%%%%% Movable Type generated this part; don't remove this line! %%%%%%%
# Disable fancy indexes, so mtview.php gets a chance...
Options -Indexes +SymLinksIfOwnerMatch
<IfModule mod_rewrite.c>
# 中略, あとコメント部とかも略
  RewriteEngine on
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ /online/mtview.php [L,QSA]
</IfModule>

<IfModule !mod_rewrite.c>
# mod_rewriteが無効なのでこっちが有効になってる
  ErrorDocument 404 /online/mtview.php
  ErrorDocument 403 /online/mtview.php
</IfModule>
## ******* Movable Type generated this part; don't remove this line! *******

ということで、まずはmod_rewriteを有効に(サーバーはUbuntu)。


sudo a2enmod rewrite
sudo /etc/init.d/apache2 restart

Ubuntu簡単。これでステータスは200になったけど、mtview.phpも修正。Last-Modified ヘッダを付ける (MTがちゃんとしてないというわけでなくて、クエリーの付いていないリクエストであれば $mt->caching = true; $mt->conditional = true; で済む)。

mtview.php


<?php
    $server_cache = 86400; //サーバー側のキャッシュの有効期間=1日
    $cache_dir = '/path/to/cache/';
    include('<$MTCGIServerPath$>/php/mt.php');
    $mt = new MT(<$MTBlogID$>, '<$MTConfigFile$>');
    $pattern = '/(search¥.html|feed¥.xml)/';
    if (preg_match($pattern, $_SERVER['REQUEST_URI'])) {
        $search = true;
        //check own cache
        $path = getenv('REQUEST_URI');
        $path = str_replace('/', '%2F', $path);
        $path = str_replace('..', '', $path);
        $cache = $cache_dir . $path;
        if (file_exists($cache)) {
            $mtime = filemtime($cache);
            if ((time() - $mtime) > $server_cache) {
                unlink($cache);
                $match = 0;
            } else {
                header( "Last-Modified: " . gmdate( "D, d M Y H:i:s", $mtime ) . ' GMT' );
                //キャッシュの更新日を Last-Modified ヘッダに付ける
                $match = 1;
            }
        } else {
            $match = 0;
        }
        if ($match) {
            if (!($fcache = fopen($cache, 'r'))) {
                $match = 0;
            } else {
                $rec = file($cache);
                foreach ($rec as $line) {
                    echo "$line¥n";
                }
                return;
            }
        }
    } else {
        $mt->caching = true;
    }
    $mt->conditional = true;
    ob_start();
        $mt->view();
        $output = ob_get_contents();
    ob_end_clean();
    $ctime = time();
    header( "Last-Modified: " . gmdate( "D, d M Y H:i:s", $ctime ) . ' GMT' );
    //キャッシュがなければ現在の時刻を Last-Modified ヘッダに付ける
    echo $output;
    if ($search) {
        //save own cache
        if (!($match)) {
            if (!($fh = fopen($cache, 'w'))) {
                return;
            }
            fwrite($fh, $output, 128000);
            fclose($fh);
            touch ($cache, $ctime); //矛盾がないようにtouchしてやる
        }
    }
?>

確認!


$ telnet junnama.alfasado.net 80
Trying 61.205.62.101...
Connected to junnama.alfasado.net.
Escape character is '^]'.
HEAD /online/feed.xml?query=Movable%20Type&offset=1&limit=20&blog_id=1&tag=1 HTTP/1.0

HTTP/1.1 200 OK
Date: Tue, 23 Oct 2007 11:01:56 GMT
Server: Apache/2.0.55 (Ubuntu) mod_fastcgi/2.4.2 PHP/5.1.2
X-Powered-By: PHP/5.1.2
Last-Modified: Tue, 23 Oct 2007 10:57:43 GMT
Connection: close
Content-Type: text/html

Connection closed by foreign host.

200 OK, Last-Modified:ヘッダもちゃんと返って来てるね。

RSSリーダーがHEADリクエスト送って来てタイムスタンプチェックしてるかとか、If-Modified-Sinceヘッダ送って来てるかとか知らんけど、更新されてなかったら304 Not Modifiedを返すと尚良いだろうね。

If-Modified-Sinceヘッダをチェックして、日付フォーマット解析して(Perlだったっらモジュールで一発だけど、PHPはどうなんでしょうね)、キャッシュのタイムスタンプと比較して更新されてなければ304 Not Modified を返すということ。

まぁ、これは週末にでもやるとしよう。あと、検索結果が該当しなかった時は...リクエストが不正なわけじゃないんだけどfeedの場合は400が良いのか? 検索結果だから後々有効になる可能性もあるし200で良いわな。何らかのメッセージは返すのが良いのだろう。

そろそろamebloのフィードの件について...

あ、一応書いておく(amebloのRSSの件ね)。200とか以前に、Last-Modifiedヘッダも吐いてないし、JSPってか動的に吐いてるのだろうか?

GET /staff/rss20.xml


...
Set-Cookie: JSESSIONID=XXXXXXXXXXXXXXXXXXXXXXXXX; Path=/
Connection: close
Transfer-Encoding: chunked
...

CGIほどのオーバーヘッドはないにしても...。静的なファイルを都度生成でなくとも少なくともサーバーサイドにキャッシュくらいしてるよね。HEADリクエスト送っても更新日情報返ってこないからクライアントはIf-Modified-Since付けないでGETリクエスト送るしかないし、サーバーも304も返しようがない。ヘッダ自前でコントロールするの面倒ならApacheに任せちゃえばいいんじゃないか? (ファイル作っときゃいいし)

この調子だからコンテンツも動的なんだろうね、と思ったらURLは~.htmlだ。

GET /staff/entry-10051436729.html


...
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Expires: Mon, 23 Oct 2006 10:49:21 JST
...

no-cacheって...これもおそらく動的生成なんだろうね。.htmlって、あ、SEOかSEOそんなに(というより、負荷対策よりも)大事なのか?

物理的にサーバー増設とか変更するにしてもリバースプロキシとかロードバランサとかでURL変更しない手は無いのか。それ以前にキャッシュ (現金じゃなくて!) という言葉をご存知か?(サーバー側でどう処理してるかは知らんけど少なくともクライアントキャッシュは使われないわけだし) ステータスコード云々よりも負荷対策きちんとやったりURL変更しなくて良い手を考えるのが先、ってもう遅いか。

とりあえずキャッシュの有効時間1時間ということで実装してみた。mt.php の view を上書きしてSmartyのキャッシュを使おうかとも考えたけど、mtview.php に直接書いた方が早そうだったので。

MT3では Dynamic Site Bootstrapper っていう名前のインデックス・テンプレートがあったけど、MT4ではテンプレートではない。キャッシュのオンオフは管理画面からチェックボックスひとつで行えるようになってる。なのでMT4の場合は Dynamic Site Bootstrapper テンプレートを改めて作成してから以下のような感じで(出力ファイル名はmtview.php)。

ob_start, ob_get_contents ってのがあるのね。知らんかった。ってか、PHPって本当何でも関数になってる。好きな人多いの分かるわ、いや本当。

あとはキャッシュクリアのタイミングをPerl側プラグインで書いてCMS側で行うようにすれば良いわけですね。


<?php
    $server_cache = 3600; //キャッシュの有効時間(秒)
    $cache_dir = '/path/to/cache/'; //キャッシュの保存場所
    include('<$MTCGIServerPath$>/php/mt.php');
    $mt = new MT(<$MTBlogID$>, '<$MTConfigFile$>');
    $pattern = '/(search¥.html|feed¥.xml)/';
    if (preg_match($pattern, $_SERVER['REQUEST_URI'])) {
        //キャシュ条件を細かく指定する場合はこの辺であれこれと...
        //この状態だとキーワード検索だろうが何だろうがキャッシュする
        $search = true;
        //check own cache
        $path = getenv('REQUEST_URI');
        $path = str_replace('/', '%2F', $path); //
        $path = str_replace('..', '', $path); // ../とか含めない
        $cache = $cache_dir . $path;
        if (file_exists($cache)) {
            $mtime = filemtime($cache);
            if ((time() - $mtime) > $server_cache) {
                //unlink($cache);
                $match = 0;
            } else {
                $match = 1;
            }
        } else {
            $match = 0;
        }
        if ($match) {
            if (!($fcache = fopen($cache, 'r'))) {
                $match = 0;
            } else {
                $rec = file($cache);
                foreach ($rec as $line) {
                    echo "$line¥n";
                }
                return;
            }
        }
    } else {
        $mt->caching = true;
    }
    $mt->conditional = true;
    ob_start();
        $mt->view();
        $output = ob_get_contents();
    ob_end_clean();
    echo $output;
    if ($search) {
        //save own cache
        if (!($match)) {
            if (!($fh = fopen($cache, 'w'))) {
                return;
            }
            fwrite($fh, $output, 128000);
            fclose($fh);
        }
    }
?>

追記:

MT4ではメニューにしてみた。MT3は...どうするかな。

プルダウンメニューからキャッシュのクリアが出来る

あと、mtview.phpを少し修正。

続き。

某? SNSの某コミュで「作って欲しいプラグイン」ってなトピが立っていて、前回公開した StylelessImage について盛り上がってまして...

yujiroさんがちょっとの差で同じ目的のもの書いて公開したり

今度は画像の前後に付くspan タグの件で p がいい、いやdivがいい、いや、選べるのがいい...という話に移行してます。

どういうことかどいうと、こちらのエントリーが詳しいようです。

要するに、

  1. WYSIWYGエディタが画像の位置指定のために class, style をインラインで入れる(前回解決)
  2. objectassetの保存のために(要するにエントリーと画像の関連づけのために) 画像を formタグ
  3. で囲む
  4. formタグのままだと「あんまり」だけどMT側で「span」に変換してくれるよ

ところが、MT側で「span」に変換するので、まぁいいか...でもやっぱり嫌だなぁ... p がいいなぁ いや div が...という皆さんの意見が飛び交っていたのです。

いや、そういうの嫌いじゃないですよ。はい。僕も昔はそういうのすごく気になってた。今? 今はスーツだから、まぁ何だ、工数が見合うなら...

StylelessImage プラグイン v0.2

画像挿入の際に、位置指定「なし」を選択できるようにします。「なし」を選択すると、img要素のclass, styleをカットするようになります。さらに タグ属性 assetelement を指定出来るようにして、スタティック/ダイナミックパブリッシング両対応にしました。

タグ属性の指定の仕方

元のソースが以下とします。

<form mt:asset-id="12" class="mt-enclosure mt-enclosure-image"><img alt="foo" src="bar" width="n" height="n" /></form>

このまま保存するとform部分は<span class="mt-enclosure mt-enclosure-image"...に変換されます。そこで、属性 assetelement を指定します。

例1)
<$MTEntryBody assetelement="p"$>
         ↓
<p><img alt="foo" src="bar" width="n" height="n" /></p>

例2)
<$MTEntryBody assetelement="p,photo"$>
         ↓
<p class="photo"><img alt="foo" src="bar" width="n" height="n" /></p>

例3)
<$MTEntryBody assetelement="div,thumb"$>
         ↓
<div class="thumb"><img alt="foo" src="bar" width="n" height="n" /></div>

例4)
<$MTEntryBody assetelement="0"$>
         ↓
<img alt="foo" src="bar" width="n" height="n" />

そろそろ受託仕事での商用サイトをMT4でリリースしていくケースが増えてきた。商用サイトであればそれなりにヘビーに使い込むことになるし、僕のようにプラグイン書いたりソースと睨めっこして徹夜して悩んだりする人間はバグも仕様も含め色んなものが都度発見されて、それはその都度フィードバックするようにしている。

このくらいの機能を持ったソフトウェアにバグがないなんてことは考えにくいわけで、きちんとそれを把握して適切な処置をして次のリリースで修正するとかパッチを提供するとかやっていけばいいのだと思う。ただユーザーとのコミュニケーションとの取り方とかアナウンスの仕方って結構難しい。時代が時代(CGM? つまり簡単に発信できてしまう時代) というかソフトウェアがCGM生成ソフトそのものであるわけだから、対応を間違うと(間違えなくても受けた側が「間違えやがった」と思ってしまえばそれまで) 何を書かれるかわかったものじゃない。

(あ、別にこのエントリーは不満を書いているわけではないのでお断りしておきます)。

企業がプロダクトとしてリリースしているソフトウェアであれば各種「大人の事情」があってリリースの方針もあるだろう。例えば毎日のように修正版がリリースされることが良いという人も入れば悪いという人もいるのだし(もっとちゃんとテストなりしてから出せよとか、まとめてくれよ、手間かかるとか)。

などと考えたのは、僕も何らかの製品をユーザーに提供する形のビジネスをやっていきたいという思いがあるっていうのと、MTOSってどうなったんだっけ? ということを考えたからだ。MTOS(というよりオープンソース)のことを考えたのは、MT4で一つわかりやすい? 不具合を見つけて、それは既にフィードバックしたし次回は確実にFixされるだろうからいいとして、既に動き出しているサイトにおいてはそれを待つわけにもいかないし。

「タグ」を空に出来ない問題

わかりやすい不具合? と書いたのは『エントリーの編集時に一度「タグ」を付けると空にできない』件。あんまり話題になってる風もないなぁと思っていたのだけど今進めているプロジェクトでは運用上困る。

タグ入力欄

なので、パッチではなくプラグインを書いた。MTではそういう対処が出来る。本体に手を入れるとサポート対象外になるし、かといって目の前の問題をクリアしなければならない時にフィードバックを投げつつプラグインで対処しつつ次のリリースで修正してもらう、というサイクル。

せっかくなので晒しておきます。期間限定ですけどご自由にお使いください

TagRemover Plugin

受託のWeb屋は顧客にサービス提供する立場だから、何か問題を見つけてメーカーにキレてる暇があったら簡単なプラグインの書き方くらいは身につけておいた方がいいよ。

MTOSは12月?

話を戻す。製品でなくオープンソースであった場合、もちろんそのコードにコミットする人とかそれを支える人たちが問題の認識をして、対応するコードを書いて、アナウンスして、利用する人は自己責任でそれを使う。それでもソフトウェアやプロジェクトによってルールや風土も違う部分があるだろう。

現状ではMTOSにおいてはその辺の話題もさっぱりだし、何かその後の展開が予測しづらい。

Movable Type.org - Home for the MT Community: Welcome to MTOS: the Movable Type Open Source Project では時折投げられるコメントに対してByrne Reese氏が返答を返す形で9月頭くらいで一旦やりとりがなくなり、1月以上経過して、

What is happening with MTOS? Will it be available soon?

という書き込みがあり

What is the Movable Type Open Source project? のブロックではQ3に打ち消し線が引かれ、Q4に訂正されている(書き込みと訂正のどちらが先かは知らない)。

to be released in Q3 2007 Q4 2007,

(del要素でなくstrikeなのが何だか...ではあるけど)

すんごく控えめに告知

まぁ、MTOSをどうするかについてはリリースするSixApartさんが最終的に色んな判断をして行うことになるだろうしそれでいいと思う。

ただ...11月半ばならさすがにいい加減何か形が見えてるからなってことで、話す予定入れちゃったのよね。

関西方面の方でMTのヘビーユーザーの方、MTOSに興味津々のそこのあなた。関西オープンフォーラム (関西オープンソース2007) で宜しければお会いしましょう。

元ネタ↓というか、酔った勢い!?で「書くよ」とか言ったみたい? なので。

画像挿入時に位置指定「なし」が選択出来る

「<div><br /></div>」が入るのはFirefoxの挙動みたいというか全体的にWYSIWYGの挙動はブラウザによって違うので色々試せたわけじゃないので気づいたことがあれば教えてください。Mac Firefoxでしか確認してない(Safariでも確認したけどそもそもSafariだとWYSIWYG で画像貼れないし)。

画像貼るのにも色んなパターンがあるみたいで(画像アップ→新規エントリーを作成とか、エントリーの編集画面から画像挿入とか)、ケースによってクリーンアップするタイミングが少し違うので、WYSIWYGで見た時に何か影響が出るのではないかとかその辺は分かりません(笑)。

あと、元々美しくないものに対してごにょごにょしてるし、こいつ自身美しくないので先に謝っておきます(謎)。

ダウンロード

追記:タグ属性の指定の仕方

元のソースが以下とします。

<form mt:asset-id="12" class="mt-enclosure mt-enclosure-image"><img alt="foo" src="bar" width="n" height="n" /></form>

このまま保存するとform部分は<span class="mt-enclosure mt-enclosure-image"...に変換されます。そこで、属性 assetelement を指定します。

例1)
<$MTEntryBody assetelement="p"$>
         ↓
<p><img alt="foo" src="bar" width="n" height="n" /></p>

例2)
<$MTEntryBody assetelement="p,photo"$>
         ↓
<p class="photo"><img alt="foo" src="bar" width="n" height="n" /></p>

例3)
<$MTEntryBody assetelement="div,thumb"$>
         ↓
<div class="thumb"><img alt="foo" src="bar" width="n" height="n" /></div>

例4)
<$MTEntryBody assetelement="0"$>
         ↓
<img alt="foo" src="bar" width="n" height="n" />

まずはお詫び。

先週土曜日の分科会、「テーブルリーダー」ということだったのですが、僕のテーブル以外は「セミナー」なのか「テーマ設定して取り組み」なのかはっきりやられていたみたいです。僕のところは「ゆるい」設定で各自のテーマ! ってことでやってしまったので限られた時間の中で成果を実感できなかったかもしれません。ごめんなさい(ということで、ちゃんとしたレポートになってませんが重ねてごめんなさい!)。

うまくいかなかったらちゃんとフォローアップすればいいんだと考えるのだ!

という反省もあって、当日出ていた話題に対する実装方法の例をここで挙げてみたい。当日うまく出来なくて何か消化不良だった人は参考にしてください。

MTSetVarとMTIfを駆使しよう

荒木さん藤本さんも、MT4のテンプレート使いこなしのキモ? は MTSetVar (mt:setvar) と MTIf (mt:if) って言ってらっしゃった(と解釈しています) し、黒野さんが挙げていたテーマもそのあたりを活用した分岐のさせ方だったので、実際に話に出ていた「同じタグを含む他のエントリーのリストをエントリーアーカイブに表示させる」という例で少し解説してみたいと思います(一応実際に書いて動作させてみてますけど間違いあったらツッコミ歓迎)。

「関連するエントリー」を簡単に実現する例 (同一のタグ付きエントリー一覧をエントリーアーカイブに表示する)

関連するエントリーを判断するのに何をもって行うかという話はあるとして、今回は「同一のタグが付いているものを関連するエントリーとして」扱いたい、ということでした。 プラグインを使えば出来るよってな話はあがっていたのですが(小川さんのTagSupplementalsプラグイン)、今回はMTタグだけで実現してみたいということだったので以下のように考えてやってみました。

最初のテンプレート


<MTEntryIfTagged>
<MTSetVarBlock name="entrytags"> <!--(*注)-->
    <MTEntryTags glue=" OR "> <!--←関連性をより強くするなら AND-->
        <$MTTagName$>
    </MTEntryTags>
</MTSetVarBlock>

<!--$entrytags は例えば「Movable Type OR アクセシビリティ」のようになる -->

<MTEntries tag="$entrytags" lastn="10">
<!--<MTEntries tag="Movable Type OR アクセシビリティ" lastn="10">と同義-->
    <MTEntriesHeader>
    <ul>
    </MTEntriesHeader>
        <li><a href="<MTEntryPermalink>"><MTEntryTitle></a></li> 
    <MTEntriesFooter>
    </ul>
    </MTEntries>
</MTEntryIfTagged>

(*注: スタティック生成の時には MTSetVarBlockの中に改行を入れると複数タグが含まれるエントリーでエラーになります。MTSetVarBlock内に改行を入れずに記述すれば通ります)

このテンプレートによってエントリーに付けたタグと同じタグの付いたエントリーのリスト(Max)10件が出力されます。手順としては、

  • MTSetVarBlock で MTEntries のモディファイア (アトリビュート) を組み立てて
  • <MTEntries tag="$entrytags" lastn="10"> のように変数 $entrytags をモディファイア (アトリビュート)に指定する

という流れです。

さて、これで同じタグの付いたエントリーを表示できることはできましたが、このやり方だと当該エントリー (そのエントリー自身) が関連項目としてリストアップされて不細工です。そこで以下のように修正します。

改良版テンプレート


<MTEntryIfTagged>
<MTSetVarBlock name="entrytags">
    <MTEntryTags glue=" OR ">
        <$MTTagName$>
    </MTEntryTags>
</MTSetVarBlock>

<MTSetVarBlock name="thisid"><MTEntryID></MTSetVarBlock>
<!--↑当該エントリーのIDを 変数 $thisid にセット-->

<MTEntries tag="$entrytags" lastn="10"> 
    <MTSetVarBlock name="eid"><MTEntryID></MTSetVarBlock>
    <!--↑ループ内で呼ばれたエントリーのID を $eidにセット-->
    <MTEntriesHeader> 
    <ul> 
    </MTEntriesHeader> 
        <MTUnless name="eid" eq="$thisid"> <!--$thisid と $eid が同じでなければ-->
        <li><a href="<MTEntryPermalink>"><MTEntryTitle></a></li> 
        </MTUnless> 
    <MTEntriesFooter> 
    </ul> 
    </MTEntriesFooter> 
</MTEntries>
</MTEntryIfTagged>

これで当該エントリーは除外されるようになりました。

さてさて、まだ改善の余地がありそうですね。

  • 当該エントリー以外に同一のタグが付いたエントリーがなかった時(つまり、ブログ内ではじめて付けたタグの場合)、中身がないのに<ul>,</ul>を出力してしまう。
  • Max10件を表示させたいのに 場合によっては 9件しか表示されない場合が出てくる。

これらの点をクリアするようにテンプレートを修正してみます。

最終版テンプレート

もうこのくらいになると答えは一つではないけれど、これで意図した結果が得られます。ちなみに、関連するエントリーの並び順を明示したい場合は sort_by, sort_orderを指定することになります。


<MTEntryIfTagged>
<MTSetVarBlock name="entrytags">
    <MTEntryTags glue=" OR ">
        <$MTTagName$>
    </MTEntryTags>
</MTSetVarBlock>

<MTSetVarBlock name="thisid"><MTEntryID></MTSetVarBlock>
<MTSetVar name="match" value="0"> <!--←当該エントリーがマッチしたかどうか-->

<MTEntries tag="$entrytags" lastn="11"> <!--←10件でなく11件にしておく-->
<MTSetVarBlock name="eid"><MTEntryID></MTSetVarBlock>

<MTSetVarBlock name="entryCount"><MTEntriesCount></MTSetVarBlock> <!--←何件マッチしたか-->
<MTIf name="entryCount" gt="1">
<!--↑1件以上マッチしたら出力 / 1件だったら当該エントリのみと判断できる-->
    <MTEntriesHeader> 
        <ul> 
    </MTEntriesHeader>
    <MTUnless name="__last__"> <!--←ループの最後でなければ-->
        <MTUnless name="eid" eq="$thisid">
            <li><a href="<MTEntryPermalink>"><MTEntryTitle></a></li>
        <MTElse>
            <MTSetVar name="match" value="1"> <!--←当該エントリーがマッチしたことを覚えておく!-->
        </MTElse>
        </MTUnless>
    <MTElse> <!--←ループの最後ならば-->
        <MTIf name="match"> <!--←当該エントリと既に一致していた場合は11件目を表示-->
            <li><a href="<MTEntryPermalink>"><MTEntryTitle></a></li>
        <MTElse>
            <MTIf name="__counter__" ne="11"> <!--←11件に満たない場合も無条件に表示-->
                <li><a href="<MTEntryPermalink>"><MTEntryTitle></a></li>
            </MTIf>
        </MTElse>
        </MTIf>
    </MTElse>
    </MTUnless>

    <MTEntriesFooter> 
        </ul>
    </MTEntriesFooter>
</MTIf>
</MTEntries>
</MTEntryIfTagged>

最後の</ul>は__last__のブロックに突っ込んでも一向に構わないだろうけど、テンプレートの可読性を考えてこのように。 結構複雑になるけれども、プラグインが無くてもテンプレートタグ(特に MTSetVar と MTIf を活用すれば) このくらいの分岐は可能です。

ループの途中でマッチした際に $match に「1」をセットするあたり、もはや「タグ」でもなくてプログラミング的な思考が問われるところですが、MT扱うマークアップエンジニアの人はMTSetVar と MTIf を駆使できるようになると幅が広がると思うよ!

テンプレートを使えばもっと簡単にできるんじゃないかなぁと思って。

今日はWebSig MT分科会だったのですが、テーマ的に「テンプレート」だったので「ハック」ネタじゃなくって「テンプレート」ネタです。

追記:

ここにありますね。例が。なぁんだ...

MT3のエクスポート形式で書き出してMT4へインポートすると「出力ファイル名(basename)」とか「タグ」とかが引き継がれないので、その対処法。つまり、「書き出し」の代わりののテンプレートを作ってしまえないかな、というお話。

殆どMT4の lib/MT/ImportExport.pm のまま。改行位置とかがMT3だとうまくいかないところがあるのでちょっと調整しました。

MTのエクスポートフォーマットではセパレーターに連続したハイフン(-)が使われるので、ついでに行頭の-を数値参照に変換するプラグインも一緒に作った。


<$MTEntryBody exportformat="1"$>

プラグインのダウンロード

MT3側でインデックス・テンプレートとして以下のテンプレートを作成して再構築。生成されたソースを保存してMT4へインポートするとbasenameやタグをそのまま引き継げます。


<MTEntries lastn="1000">
AUTHOR: <$MTEntryAuthor strip_linefeeds="1"$>
TITLE: <$MTEntryTitle strip_linefeeds="1"$>
BASENAME: <$MTEntryBasename$>
STATUS: <$MTEntryStatus strip_linefeeds="1"$>
ALLOW COMMENTS: <$MTEntryFlag flag="allow_comments"$>
CONVERT BREAKS: <$MTEntryFlag flag="convert_breaks"$>
ALLOW PINGS: <$MTEntryFlag flag="allow_pings"$>
<MTIfNonEmpty tag="MTEntryCategory">
PRIMARY CATEGORY: <$MTEntryCategory$></MTIfNonEmpty>
<MTEntryCategories>
CATEGORY: <$MTCategoryLabel$>
</MTEntryCategories>
DATE: <$MTEntryDate format="%m/%d/%Y %I:%M:%S %p"$>
<MTEntryIfTagged>
TAGS: <MTEntryTags include_private="1" glue=","><$MTTagName quote="1"$></MTEntryTags></MTEntryIfTagged>
-----
BODY:
<$MTEntryBody convert_breaks="0" exportformat="1"$>
-----
EXTENDED BODY:
<$MTEntryMore convert_breaks="0" exportformat="1"$>
-----
EXCERPT:
<$MTEntryExcerpt no_generate="1" convert_breaks="0" exportformat="1"$>
-----
KEYWORDS:
<$MTEntryKeywords exportformat="1"$>
-----
<MTComments>
COMMENT:
AUTHOR: <$MTCommentAuthor strip_linefeeds="1"$>
EMAIL: <$MTCommentEmail strip_linefeeds="1"$>
IP: <$MTCommentIP strip_linefeeds="1"$>
URL: <$MTCommentURL strip_linefeeds="1"$>
DATE: <$MTCommentDate format="%m/%d/%Y %I:%M:%S %p"$>
<$MTCommentBody convert_breaks="0" exportformat="1"$>
-----
</MTComments>
<MTPings>
PING:
TITLE: <$MTPingTitle strip_linefeeds="1"$>
URL: <$MTPingURL strip_linefeeds="1"$>
IP: <$MTPingIP strip_linefeeds="1"$>
BLOG NAME: <$MTPingBlogName strip_linefeeds="1" exportformat="1"$>
DATE: <$MTPingDate format="%m/%d/%Y %I:%M:%S %p"$>
<$MTPingExcerpt exportformat="1"$>
-----
</MTPings>
--------
</MTEntries>

しばらくご無沙汰だったけど、来やがった。久しぶりに。でもって一旦来たら津波のよう(大げさ)に押し寄せるのがトラックバックスパム。

一通りの対策はしたつもりだけど...

JavaScriptでトラックバックURL生成するって方法もあるんでしょうが、JavaScript 前提ってのにどうしても踏み出せない性格なのです。

えーー、それではこんな手はどうだ。トラックバックURLにimg要素を挟んでしまう。

http://junnama.alfasado.net/mt/tr9ErXz.mt/22<img src="/spacer.gif" alt="" width="1" height="1" style="display:none" />28/mt4_2.html

ってな具合。

実際には

http://junnama.alfasado.net/mt/tr9ErXz.mt/2231/mt4_2.html

という感じで普通にブラウザからコピーできるし (IEでも出来るよね? 試してないけど) 、altは空だから音声読み上げでも普通に読めるし。

どうせならどの位置にimg要素を挿入するかランダムにしてまえってことで、MTのプラグインまで書いたぞ!


<$MTEntryTrackbackLink spam_uzeeeeeeeee="/spacer.gif"$>/<$MTEntryBasename$>.html

プラグイン名は Spam Uzeeeeeeeee!! です。MT3/4両対応の筈。MT3.3で確認しました。ad hocちゃぁそうですけど使ってみようと思う方はご自由にどうぞ!

エントリーのプレビューを繰り返しているといつのまにかゴミがたまる?

MT4からエントリーのプレビューにテンプレートのデザインが適用されるようになった。「確認」ボタンをクリックすると一時的にプレビュー用のファイルを構築した後管理画面内の iframeで表示させるようになっている。

一時ファイルはエントリーを「保存」した際に削除される。

タスクを定期的に実行するように設定されている環境では、remove_temporary_files (Core.pm) というルーチンが定期的に実行されて、一時ファイルは削除される(のだと思う)。

ということは「タスクが定期的に実行されない環境」で、且つ「プレビュー時にボタンクリックで遷移しないでウィンドウを閉じてしまった場合(あるいはバックボタンをクリックしたり他のページへ移動してしまった場合とか、極端な話ブラウザがクラッシュしたとか等)」は、一時ファイルは残るということ。実際にそこそこのボリュームのサイト制作後にディレクトリの中を見ると、結構残ってる。使い方が悪い? って言えばそうなのかもしれないけど「一時ファイル残るかもしれないから気をつけろよ」ってのも何だか違う気がする。

利用方法

mt-config.cgi に以下の一行を入れてください(※FastCGI環境では動作しません)。


LaunchBackgroundTasks 1

プラグインディレクトリにTemporaryFileCleaner.plを入れると有効になります。

LaunchBackgroundTasks が有効な環境でないと動作しません (プレビュー時に3秒程待たされてから404ってなことになります)。

ダウンロード

まだやってるし...もうちょっと続くかもしれないけど、皆さんもう飽きました?

ブログ内検索




  

実は検索だけじゃないのだ

ブログにおけるエントリーの関連性や体系付けを行うしくみとして、カテゴリーやタグというものがある。一方、読者が欲しい情報を探す一つの手段として「検索」がある。

カテゴリーもタグも発信者の主観で付けられるものだ。ソーシャルブックマーク等のタグはそうではないけれども。

高速なタグ・アーカイブが欲しい

発信者が主観で付けられるものゆえ、検索語の選択が無限であるのに対してカテゴリーやタグは有限だ。であるならばこれらのアーカイブは静的アーカイブにすることが可能で、MTのカテゴリーアーカイブが静的生成できるのは良いとして、タグ・アーカイブも静的生成できるのが望ましいのではないかと思う。

できないのか? と思ってググってみると、同じようなことを考えて既に行っている方がいらっしゃるわけですね(静的生成ではないけど)...

現在作成中のダイナミックパブリッシングを利用した検索ページではタグ検索ができるようになっている。MT4のデフォルトテンプレートでは、タグ検索はmt-searchに渡される動的ページだから、mt-searchが高速動作できる環境でないと遅い(その仕組み上キーワード検索程遅くはないが)。また、CGIによって動的生成されるページへのリンクには nofollowを付けたいところ。でないとGooglebotが (に限らずロボットが) mt-searchを叩くのだ。MTインストール済みレンタルサーバーとかで、ロボットが多くのmt-search を叩いている状況ってのは何だか恐い。ただでさえスパマーがtrackbackやcommentのCGIを叩きまくっているのだし。

モジュール版PHPで動作しているサーバーの場合、CGI(Not FastCGI)の起動よりも早くレスポンスを返せるからこの(PHP)ダイナミック版タグ検索は一つの選択肢になると思う。但し、MTのダイナミックパブリッシングではパラメータ付きのページをキャッシュしてくれない。ここが何とかなるといんだけど。もう少しダイナミック版のソースを追いかけてみよう。

さすがにキーワード検索は動的生成じゃないと、ってかそれでいいと思うし

話がそれた。一方「検索」は発信者ではなく読者が自由に設定するものだ。一部の読者は発信者の意図しないカテゴリの情報を求めているかもしれない。なので基本的に動的生成で良い。問題はMTのデフォルトテンプレートで「検索結果」フィードへのリンクが表示される点だ。確かに検索結果のフィードがとれるってのは便利かと思う。

それでもやっぱり読者がRSSリーダーに mt-search が生成する動的ページのフィードを登録するってのもやっぱり勇気がいるなぁ (サーバーへの負担という点で)。パワーに余裕の無いサーバーではRSSリーダーが取得に失敗するってことも多いのではないだろうか。

ということで、高速・軽量な検索ってのは単なる検索ではなくて、タグ・アーカイブや検索結果のフィード配信の代替プログラムでもあり、カテゴリ+タグでの絞り込みとか、カテゴリ+キーワードの絞り込みとかを実現するためのものなのです。 キーワードはともかく、このあたりを柔軟に指定できて且つ静的に吐き出すことができれば、関連性, 体系付けの選択肢も広がるのではないかな?

分かち書き+MySQL FULLTEXTインデックス検索 VS LIKE検索

両方とも実装してみたのだが、400エントリー弱(実際にはテスト用ブログ等でDBにはその倍くらいが登録されている)のこのブログで、現状のサーバーでは2倍程度の差が出ている。とはいえ20件表示であれば0.08秒前後と0.16秒前後、体感速度に大して差はない。

フィードの生成はもっと高速。各々約半分くらいの時間で検索と構築が終わる。これは検索結果ページではトータルヒット件数を取得してページ送りを実現するためにSQLを2回発行しているからだ(まず全てのヒット件数をカウントしてからoffset, limitを指定して表示すべきエントリーを得ている)。フィードではページ送りが不要だからこの分のSQLが不要。

FULLTEXTインデックス検索には速度以外にもメリットがあって、関連性の高い順に表示することができることだ。また、インデックス作成の際に画像のALTを展開した上でHTMLタグを削除しているから、「タグの中」がひっかからない。
LIKE検索ではたとえば「HTML」について書かれたエントリーを検索したい場合に、アンカータグの中の foo.html とかにもマッチしてしまう。

逆にこれを逆手に取れば、特定のエントリーにリンクしているエントリーの検索とかにはLIKE検索使えるね。例えば、「音声ブラウザと相性の良いHTMLを作る(1)。」にリンクしているエントリーはページのパスをクエリーとして指定してやれば良いわけだ。

ということでね、色々活用法が考えられて 夢がひろがりんぐ でしょ?

変更内容

  • テンプレート保存時にエラーになるバグがありました。
  • ダイナミックパブリッシングに対応しました。
  • テンプレートタグ mt:templateselector → mt:iftemplateselector に変更しました(ダイナミック対応のため(* 互換性のためにスタティック版では mt:templateselector タグも使えるようになっています。

利用方法

プラグインフォルダに TemplateSelector フォルダをアップロードします。Upgrader が起動してデータベースをアップデートします。

テンプレート・モジュールの作成

テンプレートは「モジュール」として設定します。

「デザイン→テンプレート」→「テンプレートモジュール」→「モジュールテンプレートを作成」を選択、テンプレートのバリエーションを複数作成し、「このテンプレートをテンプレート・セレクタに登録する」チェックボックスをオンにして保存します。

テンプレートモジュールの編集画面

アーカイブ(インデックス)テンプレートの設定

アーカイブ・テンプレート又はインデックス・テンプレートを編集します。

MTTemplateSelectorはblockタグ(MT3でいうところの条件タグ)です。name属性にテンプレート名を指定します。以下は編集例です。


<MTIfTemplateSelector name="標準記事">
<$MTInclude module="標準記事"$>
</MTIfTemplateSelector>
<MTIfTemplateSelector name="フォトアルバム">
<$MTInclude module="フォトアルバム"$>
</MTIfTemplateSelector>
<MTIfTemplateSelector name="">
<$MTInclude module="デフォルトブログ記事"$>
</MTIfTemplateSelector>

インストール直後は既に存在する各エントリーにおいてテンプレートが選択されていませんので、<MTTemplateSelector name=""> のように name属性を空にしたブロックを用意してください。テンプレートが指定されていない場合の出力ブロックとなります。

エントリー編集時のテンプレート指定方法

エントリー編集画面にセレクトボックスが表示されますので、ここからテンプレートを指定して各エントリーを保存してください。

エントリー編集時のテンプレート指定

ダウンロード

さらに検索!

| コメント(4) | トラックバック(0)

どんだけ検索好きですか、おいら...さらにブログ内検索の続き

MySQLのFULLTEXT検索を行えるようにしてみた(素直にSenna入れたら? って言わないでね!)。まだ実験レベル。もう少しチューニングしたら使えそうだ。

現状、以下のフォームから検索する場合の仕様というか制限。

  • タグ検索の場合はFULLTEXT検索にはならない(両方にチェック入れた時はタグ検索が優先される)
  • FULLTEXT検索の場合4文字未満はマッチしない→set-variable=ft_min_word_len=1を指定したので解決
  • FULLTEXT検索の場合、検索語は分かち書きされない→クエリも分かち書きしてAND検索とした
  • FULLTEXT検索の場合、順位付の指定は無効で関連性順に表示
  • FULLTEXT検索の場合、ストップワード(半分以上のレコードに含まれるワード)は検索対象にならない
  • スペースで区切るとAND検索、但しタグ検索の場合はカンマで区切ることでOR検索となる

FULLTEXT検索の仕様についてはここらへんを参照ください。




  

ウェブ上のインターフェイス作ってダイナミックパブリッシング用のMTタグ作って試してみるとFULLTEXT検索もLIKE検索もこのブログ(400エントリー弱)だと殆ど変わんない(検索速度がカンマ1秒以下のレベルになるとむしろテンプレートからのページ構築とかプログラムの起動とかネットワークとかブラウザのレンダリングとかそっちの方の速度の影響が目立ってしまうね...追記: SQL見直したら倍程度の差は出た)。

このあたり、実際にはエントリー数をもっとべらぼうに増やしてテストしてみたりしてみたい。そのうちやってみる。

仕様、実装、考察とか課題なんか

拡張したエントリーのフィールド(entry_fulltext)に「タイトル(×3)」「概要(Excerpt)」「内容(Body)」の内容をつないだもの (タグを削除、但し画像のALT属性は含める) をMeCabで分かち書きして入れて (←ここはPerlで書いたプラグインで行う) FULLTEXTインデックスを作成。MySQLのFULLTEXT検索を行い、結果をMTのダイナミックパブリッシング用のプラグインを作成してMTのテンプレートで組み立てる。

「タイトル(×3)」としたのは検索結果におけるタイトルの重みづけをするため。つまりタイトルに検索語が含まれる方がスコア? を高くする。このブログでは「概要(Excerpt)」には殆ど何も入れていないので、「内容(Body)」の先頭100文字程度が重複して入る。エントリーの本文の先頭付近にも重み付けする。つまり、(初期の)SEOのように、出現頻度とか先頭付近のテキスト重視とかでスコアリングされるようにチューニングしてみる。

現状、「ウェブアクセシビリティ」と「アクセシビリティ」は別のものとして扱われる。IPA 辞書に「ウェブアクセシビリティ」が単語として登録されているみたいなので。

つまり、こういった類似語みたいなものをどう吸収するか、とか十分な件数が得られなかった時にあわせてLIKE検索の結果をマージするとか、そのあたりがキモになるんでしょうね。

今日はここまで。続きはまたいずれ。


追記: 何でこんなに検索こだわるかっていうと、色々ニーズがあるのですね。最近は「イントラブログで社内ポータル」っていうような依頼も多くて。なので、導入のしやすさと顧客のニーズにあわせた選択肢を広げておきたいなぁ、というあたりと、あとは単に面白いかな。カンマ何秒短縮出来るか、あるいは求めている結果がどうやればズバリ出てくるのか。

「面白い」といえば、ここ1週間程MTのダイナミックパブリッシングが面白くて、そっちの理由もありますけどね。

最近リニューアルしたmixiのデザインとかマークアップとか色んなことについて色々と話題になっているようで、且つ上記の記事が僕のブログに劣らず香ばしい気配のようだ。

All Aboutさんに何か言う気もないけれど、専門家に聞く必要が果たしてあるんだろうか? CSSや文書構造とか、PerlとかPHPとか話がアサッテの方向に向いてしまうわけで。

(僕も含めて)ブログにあれこれ書いたりする人やネットメディアが「専門家」と呼ぶ人たちの意見など、あまり気にする必要はないんじゃないかな。mixiはそんな「アルファ」な人たちのメディアじゃないもの。

実際に大きく戸惑っているのは、DTP系のデザイナー等のMacOS 9ユーザーとか(事実怒っている人もいるみたい) 音声ブラウザで読み上げて利用している人とか(構造が良くなったとしても戸惑うと思う)、新しいことを覚えるのが面倒/苦手な高齢の方とか、比較的新しいWebサービスとの相性があまり良くない環境下の人じゃないだろうか。それ以外の人はまぁそのうち落ち着くと思うし、一部離れる人もいるだろうがそれはそれで良いと思う(以前も書いたけど、「マイページ」ってのがあるサービスで勝手にあれこれ変更すると、ユーザーは賃貸マンションの自分の部屋を大家に勝手にいじられたような気分になるから、あれこれ言うのは想定内だし)。

これだけ一般化したサービスであるという面からは、むしろ「Yahoo!」の割り切りっぷり (未だに堂々とテーブルレイアウト)」を見習っても良いと思うし、自分たちのサービスを「くらしに密着した公共のインフラ」だというくらいの自負を持って自社サービスの品質向上につなげて行けばいいのではないだろうか。実際にサービスの(ネットワークやサーバーとかDBとかのインフラにおいては、すんごいトラフィックを処理しつつサービスを継続して提供しているわけだし (Twitterの重さって何とかならない...ってか、みんな良く我慢してるなぁ)、ましてやPerlだとかPHPとかそんな話は全くもってどうでも良いし。

もちろん、印刷メディアじゃないんだし「リリースしたら終了」ってわけじゃないからこれからいくらでも変えていけるわけだし、どうかネットの大きな声の人の意見に惑わされること無く、本当のユーザーの声を聞いて改良していって欲しいと思う。

僕自身もマークアップの妥当性とかとても大切だと思っていたけれど、最近はむしろトータルでユーザーの期待にバランスよく応えることが大切という思い(の比重)が強くなっている。

どんなにきれいな構造だろうとサービス重けりゃ意味ないし、デザインには好き嫌いは付き物だし。ターゲットセグメントして尖ったサービス展開するのはそれはそれで良いけれど、「みんなが日常に使うサービスであることが価値」なんだったら、むしろ「ブログやネットメディアに積極的に書かない」一般の? ユーザーの声を拾っていくことが大切ではないかね?

あと僕の考えを一つ付け加えれば、携帯で殆どのことができるのだから旧OSユーザーとか「使いたくても使えない」人には、PCからも「モバイル互換モード」とか用意して、プルダウンも何もなくて良いから「使える」道を残してあげるってのが良いと思うのだが。意外とそちらを使う人も多くいそうな気がする。最近のブログツールの複雑化/重量化についても同じことを思っている。

検索は男のロマン!?

| コメント(2) | トラックバック(0)

相変わらず素早いお仕事? きれいなコード、エレガントな設計、頭が下がります。

ちょっとだけ釣られてみる*。↓この一節にのみ、ですけど :-)

まあなんですか?「文書ドラフト」形式でエントリ別アーカイブを静的生成するとかダルいでしょ?

* もう、釣られると言うか、キャラ定着しつつあるなぁ。イメチェンしなきゃ!

ちょwwこれのこと!? このブログでは「文書ドラフト」形式のアーカイブテンプレートは登録しているけれど静的生成はしていない(昔はしてたけど今はプラグイン書いてる、ってかこの間書いた。公開してないけど案件とかでは使ってます)。

アーカイブマッピングの指定はせずに「登録するデータを組み立てるため」にテンプレートを使っています。

何故か? インデックスに登録したい内容が案件毎に違うから。titleとtextは検索したいけどtext_moreはしたくないとか、拡張フィールド(拡張テーブル)とかMT4だったらassetの内容とかを含めたいとかいう時に(今作成中のこいつもそうだし)、テンプレートの形だったらプログラムに手を入れないでテンプレ担当が対処できるでしょ? きわめて「お仕事」的な事情だけど、そのあたりは自由な方が現場的には使い回しがきくのです。

あとは、タイトルとか、text内でも見出しだったらhiddenの値として出現回数増やして突っ込んで重みづけするとか、タグはremoveするけど画像のalt属性値は活かすとか。

しつこいようだけど、まだ「検索」やってる。

ええっと、何だかいつも(o)さんに怒られてる気分ですが、まだ検索しつこくやってます。

今度は検索してMySQLのFULLTEXTインデックスを作成してMATCH ~ AGAINST で検索を試してみた(ちゃんと日本語通るのね)。

(searchlite_textってのにはmecabで分かち書きした検索対象テキストを入れてある。ええ...「文書ドラフトもどき」のテンプレート作って組み立ててますです)


SELECT * 
FROM  `mt_searchlite` 
WHERE  `searchlite_text` LIKE  '%Movable Type%'

0.0124 秒(10回の平均値)


SELECT * 
FROM  `mt_searchlite` 
WHERE MATCH (
`searchlite_text`
)
AGAINST (
'Movable Type'
)

0.0016 秒(10回の平均値)

エントリーは400件弱、速いっちゃぁ速いけどこの差にこだわるべきか悩んでいたところです。まぁ、検索が流行ってるようなので(<お前やろ!)、どうせなら作ってみようかと。スピードもあるけど、やっぱり全文検索はマッチングの順番とかが魅力だし(もちろんHyper EstraierでもNamazuでもそうなんですけど)。

メモ。

MAMPには my.confがないので、


/Applications/MAMP/db/mysql/my.conf

に置いたら怒られた。


/Applications/MAMP/Library/my.cnf

が正しいようです。

my.cnfに以下、記述しておかないと「MT」とかでかからない(デフォルト値は4)。


[mysqld]
set-variable=ft_min_word_len=2

あと、MT3では「ダイナミック」もしくは「ハイブリッド」にして一回再構築しないとMTEntryPermalinkが拾えないこともメモしておこう。ダイナミックパブリッシングのページで検索して、スタティックな別ブログのエントリーのタイトルとかはもちろん拾えるけれど、mt_fileinfoに静的生成のブログだとデータが入らないことが関係しているようです。

もしかしてMovable Type?

| コメント(0) | トラックバック(0)

ダイナミック VS スタティック はたまた「ハイブリッド」

ここ数日MTのダイナミックパブリッシングの拡張が面白いなということであれこれやっています。

ダイナミックパブリッシングかスタティックかあるいはハイブリッドかっていう論争? で良く出るのが再構築の負荷の話なわけですが、一方でダイナミックにはダイナミックに適した活用法ってのがある筈です。

例えば、今作っているのがサイト内検索。カテゴリーでの絞り込みや検索条件指定なんかが詳細に出来ます(こういうものはやはりスタティックでは不可能ですし、ダイナミックならではでしょう)。

作成している途中でふと思い立って作ってみました。やりたかったのはこんな処理です。

類似のアイテムを表示させる例

とりあえず公開するのは分岐のところだけなので、何に使えるかわかりませんが、パラメタを付けることでページの分割をしたりする用途何かには使えるのではないでしょうか? (良い活用方法があったら教えてください)。

利用方法


<MTIfMatchParam name="query" value="MT">
  MTに部分一致
</MTIfMatchParam>
<MTIfParamEqual name="query" value="MT">
  MTに完全一致
</MTIfParamEqual>

これで、該当のページにアクセスする際に foo.html?query=MT とかでアクセスすることで分岐可能になります。別にqueryでなくても何でも構いません。

MT3系の場合はmtフォルダ以下のphp/pluginsフォルダに放り込んでください。MT4の場合はファイル名を全て小文字にして、plugins/IfParam/php以下にプラグインを設置すれば使えると思います。当然ですがダイナミックパブリッシング専用です。

ダウンロード

mt-search.cgiに恨みもなにも無いんですけど...mt-search.cgiって色んな機能を担っていて例えばデフォルトテンプレートだと検索結果に「購読」って出ますよね。

フィードの購読ボタン

例えばMT絡みの記事は読みたいけどおっさんの戯れ言は読みたくないといった場合に便利です。タグが付いているエントリーの一覧もまたフィードで配信されるのでこれも便利です(「タグ」一覧は仕組み上「キーワード検索」程は重くないです。それでもやっぱりもたつく感じはする)。

フィードみたいに定期的にアクセスしにくるものを動的生成、というかmt-search.cgiがしょっちゅう起動していると思うだけで夜も眠れないのですよ、僕のような小心者としては(嘘)。

ということで、LIKE検索版を作ったついでに、ダイナミックパブリッシング版検索結果の(&タグの)フィードを生成するテンプレートを作ってみました。

特定のテーマだけ読んでやろうという方は宜しければどうぞ。検索結果のページの右カラムにフィードへのリンクが表示されます。

「タグ検索」にチェックを入れるとタグが付けられているエントリーの一覧が取得できます (フィードへのリンクも表示されます) 。

 

ダイナミックパブリッシングのプラグイン大分慣れましたが、つまづいたところとか気になるところを少しメモっておきます。

  • SQL投げるときはテーブルの連結とか使ってなるべくリクエストの回数を減らす (やっぱりSQL投げる回数多い程もたつく)
  • ブロックタグで <?php ?> の外に空行があると「先頭」に空行が1行入る(フィードがXML宣言から始まってない! ってなエラーに悩まされた)。
  • テンプレートに直接PHPが書ける。でもやっぱりプラグインにしてテンプレートにはMTタグ書いた方が美しいよね。
  • それでもやっぱりドキュメントが少ないし取り上げてる人も少ないな。6Aさんにはもっとダイナミックの情報を出して欲しい!
  • 僕の趣味は「再構築」だけど、これはこれで面白い!

MT界隈でダイナミックパブリッシングに対応したプラグインが少ない! っていうのは、確かに面倒ってのもあるけどやっぱり情報やサンプルが少ないのが影響してるんじゃないかなぁ。僕自身も今まで放置していたのだけど、ちょっとソース覗きながらやってみたのでまとめておきます(間違いがあるかもしれませんがお気づきの点があればツッコミください。あと想定読者はPerlでプラグイン書いてる人、なのであんまり詳細の説明は書きませんのでそのあたりご了承くださいませ)。

追記: ってか、あるじゃん orz.

何せ今回これ (SQLでのLIKE検索) 作るためにはじめて書いたので色々試行錯誤でした。

さぁ、これで一ヶ月後くらいにはダイナミックパブリッシング対応が劇的に進む...かな?

サンプルは、こんなタグから...


<mt:blognamefromdb><br />
<mt:helloworld>
<ol>
    <mt:phppluginloop>
        <li><mt:foo addos="1">
            <mtiffoo var="Mac">
                &lt;-Mac!
            </mtiffoo>
        </li>
    </mt:phppluginloop>
</ol>

テンプレートを「ダイナミックパブリッシング」に指定して、foo.html?blog_id=1 とか引数を付けてアクセスすると以下のように出力される例です。MT4である前提で書きます。

レンダリング結果

/mt/plugins/plugin_name/php/以下にPHPで書いたプラグインを置きます。基本的に1つのblock,functionやmodifierにつき1つの拡張子phpファイルになります。

function.mthelloworld.php のように、種類.名前.php というファイル名を付けます。このタグは <mt:helloworld> のように呼び出すことができます。

1.Functionタグ(お決まりのHello World!から行ってみよう)


<?php
function smarty_function_mthelloworld ($args, &$ctx) {
    return 'Hello World!';
}
?>

Hello World!と出力する、ただそれだけです。

ちょっと鬱陶しいなって思うのは、perl側ではこのタグが登録されたことを認識してくれないので、テンプレートの保存時に「テンプレートでエラーが見つかりました。 <mt:helloworld> は存在しません(1行目)」のようなエラーが出てしまうことです。これはもちろんPerl版プラグインをPHP対応させよう、という場合等既にPerl側でタグが登録されている場合は表示されません。まぁ、今回はPHP版だけなので最後の仕上げでエラーが出ないようにダミーのタグだけ登録してやることにしましょう。

2.Blockタグ(MT3で言うところのコンテナ・タグ)

ファイル名は block.mtphppluginloop.php になります。いきなりちょっと長くなりましたね。でもこれ先にやっとかないと条件分岐とかに進めないので。


<?php
function smarty_block_mtphppluginloop($args, $content, &$ctx, &$repeat) {
    $localvars = array( 'foo', 'vars', '_counter');
    if (!isset($content)) {
        //初期化
        $ctx->localize($localvars);
        $counter = 0;
    } else {
        $counter = $ctx->stash('_counter');
    }
    $vars = $ctx->stash('vars');
    if (!isset($vars)) {
        //配列のセット
        $vars = array('Mac','Win','Linux');
        $ctx->stash('vars', $vars);
    } else {
        $counter = $ctx->stash('_counter');
    }
    if ($counter < count($vars)) {
        //ループ処理
        $foo = $vars[$counter];
        $ctx->stash('foo', $foo);
        $ctx->stash('_counter', $counter + 1);
        $repeat = true;
    } else {
        //処理の終了
        $ctx->restore($localvars);
        $repeat = false;
    }
    return $content;
}
?>

この例では、('Mac','Win','Linux')ってな配列を作って3回だけループするタグを作ろうとしています。まぁ実際はSQLでDBから値取って来てループさせるようなものになるのでしょう。 $localvarsに入れている 'foo', 'vars', '_counter' がそれぞれ取り出す値, 配列, カウンタになります。

3.Functionタグ(<mt:phppluginloop>〜</mt:phppluginloop>ループの中で使えるタグ)

ファイル名 function.mtfoo.php, タグは<mt:foo>。これは至ってシンプルですね。ブロックタグで定義した $ctx->stash('foo', $foo); を出力するだけです。


<?php
function smarty_function_mtfoo ($args, &$ctx) {
    return $ctx->stash('foo');
}
?>

4.Modifier(タグ属性)

ついでに頭に「OS:」ってのを追加するタグ属性を作りましょう。ファイル名はmodifier.addos.php, タグ属性は<mt:foo addos="1">ってな具合に指定します。これも説明するまでもないですね。これで「OS:Mac」のように頭に「OS:」が付きます(テンプレート側で書けるし意味ねぇですけどね。実際は置換したりフォーマット化したりそんな処理になるでしょう)。


<?php
function smarty_modifier_addos($text) {
    return "OS:$text";
}
?>

5.Blockタグ(MT3で言うところの条件タグ)

ファイル名は block.mtiffoo.php, つまりこんな感じで↓mtif〜で分岐できる奴です。


<mtiffoo var="Mac">
    &lt;-Mac!
</mtiffoo>

せっかくなので (何が「せっかく」なのか分からんけどね) 属性値 varを指定してfoo=varかどうかで分岐するようにしてみます。


<?php
function smarty_block_mtiffoo ($args, $content, &$ctx, &$repeat) {
    $var = $args['var'];
    $foo = $ctx->stash('foo');
    if ($var == $foo) {
        return $ctx->_hdlr_if($args, $content, $ctx, $repeat, 1);
    } else {
        return $ctx->_hdlr_if($args, $content, $ctx, $repeat, 0);
    }
}
?>

6.DBから値を引っ張る、URLのパラメタを取得する

BlockタグでもFunctionタグでも良いですが、DBから値を取得する例&URLのパラメタを取得する例です(この例はFunctionタグ)。引数によって処理を変えればページ分割とか簡単に実装できますよね。

内容はブログ名を表示するだけ。別に標準のMTタグで出来るわけですが、サンプルですからね。ファイル名 function.mtblognamefromdb.php, タグは <mt:blognamefromdb>。 foo.html?blog_id=1 のようにパラメタ付けることでブログを指定できるようにしてみます。


<?php
function smarty_function_mtblognamefromdb ($args, &$ctx) {
    if (isset($_SERVER['REDIRECT_QUERY_STRING'])) {
        //foo.html?blog_id=1
        $_SERVER['QUERY_STRING'] = getenv('REDIRECT_QUERY_STRING');
    }
    $param = getenv('QUERY_STRING');
    parse_str($param);
    if ($blog_id) {
        if (preg_match("/^[0-9]+$/", $blog_id)) {
            $sql = "SELECT blog_name "
                 . "FROM mt_blog "
                 . "WHERE blog_id=$blog_id ";
            $results = $ctx->mt->db->get_results( $sql , ARRAY_N );
            foreach ($results as $row) {
                return $row[0];
            }
        }
    }
}
?>

実はパラメタ取得のところは一瞬ハマってしまったのですが、MTのダイナミックパブリッシングは mod_rewrite 又は 404,403ページとしてphpへアクセスさせて動作しているので mod_rewrite の場合 $blog_id = $_GET['blog_id']; のようにしても値を取得できません。

なので REDIRECT_QUERY_STRING を取得して parse_str で分解、という手順になります(この辺はサーバー環境によってちょっとハマることがあるかもしれませんね)。

DBへのアクセスですが、Perlと比較すると抽象化されていない...というか要はSQLです(抽象化というより、php/extlibにはezsqlとsmartyフォルダがありますね。ezsqlというライブラリを使ってるということでしょうか)。

普段Perlのプラグイン書き慣れていると、mt_ 付けるのとか entry_ とか付けるの忘れそうになります(ここは省略可にして欲しいなぁ)。SQLに値を突っ込むので、浄化は忘れないようにしましょうね(この例では数字かどうかチェックしてますが、通常は「'」とかいわゆるSQLインジェクション系の対策が必要* かと思います)。SQLに突っ込む直前ですよ。取得した時じゃないよ。Web2.0言うな! サニタイズ言うな! (笑)。

*ezsqlの仕様を知らないのですけど、まぁ必要でしょう。

7.テンプレート編集画面でエラーが出ないようにPerlプラグインを用意

手順が通常と逆なので(今回はPerlプラグインをダイナミックにも対応させるってわけじゃないので)、Perl版はダミープラグインみたいになりますが、CMSにタグを登録してエラーが出ないようにしましょう。

ファイル名は仮にPluginsPHP.plとします。これは想定読者の方には説明するまでもないでしょう。


package MT::Plugin::PluginsPHP;
use strict;
use MT;
use MT::Plugin;
our $VERSION = 0.1;

use base qw( MT::Plugin );

@MT::Plugin::PluginsPHP::ISA = qw(MT::Plugin);

my $plugin = new MT::Plugin::PluginsPHP({
    id => 'PluginsPHP',
    key => 'pluginsphp',
    description => 'Sample PHP Plugins',
    name => 'PluginsPHP',
    author_name => 'Junnama Noda',
    author_link => 'http://junnama.alfasado.net/online/',
    'version' => $VERSION,
});

MT->add_plugin($plugin);

sub init_registry {
    my $plugin = shift;
    $plugin->registry({
        tags => {
            block => {
                'phppluginloop' => ¥&_foo,
                'iffoo?' => ¥&_foo,
            },
            function => {
                'helloworld' => ¥&_foo,
                'foo' => ¥&_foo,
                'blognamefromdb' => ¥&_foo,
            },
            modifier => {
                'var' => ¥&_foo,
            },
        }
   });
}

sub _foo {
}

1;

ふぅ...何とかまとまったかな。

どんだけ「検索」好きですか、おいら。

MTのプラグイン、バージョン間の互換性にはすごく配慮されてるけど、PHP対応プラグインは(両対応は)ちと厄介(*以下間違いあったらツッコミ宜しくお願いします)。

ちょっと厄介だなぁと思うこと

MT4からタグの大文字小文字の違いを吸収しているから起こることなんだろうけど、ファイル名を小文字にしておかないといけない(何か小文字に変換してからマッチさせてるようだ)。


function.mtaltsearchresultoffset.php

って長くて分かりにくいじゃない。


function.MTAltSearchResultOffset.php

の方が直感的だよね。

で、逆にMT4前提で小文字で function.mtaltsearchresultoffset.php って書くと、今度はMT3のテンプレートエンジンの方がMTタグと認識してくれない。

# 実はちょっとハマった。っていうのもMacローカルでやってると大文字小文字が違ってても通るのよね。つまりファイルシステム上で同一と見なされれば良いみたい。

タグごとにファイルが要るってのもメンテのことなんかも考えるとPerl版と比べると大変だし。あとは圧倒的にドキュメントとかサンプルが少ない。って愚痴言うな! ってことで後でちょっとまとめます。MTプラグインのPHP対応について。

* (追記) 早速まとめたよ!

PHP+LIKE検索

PHP+Hyper Estraier

mt-search.cgi (Not FastCGI)

テンプレート違うとかFastCGIじゃないとか前提条件が違うけどやっぱりこの位の体感速度が出ないと使う気にはならないよなぁ。

前回(14回)は秋葉原でアウェイ(?)だったのですが今回は大阪、「ホーム」だったので前回よりはリラックスして話せたと思う(後でわかったことですけど、東京から来たと思っていた方が意外と多かったようで、僕もまだまだだなぁ...ということでもうちょっとこれからは大阪でもあちこち顔出したりしよう)。

会場の風景

最初にお礼。沢山集まってもらって嬉しかったです。ありがとうございました。 また、蒲生さん、中野さんをはじめ遠くからお越しいただいた方、ありがとうございました & お疲れさまでした。次は僕がそちらへ伺いますので懲りずにまた呼んでください(野田にはあんまり飲ますな、ってことになってたりしないでしょうか...それだけがちと心配)。

まずは雑感

前回「だいたいおまいらマジメ過ぎなんだよっ!!」発言(僕じゃないよ :-) ) しておられる方がいらっしゃいましたが、秋葉原の時に輪をかけてみんなマジメだと思った。しかも大人しい。東京の時はほんとにあれこれ質問とかツッコミが来てたんですが今回はポツリ、ポツリといった感じ。

まぁその後飲みに行くと大阪的? なノリになって少しホっとしたけど(じゃないと僕が浮きまくるので!)、何と言うか最近のWeb屋さんはマジメで大人しいのかなぁ...ブログもあんまり書かないみたいだし何だか疲れてんですか!?元気出そうよ、大阪のWeb屋の皆さん!

MT4は複雑?

「ウィジェットって何?」ってネタ何度か使わせてもらったけど、Web屋さんですら慣れるまでは迷うことがあるインターフェイスなわけだし (蒲生さんもちょっと迷ってましたね! :-) ) 、クライアントにそのまま渡した後に「こういう時はどーすんの?」って来ることは覚悟しとかなきゃなんない。だからこそ「顧客向け思いっきりカスタマイズのススメ」ということで事例をいくつか紹介したわけです。中でも一番言いたかったこと、繰り返しになるけれど改めて書くよ。

機能の追加よりむしろ「削る」ことを考えよう

alt_tmplやプラグインを使ったカスタマイズ手法を紹介したけれど、標準機能で出来ることもある。「権限」の設定ですね。つまり、テンプレート触る必要のない人にとって「デザイン」とかのメニュー項目要らないよね。たとえ「投稿する方=管理者」であったとしてもちゃんと投稿とか修正が出来る権限のユーザーを作って、そのユーザーで操作してもらう方がいい。使えない機能はグレーアウトして選択できなくなるし(本当は消えて欲しいところだけど...)。エントリーのメタ情報(概要とかキーワードとか)を使わない運用なら、それらは非表示にするとかも標準の機能で出来るし、トラックバックやコメント使わないならそいつも非表示にして、コントロール(フォームやボタン等)をできるだけ減らして、フォームの項目のラベルはできるだけ実態に即してカスタマイズして渡す。これだけでもかなり改善される筈。何でもかんでもRightFields、って考えなくてもそれなりに標準+カスタマイズでCMSのユーザビリティは向上します。

だから、顧客案件でMT使ってそのままおさめる場合は「使わないものをいかに削るか」という考え方でカスタマイズして渡すのが良いと思う。「それできないの?」に応えることも大切だけど、逆に「これはできません」って割り切ることも大切じゃないかな。

MT vs WP

結局どこでも絶対出る話題なのか。質疑応答の時にも言ったけど、だいたい会社が品質管理して組織的に開発を行っている「製品」とGPLライセンスのソフトをシェアとかで比較することに意味ないし。6Aさんのサポートがどうとかいうことを話しておられる方もいたけど、逆に考えれば日本法人がちゃんとあって日本語で問い合わせ出来るってのは大きなアドバンテージ(顧客先の案件に導入する場合は特に)じゃないのかなぁ。

みんながみんな自己「責任」でGPLのソフト(WPに限らず)を扱える程精通しているわけでもないだろうし。

あとは話した通り。MTは分業に向いてるのです。V担当はPHPとかかかなくていいし、C担当はHTML書かなくていい。

動的 vs 静的

これも何かもう「お腹いっぱい」気分の話題だけど、ケースバイケース、メリット・デメリットを考えて判断すれば良いと思う。別に静的生成マンセーとか思ってないです。趣味「再構築」はネタですよ。「Mなんですか?」とか聞かないで! (懇親会の時に本当に聞かれちゃったよ。しかも女の子に!)。

そんなこともあって、ついカッとなってPHP対応プラグインのソースを読んだりしてちょっと週末のうちに書いてみたりしたので、いくつかのプラグインについて近日PHP対応版をアップしたいと思ってます。

人月 vs パッケージビジネス

実はこれが一番考えさせられたというか痛いところにツッコミ来た感じで、トークの時に紹介した社内で使っているプラグイン「売らないんですか!?」の件。

「売るとサポート必要でしょう?」ってあたりまえの答えをしていたのですが、これは本音で、単価が100万円で売れるものならともかく、数万円のものを大量に売って(売れれば、だけど)顧客を管理してサポートして(それ以前にちゃんと集金して)ってまずそういうノウハウも体制もないわけで。

だからどこかが代わりに売ってくれれば考えるかな、ってのも本音ですね。レンタルサーバー会社さんとかがウチのプラグインとかライセンス込みプリインストールとかで高付加価値レンタルサーバーとかやってくれるとか。レンタルサーバー会社さんがよく制作会社にアフェリエイト的? な販売パートナーになりませんかとか案内来たり電話来たりするけど、単にサーバー紹介してマージンもらうみたいな商売はやりたくない。かといってホスティングビジネスやるだけのリソースもないし。

さて、どこか乗ってくれる方いらっしゃいませんか!? いや、もしくは一緒にやろうっていう提案でもいいですし。

Web屋として「会社」をどう捉えるか

中野さんと話していて、すごく真剣に会社とか業界のこと考えてる人だなぁと感じた。自分が死んだら会社がどうなるとか、正直まだまだ出来てない気がするし時間もかかることだけど、僕もちゃんと考えないとなぁ。

結局のところ、僕はWeb屋における会社というものって「技術を効率よく換金するためのしくみ」じゃないかと思うんです(もちろん理念とか技術の方向性とかは前提として)。だから、その効率において理にかなっていると思えば何らかの売り方をすると思うし、そうならなければ売らないと思う。まだ結論は出ていないけど。

3次会あたりで言ってましたが、今度東京でもどこでもいいんですけど、小規模Web屋経営者の会っての是非企画してください。懇親会とかすんごいことになりそうですけどネ。

何だかいつもまとまらない話になっちゃうのですが、会社と個人というところでは僕はひとつだけスタッフにはっきり言っていることがあるんです。「嫌なら辞めたらいいけど、自分がどこに行っても通用するくらいのものは身につけておけよ」って。「俺が死んだらどーすんの?」ってのも普通に普段から言ってたりする自分は変なんだろうか? でもそうだと思う。

経営者だけじゃなくて会社で働いている人も今一度考えておこうよ。「明日会社がなくなっても俺は大丈夫」って言えるかどうか。そして要求が高いのはお前ら(←読んでる皆さんのことじゃないですよ)のためだ! とは言わないけど、どんどんチャレンジして行こう!

Facebook

Twitter

このアーカイブについて

このページには、2007年10月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2007年9月です。

次のアーカイブは2007年11月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.2.6