PHP でカスタム検索エンジンを作成する

Sphinx でコンテンツに索引を付けてテキストを素早く検索し、有益な検索結果を提供する

Google などの検索エンジンは実質的にあらゆる情報を網羅しますが、すべてのサイトに Web の強力な検索エンジンが最適というわけではありません。サイトのコンテンツが極めて専門的だったり、はっきりとカテゴリー化されている場合には、Sphinx と PHP を使って、きめ細かく調整したローカル検索システムを作成してください。

Martin Streicher (mstreicher@linux-mag.com), Editor in Chief, Linux Magazine

Martin StreicherはLinux Magazineの編集長です。以前はBerkeley Systemsのエグゼクティブ・プロデューサーとして、YOU DON'T KNOW JACK(ゲーム)やAfter Darkを含め、様々な賞を受けたソフトウェアを開発してきました。彼はPurdue Universityにて、コンピューター・サイエンスで修士を取得しています。卒業後初めての仕事は、CONVEXスーパーコンピューター用のUNIXプログラミングでした。



2007年 7月 31日

このインターネット時代、人々はまるでファースト・フードのようにパッケージ化された情報を求めています。つまり、素早く出てきて手間もかからず、一口サイズ (情報で言えば、バイト・サイズ) の情報です。そんなせっかちな大衆の食欲を満たすため、今やそれほど大きくない Web サイトでさえも、以下のような多様なメニューをわかりやすい形で用意していることが期待されています。

  • RSS はピザの配達人のように、最新のデータを自宅まで届けてくれます。
  • ウェブログは、好みの香辛料の効いた料理が並ぶ近所のテイク・アウトの中華料理店といったところです。
  • フォーラムは、料理を持ち寄って開くお隣同士のパーティーのようなものです (それよりも、映画「アニマル・ハウス」の食べ物を投げ合ってふざけるシーンと言ったほうがいいかもしれません)。
  • そして検索はいわば、地元のフードコートでの料理食べ放題です。食欲が続く限り (そして椅子が体重に持ちこたえる限り)、好きな料理を何でも何度でも皿に盛ることができます。

PHP の開発者たちがサイトを作成あるいは修正するには、幸い多種多様な RSS、ブログ、フォーラムのソフトウェアがあります。そして Google などの検索エンジンは実質的にあらゆる情報を網羅していますが、検索エンジンがすべてのサイトに最適であるとは限りません。

例えば、ある Web サイトに数十万もの新品とリファービッシュ品のポルシェのパーツが記載されているとすると、Google では「カレラのパーツ」といった広範な検索ではそのサイトを表示するでしょうが、「1991年型ポルシェ 911 タルガの中古ヘッドライト・ベゼル」という絞り込んだ検索には正確な結果を出さないでしょう。

コンテンツが極めて専門的だったり、サイトの訪問者が現実世界のワークフローと平行した検索機能を期待する場合には、Web のグローバル検索エンジンをサイトに合わせて調整したローカル検索システムで補強するのが最善の策です (特化した検索の例については、「大量の干し草のなかの 1 本の針」を参照)。

この記事では、高速で有能なオープン・ソースの無料検索エンジンを PHP サイトに追加する方法を説明します。ここでは特定の Web サイトを開発することはしませんが、効果的な検索結果をもたらすために必要なコンポーネントとして、データベース、索引、検索エンジン、そして PHP アプリケーション・プログラム・インターフェース (API) に焦点を当てます。

偉大なる Sphinx を訪れる

サイトにカスタム検索機能を提供するためには、データ・ソース、そしてそのソースを検索する機能がなければなりません。Web アプリケーションのデータ・ソースは通例、リレーショナル・データベースで、リレーショナル・データベースには何らかの形で検索機能が組み込まれています (Equality は、SQL の演算子 LIKE と同じく単純な検索演算子です)。ただし、一部の検索はデータベースが実行できないほど特殊化されていたり、あるいは検索が複雑すぎて SQL に本来備わっている JOIN では時間がかかり過ぎる場合があります。

大量の干し草のなかの 1 本の針

