2012年4月2日月曜日

PHP と Apache Solr によるエンタープライズ検索


私は「PHP でカスタム検索エンジンを作成する」の中で、PHP とオープンソースの Sphinx 検索エンジンを組み合わせ、LIKE (MySQL の場合には MATCH ) などに代わる、テキスト中心のデータベース・クエリーのための驚くほど高速な機能を作成しました (Sphinx に関連する情報については「参考文献」を参照)。

Sphinx はインストールや維持管理が容易であり、また非常に高機能です。しかも Sphinx の最近のリリースでは、ネイティブの MySQL エンジンを提供しており、Sphinx デーモンを別途実行する必要がありません。V0.9.8 (この記事の執筆時点での最新リリース) では、指定されたロケーションからの距離でカバーされるレコードを発見する geodistance クエリーと、複数のクエリーと結果セットを 1 つのネットワーク接続にまとめて最適化を行う、マルチ・クエリーという名前の機能が追加されています。

Sphinx は時間と共に改善が続けられており、ショッピング・サイトやブログ、その他数多くのアプリケーションに理想的なものになっています。Sphinx のサイトによれば、1 つのアプリケーションが今や 7 億の文書、または約 1.2 テラバイトのデータを索引付けすることができます。私は迷わず Sphinx をお薦めします。

しかし Sphinx は、アプリケーションやサイトの人気が高まり、利用数が増えるにつれて求められる、あるいは提供が必要となる、いくつかの機能をまだサポートしていません。特に、Sphinx はまだ索引を自動的に複製したり配布したりする機能がないため、Sphinx のデーモンが単一障害点になっています。(この回避策として、数台のマシンが同じデータベースに索引を付けるようにし、そうしたシステムをクラスター化する方法があります。) Sphinx は (Google がキャッシュしたページを表示する際に強調するように) 検索結果を強調表示することはなく、最近の検索結果を保持したりキャッシュしたりすることはなく、また正規表現 (regex) も日付に基づく操作もサポートしていません。

こうした機能が必要な場合には、あるいはエンタープライズ・グレードのソリューションを即座に求める場合には、Apache Software Foundation の Solr プロジェクトを検討してみてください。Solr は Lucene 検索エンジンをベースに、寛大な Apache Software License の条件でオープンソースとして提供されており、 (Lucene のサイトによれば)「Lucene の Java™ 検索ライブラリーをベースとするオープンソースのエンタープライズ検索サーバーであり、XML/HTTP と JSON の API、ヒットの強調表示、ファセット検索、キャッシング、複製、そして Web 管理インターフェースを備えています。」

何よりも注目すべきこととして、非常にトラフィックの多い Web サイトである Netflix、Digg、そして CNETの News.com や CNET Reviews は、Solr を使って強力な検索を行っています。Solr のウィキには、Solr を利用している公開サイトの長いリストが用意されています (「参考文献」を参照)。

ここでは Solr と PHP を組み合わせ、自動車部品のデータベースを検索する小さなアプリケーションを作成する方法を学びましょう。サンプルのデータベースはほんのわずかのレコードしか含んでいませんが、何百万というレコードを含めることも簡単にできます。この記事で使用しているソース・コードは、すべて「ダウンロード」セクションから入手することができます。

Solr をインストールする

Solr と PHP とを組み合わせるためには、Solr をインストールし、索引を設計し、Solr によって索引付けするデータを用意し、その索引をロードし、クエリーを実行するための PHP コードを作成し、そしてその結果を表示する必要があります。検索可能な索引を作成するために必要な作業の大部分は、コマンドラインから実行することができます。当然ですが、Solr 用の PHP のプログラム・インターフェースも索引の内容に影響を与えます。

Solr はJava 技術で実装されています。Solr と Solr の管理ツールを実行するためには、Java V1.5 の SDK (software development kit) である Java 5 SDK をインストールする必要があります。何社かのベンダーが Java V1.5 SDK を提供しており (例えば Sun Microsystems や IBM® 、 BEA Systems など)、どの実装を使っても Solr を活用することができます。そこで、オペレーティング・システムに適した Java パッケージを単純に選択し、完全なインストールを行うための指示に従います。

