ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

【言語処理100本ノック 2020】 3章をPythonで解いた

f:id:taxa_program:20200502163654p:plain

こんにちは。たかぱい(@takapy0210)です。

本エントリは言語処理100本ノック 2020の3章を解いてみたので、それの備忘です。

nlp100.github.io

例によってコードはGithubに置いてあります。

github.com

第3章: 正規表現

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.

  • 1行に1記事の情報がJSON形式で格納される
  • 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
  • ファイル全体はgzipで圧縮される

以下の処理を行うプログラムを作成せよ.

20. JSONデータの読み込み

"""
Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.
問題21-29では,ここで抽出した記事本文に対して実行せよ.
"""
import pandas as pd

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]
print(uk_wiki)

read_jsonで圧縮されているデータも読み込めます。便利ですね。

21. カテゴリ名を含む行を抽出

"""
記事中でカテゴリ名を宣言している行を抽出せよ.
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

pattern = re.compile(r'^(.*\[\[Category:.*\]\].*)$', re.MULTILINE)
ans = '\n'.join(pattern.findall(uk_wiki))
print(ans)

この辺りの記事を参考に正規表現を使って抽出しました。

22. カテゴリ名の抽出

"""
記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

pattern = re.compile(r'^.*\[\[Category:(.*?)(?:\|.*)?\]\].*$', re.MULTILINE)
ans = '\n'.join(pattern.findall(uk_wiki))
print(ans)

23. セクション構造

"""
記事中に含まれるセクション名とそのレベル(例えば”== セクション名 ==”なら1)を表示せよ.
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

pattern = re.compile(r'^(\={2,})\s*(.+?)\s*(\={2,}).*$', re.MULTILINE)
ans = '\n'.join(i[1] + ':' + str(len(i[0])-1) for i in pattern.findall(uk_wiki))
print(ans)

==も抽出対象とし、文字列の長さをセクション名と同時に表示させています。

24. ファイル参照の抽出

"""
記事から参照されているメディアファイルをすべて抜き出せ.
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

pattern = re.compile(r'\[\[ファイル:(.+?)\|')
ans = '\n'.join(pattern.findall(uk_wiki))
print(ans)

File は無さそうだったのでファイルだけにしています。

25. テンプレートの抽出

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

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

# 基礎情報テンプレートの抽出
pattern = re.compile(r'^\{\{基礎情報.*?$(.*?)^\}\}', re.MULTILINE + re.S)
base = pattern.findall(uk_wiki)

# 抽出結果からのフィールド名と値の抽出
pattern = re.compile(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))', re.MULTILINE + re.S)
ans = pattern.findall(base[0])

ans = dict(ans)
for k, v in ans.items():
    print(k + ':' + v)

はじめにbase変数に基礎情報テンプレートの値を設定し、その情報をさらに正規表現を用いてansに格納します。
後半の正規表現では、=の前後に空白があったり無かったりするので\s*(空白文字0文字以上)を指定しています。
ansにはtuple型で格納されるため、最後にdictへと変形して表示させています。

26. 強調マークアップの除去