多くのサイトでは、医学、法律、音楽、自動車メンテナンスなど、ある業種や、職種、娯楽に特有のコンテンツを提供しています。このようなコンテンツを掘り下げて調べるには特殊なツールや訓練が必要になりますが、関連する結果や実用的な結果を引き出すための索引が 1 つあれば事足りる場合もあります。

カスタマイズした検索システムが必要となる一般的な検索シナリオには、次のような例があります。

  • Joe Hockey が書いたスタンレー・カップに関するすべての記事を検索する
  • HP LaserJet 3015 All-in-One プリンターの最新ドライバーを検索する
  • テレビ番組「Late Show With David Letterman」で Dinosaur Jr. が演奏した場面を検索する

検索を迅速にするには、基礎となるクエリーを単純にするためにテーブルを再配置する能力が求められます (テーブルおよび SQL クエリーの最適化は、使用しているスキーマとエンジンに極めて依存します。オンライン検索で、データベース・パフォーマンス専門の膨大な数の記事や本を探さなければなりません)。あるいは、特殊化された検索エンジンを追加するという手もあります。どの検索エンジンを適用するかは、同じくデータのフォーム (そして量) と予算次第です。数多くの選択肢があり、Google アプライアンスをネットワークに接続することも、Endeca といった大規模な市販の検索用製品を購入することも、あるいは Lucene を試してみることもできます。しかし大抵の場合、市販の製品では行き過ぎだったり、運用予算を無駄にしてしまうだけです。また、Lucene は、この記事を書いた 2007年7月の時点では PHP API を提供していません。

そこで代わりの手段として検討して欲しいのが、Sphinxです。オープン・ソースの Sphinx は (謳い文句のとおり) 無料の検索エンジンで、テキストを極めて素早く検索するために設計されています。例えば実際のデータベースに 300,000 近くもの行があり、各行は 5 つの索引付き列で構成されていて、それぞれ列には約 15 ワードが含まれているとすると、Sphinx では「いずれかのワード」を検索するのに 100 分の 1 秒しか時間がかかりません (Debian Linux® Sarge が動作している 1 GB の RAM を搭載した 2-GHz AMD Opteron プロセッサーの場合)。

Sphinx には以下をはじめとする多数の機能があります。

  • ストリングとして表現可能なあらゆるデータに索引を付けられます。
  • 同じデータに異なる方法で索引を付けられます。それぞれ特定の目的に合わせて調整された複数の索引から、もっとも適切な索引を選んで検索結果を最適化することができます。
  • 索引付きデータのそれぞれに属性を関連付けられるため、1 つ以上の属性を使って検索結果を絞り込むことができます。
  • 言語形態をサポートするため、「cats」という単語の検索では元の語である「cat」も検出されます。
  • Sphinx の索引を多数のマシンに分散させて、フェイルオーバーを設けることができます。
  • 任意の長さの単語の接頭辞索引、さまざまな長さのサブストリングの接中辞索引を作成することができます。例えば、長さ 10 文字のパーツ番号があるとします。接頭辞索引は、ストリングの先頭から始まる考えられるすべてのサブストリングを照合します。接中辞索引は、ストリング内の任意の場所にあるサブストリングと一致することになります。
  • MySQL V5 内のストレージ・エンジンとして Sphinx を実行し、障害点の追加と見なされることが多い、デーモンの追加の必要性をなくすことができます。

全機能を網羅したリストは、オンラインおよび Sphinx ソース・コードに付属の README ファイルで調べられます。Sphinx Web サイトには、Sphinx を採用しているプロジェクトもリストされています。

C++ で作成され、GNU コンパイラーでビルドする Sphinx は、64 ビット対応プラットフォーム上では、64 ビットをサポートし、Linux、UNIX®、Microsoft® Windows®、および Mac OS X で動作します。Sphinx のビルド手順は単純で、コードをダウンロードして抽出し、コマンド./configure && make && make install を実行するだけです。

デフォルトでは、Sphinx ユーティリティーは /usr/local/bin/ にインストールされ、すべての Sphinx コンポーネントを対象とした構成ファイルは /usr/local/etc/sphinx.conf にインストールされます。

