言語処理100本ノック 2020をやった 第3章

# はじめに
満を持して[言語処理100本ノック 2020](https://nlp100.github.io/ja/)が公開されたので早速やってみます。

第３章ではWikipediaの記事から正規表現で必要な情報を抽出・整形します。

Wikipediaのマークアップ情報は[Help:早見表 - Wikipedia](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)、API情報は[API:画像の情報 - MediaWiki](https://www.mediawiki.org/wiki/API:Imageinfo/ja)に書いてあります。が、マークアップ情報は情報が不完全なのでデータを見たり、[Wikipediaのページ](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%82%AE%E3%83%AA%E3%82%B9)をみてパターンを洗い出す必要があります。

# 第3章: 正規表現
Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある．

* 1行に1記事の情報がJSON形式で格納される
* 各行には記事名が”title”キーに，記事本文が”text”キーの辞書オブジェクトに格納され，そのオブジェクトがJSON形式で書き出される
* ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ．

## 20. JSONデータの読み込み
Wikipedia記事のJSONファイルを読み込み，「イギリス」に関する記事本文を表示せよ．問題21-29では，ここで抽出した記事本文に対して実行せよ．

```python:コード
import gzip
import json

with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]
    print(eng[0]['text'])
```

```:出力結果（一部）
{{redirect|UK}}
{{redirect|英国|春秋時代の諸侯国|英 (春秋)}}
{{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}
{{基礎情報 国
|略名  =イギリス
︙
```
`jawiki-country.json`がJSON Linesであることに注意する。


## 21. カテゴリ名を含む行を抽出
記事中でカテゴリ名を宣言している行を抽出せよ．

```python:コード
import gzip
import json
import regex as re

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
texts = eng_data['text'].split('\n')

# カテゴリを含む行を抽出
cat_rows = list(filter(lambda e: re.search('\[\[カテゴリ:|\[\[Category:', e), texts))
print('\n'.join(cat_rows))
```

```:出力結果
[[Category:イギリス|*]]
[[Category:イギリス連邦加盟国]]
[[Category:英連邦王国|*]]
[[Category:G8加盟国]]
[[Category:欧州連合加盟国|元]]
[[Category:海洋国家]]
[[Category:現存する君主国]]
[[Category:島国]]
[[Category:1801年に成立した国家・領域]]
```

[Help:早見表 - Wikipedia](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)では「[[Category:ヘルプ|はやみひよう]]」となっているが実際には「[[カテゴリ:ヘルプ|はやみひよう]]」のパターンもある（イギリスの記事にはたまたまないが）。

## 22. カテゴリ名の抽出
記事のカテゴリ名を（行単位ではなく名前で）抽出せよ．

```python:コード
import gzip
import json
import regex as re

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
texts = eng_data['text'].split('\n')

# カテゴリを含む行を抽出
cat_rows = list(filter(lambda e: re.search('\[\[カテゴリ:|\[\[Category:', e), texts))

# カテゴリを含む行からカテゴリ名のみを抽出
cat_rows = list(map(lambda e: re.search('(?<=(\[\[カテゴリ:|\[\[Category:)).+?(?=(\||\]))', e).group(), cat_rows))
print('\n'.join(cat_rows))
```

```:出力結果
イギリス
イギリス連邦加盟国
英連邦王国
G8加盟国
欧州連合加盟国
海洋国家
現存する君主国
島国
1801年に成立した国家・領域
```

正規表現ライブラリとして標準の[re](https://docs.python.org/ja/3/library/re.html)があるが、後読みで`look-behind requires fixed-width pattern`エラーが出るため[regex](https://pypi.org/project/regex/)を使用。

## 23. セクション構造
記事中に含まれるセクション名とそのレベル（例えば”== セクション名 ==”なら1）を表示せよ．

```python:コード
import json
import re

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
texts = eng_data['text'].split('\n')

# セクションを含む行を抽出
sec_rows = list(filter(lambda e: re.search('==.+==', e), texts))

# =の数からレベルを算出
sec_rows_num = list(map(lambda e: e + ':' + str(int(e.count('=') / 2 - 1)), sec_rows))

# =と空白を削除
sections = list(map(lambda e: e.replace('=', '').replace(' ', ''), sec_rows_num))
print('\n'.join(sections))
```

```:出力結果（一部）
国名:1
歴史:1
地理:1
主要都市:2
気候:2
︙
```

## 24. ファイル参照の抽出
記事から参照されているメディアファイルをすべて抜き出せ．

```python:コード
import json
import regex as re

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
texts = eng_data['text'].split('\n')

# ファイルを含む行を抽出
file_rows = list(filter(lambda e: re.search('\[\[ファイル:|\[\[File:|\[\[file:', e), texts))

# ファイルを含む行からファイル名のみを抽出
file_rows = list(map(lambda e: re.search('(?<=(\[\[ファイル:|\[\[File:|\[\[file:)).+?(?=(\||\]))', e).group(), file_rows))
print('\n'.join(file_rows))
```

```:出力結果
Royal Coat of Arms of the United Kingdom.svg
United States Navy Band - God Save the Queen.ogg
Descriptio Prime Tabulae Europae.jpg
Lenepveu, Jeanne d'Arc au siège d'Orléans.jpg
London.bankofengland.arp.jpg
Battle of Waterloo 1815.PNG
Uk topo en.jpg
BenNevis2005.jpg
Population density UK 2011 census.png
2019 Greenwich Peninsula & Canary Wharf.jpg
Leeds CBD at night.jpg
Palace of Westminster, London - Feb 2007.jpg
Scotland Parliament Holyrood.jpg
Donald Trump and Theresa May (33998675310) (cropped).jpg
Soldiers Trooping the Colour, 16th June 2007.jpg
City of London skyline from London City Hall - Oct 2008.jpg
Oil platform in the North SeaPros.jpg
Eurostar at St Pancras Jan 2008.jpg
Heathrow Terminal 5C Iwelumo-1.jpg
UKpop.svg
Anglospeak.svg
Royal Aberdeen Children's Hospital.jpg
CHANDOS3.jpg
The Fabs.JPG
Wembley Stadium, illuminated.jpg
```

[Help:早見表 - Wikipedia](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)では「[[ファイル:Wikipedia-logo-v2-ja.png|thumb|説明文]]」となっているが実際には「[[File:Wikipedia-logo-v2-ja.png|thumb|説明文]]」「[[file:Wikipedia-logo-v2-ja.png|thumb|説明文]]」のパターンもある。

## 25. テンプレートの抽出
記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し，辞書オブジェクトとして格納せよ．

```python:コード
import json
import regex as re
import pprint

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
text = eng_data['text']

# 基礎情報を抽出
basic_text = re.search('{{基礎情報[\s\S]+?}}\n\n', text).group().replace('\n*', '*')

# 改行区切りでリスト化し、不要部分を削除
basic_ary = basic_text.split('\n')
del basic_ary[0]
del basic_ary[-3:]

# 字書型に変更
basic_dict = {}
for basic in basic_ary:
    key, *values = basic.split('=')
    key = key.replace(' ', '').replace('|', '')
    basic_dict[key] = ''.join(values).strip()
pprint.pprint(basic_dict)
```

```:出力結果（一部）
{'GDP/人': '36,727<ref name"imf-statistics-gdp" />',
 'GDP値': '2兆3162億<ref name"imf-statistics-gdp" />',
 'GDP値MER': '2兆4337億<ref name"imf-statistics-gdp" />',
︙
 '首相等氏名': '[[ボリス・ジョンソン]]',
 '首相等肩書': '[[イギリスの首相|首相]]',
 '首都': '[[ロンドン]]（事実上）'}
```

## 26. 強調マークアップの除去
25の処理時に，テンプレートの値からMediaWikiの強調マークアップ（弱い強調，強調，強い強調のすべて）を除去してテキストに変換せよ（参考: マークアップ早見表）．

```python:コード
import json
import regex as re
import pprint

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
text = eng_data['text']

# 基礎情報を抽出
basic_text = re.search('{{基礎情報[\s\S]+?}}\n\n', text).group().replace('\n*', '*')

# 改行区切りでリスト化し、不要部分を削除
basic_ary = basic_text.split('\n')
del basic_ary[0]
del basic_ary[-3:]

# 辞書型に変更
basic_dict = {}
for basic in basic_ary:
    # キーと値に分割
    key, *values = basic.split('=')
    # キーを整形
    key = key.replace(' ', '').replace('|', '')
    # 値がリストになっているため結合
    value = ''.join(values).strip()
    # 強調マークアップの除去
    value = value.replace("'''''", '').replace("'''", '').replace("''", '')
    basic_dict[key] = value
pprint.pprint(basic_dict)
```

```:出力結果（一部）
{'GDP/人': '36,727<ref name"imf-statistics-gdp" />',
 'GDP値': '2兆3162億<ref name"imf-statistics-gdp" />',
 'GDP値MER': '2兆4337億<ref name"imf-statistics-gdp" />',
︙
'確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更',
︙
'首相等氏名': '[[ボリス・ジョンソン]]',
 '首相等肩書': '[[イギリスの首相|首相]]',
 '首都': '[[ロンドン]]（事実上）'}
```
[Help:早見表 - Wikipedia](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)における「他との区別（斜体）」「強調（太字）」「斜体と強調」をここでいう「強調マークアップ」と理解した。

## 27. 内部リンクの除去
26の処理に加えて，テンプレートの値からMediaWikiの内部リンクマークアップを除去し，テキストに変換せよ（参考: マークアップ早見表）．

```python:コード
import json
import regex as re
import pprint

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
text = eng_data['text']

# 基礎情報を抽出
basic_text = re.search('{{基礎情報[\s\S]+?}}\n\n', text).group().replace('\n*', '*')

# 改行区切りでリスト化し、不要部分を削除
basic_ary = basic_text.split('\n')
del basic_ary[0]
del basic_ary[-3:]

# 字書型に変更
basic_dict = {}
for basic in basic_ary:
    # キーと値に分割
    key, *values = basic.split('=')
    # キーを整形
    key = key.replace(' ', '').replace('|', '')
    # 値がリストになっているため結合
    value = ''.join(values).strip()
    # 強調マークアップの除去
    value = value.replace("'''''", '').replace("'''", '').replace("'", '')
    # 内部リンク文字列を取得
    targets = re.findall('((?<=({{)).+?(?=(}})))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('{{.+?}}', target[0].split('|')[-1], value, count=1)
    # 内部リンク文字列を取得
    targets = re.findall('((?<=(\[\[)).+?(?=(\]\])))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('\[\[.+?\]\]', target[0].split('|')[-1], value, count=1)
    basic_dict[key] = value
pprint.pprint(basic_dict)
```

```:出力結果（一部）
{'GDP/人': '36,727<ref name"imf-statistics-gdp" />',
 'GDP値': '2兆3162億<ref name"imf-statistics-gdp" />',
 'GDP値MER': '2兆4337億<ref name"imf-statistics-gdp" />',
︙
'確立形態3': 'グレートブリテン及びアイルランド連合王国成立<br />（1800年合同法）',
︙
 '首相等氏名': 'ボリス・ジョンソン',
 '首相等肩書': '首相',
 '首都': 'ロンドン（事実上）'}
```
[Help:早見表 - Wikipedia](https://ja.wikipedia.org/wiki/Help:%E6%97%A9%E8%A6%8B%E8%A1%A8)では「[[記事名|表示文字]]」などのパターンがあるが、実際には「{{記事名|表示文字}}」のパターンもある模様。

## 28. MediaWikiマークアップの除去
27の処理に加えて，テンプレートの値からMediaWikiマークアップを可能な限り除去し，国の基本情報を整形せよ．

```python:コード
import json
import regex as re
import pprint

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
text = eng_data['text']

# 基礎情報を抽出
basic_text = re.search('{{基礎情報[\s\S]+?}}\n\n', text).group().replace('\n*', '*')

# 改行区切りでリスト化し、不要部分を削除
basic_ary = basic_text.split('\n')
del basic_ary[0]
del basic_ary[-3:]

# 字書型に変更
basic_dict = {}
for basic in basic_ary:
    # キーと値に分割
    key, *values = basic.split('=')
    # キーを整形
    key = key.replace(' ', '').replace('|', '')
    # 値がリストになっているため結合
    value = ''.join(values).strip()
    # 強調マークアップの除去
    value = value.replace("'''''", '').replace("'''", '').replace("'", '')
    # 内部リンク文字列を取得
    targets = re.findall('((?<=({{)).+?(?=(}})))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('{{.+?}}', target[0].split('|')[-1], value, count=1)
    # 内部リンク文字列を取得
    targets = re.findall('((?<=(\[\[)).+?(?=(\]\])))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('\[\[.+?\]\]', target[0].split('|')[-1], value, count=1)
    # タグ除去
    value = value.replace('<br />', '')
    value = re.sub('<ref.+?</ref>', '', value)
    value = re.sub('<ref.+?/>', '', value)
    basic_dict[key] = value
pprint.pprint(basic_dict)
```

```:出力結果（一部）
{'GDP/人': '36,727',
 'GDP値': '2兆3162億',
 'GDP値MER': '2兆4337億',
︙
 '首相等氏名': 'ボリス・ジョンソン',
 '首相等肩書': '首相',
 '首都': 'ロンドン（事実上）'}
```

## 29. 国旗画像のURLを取得する
テンプレートの内容を利用し，国旗画像のURLを取得せよ．（ヒント: MediaWiki APIのimageinfoを呼び出して，ファイル参照をURLに変換すればよい）

```python:コード
import json
import regex as re
import requests

eng_data = {}
with gzip.open('jawiki-country.json.gz', mode='rt') as f:
    jsons = []
    # 元データがJSON Lines≠JSONのため、１行ずつ読み込み
    lines = f.readlines() 
    for line in lines:
        jsons.append(json.loads(line))
    # イギリスを抽出
    eng = list(filter(lambda e: e['title'] == 'イギリス', jsons))
    eng_data = eng[0]

# 本文を抽出
text = eng_data['text']

# 基礎情報を抽出
basic_text = re.search('{{基礎情報[\s\S]+?}}\n\n', text).group().replace('\n*', '*')

# 改行区切りでリスト化し、不要部分を削除
basic_ary = basic_text.split('\n')
del basic_ary[0]
del basic_ary[-3:]

# 字書型に変更
basic_dict = {}
for basic in basic_ary:
    # キーと値に分割
    key, *values = basic.split('=')
    # キーを整形
    key = key.replace(' ', '').replace('|', '')
    # 値がリストになっているため結合
    value = ''.join(values).strip()
    # 強調マークアップの除去
    value = value.replace("'''''", '').replace("'''", '').replace("'", '')
    # 内部リンク文字列を取得
    targets = re.findall('((?<=({{)).+?(?=(}})))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('{{.+?}}', target[0].split('|')[-1], value, count=1)
    # 内部リンク文字列を取得
    targets = re.findall('((?<=(\[\[)).+?(?=(\]\])))', value)
    # 内部リンク文字列の整形
    if targets:
        for target in targets:
            value = re.sub('\[\[.+?\]\]', target[0].split('|')[-1], value, count=1)
    # タグ除去
    value = value.replace('<br />', '')
    value = re.sub('<ref.+?</ref>', '', value)
    value = re.sub('<ref.+?/>', '', value)
    basic_dict[key] = value

# API呼び出し
session = requests.Session()
params = {
    'action': 'query',
    'format': 'json',
    'prop': 'imageinfo',
    'titles': 'File:' + basic_dict['国旗画像'],
    'iiprop': 'url'
}

result = session.get('https://ja.wikipedia.org/w/api.php', params=params)
res_json = result.json()
print(res_json['query']['pages']['-1']['imageinfo'][0]['url'])
```

```:出力結果
https://upload.wikimedia.org/wikipedia/commons/a/ae/Flag_of_the_United_Kingdom.svg
```

# おわりに
3章で学べること

* 正規表現の書き方
* Web APIの呼び方