ヘルプセンター

PDFの内容でもヒットするサイト内検索をNILTOで実装する方法

はじめに

多くの企業サイトでは、技術資料やサービス紹介資料などがPDFで管理されています。しかし、標準的なサイト内検索機能ではウェブページの内容しか検索できず、PDF資料内の情報がユーザーに届かないという課題があります。

この課題は、ヘッドレスCMS「NILTO」の柔軟なAPIと、Pythonによるテキスト解析を組み合わせることで解決可能です。

ウェブコンテンツとPDFファイルの中身を横断的に検索できる高機能なサイト内検索の実装方法を、具体的な3つのステップに沿って解説します。

検索機能の全体像

今回実装する検索機能は、大きく2つの処理に分かれています。

事前準備(オフライン処理)

Pythonプログラムを使用し、あらかじめPDFファイルからテキスト情報を抽出します。そして、検索しやすいように「キーワード群」へ変換し、NILTOに登録します。

検索実行(オンライン処理)

ユーザーがサイトで検索を実行した際、NILTOのAPIを呼び出します。ウェブページのコンテンツと、事前に登録したPDFのキーワード群をまとめて検索し、結果を表示します。

検索機能実装のステップ

それでは、具体的な実装ステップを見ていきましょう。

Step 1. PythonでPDFを解析し、検索用キーワードを準備する

まず、PDFファイルを「検索可能なデータ」に変換する準備から始めます。PDFはそのままではファイルとして扱われるため、中身のテキストを抽出し、NILTOが検索できるキーワードの形に加工する必要があります。

この処理には、以下のPythonライブラリを使用します。

  • PyMuPDF: PDFファイルからテキストを効率的に抽出します。
  • Janome: 抽出した日本語の文章を単語単位に分解(形態素解析)します。

【コード例】Janomeを使ったキーワード抽出処理

以下は、実際にJanomeを使用してテキストから意味のある単語だけを抽出し、最終的にカンマ区切りの文字列へ変換するPythonコードの例です。

import fitz  # PyMuPDF
import re
from janome.tokenizer import Tokenizer

def extract_text_from_pdf(file_path):
    """PDFファイルから全てのテキストを抽出する関数"""
    try:
        doc = fitz.open(file_path)
        full_text = " ".join(page.get_text("text") for page in doc)
        doc.close()
        return full_text
    except Exception as e:
        print(f"エラー: ファイル "{file_path}" の処理中にエラー: {e}")
        return None

def clean_text(text):
    """テキストから不要な改行、空白、記号などを除去する関数"""
    cleaned_text = text.replace("\n", " ").replace("\r", "")
    cleaned_text = re.sub(r"[\u200b-\u200d\ufeff]", "", cleaned_text) # 見えない制御文字
    cleaned_text = re.sub(r"[0-90-9]+", " ", cleaned_text)       # 数字
    cleaned_text = re.sub(r"[.()~「」『』【】◆●・()]+", " ", cleaned_text) # 記号
    cleaned_text = re.sub(r"\s+", " ", cleaned_text)               # 連続する空白
    return cleaned_text.strip()

def extract_all_meaningful_words(text, tokenizer):
    """テキストを形態素解析し、意味を持つ単語を抽出する関数"""
    stop_words = {"こと", "もの", "ため", "いる", "する", "できる", "なる", "よう"}
    unique_words = set()
    for token in tokenizer.tokenize(text):
        part_of_speech = token.part_of_speech.split(",")[0]
        if part_of_speech in ["名詞", "動詞", "形容詞", "副詞"]:
            base_form = token.base_form
            if base_form in stop_words or len(base_form) == 1:
                continue
            unique_words.add(base_form)
    return list(unique_words)

# 1. 処理対象のPDFファイルパスを指定
pdf_file_path = "path/to/your/sample.pdf" # 実際のファイルパスに置き換えてください

# 2. 形態素解析器を初期化
t = Tokenizer()

# 3. PDFからテキストを抽出
print(f""{pdf_file_path}" からテキストを抽出中...")
raw_text = extract_text_from_pdf(pdf_file_path)

if raw_text:
    # 4. テキストをクリーニング
    cleaned_text = clean_text(raw_text)
    
    # 5. 意味のある単語を抽出(形態素解析)
    keywords_list = extract_all_meaningful_words(cleaned_text, t)
    
    # 6. リストをカンマ区切りの文字列に変換
    keywords_string = ",".join(keywords_list)
    
    # 7. 最終的な文字列を出力
    print("\n--- 抽出結果 ---")
    print(keywords_string)
    # 出力例: NILTO,CMS,ヘッド,高速,サイト,検索,実装,方法,内容,ヒット