"""
25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)
を除去してテキストに変換せよ(参考: マークアップ早見表).
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

# 基礎情報テンプレートの抽出
pattern = re.compile(r'^\{\{基礎情報.*?$(.*?)^\}\}', re.MULTILINE + re.S)
base = pattern.findall(uk_wiki)

# 抽出結果からのフィールド名と値の抽出
pattern = re.compile(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))', re.MULTILINE + re.S)
ans = pattern.findall(base[0])

# 強調マークアップの除去
pattern = re.compile(r'\'{2,5}', re.MULTILINE + re.S)
ans = {i[0]:pattern.sub('', i[1]) for i in ans}

for k, v in ans.items():
    print(k + ':' + v)

25のコードに強調マークアップの除去処理を追加

27. 内部リンクの除去

"""
26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,
テキストに変換せよ(参考: マークアップ早見表)
"""
import pandas as pd
import re

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

# 基礎情報テンプレートの抽出
pattern = re.compile(r'^\{\{基礎情報.*?$(.*?)^\}\}', re.MULTILINE + re.S)
base = pattern.findall(uk_wiki)

# 抽出結果からのフィールド名と値の抽出
pattern = re.compile(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))', re.MULTILINE + re.S)
ans = pattern.findall(base[0])

# 強調マークアップの除去
pattern = re.compile(r'\'{2,5}', re.MULTILINE + re.S)
ans = {i[0]:pattern.sub('', i[1]) for i in ans}

# 内部リンクの除去
pattern = re.compile(r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]', re.MULTILINE + re.S)
ans = {k:pattern.sub('', v) for k, v in ans.items()}

for k, v in ans.items():
    print(k + ':' + v)

26のコードに内部リンクの除去処理を追加

28. MediaWikiマークアップの除去

"""
27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.
"""
import pandas as pd
import re

def rm_markup(target):
    # 強調マークアップの除去
    pattern = re.compile(r'\'{2,5}', re.MULTILINE)
    target = pattern.sub('', target)

    # 内部リンクの除去
    pattern = re.compile(r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]', re.MULTILINE)
    target = pattern.sub('', target)

    # Template:Langの除去 {{lang|言語タグ|文字列}}
    pattern = re.compile(r'\{\{lang(?:[^|]*?\|)*?([^|]*?)\}\}', re.MULTILINE)
    target = pattern.sub('', target)

    # 外部リンクの除去 [http://xxxx]/[http://xxx xxx]
    pattern = re.compile(r'\[http:\/\/(?:[^\s]*?\s)?([^]]*?)\]', re.MULTILINE)
    target = pattern.sub('', target)

    # <br>、<ref>の除去
    pattern = re.compile(r'<\/?[br|ref][^>]*?>', re.MULTILINE)
    target = pattern.sub('', target)

    pattern = re.compile(r'({{Cite.*?}})$')
    target = pattern.sub('', target)

    # 改行コードの除去
    target = target.replace('\n', '')

    return target

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

# 基礎情報テンプレートの抽出
pattern = re.compile(r'^\{\{基礎情報.*?$(.*?)^\}\}', re.MULTILINE + re.S)
base = pattern.findall(uk_wiki)

# 抽出結果からのフィールド名と値の抽出
pattern = re.compile(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))', re.MULTILINE + re.S)
ans = pattern.findall(base[0])

ans = {i[0]:rm_markup(i[1]) for i in ans}

for k, v in ans.items():
    print(k + ':' + v)

今までの処理をrm_markup関数にまとめました。
頑張ればもう少し綺麗になると思いますが、今回はいったんこれで妥協しました汗

29. 国旗画像のURLを取得する

"""
テンプレートの内容を利用し,国旗画像のURLを取得せよ.
(ヒント: MediaWiki APIのimageinfoを呼び出して,ファイル参照をURLに変換すればよい)
"""
import pandas as pd
import re
import requests

def rm_markup(target):
    # 強調マークアップの除去
    pattern = re.compile(r'\'{2,5}', re.MULTILINE)
    target = pattern.sub('', target)

    # 内部リンクの除去
    pattern = re.compile(r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]', re.MULTILINE)
    target = pattern.sub('', target)

    # Template:Langの除去 {{lang|言語タグ|文字列}}
    pattern = re.compile(r'\{\{lang(?:[^|]*?\|)*?([^|]*?)\}\}', re.MULTILINE)
    target = pattern.sub('', target)

    # 外部リンクの除去 [http://xxxx]/[http://xxx xxx]
    pattern = re.compile(r'\[http:\/\/(?:[^\s]*?\s)?([^]]*?)\]', re.MULTILINE)
    target = pattern.sub('', target)

    # <br>、<ref>の除去
    pattern = re.compile(r'<\/?[br|ref][^>]*?>', re.MULTILINE)
    target = pattern.sub('', target)

    pattern = re.compile(r'({{Cite.*?}})$')
    target = pattern.sub('', target)

    # 改行コードの除去
    target = target.replace('\n', '')

    return target


def get_url(text):
    url_file = text['国旗画像'].replace(' ', '_')
    url = 'https://commons.wikimedia.org/w/api.php?action=query&titles=File:' + url_file + '&prop=imageinfo&iiprop=url&format=json'
    data = requests.get(url)
    return re.search(r'"url":"(.+?)"', data.text).group(1)

df = pd.read_json('jawiki-country.json.gz', lines=True)
uk_wiki = df.query('title == "イギリス"')['text'].values[0]

# 基礎情報テンプレートの抽出
pattern = re.compile(r'^\{\{基礎情報.*?$(.*?)^\}\}', re.MULTILINE + re.S)
base = pattern.findall(uk_wiki)

# 抽出結果からのフィールド名と値の抽出
pattern = re.compile(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)| (?=\n$))', re.MULTILINE + re.S)
ans = pattern.findall(base[0])

ans = {i[0]:rm_markup(i[1]) for i in ans}
print(get_url(ans))