Sphinx には、索引生成プログラム、検索エンジン、コマンド・ライン検索ユーティリティーの 3 つのコンポーネントがあります。

  • 索引生成プログラムは indexer と呼ばれます。その実行内容は、データベースの照会、結果の各行に含まれる列それぞれの索引付け、そして各索引エントリーと行の主キーとの結合です。
  • 検索エンジンは、searchd という名前のデーモンです。このデーモンは、検索語やその他のパラメーターを受け取り、1 つ以上の索引を探して結果を返します。一致するものが見つかると、searchd は主キーの配列を返します。これらのキーによって、アプリケーションは関連付けられたデータベースに対してクエリーを実行し、全ての一致した結果からなるレコードを見つけることができます。searchd はポート 3312 でのソケット接続によってアプリケーションと通信します。
  • search は便利な検索ユーティリティーで、コードを作成しなくてもこのユーティリティーを使えばコマンド・ラインから検索を行うことができます。searchd が一致結果を返すと、search はデータベースに対してクエリーを実行して一致セットに含まれる行を表示します。search ユーティリティーは、Sphinx 構成のデバッグや即座に検索をしたいときに役立ちます。

さらに、Sphinxの作成者である Andrew Aksyonoff 氏とその他の貢献者たちにより、PHP、Perl、C/C++ やその他のプログラミング言語に対応した API が提供されています。


ボディー・パーツを検索する

例として、Body-Parts.com では珍しい収集価値のある自動車を対象として、自動車のボディー・パーツ (フェンダー、クローム・バンパーなど) を販売しています。現実の世界と同じく、Body Parts サイトの訪問者が検索の基準にすると考えられるのは、メーカー (例えば、ポルシェや同等のものを製造しているサード・パーティー)、パーツ番号、車名、モデル、製造年、状態 (中古、新品、修理済み)、説明、あるいはこれらの情報の組み合わせです。

この Body Parts の検索機能を作成するため、ここでは MySQL V5.0 をデータ・ソースとして使用して、Sphinx 検索デーモンで素早く正確なテキスト検索を行えるようにしてみましょう。MySQL V5.0 は有能なデータベースですが、拡張フルテキスト検索機能についてはそれほど充実していません。実際、この機能は MyISAM テーブル (外部キーをサポートしないテーブル形式) にしか使えないため、用途が限られます。

リスト 1 から 4 に、この例に関連する Body Parts スキーマを抜粋します。リスト 1は Model テーブル、リスト 2 は Assembly テーブル、リスト 3 は Inventory テーブル、リスト 4 は Schematic テーブルです。

Model テーブル

リスト 1 の Model テーブルは単純なもので、label 列でモデルの名前 (「Corvette」) を列挙し、description で消費者にわかりやすいようにその自動車を描写します (「ツー・ドア・タイプのオープン・カー、発売初年度」)。begin_productionend_production はそれぞれ、このバージョンの製造開始年と終了年です。以上の列に含まれる値は一意ではないため、4 つの列の値の組み合わせ (label、description、begin_production、end_production) を個別の ID で表し、他のテーブルではこの ID が外部キーとなります。

