Docker+ElasticsearchにWikipediaの情報をインデクシングする
はじめに
この記事は ZOZO #2 Advent Calendar 2022 1日目の記事になります。
Elasticsearchに大量のデータを投入して、様々な検索処理を実験したいと思いました。
このような場合、よくWikipedia
のデータが利用されますが、巷のWikipediaのデータをインデクシングする記事が古くて、最新の環境だと難航したので、今回まとめることにしました。
やること3行
- Dockerで環境構築
- Wikipediaのデータをダウンロード&整形
- 整形したデータをElasticsearchにインデクシング
環境
- M1 Mac
- Docker & Compose v2
- イメージ:Python 3.10
準備
Elasticsearchにインデクシングするためのデータを用意したり整形したりといった準備について以下では説明しています。
最終的なファイル構成
最終的なファイル構成は下記のようになっている。
$ tree .
.
|-- Dockerfile
|-- compose.yml
|-- jawiki-latest-pages-articles.xml.bz2
|-- mapping.json
|-- output
| `-- AA
| |-- wiki_00
| |-- wiki_00_new.ndjson
| |-- wiki_01
| |-- wiki_01_new.ndjson
| |-- ...
| |-- wiki_42_new.ndjson
| |-- wiki_43
| `-- wiki_43_new.ndjson
`-- reformat_to_ndjson.py
Wikipediaのデータを用意
下記サイトから、Wikipediaの本文データをダウンロードします。
今回はwget
コマンドでダウンロードします
$ wget https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2
環境構築
Dockerを使ってPython環境とWikipediaを整形するための環境を作ります。
なお今回は、Compose v2を使用しています。
FROM python:3.10
WORKDIR /app
RUN git clone --depth 1 https://github.com/zaemyung/wikiextractor.git
COPY jawiki-latest-pages-articles.xml.bz2 .
COPY reformat_to_ndjson.py .
Wikipediaのデータを整形するために、下記リポジトリをクローンしています。
このリポジトリは下記リポジトリ(本家)をフォークしたものになります。
本家を使わない理由としては、実行時エラーが大量発生するため、これらを解決したフォーク先のリポジトリを今回は使用しています。
services:
tool:
build: .
volumes:
- ./output:/app/output
コンテナを構築します。
$ docker compose build
以降は、構築したコンテナに入り作業を行うので下記コマンドを実行します。
# 構築したコンテナの中に入る
$ docker compose run --rm tool bash
データ整形
インデクシングできるようにダウンロードしたデータを整形していきます。
json形式に整形
Wikipediaの情報をjson形式に変換します。
$ python wikiextractor/WikiExtractor.py -o output -b 80M jawiki-latest-pages-articles.xml.bz2 --json
このあと、変換したデータを ElasticsearchのBulk API を使ってインデキシングしますが、このときBulk APIに渡せるデータ容量が最大で100MiBなので、小分けにする必要があります。
そのため、 -b
オプションで80MiBを指定しています。
また、-o
オプジョンは出力先フォルダを指定して、小分けにされたデータがoutput
に出力されるようにしています。
コマンド実行後、完了まで数分~10分ほど待ちます。
処理が終わると、output/AA
配下にwiki_xx
のようなファイルが複数出力されていると思います。
{"id": "3577", "url": "https://ja.wikipedia.org/wiki?curid=3577", "title": "新京成電鉄新京成線", "text": "新京成電鉄新..."}
{"id": "3588", "url": "https://ja.wikipedia.org/wiki?curid=3588", "title": "総武本線", "text": "総武本..."}
.
.
.
Bulk API用にデータを整形
次に、このようなjson形式のデータをBulk APIに渡せるように下記のような形式に整形します。
{"index":{}}
{"id": "3577", "url": "https://ja.wikipedia.org/wiki?curid=3577", "title": "新京成電鉄新京成線", "text": "新京成電鉄新..."}
{"index":{}}
{"id": "3588", "url": "https://ja.wikipedia.org/wiki?curid=3588", "title": "総武本線", "text": "総武本..."}
.
.
.
スクリプトファイルとしてbashから実行できるように実行権限を渡します。
$ chmod +x reformat_to_ndjson.py
並列で整形処理を実行します。
$ ls ./output/AA/* -d | xargs -L 1 -P 10 bash -c './reformat_to_ndjson.py $0'
reformat_to_ndjson.py
では、wiki_xx
を読み込んで各行毎に{"index":{}}
を追加する処理を書いています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import datetime
# 第一引数に処理するjsonファイルのパスを指定すること
filepath = sys.argv[1]
# ndjsonに変換したデータを入れる用の配列
ndjson = []
start_datetime = datetime.datetime.now()
print(f"{start_datetime.strftime('%Y/%m/%d %H:%M:%S')}: start converting {filepath}.")
# ファイルの各行を読み込んでBulk API用のactionを追加する
with open(filepath, encoding="utf-8") as f:
for line in f:
# 空行はスキップ
if(line == '\n'):
ndjson.append('\n')
continue
ndjson.append('{"index":{}}\n')
ndjson.append(line)
# 作成したndjsonを新規ファイルに書き込む
new_filepath = filepath + "_new.ndjson"
with open(new_filepath, mode='w', encoding="utf-8") as f:
f.writelines(ndjson)
finish_datetime = datetime.datetime.now()
processing_time = finish_datetime - start_datetime
print(f"{finish_datetime.strftime('%Y/%m/%d %H:%M:%S')}: {filepath} is converted to {new_filepath}({processing_time.seconds} sec).")
コマンド実行後、wiki_xx_new.ndjson
が./output/AA
配下に保存されているはずです。
インデクシング
Mappingの作成
indexのMappingを作成します
index名はwiki
としています。
$ curl -H 'Content-Type: application/json' -X PUT 'http://127.0.0.1:9200/wiki?pretty' -d @mapping.json
Mapping情報はmapping.json
に記載されたものを指定しています。
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"analysis": {
"analyzer": {
"ngram": {
"filter": [
"cjk_width",
"lowercase"
],
"char_filter": [
"html_strip"
],
"type": "custom",
"tokenizer": "ngram"
}
},
"tokenizer": {
"ngram": {
"token_chars": [
"letter",
"digit"
],
"min_gram": "1",
"type": "nGram",
"max_gram": "2"
}
}
}
},
"mappings": {
"properties": {
"text": {
"analyzer": "ngram",
"type": "text"
},
"url": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"title": {
"analyzer": "ngram",
"type": "text"
}
}
}
}
インデクシング
下記コマンドで作成したindexに対してインデクシングします。
$ ls ./output/AA/*_new.ndjson -d | xargs -L 1 -P 3 bash -c 'echo $0 ; cat $0 | curl -s -X POST -H '\''Content-Type: application/x-ndjson'\'' '\''http://127.0.0.1:9200/wiki/_bulk?pretty'\'' --data-binary @-;'
結果
下記コマンドを叩くと、登録されたドキュメント数とサイズが返ってきます。
$ curl -X GET 'http://127.0.0.1:9200/wiki/_stats?pretty' -s | jq '{ count: .indices.wiki.primaries.docs.count, size: .indices.wiki.primaries.store.size_in_bytes }'
# 結果
{
"count": 1352779,
"size": 7142651721
}
検索
実際に検索してみると結果が返って来ることが確認できました。
# 検索
$ curl -s -XGET 'http://127.0.0.1:9200/wiki/_search?pretty' -d '{"query":{"match":{"text": "hello world"}},"size":1}' -H 'Content-Type: application/json'
# 結果
{
"took" : 54,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10000,
"relation" : "gte"
},
"max_score" : 64.4458,
"hits" : [
{
"_index" : "wiki",
"_type" : "_doc",
"_id" : "5d1veoQBbbruqLk5VZIW",
"_score" : 64.4458,
"_source" : {
"id" : "1346091",
"url" : "https://ja.wikipedia.org/wiki?curid=1346091",
"title" : "CherryPy",
"text" : "CherryPy\n\nCherryPy は、...中略...Hello World は ...中略...\n\n\n"
}
}
]
}
}
おわりに
ElasticsearchにWikipediaのデータを投入する方法をまとめました。
参考になれば幸いです。
Discussion