# 前準備
### 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()