wikipediaの本文記事をMySQLにいれる(2022.04)

# 記事の内容

wikipediaの本文記事をローカル環境にインストールします。
久しぶりにwikipediaのdumpを触っていたら、数年前とはちょっとずつやり方が変わってきていたので防備録的にメモします。

# 環境
- ubuntu 20.04
- mysql 8.0.28


# xmlファイルをSQLファイルにする
xml2sqlのインストール
```
$ cd ~/
$ git clone https://github.com/Tietew/mediawiki-xml2sql.git
$ cd mediawiki-xml2sql
$ ./configure
$ make
$ sudo make install
```

xmlデータのダウンロードとsql化
```
$ cd ~/
$ mkdir wikipedia
$ cd wikipedia
$ wget https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2
$ bzip2 -d jawiki-latest-pages-articles.xml.bz2
$ cat jawiki-latest-pages-articles.xml | sed -e 's/<dbname>.*<\/dbname>//' -e 's/<ns>.*<\/ns>//' -e 's/<parentid>.*<\/parentid>//' -e 's/<sha1>.*<\/sha1>//' -e 's/<model>.*<\/model>//' -e 's/<format>.*<\/format>//' -e 's/<redirect>.*<\/redirect>//' -e 's/<redirect.*\/>//' | xml2sql
```

# MySQLのインストール

```
$ sudo apt update
$ sudo apt install -y mysql-server
$ mysql --version
mysql  Ver 8.0.28-0ubuntu0.20.04.3 for Linux on x86_64 ((Ubuntu))
```
使い慣れた5.7がよかったのですが、デフォルトで8.0が入ることも多くなってきたので今後を見越してこっちを使います。

```
$ sudo mysql_secure_installation

// パスワード検証プラグインをいれるかどうか。wikipediaデータ以外のデータはMySQLにいれないため、切っておく（入れても当然OK）
VALIDATE PASSWORD COMPONENT~~ : n

// パスワードは今回は「password」とします。他のパスにする場合は、パスワードに該当する部分は各自読み替えて下さい。
New password: password

// パスワード再入力
Re-enter new password: password

// テストユーザを削除します
Remove anonymous users?: y

// リモートログインを禁止します
Disallow root login remotely?: y

// テストデータベースを削除します
Remove test database and access to it?：y

// 上記のルールを即時反映します
Reload privilege tables now? ：y

All done! 
```
ユーザの作成
```
// 最初はrootでしか入れないので、sudoをつけます
$ sudo mysql

// wiki-userのパスワードも「password」にしました。ご自由に設定して下さい。
> CREATE USER 'wiki-user'@'localhost' IDENTIFIED BY 'password';

// 全権限を付与します。（ちゃんとした運用するなら非推奨です）
> GRANT ALL PRIVILEGES ON *.* TO 'wiki-user'@'localhost' WITH GRANT OPTION;
```

ログインできるかどうか確認
```
> exit;
$ mysql -u wiki-user -p
> exit;
```

読み込みを早くするために、MySQLの設定ファイルを変更します。

```
$ sudo cat /etc/mysql/my.cnf

（前略）
!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/
```
上記のファイルを直接変更してもいいのですが、/etc/mysql/conf.d/ の中にあるファイルを読み込んでくれるそうですのでdump import用の設定ファイルをこちらに作りましょう。
```/etc/mysql/conf.d/wiki.cnf
[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
character-set-server=utf8
innodb_flush_log_at_trx_commit=0
skip_innodb_doublewrite
expire_logs_days=1

[mysqldump]
default-character-set=utf8
```
書いてあるのは、文字コードをutf8に統一するものと、insert高速化のためのおまじないです。
secure-file-priv=""
は今回は使わない方針で（mysqlがファイルを入出力する機能に関する設定）

設定を反映します。
```
$ sudo systemctl restart mysql
```

反映されているかどうか確認したいので、ログインして文字コードをみてみます

```
$ mysql -u wiki-user -p
> show variables like '%char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb3                    |
| character_set_connection | utf8mb3                    |
| character_set_database   | utf8mb3                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb3                    |
| character_set_server     | utf8mb3                    |
| character_set_system     | utf8mb3                    |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)
```

utf8mb4に全修正したい気持ちもありましたが、とりあえずこちらで良いでしょう。

# wikipediaデータの加工