多くの場合、Java V1.5 のインストールは、自己解凍のアーカイブを実行して使用許諾条件を受け入れるのみ、という単純なものです。アーカイブの中のスクリプトが、面倒な作業のすべてをあっという間に行ってくれます。他のオペレーティング・システム (Debian など) では、APT リポジトリーの中で Java 5 SDKを提供しています。例えば Debian または Ubuntu を使用する場合であれば、sudo apt-get install sun-java5-jdk というコマンドで Java V1.5 ソフトウェアをインストールすることができます。

便利なことに、APT は Java 5 SDK を使うために必要なすべての依存ファイルも自動的にダウンロードします。

もし Java ソフトウェアが既にインストールされており、Java 実行可能ファイルが PATH にあるならば、java -version を実行して、どの Java コードが入っているのかを確認します。

ここでは、Mac OS X V10.5 Leopard オペレーティング・システムをデモのベースとして使います。Apple の Leopard には Java V1.5 が含まれています。Apache のデフォルト構成を少し変更するだけで、Leopard は PHP アプリケーションも実行することができます。Leopard のターミナル・ウィンドウで java -version を実行すると、以下の結果が得られます。


リスト 1. Leopard のターミナル・ウィンドウで java -version を実行する
                  $ which java /usr/bin/java  $ java -version java version "1.5.0_13" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-237) Java HotSpot(TM) Client VM (build 1.5.0_13-119, mixed mode, sharing) 

注意: Leopard では、/Applications/Utilities/Java の Java Preferences アプリケーションの中で Java V1.4 と V1.5 とを切り替えることができます。もし Leopard システムが V1.4 とレポートしたら、Java Preferences を開き、設定を図 1 のように変更します。


シリアル番号は1931年モデルを見つけるためにどのように

図 1. Leopard の Java Preferences アプリケーション

Solr をインストールするためには、Apache.org のサイトに行き、Resources > Download をクリックし、プロジェクトの適当なミラー・サイトを選択し、そして表示されるフォルダー内をナビゲートして Solr V1.2 の tarball (.tgz ファイル) を選択します。ダウンロードを行うと、apache-solr-1.2.0.tgz のような名前のファイルが転送されます。この tarball を以下のコードを使って解凍します。


リスト 2. tarball を解凍する
                  $ tar xzf apache-solr-1.2.0.tgz  $ ls -F apache-solr-1.2.0 CHANGES.txt NOTICE.txt  dist/ lib/ KEYS.txt  README.txt  docs/   src/ LICENSE.txt build.xml example/ 

新しく作成されたディレクトリーの中の dist という名前のフォルダーが、JAR (Java archive) としてバンドルされた Solr コードを含んでいます。サブディレクトリー example/exampledocs は、(通常は XML コードとして) フォーマットされた、Solr が索引付けできるデータのサンプルを含んでいます。

example ディレクトリーには、サンプル用の完全な Solr アプリケーションが含まれています。このアプリケーションを実行するためには、このアプリケーションのアーカイブ (start.jar) を使って単純に Java エンジンを起動するだけです。


リスト 3. Java エンジンを起動する
                  $ java -jar start.jar 2007-11-10 15:00:16.672::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog 2007-11-10 15:00:16.866::INFO:  jetty-6.1.3 ... INFO: SolrUpdateServlet.init() done 2007-11-10 15:00:18.694::INFO:  Started SocketConnector @ 0.0.0.0:8983 

これで、アプリケーションをポート 8983 で利用することができます。ブラウザーを起動し、アドレス・バーに

に用意されています。参考のために、デフォルトのスキーマのスニペットを以下に示します。


リスト 3. Solr のデフォルト・スキーマのスニペット
                     ...                   ...         id   ...      ...  

