# 前準備
### jsonデータ生成
ライブドアニュースコーパスをDL
ダウンロード(通常テキスト):ldcc-20140209.tar.gz
### コーパスの取り込み
import json import datetime from typing import List, Dict from pathlib import Path import random CORPUS_DIR = './livedoor-corpus' # ライブドアコーパスをここにおく QDRANT_JSON = 'livedoor.json' SAMPLE_TEXT_LEN: int = 500 # ドキュメントを500文字でトランケート def read_document(path: Path) -> Dict[str, str]: """1ドキュメントの処理""" with open(path, 'r') as f: lines: List[any] = f.readlines(SAMPLE_TEXT_LEN) lines = list(map(lambda x: x.rstrip(), lines)) d = datetime.datetime.strptime(lines[1], "%Y-%m-%dT%H:%M:%S%z") created_at = int(round(d.timestamp())) # 数値(UNIXエポックタイプ)に変換 return { "url": lines[0], "publisher": path.parts[1], # ['livedoor-corpus', 'it-life-hack', 'it-life-hack-12345.txt'] "created_at": created_at, "body": ' '.join(lines[2:]) # 初めの2行をスキップし、各行をスペースで連結し、1行にする。 } def load_dataset_from_livedoor_files() -> (List[List[float]], List[str]): # NB. exclude LICENSE.txt, README.txt, CHANGES.txt corpus: List[Path] = list(Path(CORPUS_DIR).rglob('*-*.txt')) random.shuffle(corpus) # 記事をシャッフルします with open(QDRANT_JSON, 'w') as fp: for x in corpus: doc: Dict[str, str] = read_document(x) json.dump(doc, fp) # 1行分 fp.write('\n') if __name__ == '__main__': load_dataset_from_livedoor_files()
$ python3 corpus.py
$ ls
corpus.py livedoor-corpus livedoor.json
このlivedoor.jsonをGoogle collaboで使います。
### Google colabo
!pip install -U ginza spacy
!pip install -U numpy pandas ja_ginza
colaboで文章をベクトル化 … 約10分
import numpy as np import pandas as pd import spacy # from multiprocessing import Pool, cpu_count <- マルチプロセス関連は不要 # GiNZAモデルのロード (インストールが完了している前提) try: nlp: spacy.Language = spacy.load('ja_ginza', exclude=["tagger", "parser", "ner", "lemmatizer", "textcat", "custom"]) print("✅ GiNZAモデルのロードに成功しました。") except OSError: print("❌ GiNZAモデルが見つかりません。再度インストール手順を確認してください。") # ここでエラーになる場合は、!pip install -U ginza を実行してください。 QDRANT_NPY = 'vectors-livedoor-ginza.npy' # 出力ファイル名 def f(x): # NaNやNone値のチェック (エラー回避のため) if pd.isna(x): # 空のベクトルを返す、または処理をスキップ return np.zeros(nlp.vocab.vectors_length) doc: spacy.tokens.doc.Doc = nlp(x) # GiNZAでベクトル化 return doc.vector def main(): try: df = pd.read_json('livedoor.json', lines=True) except FileNotFoundError: print("❌ livedoor.json が見つかりません。ファイルが /content/ にアップロードされているか確認してください。") return print("\nデータフレームの先頭5行:") print(df.head()) print(f"\n合計 {len(df)} 件の文書をベクトル化中... (シングルプロセス)") # 修正箇所: df.body.apply(f) を使用してシングルプロセスでベクトル化 vectors_list = df.body.apply(f).tolist() print("ベクトル化完了。NumPyファイルに保存中...") # リストをNumPy配列に変換して保存 vectors_array = np.array(vectors_list) np.save(QDRANT_NPY, vectors_array, allow_pickle=False) print(f"\n========================================================") print(f"✅ 保存完了: {QDRANT_NPY}") print(f"配列の形状 (Shape): {vectors_array.shape}") print(f"========================================================") # 処理の実行 main()
vectors-livedoor-ginza.npy ができます。
### Qdrantの使い方
### docker pull
$ sudo docker pull qdrant/qdrant
$ sudo docker run -p 6333:6333 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
_ _
__ _ __| |_ __ __ _ _ __ | |_
/ _` |/ _` | ‘__/ _` | ‘_ \| __|
| (_| | (_| | | | (_| | | | | |_
\__, |\__,_|_| \__,_|_| |_|\__|
|_|
Version: 1.15.4, build: 20db14f8
Access web UI at http://localhost:6333/dashboard
2025-09-27T03:10:37.414937Z INFO storage::content_manager::consensus::persistent: Initializing new raft state at ./storage/raft_state.json
2025-09-27T03:10:37.427278Z INFO qdrant: Distributed mode disabled
2025-09-27T03:10:37.428004Z INFO qdrant: Telemetry reporting enabled, id: 4ab6c13b-1d33-4b1f-ac4a-baff31ff55ad
2025-09-27T03:10:37.460847Z INFO qdrant::actix: TLS disabled for REST API
2025-09-27T03:10:37.463591Z INFO qdrant::actix: Qdrant HTTP listening on 6333
2025-09-27T03:10:37.465943Z INFO actix_server::builder: starting 1 workers
2025-09-27T03:10:37.466167Z INFO actix_server::server: Actix runtime found; starting in Actix runtime
2025-09-27T03:10:37.466216Z INFO actix_server::server: starting service: “actix-web-service-0.0.0.0:6333”, workers: 1, listening on: 0.0.0.0:6333
2025-09-27T03:10:37.467991Z INFO qdrant::tonic: Qdrant gRPC listening on 6334
2025-09-27T03:10:37.468111Z INFO qdrant::tonic: TLS disabled for gRPC API
### SDKインストール
$ pip3 install qdrant-client
### Qdrantの説明
– コレクション: RDBテーブル
– ポイント: RDBレコード ポイントには、ペイロード(Payload)と呼ばれるメタ情報も一緒に登録できる。メタ情報はフィルター検索に使用する。
### コレクションの作成
from qdrant_client import QdrantClient from qdrant_client.http.models import VectorParams, Distance collection_name = 'livedoor' qdrant_client = QdrantClient(host='localhost', port=6333) qdrant_client.recreate_collection( collection_name=collection_name, vectors_config=VectorParams(size=300, distance=Distance.COSINE) # GiNZAは300次元 )
### コレクションの確認
from qdrant_client import QdrantClient # Qdrantクライアントを起動中のDockerコンテナに接続 qdrant_client = QdrantClient(host='localhost', port=6333) # 存在する全てのコレクション名を取得 collections = qdrant_client.get_collections() collection_names = [c.name for c in collections.collections] print("====================================") print("✅ Qdrantに存在するコレクション:") if 'livedoor' in collection_names: print(f" [O] 'livedoor' コレクションが見つかりました。") # 詳細情報の取得でエラーが出るため、シンプルな情報に修正 collection_info = qdrant_client.get_collection(collection_name='livedoor') print(f" - ステータス: {collection_info.status.value}") print(f" - ポイント数: {collection_info.points_count} (現在は0のはずです)") else: print(" [X] 'livedoor' コレクションは見つかりませんでした。") print(f" 現在存在するコレクション: {collection_names}") print("====================================")
$ python3 check_collection.py
====================================
✅ Qdrantに存在するコレクション:
[O] ‘livedoor’ コレクションが見つかりました。
– ステータス: green
– ポイント数: 0 (現在は0のはずです)
====================================
### upload
import json import numpy as np import pandas as pd from qdrant_client import QdrantClient # ========================================================================= # 1. 補助関数の定義 (JSONファイルの読み込み用) # livedoor.jsonには不要なキーが含まれている可能性があるため、 # 必要なキーだけを抽出する目的の関数です。 # ========================================================================= def hook(obj): """ JSONオブジェクトから必要なペイロードデータのみを抽出するフック関数。 """ if 'body' in obj: # 必要なキー(本文、タイトル、カテゴリ)を抽出して返す return { "title": obj.get("title", ""), "body": obj.get("body", ""), "category": obj.get("category", "") } return obj # ========================================================================= # 2. メイン処理 # ========================================================================= def main(): # 接続情報 collection_name = 'livedoor' qdrant_client = QdrantClient(host='localhost', port=6333) # データの読み込み try: # ベクトルデータの読み込み vectors = np.load('./vectors-livedoor-ginza.npy') # JSONファイルの読み込みとペイロードの準備 print("JSONファイルを読み込んでペイロードを準備中...") docs = [] with open('./livedoor.json', 'r', encoding='utf-8') as fd: # 各行(一つのJSONオブジェクト)を読み込み、hook関数で必要なキーを抽出 for line in fd: docs.append(json.loads(line, object_hook=hook)) print(f"✅ 読み込み完了。ベクトル数: {vectors.shape[0]}、文書数: {len(docs)}") except FileNotFoundError as e: print(f"❌ ファイルが見つかりません: {e.filename}") print("ファイル(livedoor.json, vectors-livedoor-ginza.npy)が同じディレクトリにあるか確認してください。") return # コレクションへのアップロード print("Qdrantコレクションにデータをアップロード中...") qdrant_client.upload_collection( collection_name=collection_name, # コレクション名 vectors=vectors, # ベクトルデータ (NumPy配列) payload=iter(docs), # ペイロードデータ (ジェネレータまたはイテレータ) ids=None, # IDの自動発番 batch_size=256 # バッチサイズ ) print("✅ データアップロード完了。") # 最終確認 collection_info = qdrant_client.get_collection(collection_name='livedoor') print(f"最終ポイント数: {collection_info.points_count}") # スクリプトの実行 if __name__ == "__main__": main()
$ python3 upload_data.py
JSONファイルを読み込んでペイロードを準備中…
✅ 読み込み完了。ベクトル数: 7367、文書数: 7367
Qdrantコレクションにデータをアップロード中…
✅ データアップロード完了。
最終ポイント数: 7367
$ pip3 install spacy ginza
import numpy as np import spacy from qdrant_client import QdrantClient from qdrant_client.http.models import ScoredPoint # ========================================================================= # 1. 初期設定とモデルロード # ========================================================================= # ベクトル化に使用したモデルと同じものをロード # 以前のステップでインストールが完了していることを前提とします try: nlp: spacy.Language = spacy.load('ja_ginza', exclude=["tagger", "parser", "ner", "lemmatizer", "textcat", "custom"]) print("✅ GiNZAモデルのロードに成功しました。") except OSError: print("❌ GiNZAモデルが見つかりません。") exit() # 処理を中断 # Qdrant接続情報 collection_name = 'livedoor' qdrant_client = QdrantClient(host='localhost', port=6333) # 検索クエリ QUERY_TEXT = "男磨きの動画を見ています" # ========================================================================= # 2. クエリテキストのベクトル化 # ========================================================================= def get_vector_from_text(text: str) -> np.ndarray: """ GiNZAを使用してテキストをベクトルに変換します。 """ doc: spacy.tokens.doc.Doc = nlp(text) # GiNZAのdoc.vectorはNumPy配列を返します return doc.vector # ========================================================================= # 3. Qdrantでの検索実行 # ========================================================================= def main(): print(f"\n========================================================") print(f"🔍 検索クエリ: {QUERY_TEXT}") print(f"========================================================") # クエリテキストをベクトルに変換 query_vector = get_vector_from_text(QUERY_TEXT) # Qdrantで検索を実行 hits = qdrant_client.search( collection_name=collection_name, query_vector=query_vector, # ベクトル化したクエリー query_filter=None, with_payload=True, # レスポンスにペイロードを含める limit=5 # 上位5件を取得 ) # 検索結果の表示 print("\n[検索結果 - 上位 5件]") if not hits: print("類似記事は見つかりませんでした。") return for i, hit in enumerate(hits): h: ScoredPoint = hit # ペイロードからタイトルと本文を取得 title = h.payload.get('title', 'N/A') body_snippet = h.payload.get('body', 'N/A')[:100] + '...' # 本文は先頭100文字を抜粋 print(f"--- 順位 {i+1} (スコア: {h.score:.4f}) ---") print(f"タイトル: {title}") print(f"本文抜粋: {body_snippet}") # スクリプトの実行 if __name__ == "__main__": main()
import numpy as np import spacy from qdrant_client import QdrantClient from qdrant_client.http.models import ScoredPoint # ========================================================================= # 1. 初期設定とモデルロード # ========================================================================= # ベクトル化に使用したモデルと同じものをロード # 以前のステップでインストールが完了していることを前提とします try: nlp: spacy.Language = spacy.load('ja_ginza', exclude=["tagger", "parser", "ner", "lemmatizer", "textcat", "custom"]) print("✅ GiNZAモデルのロードに成功しました。") except OSError: print("❌ GiNZAモデルが見つかりません。") exit() # 処理を中断 # Qdrant接続情報 collection_name = 'livedoor' qdrant_client = QdrantClient(host='localhost', port=6333) # 検索クエリ QUERY_TEXT = "野球情報が知りたい" # ========================================================================= # 2. クエリテキストのベクトル化 # ========================================================================= def get_vector_from_text(text: str) -> np.ndarray: """ GiNZAを使用してテキストをベクトルに変換します。 """ doc: spacy.tokens.doc.Doc = nlp(text) # GiNZAのdoc.vectorはNumPy配列を返します return doc.vector # ========================================================================= # 3. Qdrantでの検索実行 # ========================================================================= def main(): print(f"\n========================================================") print(f"🔍 検索クエリ: {QUERY_TEXT}") print(f"========================================================") # クエリテキストをベクトルに変換 query_vector = get_vector_from_text(QUERY_TEXT) # Qdrantで検索を実行 hits = qdrant_client.search( collection_name=collection_name, query_vector=query_vector, # ベクトル化したクエリー query_filter=None, with_payload=True, # レスポンスにペイロードを含める limit=5 # 上位5件を取得 ) # 検索結果の表示 print("\n[検索結果 - 上位 5件]") if not hits: print("類似記事は見つかりませんでした。") return for i, hit in enumerate(hits): h: ScoredPoint = hit # ペイロードからタイトルと本文を取得 title = h.payload.get('title', 'N/A') body_snippet = h.payload.get('body', 'N/A')[:100] + '...' # 本文は先頭100文字を抜粋 print(f"--- 順位 {i+1} (スコア: {h.score:.4f}) ---") print(f"タイトル: {title}") print(f"本文抜粋: {body_snippet}") # スクリプトの実行 if __name__ == "__main__": main()