FastAPIで完璧なブログを構築:投稿の全文検索

要約
この記事では、PostgreSQLの組み込みFTSを使用してFastAPIブログに強力な全文検索機能を追加する方法を説明します。データベースのセットアップ、検索APIの作成、フロントエンドの統合をカバーします。
意見はこのエリアに表示されます
アイキャッチ画像

前回の記事で、ブログ投稿に画像アップロード機能を追加しました。

時間が経つにつれて、ブログにはかなりの数の記事が蓄積されていることでしょう。新しい問題が徐々に現れます。読者は目的の記事を素早く見つけるにはどうすればよいでしょうか?

もちろん、答えは検索です。

この記事では、ブログに全文検索機能を追加します。

SQLのLIKE '%keyword%'クエリを使用して検索を実装することはできないかと思うかもしれません。

簡単なシナリオでは、確かに可能です。しかし、LIKEクエリは大量のテキストを扱う場合にパフォーマンスが悪く、あいまい検索(例:「creation」を検索しても「create」に一致しない)を処理できません。

そのため、より効率的なソリューションを採用します。PostgreSQLの組み込み全文検索(FTS)機能を利用します。これは高速なだけでなく、ステミングや関連性によるランキングなどの機能もサポートしており、LIKEよりもはるかに優れた検索機能を提供します。

ステップ1:データベース検索インフラストラクチャ

PostgreSQLのFTS機能を使用するには、まずpostテーブルにいくつかの変更を加える必要があります。中心的な考え方は、高速で検索可能な最適化されたテキストデータを格納するための専用列を作成することです。

コアコンセプト:「tsvector」

postテーブルにtsvector型の新しい列を追加します。これは、記事のタイトルとコンテンツを個々の単語(レキシム)に分解し、正規化します(例:「running」と「ran」を「run」に処理)。これは、後続のクエリのためです。

テーブル構造の変更

PostgreSQLデータベースで次のSQLステートメントを実行して、postテーブルにsearch_vector列を追加します。

データベースがLeapcellで作成された場合、

Leapcell

グラフィカルインターフェイスを使用してSQLステートメントを簡単に実行できます。ウェブサイトのデータベース管理ページに移動し、上記のステートメントをSQLインターフェイスに貼り付けて実行するだけです。

ImageP0

既存の投稿の検索ベクターの更新

検索ペア(search_vector)が更新されると、投稿が検索可能になります。

ブログにすでにいくつかの記事があるので、次のSQLステートメントを実行するだけで、それらのsearch_vectorデータを生成できます。

トリガーによる自動更新

投稿が作成または更新されるたびにsearch_vector列を手動で更新したい人はいません。最善の方法は、データベースにこの作業を自動的に実行させることです。これはトリガーを作成することで達成できます。

まず、上記のクエリと同様に、投稿のsearch_vectorデータを生成する関数を作成します。

setweight関数を使用すると、異なるフィールドからのテキストに異なる重みを割り当てることができます。ここでは、タイトルの重み('A')をコンテンツ('B')よりも高く設定しました。これは、検索結果で、タイトルにキーワードが含まれる記事のランキングが高くなることを意味します。

次に、新しい投稿が挿入(INSERT)または更新(UPDATE)されるたびに、先ほど作成した関数を自動的に呼び出すトリガーを作成します。

検索インデックスの作成

最後に、検索パフォーマンスを確保するためにsearch_vector列にGIN(Generalized Inverted Index)を作成する必要があります。

これで、データベースは検索準備が整いました。すべての記事の効率的な検索インデックスを自動的に維持します。

ステップ2:FastAPIでの検索ロジックの構築

データベースレイヤーを準備したので、検索リクエストを処理するバックエンドコードを記述するために、FastAPIプロジェクトに戻りましょう。

検索ルートの作成

検索関連のロジックは、routers/posts.pyファイルに直接追加します。SQLModelはSQLAlchemyをベースにしているため、SQLAlchemyのtext()関数を使用して生のSQLクエリを実行できます。

routers/posts.pyを開き、次の変更を行います。

コード解説

  • ファイルの先頭にfrom sqlalchemy import textを追加します。
  • 新しい/posts/searchルートが追加されます。/posts/{post_id}ルートと競合しないように、この新しいルートをget_post_by_idルートの前に配置してください。
  • q: str = Query(None):FastAPIはqの値をURLのクエリ文字列(例:/posts/search?q=keyword)から取得します。
  • to_tsquery('english', :query):この関数は、ユーザーが提供した検索文字列を、tsvector列と照合できる特別なクエリタイプに変換します。複数の単語を&で結合して、すべての単語が一致する必要があることを示します。
  • @@演算子:これは全文検索の「一致」演算子です。WHERE search_vector @@ ...という行が検索操作の核心です。
  • ts_rank(...):この関数は、クエリ用語がブログ記事とどれだけうまく一致するかを基にした「関連性ランキング」を計算します。最も関連性の高い記事が最初に表示されるように、このランクで降順にソートします。
  • session.exec(statement, {"query": search_query}).mappings().all():生のSQLクエリを実行し、.mappings().all()を使用して結果を辞書のリストに変換し、テンプレートで簡単に使用できるようにします。

ステップ3:検索機能をフロントエンドに統合

バックエンドAPIが準備できました。次に、ユーザーインターフェイスに検索ボックスと検索結果ページを追加しましょう。

検索ボックスの追加

templates/_header.htmlファイルを開き、ナビゲーションバーに検索フォームを追加します。

検索結果ページの作成

templatesディレクトリにsearch-results.htmlという名前の新しいファイルを作成します。このページは検索結果を表示するために使用されます。

実行とテスト

アプリケーションを再起動します。

ブラウザを開き、ブログのホームページに移動します。

「testing」というキーワードを含む新しい記事を書いてみましょう。

ImageP1

投稿を保存した後、検索ボックスに「test」と入力して検索を実行します。

検索結果ページで、作成したばかりの記事が結果に表示されます。

ImageP2

これで、ブログは全文検索機能をサポートするようになりました。どれだけ書いても、読者が迷うことはもうありません。


Xでフォローする:@LeapcellJP


ブログでこの記事を読む

関連記事:

Explore More
関連記事はありません。
Trends