このスキーマの大部分は自明ですが、いくつかの点を説明しておく必要があります。

  • 上記にあるように、id フィールドはストリングであり (type="string")、そのため索引付けする必要があります (indexed="true")。また id フィールドは必須のフィールドでもあります (required="true")。このスキーマを使う場合には、Solr にロードされるすべてのレコードは、このフィールドに値を持つ必要があります。id 修飾子はさらに、id フィールドが一意の値でなければならないと宣言しています。(これはデフォルトの索引スキーマの中で設定されたルールにすぎず、Solr が一意の ID フィールドを要求するわけではありません。) id フィールドの属性 stored="true" は、id フィールドがリポジトリーから取得可能でなければならないことを示しています。

    では一体どのようなときに storedfalse に設定するのでしょう。リポジトリーからの取得不能という設定をフィールドにすると、そのフィールドでは結果を異なる順序で並べ替えることができます。例えば nameSort の場合、このフィールドは (最後の行に copyField コマンドがあるため) name フィールドのコピーですが、name フィールドとは動作は異なります。nameSortstring であり、nametext であることに注目してください。デフォルトの索引スキーマでは、この 2 つの型の扱い方は少し異なります。

  • フィールド catmultiValued です。ある 1 つのレコードが、このフィールドに対していくつかの値を定義するかもしれません。例えばアプリケーションがコンテンツを管理する場合、1 つのストーリーにいくつかのトピックが割り当てられるかもしれません。cat フィールドを使うと (あるいはこれに似た独自のフィールドを定義すると)、すべてのトピックを取り込むすることができます

リスト 4 はファイル example/exampledocs/ipod_other.xml を示しています。このファイルは iPod のアクセサリー・カタログの 2 つのエントリーを示しています。


ナイアガラ·フォールズオンタリオ州の民間中古車ディーラー

リスト 4. Solr のデフォルトの索引スキーマに対してフォーマットされたデータ
                      F8V7067-APL-KIT   Belkin Mobile Power Cord for iPod w/ Dock   Belkin   electronics   connector   car power adapter, white   4   19.95   1   false      IW-02   iPod & iPod Mini USB 2.0 Cable   Belkin   electronics   connector   car power adapter for iPod, white   2   11.50   1   false   

add 要素は、エンベロープに入れられたレコードを索引に追加するための Solr のコマンドです。各レコードは、一連の名前付き field 要素を使ってフィールド値を指定する doc 要素の中に取り込まれます。フィールド weightpriceinStockmanufeatures、そして popularity も、Solr のデフォルトの索引スキーマの中で定義されるフィールドです。features フィールドは cat と同じ属性を持っていますが、データが持つ意味合いは異なります。このフィールドは、ある製品の機能を (場合によると多数) 列挙します。

自動車の部品を検索する

この例では、自動車部品の集合に索引を付けします。各部品はいくつかのフィールドを持ち、そのうちの最も重要なフィールドの例を表 1 に示します。最初の列にはフィールドの名前が記載されています。2 列目には簡単な説明があり、3 列目はフィールドの論理型を記載しています。4 列目は、そのデータを表現するために使われる (リスト 5 のスキーマで定義される) 索引型を示しています。


表 1. 自動車部品のレコードのフィールド
フィールド名説明Solr の型
Part number (一意 (unique)、必須 (mandatory))識別番号String partno
Name簡単な説明String name
Model (必要 (required)、複数値 (multi-value))モデル名、例えば「Camaro」などString model
年式 (複数値)モデル年式、例えば 2001 などString year
Price単価Float price
In stock在庫の有無Boolean inStock
Featuresその部品の機能String features
Timestampその部品の取扱記録String timestamp
Weight出荷重量Float weight

リスト 5 は、自動車部品の索引に使われる Solr スキーマの一部を示しています。このスキーマは主に Solr のデフォルト・スキーマをベースにしています。使用される特定のフィールド (名前のフィールドと属性のフィールド) は、(リスト 3 を見るとわかるように) デフォルトの中にある field 要素を単純に置き換えています。


リスト 5. 自動車部品の索引スキーマ
                      ...                                                                                                   partno      name  

上記のようにフィールドが指定されると、Solr にアップロードするためにエクスポートされ、整形された自動車部品のデータベースは、リスト 6 のようなものになります。


どこで安い自動車保険を見つけることができます

リスト 6. 索引用に整形された自動車部品のデータベース
                      1   Spark plug   Boxster   924   1999   2000   25.00   true     2   Windshield   911   1991   1999   15.00   false   

