2018年8月26日

ElasticsearchでWikipediaの全文検索にチャレンジ (1)

前置き


以下のブログで紹介されていたWikipediaの全文検索に興味を持ち、どのようにすればいいのか調査をした記録です。
http://www.mwsoft.jp/programming/munou/wikipedia_solr.html

以降の記事は、以下のWikipediaアーカイブからjawiki-latest-pages-articles.xml.bz2とjawiki-latest-stub-articles.xml.gzをダウンロードし、アーカイブファイルからデータを展開済みという前提で話を進めます。
https://dumps.wikimedia.org/jawiki/latest/

例によって例のごとく、以下のコードは基本的にPowershellコンソールに直接コピペして使う前提です。スクリプトファイルにする必要すらありません。らくちんですね。
なお、推定所要時間はファイルダウンロードに3時間、データ生成に2時間です。

初めに


2018年8月現在、Wikipedia全文を収めたXMLファイルは11GBを超えており、XMLパーサにそのまま読ませてアップロードしようとか正気の沙汰ではありません。
ファイルをシステムで処理可能なレベルまで分割して、順次処理するというのが現実的な話でしょう。
実際に、記事本文を除いた一覧「jawiki-latest-stub-articles.xml」については、ファイルサイズが1.2GBだったので読み込みを試してみたのですが、メモリーを使い果たし、且ついつまで処理が掛かるのかわからない状況であったため断念しました。

# 32GB程度の搭載メモリーでは太刀打ちできない
# $Inputfile = Join-Path $WorkDir "jawiki-latest-stub-articles.xml"
# [xml]$XmlData = Get-Content $InputFile -encoding utf8


ちなみに、環境はCore i7-7700K + 32GBメモリーです。

試行


まずは先頭1000行を読み込んでXMLの構造を確認してみます。
そうすると、<PAGE></PAGE>というタグで1つの記事を示していることがわかります。

$WorkDir = "C:\Wikipedia"
$Inputfile = Join-Path $WorkDir "jawiki-latest-stub-articles.xml" # まずは本文なしで構造確認
$TextData = Get-Content $InputFile -totalcount 1000 -encoding utf8
$TextData

では、ページ単位で取り出すことにしましょう。
5万行で記事単位でXML構造を取得できるか試してみます。

$WorkDir = "C:\Wikipedia"
$OutputDir = Join-Path $WorkDir "output"
If (-not (Test-Path $OutputDir)) { New-Item $OutputDir -ItemType Directory }

$Inputfile = Join-Path $WorkDir "jawiki-latest-stub-articles.xml" # まずは本文なしで構造確認

$PageCounter = 0 # 記事件数カウンター
$Cycle = 100 # 1ファイルに納める上限値
$Keyword = '</page>' # 記事の終了タグ
$FileBase = "File-PickupTest-"
$OutputFile = Join-Path $OutputDir $( $FileBase  + [string]$( $PageCounter / $Cycle ) + ".txt")
$OutputFile

Get-Date
Get-Content $InputFile -totalcount 50000 -encoding utf8 | ForEach { # 5万行で試行
 $_ | Out-File -FilePath $OutputFile -Encoding utf8 -Append
 if ( $_ -match $Keyword ) { # 特定件数に達したら出力ファイルを変更
  $PageCounter = $PageCounter + 1
  if ($PageCounter % $Cycle -eq 0) {
   $OutputFile = Join-Path $OutputDir $( $FileBase + [string]$( $PageCounter / $Cycle ) + ".txt")
   $OutputFile
  }
 }
}

Get-Date
Write-Output "記事件数:${PageCounter}"

今回のファイルでは2631件、3ファイルに出力されました。
5万行縛りなので、恐らく大きくぶれないでしょう。

本番開始


うまくいきそうなので、対象ファイルを本文つきXMLに切り替えて流すことにします。
多大な時間とI/Oが発生しますので、SSD構成+数時間放置推奨です。

$WorkDir = "C:\Wikipedia"
$OutputDir = Join-Path $WorkDir "output"
If (-not (Test-Path $OutputDir)) { New-Item $OutputDir -ItemType Directory }

$InputFile = Join-Path $WorkDir "jawiki-latest-pages-articles.xml"

$PageCounter = 0 # 記事件数カウンター
$Cycle = 1000 # 1ファイルに納める上限値
$Keyword = '</page>' # 記事の終了タグ
$FileBase = "File-"
$OutputFile = Join-Path $OutputDir $( $FileBase  + [string]$( $PageCounter / $Cycle ) + ".txt")
$TextData = New-Object System.Collections.ArrayList # 新規配列