以前なら[こちら](https://phabricator.wikimedia.org/source/mediawiki/browse/master/maintenance/tables.sql)から「tables.sql」をダウンロードしてきてテーブルを作成していたのですが、中身が[こちら](https://phabricator.wikimedia.org/source/mediawiki/browse/master/maintenance/tables-generated.sql)に移動したようです。また、新しい方のデータ構造にxml2sqlの出力が対応していないようで、そのままインポートしようとしてもうまく行きません。
今後も変更があるかもしれない......と考えた末、面倒くさくなってきたので自前でテーブル定義を作ることにしました。
```
$ cd ~/wikipedia
$ vi short-tables.sql
```
```short-tables.sql
CREATE TABLE /*_*/page (
  page_id INT UNSIGNED NOT NULL,
  page_title VARBINARY(255) NOT NULL,
  text_id INT UNSIGNED NOT NULL,
  PRIMARY KEY(page_id)
) /*$wgDBTableOptions*/;

CREATE TABLE /*_*/text (
  text_id INT UNSIGNED NOT NULL,
  text_body MEDIUMBLOB NOT NULL,
  PRIMARY KEY(text_id)
) /*$wgDBTableOptions*/;
```

本文タイトル（page）から、本文（text）を検索できたらいいので、必要最小限のデータだけを残して後は消します。
またこの形に合わせてxml2sqlで生成したwikiデータの方も形を変える必要があります。
```
$ cd ~/wikipedia
$ vi create_short_sql.py
```
```create_short_sql.py
// タブ区切りファイルから、必要な列だけを残したファイルを作成します。
def drop_columns_from_csv(from_file, to_file, remain_columns):
    with open(from_file) as input_f:
        with open(to_file, mode='w') as output_f:
            for line in input_f:

                lst = line.strip().split('\t')
                new_lst = []

                try:
                    new_lst = [lst[remain_columns[c_name]] for c_name in remain_columns]
                except IndexError:
                    continue

                new_line = '\t'.join(new_lst) + '\n'
                output_f.write(new_line)

// page_s.txt の生成
remain_columns = { 'page_id':0, 'page_title':2 , 'text_id':9 }
drop_columns_from_csv('page.txt', 'page_s.txt', remain_columns)

// text_s.txt の生成
remain_columns = { 'text_id':0, 'text_body':1 }
drop_columns_from_csv('text.txt', 'text_s.txt', remain_columns)
```
```
$ python create_short_sql.py
```
jawiki-latest-pages-articles.xml の定義が変わらない限りは使えるかと思います。
後、今回 revision は使っていませんが、必要な人は上と同様に作って下さい。

# wikipediaデータのインポート
データベースとテーブルを作成します。
```
$ mysql -u wiki-user -p -e 'CREATE DATABASE wikipedia;'
$ mysql -u wiki-user -p wikipedia < short-tables.sql
```
念の為、データベースとテーブル固有の文字コードを確認します。
```
$ mysql -u wiki-user -p
> SHOW CREATE DATABASE wikipedia;
(前略)
DEFAULT CHARACTER SET utf8
(後略)

> USE wikipedia;
> SHOW CREATE TABLE page;
(前略)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
(後略)

> SHOW CREATE TABLE text;
(前略)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
(後略)

> exit;
```

次に、mysqlが外部ファイルを読み込むことの出来るフォルダに、データをコピーしてインポートします。
コピー後はファイル名がそれぞれテーブル名と一致するようにしてください。
```
$ sudo cp text_s.txt /var/lib/mysql-files/text.txt
$ sudo cp page_s.txt /var/lib/mysql-files/page.txt

$ mysqlimport -u wiki-user wikipedia -p --default-character-set=utf8 /var/lib/mysql-files/text.txt
wikipedia.text: Records: 2717719  Deleted: 0  Skipped: 0  Warnings: 0

$ mysqlimport -u wiki-user wikipedia -p --default-character-set=utf8 /var/lib/mysql-files/page.txt
wikipedia.page: Records: 2717820  Deleted: 0  Skipped: 0  Warnings: 0
```
確認してみます。
```
$ mysql -u wiki-user -p
> USE wikipedia;
> SELECT * FROM text WHERE text_id = 88178744;
+----------+-----------
| text_id  | text_body 
+----------+-----------
| 88178744 | 0x7B7B4F74686572757365736C6973747C5B5BE6B48BE88F93...
+----------+-----------
```
本文がバイナリコードで格納されています。これはtext_bodyがBLOB形式で定義されているからです。TEXT形式にするとこの時点で日本語で確認することが出来ます。
このままだとよくわからないので、デコードした結果を確認しましょう。

```test.py
import sys
import pymysql.cursors
import mysql.connector

keyword = sys.argv[1]

DB_HOST = "localhost"
DB_USER = "wiki-user"
DB_PASS = "password"
DB_NAME = "wikipedia"

db = mysql.connector.connect(
    host=DB_HOST, 
    user=DB_USER, 
    password=DB_PASS
) 
cursor=db.cursor(dictionary=True, buffered=True)


cursor.execute("USE %s" % DB_NAME)
db.commit()
sql = ( "SELECT * FROM text INNER JOIN page ON page.text_id = text.text_id WHERE page.page_title LIKE \"%%%s%%\";" % keyword )
cursor.execute(sql)
db.close()
if cursor != None:
    for row in cursor:
        print('text_id: %s' % row['text_id'])
        print('text_body: \n%s' % row['text_body'].decode('utf-8'))
cursor.close()
```
```
$ pip install pymysql mysql-connector-python
$ python test.py シュークリーム
text_id: 88178744
text_body: 
{{Otheruseslist|[[洋菓子]]のシュークリーム|[[1970年代]]初期に活動していた[[女性アイドルグループ]]|ザ・シュークリーム|[[靴]]磨き用のクリーム|靴墨}}
{{Otheruses2||よしもとクリエイティブ・エージェンシー大阪所属のタレントコンビ「シュークリーム」のメンバー|しより|吉岡久美子}}
[[File:shu-cream.jpg|thumb|300px|シュークリーム]]
'''シュークリーム'''は、中が空洞になるように焼いた[[シュー生地|生地]]に[[カスタードクリーム]]などを詰めた[[洋菓子]]の一種。[[フランス語]]で「[[:fr:Chou à la crème|chou à la crème]]」（'''シュー・ア・ラ・クレーム'''）と呼ばれる菓子である<ref name="yamamoto2019">{{Cite book |和書 |author=山本ゆりこ |year=2019 |title=フランス伝統菓子図鑑 お菓子の由来と作り方 |page=15|publisher=誠文堂新光社}}</ref>。詰める[[クリーム]]については、洋菓子店・各[[製造業|メーカー]]などによって、[[チョコスプレッド|チョコレートクリーム]]や[[果汁]]（[[イチゴ]]、[[メロン]]、[[マンゴー]]等）を使ったクリームなど、様々なバリエーションが存在する。

== 名称 ==
[[File:Profiterole choc.jpg|thumb|300px|プロフィトロール（プロフィトロール・オ・ショコラ）]]
フランス語のシュ（{{lang|fr|chou}} {{IPA|ʃu}}、複数形は{{lang|fr|choux}}で発音は同じ）と、英語の[[クリーム (食品)|クリーム]]（{{lang|en|cream}}）からなる、[[和製外来語]]。フランス語では「[[:fr:Chou à la crème|chou à la crème]]」（シュー・ア・ラ・クレーム）という<ref name="yamamoto2019" />。「シュー」とはフランス語で[[キャベツ]]、[[ハクサイ]]等の[[葉菜類|葉野菜]]や、[[ハボタン]]等の植物などの総称だが、ここではキャベツを意味し、丸く絞り出して焼いた生地を結球したキャベツに見立てて「シュー」と呼ぶ。生の生地は「[[:fr:pâte à choux|{{lang|fr|pâte à choux}}]]」（パート・ア・シュー、[[シュー生地]]）と呼ばれる。

誤解例が未だに散見するが、英語の[[靴]]（shoe）とは関係はなく、仮に英語圏で「シュークリーム」と言った場合には、当該洋菓子のことを意味しない。
(後略)
```

大丈夫そうですね。
本文以外はSQLファイルが直接公式から配布されているので、難なく出来るかと思います。
https://dumps.wikimedia.org/jawiki/latest/
データの各詳細は以下の記事が参考になります。
http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html#pages-articles.xml


# 参考文献
https://qiita.com/grachro/items/11e59d16a929d27e274e

https://qiita.com/Yoshiki_Ohira/items/36b3602e81fb35dff422

https://qiita.com/yukiyoshimura/items/d44a98021608c8f8a52a

https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-20-04-ja

https://qiita.com/is0me/items/12629e3602ebb27c26a4