この新しい索引スキーマをインストールし、Solr にデータをロードしましょう。まず、もしまだ Solr デーモンが実行していたら、Ctrl+C を使ってそれを停止します。既存の Solr スキーマのアーカイブを example/solr/conf/schema.xml の中に作ります。次に、リスト 6 からテキスト・ファイルを作成し、それを /tmp/schema.xml に保存し、それを example/solr/conf/schema.xml にコピーします。リスト 7 に示すデータ用に別のファイルを作成します。今度は Solr を再度起動し、このサンプルに提供されているポスト用のユーティリティーを使います。


リスト 7. 新しいスキーマを使って Solr を起動する
                  $ cd apache-solr-1.2/example $ cp solr/conf/schema.xml solr/conf/default_schema.xml $ chmod a-w solr/conf/default_schema.xml  $ vi /tmp/schema.xml ... $ cp /tmp/schema.xml solr/conf/schema.xml  $ vi /tmp/parts.xml ...  $ java -jar start.jar  ... 2007-11-11 16:56:48.279::INFO:  Started SocketConnector @ 0.0.0.0:8983  $ java -jar exampledocs/post.jar /tmp/parts.xml SimplePostTool: version 1.2 SimplePostTool: WARNING: Make sure your XML documents are encoded in UTF-8,       other encodings are not currently supported SimplePostTool: POSTing files to http://localhost:8983/solr/update... SimplePostTool: POSTing file parts.xml SimplePostTool: COMMITting Solr index changes... 

成功です。この索引が存在し、2 つの文書を含んでいることを確認したい場合には、ブラウザーで再度 http://localhost:8983/solr/admin/ にアクセスします。ページの先頭に「(autoparts)」と表示されるはずです。表示されているようなら、ページの中ほどにあるクエリー・ボックスをクリックし、partno: 1 or partno: 2 と入力します。

結果は以下のようになるはずです。

 3 on 10 0 partno: 1 OR partno: 2 2.2 true Boxster 924 Spark plug 1 25.0 2007-11-11T21:58:45.899Z 1999 2000  false 911 Windshield 2 15.0 2007-11-11T21:58:45.953Z 1991 1999 

いくつか他のクエリーも試してみてください。Lucene のクエリー (Solr の中の検索エンジン) の構文は Lucene のウィキに説明されています (「参考文献」を参照)。

また、再度データを編集し、ロードしてみる必要もあります。partno フィールドは一意である (unique) と宣言されているため、同じ部品番号に対してアップロード操作を繰り返しても、古い索引レコードが新しいレコードと置き換えられるにすぎません。add コマンドの他に、commitoptimizedelete を使うことができます。最後のコマンドは、ID によって特定のレコードを削除し、あるいはクエリーによって多数のレコードを削除します。

今度は PHP の出番です

いよいよ、このサンプルの中に PHP が登場します。

Solr には少なくとも 2 つの PHP 用の API があります。最も堅牢な実装は、Donovan Jimenez による PHP Solr Client (「参考文献」を参照) です。このコードは Solr と同じ条件でライセンスされており、充実したドキュメンテーションを備え、Solr V1.2 と互換性があります。この記事の執筆時点での最新リリースの日付は 2007年10月2日です。

Solr Client は以下の 4 つの PHP クラスを提供しています。

  • Apache_Solr_Service は Solr サーバーに関する内容を表します。このクラスのメソッドを使って、サーバーに対して ping を実行したり、文書の追加や削除、変更のコミット、索引の最適化などを行ったり、クエリーを実行したりします。
  • Apache_Solr_Document は Solr 文書の実際を管理します。このクラスのメソッドは、(キーと値の) ペアと、複数の値を持つフィールドを管理します。フィールドの値は、直接、逆参照することによってアクセスされます (例えば $document->title = 'Something'; ... echo $document->title; など)。
  • Apache_Solr_Response は Solr のレスポンスをカプセル化します。このコードは json_decode() 関数に依存しています。この関数は PHP V5.2.0 以降にバンドルされていますが、PECL (PHP Extension Community Library、「参考文献」を参照) からインストールすることもできます。
  • Apache_Solr_Service_Balancer は Apache_Solr_Service を機能強化しており、このクラスを利用すると、あるディストリビューションの複数の Solr サービスに接続することができます。この記事では、このクラスに関しては説明しません。