Get-Date
Get-Content $InputFile -encoding utf8 | ForEach { 
 $TextData.Add($_) > $Null # 一旦配列に格納
 # $_ | Out-File -FilePath $OutputFile -Encoding utf8 -Append # 直接出力は時間が掛かるので方式変更
 if ( $_ -match $Keyword ) { # 特定件数に達したら出力ファイルを変更
  $PageCounter = $PageCounter + 1
  if ($PageCounter % $Cycle -eq 0) {
   $TextData -join "`n" | Out-File -FilePath $OutputFile -Encoding utf8
   $TextData = New-Object System.Collections.ArrayList # 新規配列
   $OutputFile = Join-Path $OutputDir $( $FileBase + [string]$( $PageCounter / $Cycle ) + ".txt")
   # 桁数を揃える場合 $( $PageCounter / $Cycle ).ToString("00000") に変更
  }
 }
}

$TextData -join "`n" | Out-File -FilePath $OutputFile -Encoding utf8

Get-Date
Write-Output "記事件数:${PageCounter}"

最初、1行ずつファイル書き出ししてたけど、とても時間が掛かったので、出力する記事1000件分を一旦配列に入れて、1000件分溜まったらまとめてファイル出力するようにしました。速度は圧倒的に後者のが早いので興味があるかたは比べてみてください。
ただ、メモリーにキャッシュする訳で、搭載メモリーが8GB以下の人は$Cycleを200とかにして利用率を確認してから実行いただくのがよいかと思います。
スペックの良いマシンで生成完了まで1時間というところでしょうか。
今回は、約50分で2300ファイル生成されました。記事件数は2,300,820件だそうです。

Elasticsearch導入とKuromojiプラグイン


長くなりましたので、Wikipediaのデータをうんぬんするのは次の記事で扱います。
その際にElasticsearchを利用するので、導入していない人は以下の記事を参考に導入してください。
REST API をPowershellで扱うための Elasticsearch のススメ (1)

ちなみに、この記事を書いてから2週間も経っていないのにバージョンが上がっていました。 そんな訳で自分は入れなおしました。運用してるソフトウェアじゃないし、最新で評価したほうがいいよね。
# URLを以下に変更。
$URL = "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.4.0.zip"
$URL = "https://artifacts.elastic.co/downloads/kibana/kibana-6.4.0-windows-x86_64.zip"

そして、日本語解析としてKuromojiを入れます。
Kuromojiが何ぞやと語れる程知識がないので、以下の記事を紹介をしておきますね。
https://qiita.com/yamamotoshu1127/items/ae081c8fa1c9b2804f83

端的に言えば日本語の文章を単語に分解して品詞を示してくれる機能です。精度としては、完璧ではないけど、実用的な範疇というところです。過信は禁物だけど自分でやらなくてもいいなんてとっても楽ちんです。

では張り切って導入しましょう。

# ■作業ディレクトリーへ移動
# WorkDirにElasticsearchが導入されている前提
$WorkDir = "C:\Elastic"
Set-Location $WorkDir

# ■plugin:kuromoji
# https://www.elastic.co/guide/en/elasticsearch/plugins/6.4/analysis-kuromoji.html#analysis-kuromoji
$URL = "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-kuromoji/analysis-kuromoji-6.4.0.zip"
$FileName = Split-Path $URL -Leaf
$FileFullPath = Join-Path $WorkDir $FileName
Invoke-WebRequest -Uri $URL -Outfile $FileFullPath
# ZIP展開は不要

$InstallPath = "file:///${FileFullPath}"
$ESbinDir = Join-Path $WorkDir "elasticsearch-6.4.0\bin"
Set-Location $ESbinDir

# プラグインリスト:事前確認
.\elasticsearch-plugin.bat list

.\elasticsearch-plugin.bat install $InstallPath

# プラグインリスト:事後確認
.\elasticsearch-plugin.bat list

install時にはZIPファイルを指定すればOKで、ZIPの展開は必要ありません。
事前にZIPを置いておけるのでオフライン環境でも安心ですね。

次回の記事に続きます。

0 件のコメント:

コメントを投稿

TIPS:VSCodeで日本語化がうまくいかないとき

前置き Visual Studio Codeで拡張機能「 Japanese Language Pack for Visual Studio Code 」を入れたら日本語になりますよね。 でも、「 Remote Development 」で色々な環境を日本語化してると、偶に...