else:
    print("テキストを抽出できませんでした。")

このコードのポイントは以下の通りです。

  • 品詞による絞り込み
    • 文章を単語に分解した後、「名詞」「動詞」といった、文章の意味を構成する上で重要な品詞を持つ単語のみを対象とします。これにより、「てにをは」などの助詞は自動的に除外されます。
  • 単語の原型(基本形)を取得
    • token.base_form を使うことで、「走った」「走る」「走れば」といった活用が異なる単語を、すべて「走る」という原型に統一して扱えます。これにより、検索精度が向上します。
  • ストップワードの除去
    • 「こと」や「もの」といった、どの文章にも頻繁に現れるものの、検索キーワードとしては意味をなしにくい単語(ストップワード)をあらかじめ除外します。

このようにして抽出したキーワード群をカンマ区切りの一つの文字列に結合し、NILTOのコンテンツモデルに用意した「複数行テキスト」フィールドに保存します。

この事前準備により、PDFファイルはNILTO上で検索可能なテキスト情報を持つコンテンツへと生まれ変わります。

Step 2. NILTO APIを活用し、コンテンツとPDFを横断検索する

キーワードの準備が完了したら、検索機能のバックエンドを実装します。
この処理で重要なのが、NILTOのDeveloper APIに用意されているcontainsというクエリパラメータです。

containsパラメータには、以下の特長があります。

  • 複数モデルの横断検索
    • model=news,faq のように指定することで、複数のモデルを一度のリクエストで検索できます。
  • 全テキストフィールドが対象
    • コンテンツ内の全てのテキスト系フィールド(1行テキスト、複数行テキストなど)を対象に、キーワードが含まれるかを自動で検索します。

このcontainsを利用することで、ウェブページの本文と、Step 1で登録したPDFのキーワード群を、一度のAPIリクエストで同時に検索できます。

// 検索対象のモデルを指定
const models = ["news", "faq", "movie", "free_page"];

// "contains"パラメータに検索キーワードを指定してAPIエンドポイントを構築
const url = `https://cms-api.nilto.com/v1/contents?model=${models.join(",")}&contains=${encodeURIComponent(query)}`;

// APIリクエストを実行
const response = await fetch(url, { /* ...headers */ });

NILTO APIの機能を活用することで、バックエンドのロジックをシンプルに実装できます。

Step 3. Astroで検索結果とスニペットを表示する

最後に、検索結果をユーザーに分かりやすく表示するフロントエンドを構築します。

ユーザー体験を高める工夫として「スニペット」の表示が挙げられます。スニペットとは、検索キーワードが文章のどこに含まれているかを示す短い抜粋文のことです。

// 検索キーワードを<mark>タグでハイライトする処理
const escapedQuery = query.replace(/[-/^$*+?.()|[]{}]/g, "$&");
const regex = new RegExp(`(${escapedQuery})`, "gi");
return snippet.replace(regex, "<mark>$1</mark>");

この処理により、ユーザーはなぜそのページが検索結果として表示されたのかを一目で把握でき、目的の情報に素早くたどり着けます。

まとめ

ヘッドレスCMS「NILTO」とPythonを連携させ、PDFファイルの中身まで検索対象に含む高機能なサイト内検索を実装する方法を解説しました。

今回の実装におけるポイントは以下の通りです。

  • Point 1: Pythonによる事前準備
    • PDFからテキストを抽出し、形態素解析によって検索用のキーワードを作成し、NILTOに登録する。
  • Point 2: NILTO APIによる効率的な検索
    • contains パラメータを活用し、ウェブコンテンツとPDFのキーワードを一度に横断検索する。
  • Point 3: フロントエンドのUX向上
    • 検索結果にスニペットやキーワードのハイライト表示を加え、ユーザーの利便性を高める。

NILTOの柔軟なAPIは、Pythonのような外部ツールともスムーズに連携できるため、標準機能だけでは実現が難しい要件にも幅広く対応可能です。

NILTOを活用したサイト構築や、より高度なカスタマイズにご興味のある方は、ぜひお気軽にお問い合わせください。

問題は解決しましたか?

回答が見つからない場合は、お問合せのサポート窓口からお気軽にお問いあわせください。

お問い合わせ