PHP Solr Client をダウンロードして (「参考文献」を参照)、作業ディレクトリーに解凍し、ディレクトリー名を SolrPhpClient に変更します。次に、ファイル Apache/Solr/Service.php をチェックします。この記事の執筆時点では、335 行目に終了用のセミコロンが抜けていました。必要であれば、このファイルを編集してセミコロンを追加します。またファイル Apache/Solr/Document.php もチェックします。112 行目から 117 行目は以下のようになっているはずです。

 if (!is_array($this->_fields[$key])) {   $this->_fields[$key] = array($this->_fields[$key]); }  $this->_fields[$key][] = $value; 

これらのファイルを修正すると、他の PHP ライブラリーと並べて Apache ディレクトリーをインストールすることができます。

以下のコードの PHP アプリケーションは、Solr サービスに接続し、索引に 2 つの文書を追加し、そして先ほど使用した部品番号のクエリーを実行します。



リスト 8. Solr 索引に接続し、その Solr 索引をロードし、その Solr 索引にクエリーを実行するサンプル PHP アプリケーション
                  ping() ) {     echo 'Solr service not responding.';     exit;   }      //   //   // Create two documents to represent two auto parts.   // In practice, documents would likely be assembled from a    //   database query.    //   $parts = array(     'spark_plug' => array(       'partno' => 1,       'name' => 'Spark plug',       'model' => array( 'Boxster', '924' ),       'year' => array( 1999, 2000 ),       'price' => 25.00,       'inStock' => true,     ),     'windshield' => array(       'partno' => 2,       'name' => 'Windshield',       'model' => '911',       'year' => array( 1999, 2000 ),       'price' => 15.00,       'inStock' => false,     )   );        $documents = array();      foreach ( $parts as $item => $fields ) {     $part = new Apache_Solr_Document();          foreach ( $fields as $key => $value ) {       if ( is_array( $value ) ) {         foreach ( $value as $datum ) {           $part->setMultiValue( $key, $datum );         }       }       else {         $part->$key = $value;       }     }          $documents[] = $part;   }        //   //   // Load the documents into the index   //    try {     $solr->addDocuments( $documents );     $solr->commit();     $solr->optimize();   }   catch ( Exception $e ) {     echo $e->getMessage();   }      //   //    // Run some queries. Provide the raw path, a starting offset   //   for result documents, and the maximum number of result   //   documents to return. You can also use a fourth parameter   //   to control how results are sorted and highlighted,    //   among other options.   //   $offset = 0;   $limit = 10;      $queries = array(     'partno: 1 OR partno: 2',     'model: Boxster',     'name: plug'   );    foreach ( $queries as $query ) {     $response = $solr->search( $query, $offset, $limit );          if ( $response->getHttpStatus() == 200 ) {        // print_r( $response->getRawResponse() );              if ( $response->response->numFound > 0 ) {         echo "$query 
"; foreach ( $response->response->docs as $doc ) { echo "$doc->partno $doc->name
"; } echo '
'; } } else { echo $response->getHttpStatusMessage(); } } ?>

このコードはまず、指定されたポートとパスにある指定された Solr サーバーに接続し、サーバーが動作しているかどうかを ping() メソッドを使って確認します。

次にこのコードは、PHP の配列として表現されたレコードを Solr 文書に変換します。フィールドの値が 1 つの場合には、単純なアクセサーが (キーと値の) ペアをこの文書に追加します。フィールドが複数の値を持つ場合には、値のリストは特別な関数 setMultiValue() を持つキーに割り当てられられます。このプロセスは Solr 文書を XML で表現する場合に非常に似ていることがわかると思います。

最適化をするために、addDocuments() は複数の文書を索引の中に挿入します。その後に行われる commit() 関数と optimize() 関数によって追加が完了します。

一番最後に、いくつかのクエリーが索引からデータを取得しています。この結果は 2 つの視点から見ることができます。getRawResponse() 関数は、構文解析されていない全体の結果を返します。一方 docs() 関数は名前付きアクセサーを使って文書の配列を返します。