リスト 1. Body Parts の Model テーブル
CREATE TABLE Model (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(256) NOT NULL,
  begin_production int(4) NOT NULL,
  end_production int(4) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

以下は、Model テーブルのサンプル・データです。

INSERT INTO Model 
  (`id`, `label`, `description`, `begin_production`, `end_production`) 
VALUES 
  (1,'X Sedan','Four-door performance sedan',1998,1999),
  (3,'X Sedan','Four door performance sedan, 1st model year',1995,1997),
  (4,'J Convertible','Two-door roadster, metal retracting roof',2002,2005),
  (5,'J Convertible','Two-door roadster',2000,2001),
  (7,'W Wagon','Four-door, all-wheel drive sport station wagon',2007,0);

Assembly テーブル

アセンブリーとは、トランスミッションや自動車に使われているガラスすべてなどのサブシステムのことです。オーナーはアセンブリー図面と関連パーツのリストを参照して、交換用パーツを見つけます。リスト 2 の Assembly テーブルも同じく単純なもので、一意の ID をアセンブリーの label と description に関連付けています。

リスト 2. Assembly テーブル
CREATE TABLE Assembly (
  id int(10) unsigned NOT NULL auto_increment,
  label varchar(7) NOT NULL,
  description varchar(128) NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

以下に Assembly テーブルのサンプル・データを記載してから説明を続けます。

INSERT INTO Assembly 
  (`id`, `label`, `description`) 
VALUES 
  (1,'5-00','Seats'),
  (2,'4-00','Electrical'),
  (3,'3-00','Glasses'),
  (4,'2-00','Frame'),
  (5,'1-00','Engine'),
  (7,'101-00','Accessories');

Inventory テーブル

Inventory テーブルは自動車パーツの正規リストです。パーツ (ボルトやバルブなど) は完成した各自動車にも、複数のアセンブリーにも存在しますが、Inventory テーブルに示されるのは 1 度だけです。Inventory テーブルの各行には以下が含まれます。

  • 行を識別する一意の 32 ビット整数のシリアル・ナンバー
  • 英数字のパーツ番号 (このパーツ番号は一意なので主キーとして十分なはずですが、英数字を含められることから Sphinx で使うにははふさわしくありません。Sphinx ではそれぞれのレコードの索引を一意の 32 ビットの整数キーにしなければならないためです)。
  • テキストによる説明
  • 価格

Inventory テーブルの仕様はリスト 3 のとおりです。

リスト 3. Inventory テーブル
CREATE TABLE Inventory (
  id int(10) unsigned NOT NULL auto_increment,
  partno varchar(32) NOT NULL,
  description varchar(256) NOT NULL,
  price float unsigned NOT NULL default '0',
  PRIMARY KEY (id),
  UNIQUE KEY partno USING BTREE (partno)
) ENGINE=InnoDB;

パーツのリスト (一部) は以下のようになります。

INSERT INTO `Inventory` 
  (`id`, `partno`, `description`, `price`) 
VALUES 
  (1,'WIN408','Portal window',423),
  (2,'ACC711','Jack kit',110),
  (3,'ACC43','Rear-view mirror',55),
  (4,'ACC5409','Cigarette lighter',20),
  (5,'WIN958','Windshield, front',500),
  (6,'765432','Bolt',0.1),
  (7,'ENG001','Entire engine',10000),
  (8,'ENG088','Cylinder head',55),
  (9,'ENG976','Large cylinder head',65);

Schematic テーブル

Schematic テーブルはパーツをアセンブリーとモデル・バージョンに結び付けます。したがって、1979年型 J クラスのコンバーチブルで使われているエンジンを構成するすべてのパーツを検索するには、Schematic テーブルを使用することになります。Schematic テーブルの各行に含まれるのは、一意の ID、Inventory テーブル内の行を参照する外部キー、アセンブリーを識別する外部キー、そして Model テーブル内の特定のモデルとバージョンを参照するさらにもう 1 つのキーです。これらの行をリスト 4 に記載します。

リスト 4. Schematic テーブル
CREATE TABLE Schematic (
  id int(10) unsigned NOT NULL auto_increment,
  partno_id int(10) unsigned NOT NULL,
  assembly_id int(10) unsigned NOT NULL,
  model_id int(10) unsigned NOT NULL,
  PRIMARY KEY (id),
  KEY partno_index USING BTREE (partno_id),
  KEY assembly_index USING BTREE (assembly_id),
  KEY model_index USING BTREE (model_id),
  FOREIGN KEY (partno_id) REFERENCES Inventory(id),
  FOREIGN KEY (assembly_id) REFERENCES Assembly(id),
  FOREIGN KEY (model_id) REFERENCES Model(id)
) ENGINE=InnoDB;

Schematic テーブルの目的をしっかり理解してもらうため、このテーブルの行を一部抜粋します。

INSERT INTO `Schematic` 
  (`id`, `partno_id`, `assembly_id`, `model_id`) 
VALUES 
  (1,6,5,1),
  (2,8,5,1),
  (3,1,3,1),
  (4,5,3,1),
  (5,8,5,7),
  (6,6,5,7),
  (7,4,7,3),
  (8,9,5,3);

上記の 4 つのテーブルによる検索

これらのテーブルが定義されれば、以下をはじめとする相当な数の検索の結果が簡単に出るようになります。

  • 特定モデルのすべてのバージョンを表示する
  • 特定のモデルとバージョンを組み立てるのに必要なすべてのアセンブリーをリストする
  • 特定のモデルとバージョンの特定のアセンブリーを構成するすべての部品を表示する

ただし少数ながらも、以下のように極めてコストがかかる検索もあります。

  • あらゆるモデルとバージョンで、パーツ番号が「WIN」で始まるパーツのすべてのオカレンスを検索する
  • 説明に「ラッカー」または「ペイント」が含まれるパーツを検索する
  • 説明に「ブラック・レザー」が含まれるすべてのパーツを検索する
  • 説明に「ペイント」が含まれる 2002年型 J シリーズのすべてのパーツを検索する

上記の検索それぞれには大量の JOIN や複雑なLIKE 節が必要になります。Inventory テーブルと Schematic テーブルが大規模であれば、それはなおさらのことです。そもそも、複雑なテキスト検索は MySQL の能力を超えています。大量のテキスト・データを検索するには、Sphinx 索引を作成して使用することを検討してください。


Sphinx ソフトウェアを統合する

Sphinx で問題に対処するには、1 つ以上のソースと 1 つ以上の索引を定義する必要があります。

ソースは索引を付けるデータベースを識別して認証情報を提供し、各行を構成するために使用するクエリーを定義します。さらにオプションで、1 つ以上の列をフィルター (Sphinx では、これをグループと呼びます) として識別することも可能です。グループは、結果をフィルタリングするために使用します。例えば、paint という単語の一致結果が 900 件になったとします。そのうち、特定の自動車モデルに関する一致結果だけに興味がある場合にはモデル・グループを使って結果をフィルタリングすることができます。

索引にはソース (すなわち、一連の行) が必要で、ソースから抽出したデータのカタログ方法は索引によって定義されます。

ソースと索引は sphinx.conf ファイル内で定義します。Body Parts のソースは MySQL データベースです。リスト 5 に、catalog という名前のソースの定義を一部記載します。このスニペットでは、接続先のデータベースと接続方法 (ホスト、ソケット、ユーザー、パスワード) を指定しています。

リスト 5. MySQL データベースにアクセスするための設定
source catalog 
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306

次に作成するのは、索引を付ける行を作成するためのクエリーです。一般的には、SELECT 文を作成し、多数のテーブルをおそらく JOIN で結合して 1 つの行にしますが、この場合には問題があります。モデルと製造年の検索には Assembly テーブルを使用しなければなりませんが、パーツ番号とパーツの説明は Inventory テーブルにしか見つからないからです。これに対処するためには、Sphinx が検索結果を 32 ビット整数の主キーと結び付けることができなければなりません。

正しいフォームのデータを取得するには、ビューを作成してください。これは、他のテーブルからの列を 1 つの複合仮想テーブルにアセンブルする MySQL V5 の新しい構成体です。実際のデータは別のテーブルに存在するとしても、ビューを使用すると、あらゆる類の検索に必要なすべてのデータが 1 箇所に集められます。一例として、Catalog という名前のビューを定義する SQL をリスト 6 に記載します。

リスト 6. 仮想テーブルにデータをアセンブルする Catalog ビュー
CREATE OR REPLACE VIEW Catalog AS
SELECT
  Inventory.id,
  Inventory.partno,
  Inventory.description,
  Assembly.id AS assembly,
  Model.id AS model
FROM
  Assembly, Inventory, Model, Schematic
WHERE
  Schematic.partno_id=Inventory.id 
  AND Schematic.model_id=Model.id 
  AND Schematic.assembly_id=Assembly.id;

これまでに記載したテーブルとデータを使って body_parts というデータベースを作成すると、Catalog ビューの表示は以下のようになります。

mysql> use body_parts;
Database changed
mysql> select * from Catalog;
+----+---------+---------------------+----------+-------+
| id | partno  | description         | assembly | model |
+----+---------+---------------------+----------+-------+
|  6 | 765432  | Bolt                |        5 |     1 | 
|  8 | ENG088  | Cylinder head       |        5 |     1 | 
|  1 | WIN408  | Portal window       |        3 |     1 | 
|  5 | WIN958  | Windshield, front   |        3 |     1 | 
|  4 | ACC5409 | Cigarette lighter   |        7 |     3 | 
|  9 | ENG976  | Large cylinder head |        5 |     3 | 
|  8 | ENG088  | Cylinder head       |        5 |     7 | 
|  6 | 765432  | Bolt                |        5 |     7 | 
+----+---------+---------------------+----------+-------+
8 rows in set (0.00 sec)

上記のビューでは、id フィールドは Inventory テーブルにエントリーされているパーツの id の値になっています。partno 列と description 列は必須の検索テキストで、assembly 列と model 列は結果をフィルタリングして絞り込むためのグループとして機能します。このビューが用意できれば、ソース・クエリーを構成するのはわけありません。リスト 7 は、catalog ソース定義の残りの部分です。

リスト 7. 索引対象の行を作成するためのクエリー
    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique
    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;
    
    sql_group_column                = assembly
    sql_group_column                = model
    
    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #
    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

sql_query には以降のルックアップに使用する主キーを組み込み、索引を付けてグループとして用いるすべてのフィールドを含める必要があります。2 つの sql_group_column エントリーは、結果のフィルタリングに Assembly と Model を使用できることを宣言しています。そして search ユーティリティーは sql_query_infoを使って一致するレコードを検索します。クエリーでは、$id が searchd によって返されたそれぞれの主キーに置き換えられます。

構成の最後の手順は、索引を作成することです。リスト 8 に、catalog ソースの索引を記載します。

リスト 8. catalog ソースに可能な索引の記述
index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

最初の行で sphinx.conf ファイル内のソースを指定し、次の行で索引データを保存する場所を定義しています。上記のように、慣例では Sphinx 索引を保存する場所は /var/data/sphinx となります。3 行目では、索引が英語の言語形態を使用することを許可しています。5 行目から 7 行目までで indexer に指示している内容は、3 文字以上の単語のみに索引を付けること、そして3 文字以上のすべてのサブストリングの接中辞索引を作成することです (リスト 9 に、参照しやすいように Body Parts のサンプル sphinx.conf ファイル全体を記載します)。

リスト 9. Body Parts のサンプル sphinx.conf
source catalog
{
    type                            = mysql
    
    sql_host                        = localhost
    sql_user                        = reaper
    sql_pass                        = s3cr3t
    sql_db                          = body_parts
    sql_sock                        =  /var/run/mysqld/mysqld.sock
    sql_port                        = 3306                  

    # indexer query
    # document_id MUST be the very first field
    # document_id MUST be positive (non-zero, non-negative)
    # document_id MUST fit into 32 bits
    # document_id MUST be unique

    sql_query                       = \
            SELECT \
                    id, partno, description, \
                    assembly, model \
            FROM \
                    Catalog;

    sql_group_column                = assembly
    sql_group_column                = model

    # document info query
    # ONLY used by search utility to display document information
    # MUST be able to fetch document info by its id, therefore
    # MUST contain '$id' macro 
    #

    sql_query_info          = SELECT * FROM Inventory WHERE id=$id
}

index catalog
{
    source                  = catalog
    path                    = /var/data/sphinx/catalog
    morphology              = stem_en

    min_word_len            = 3
    min_prefix_len          = 0
    min_infix_len           = 3
}

searchd
{
        port                            = 3312
        log                                     = /var/log/searchd/searchd.log
        query_log                       = /var/log/searchd/query.log
        pid_file                        = /var/log/searchd/searchd.pid
}

最後の searchd セクションでは、searchd デーモン自体を構成しています。このセクションのエントリーの内容は一目瞭然のはずです。query.log はとりわけ重宝で、このログは検索が実行されるたびにそれを示し、検索された文書数や一致の合計数などといった結果を表示します。


索引を作成してテストする

これで、Body Parts アプリケーションの索引を作成する準備ができました。索引を作成するには、以下の手順に従ってください。

  1. ディレクトリー階層 /var/data/sphinx を作成するため、$ sudo mkdir -p /var/data/sphinx と入力します
  2. MySQL が実行中であるという前提で、indexer を実行して以下のコードで索引を作成します。
    リスト 10. 索引の作成
    $ sudo /usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    using config file '/usr/local/etc/sphinx.conf'...
    indexing index 'catalog'...
    collected 8 docs, 0.0 MB
    sorted 0.0 Mhits, 82.8% done
    total 8 docs, 149 bytes
    total 0.010 sec, 14900.00 bytes/sec, 800.00 docs/sec

    注:-all 引数は、sphinx.conf にリストされたすべての索引を再作成します。すべてを再作成する必要がなかったら、別の引数を使って再作成する索引の数を減らすこともできます。

  3. 以下のコードを使用して、search ユーティリティーで索引をテストします (searchd が実行中でなくても search は使用できます)。
    リスト 11. search による索引のテスト
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=8, weight=1, assembly=5, model=7
            id=8
            partno=ENG088
            description=Cylinder head
            price=55
    2. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits
    
    $ /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind 
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'wind ': returned 2 matches of 2 total in 0.000 sec
    
    displaying matches:
    1. document=1, weight=1, assembly=3, model=1
            id=1
            partno=WIN408
            description=Portal window
            price=423
    2. document=5, weight=1, assembly=3, model=1
            id=5
            partno=WIN958
            description=Windshield, front
            price=500
    
    words:
    1. 'wind': 2 documents, 2 hits
    
    $ /usr/local/bin/search \
    --config /usr/local/etc/sphinx.conf --filter  model 3 ENG
    Sphinx 0.9.7
    Copyright (c) 2001-2007, Andrew Aksyonoff
    
    index 'catalog': query 'ENG ': returned 1 matches of 1 total in 0.000 sec
    
    displaying matches:
    1. document=9, weight=1, assembly=5, model=3
            id=9
            partno=ENG976
            description=Large cylinder head
            price=65
    
    words:
    1. 'eng': 2 documents, 2 hits

最初のコマンド /usr/local/bin/search --config /usr/local/etc/sphinx.conf ENG では、パーツ番号で ENG の 2 つのオカレンスが検出されました。2 番目のコマンド /usr/local/bin/search --config /usr/local/etc/sphinx.conf wind では、サブストリング wind が説明に含まれる 2 つのパーツが検出されました。そして 3 番目のコマンドでは、これらの検索結果を model3 のエントリーだけに絞り込んでいます。


コードを作成する

これでついに、PHP コードを作成して Sphinx 検索エンジンを呼び出すことができます。Sphinx PHP API はコンパクトで、簡単にマスターできます。リスト 12 は、searchd を呼び出して、上記のリストに示された最後のコマンド (名前に 'cylinder' が含まれ、model 3 に属するすべてのパーツを検索) と同じ結果を抽出する簡単な PHP アプリケーションです。

リスト 12. PHP による Sphinx 検索エンジンの呼び出し
<?php
  include('sphinx-0.9.7/api/sphinxapi.php');

  $cl = new SphinxClient();
  $cl->SetServer( "localhost", 3312 );
  $cl->SetMatchMode( SPH_MATCH_ANY  );
  $cl->SetFilter( 'model', array( 3 ) );

  $result = $cl->Query( 'cylinder', 'catalog' );

  if ( $result === false ) {
      echo "Query failed: " . $cl->GetLastError() . ".\n";
  }
  else {
      if ( $cl->GetLastWarning() ) {
          echo "WARNING: " . $cl->GetLastWarning() . "
"; } if ( ! empty($result["matches"]) ) { foreach ( $result["matches"] as $doc => $docinfo ) { echo "$doc\n"; } print_r( $result ); } } exit; ?>

上記のコードをテストするため、以下のように Sphinx のログ・ディレクトリーを作成し、searchd を起動してから PHP アプリケーションを実行します。

リスト 13. PHP アプリケーション
$ sudo mkdir -p /var/log/searchd
$ sudo /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf
$ php search.php 
9
Array
(
    [fields] => Array
        (
            [0] => partno
            [1] => description
        )

    [attrs] => Array
        (
            [assembly] => 1
            [model] => 1
        )

    [matches] => Array
        (
            [9] => Array
                (
                    [weight] => 1
                    [attrs] => Array
                        (
                            [assembly] => 5
                            [model] => 3
                        )

                )

        )

    [total] => 1
    [total_found] => 1
    [time] => 0.000
    [words] => Array
        (
            [cylind] => Array
                (
                    [docs] => 2
                    [hits] => 2
                )

        )
)

出力は 9 で、一致する唯一の行に含まれる正しい主キーです。Sphinx が一致を検出すると、連想配列 $resultresults という名前の要素が組み込まれます。print_r() の出力を見て、他に何が返されるのかを確認してください。

一点注意しておくと、total_found は索引で検出された一致件数で、found は返される結果の数ですが、この 2 つは異なる場合があります。それは、長い結果リストをページネーション (ページ分け) する際に便利なように、毎回返される一致件数とどの結果のバッチを返すかを変更できるためです。SetLimits() という API 呼び出しを見てください。ページネーションの一例は、検索エンジンを $cl->SetLimits( ( $page - 1 ) * SPAN, SPAN ) で呼び出すことです。この場合、表示するページに応じて SPAN 一致の最初、2 番目、3 番目 (等) のバッチが返されます。


Sphinx の神秘を探る

Sphinx では他にも多くの機能を利用することができます。ここではその表面をかじったに過ぎませんが、この現実世界の実用的なサンプルは専門知識を広げるための基礎になります。

Sphinx のサンプル構成ファイル /usr/local/etc/sphinx.conf.dist (配布に付属) を詳細に調べてください。このファイルに記載されているコメントが、それぞれの Sphinx パラメーターの機能、分散冗長構成を作成する手順、そして基本設定を継承してソースと索引が重複しないようにする方法を説明してくれます。また、Sphinx README ファイルも極めて有効な情報源です。このファイルには、Sphinx を MySQL V5 に直接組み込んでデーモンを不要にする方法も記載されています。

今後は、echo()print_r() よりも優れた PHP コードのデバッグ・ソリューションを探してみてください。

参考文献

学ぶために

  • Sphinxは、テキストを極めて素早く検索するために設計されたオープン・ソースの無料検索エンジンです。
  • Endecaなどの大規模な市販の検索用製品を調べてください。あるいは、Luceneを試してみることをお勧めします。
  • PHP.netは、PHP 開発者のための情報源です。
  • 「Recommended PHP reading list」を読んでください。
  • developerWorks ですべてのPHP 関連記事を調べてください。
  • IBM developerWorks のPHP project resourcesにアクセスして、PHP の腕を磨いてください。
  • ソフトウェア開発者を対象とした興味深いインタービューや討論については、 developerWorks ポッドキャストをチェックしてください。
  • PHP でデータベースを使用する場合はZend Core for IBMを調べてみてください。シームレスにすぐに使えて、インストールも簡単なこの PHP 開発および実動環境は、IBM DB2 V9 をサポートします。
  • developerWorks の Technical events and webcastsで最新情報を入手してください。
  • 世界中で近日中に予定されている IBM オープン・ソース開発者を対象とした会議、見本市、ウェブ放送やその他のイベントをチェックしてください。
  • オープン・ソース技術を使用して開発し、IBM の製品と併用するときに役立つ広範囲のハウツー情報、ツール、およびプロジェクト・アップデートについては、 developerWorks Open source ゾーンを参照してください。
  • 無料のdeveloperWorks On demand demosで、IBM およびオープンン・ソースの技術と製品機能を調べて試してみてください。

製品や技術を入手するために

  • PHP アプリケーションで使うデータベースをお探しなら、IBM DB2 Express-C 9をダウンロードしてください。これは、DB2 Express V9 データ・サーバーの無料バージョンです。
  • IBM ソフトウェアの試用版を使用して、次のオープン・ソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
  • IBM 製品の評価版をダウンロードして、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を使ってみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=257933
ArticleTitle=PHP でカスタム検索エンジンを作成する
publish-date=07312007