もしクエリーが Solr から OK を取得できない場合には、このコードはエラー・メッセージを出力します。空の結果セットの場合は何も出力されません。

さらに強力なものにするために

Solr は信じられないほど強力であり、また PHP API のおかげで、どのようなプラットフォームとも容易に統合することができます。さらに良いことに、Solr はセットアップや操作が容易であり、必要に応じて高度な機能を有効にすることができます。そして何よりも良いことに、Solr は無料です。検索エンジンにお金を払う必要はありません。費用を節約し、Solr を活用してください。

Solr の Web サイトを調べ、ソートや、結果のカテゴリー分け、複製など、より高度な構成について学んでください。Lucene は Solr システムの基礎となっている検索技術であるため、Lucene の Web サイトも豊富な情報源です。

ダウンロード

内容ファイル名サイズダウンロード形式
Sample PHP and Solr applicationos-php-apachesolr.src.zip109KBHTTP

ダウンロード形式について

参考文献

学ぶために

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

  • Solr プロジェクトのミラー・サイトの 1 つから Solr をダウンロードしてください。
  • Sphinx 検索エンジンを詳しく学び、またこのエンジンをダウンロードしてください。
  • Donovan Jimenez による PHP Solr Client をダウンロードしてください。
  • PECL リポジトリーを訪れてください。ここは最初に訪れるべき場所として、PHP の拡張機能をダウンロードし、開発するための既知の拡張機能やホスト機能をすべて用意しています。
  • 皆さんのオープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、または DVD で入手することができます。
  • IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などのアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

著者について

Martin Streicherは McClatchy Interactive の最高技術責任者であり、Linux Magazineの編集長であり、Web 開発者であり、また developerWorks への頻繁な寄稿者でもあります。彼は Purdue Universityでコンピューター・サイエンスの修士号を取得しており、1986年以来、UNIX ライクのシステムでプログラミングを行ってきています。


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

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

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

この記事を評価する

コメント



These are our most popular posts:

方法1989カマロのためにサブウーファーボックスを作成する » ウィキ便利

あなたの1989カマロのトランクトランクにサブウーファーボックスを構築し、インストール することによってあなたのために働くことを確認します。 ... にサブウーファーを配置する 最も簡単で費用対効果の高い方法は、カスタムサブウーファーボックスを構築することで あることを意味します。 ... あなたがサブボックスをマウントしたい領域に焦点を当てて、 トランクの寸法を測定します。 ... あなたが測定し、スピーカーの正しいボリュームでの スペースに収まるようにボックスを持ってまで、一度に一つの次元を調整すること によって、右の ... read more

【1stから】カマロ総合2【5thまで】 / 車種・メーカー(趣味)-2ちゃんねる過去 ...

ってかいまだにわからんのだがカマロってHIGHビームにするのってどうやるの?なに やっても ...... サブプライムで弱気になってるところをパコパコやりてえ。 ちなみに理想 ...... ここのカスタムニュースにナビが写ってるけどいくらかかるかは・・・ ...... 大金かけて カスタムするそうだ。 Fロード .... それを購入して、左のヒューズボックスのACC部分と 交換。 read more

MSDN マガジン: ASP.NET Web ページ - WebMatrix の概要

NET に MVC を適切に導入するため、LEGO ブロックのように、パーツをどのように選ん でも簡単にかみ合うよう、フレームワークのリファクタリングが少しずつ進められてきまし た。 ... WebMatrix を最初に起動すると、4 つのオプションを選択できるダイアログ ボックスが表示されます。 ... { var cars = new string [] { , , Honda var emailAddress = } @foreach ( var car in cars ) ..... ヘルパーと新しいレイアウト サブシステムの登場により、再利用が現実的に なりました。 read more

Grand Theft Auto Ⅳ(グランドセフトオート4)GTAⅣ攻略wiki - 乗り物一覧

所謂レアカー、レアカラー車等の詳細は特別仕様な車まとめを参照。 ※このページには 画像が多く掲載されています。 環境によっては、フリーズ等が発生する可能性が ありますのでご注意ください。 ※Internet Explorer Mobileのようなブラウザでは、 テーブルを ... read more

0 件のコメント:

コメントを投稿