Python
正規表現
自然言語処理
python3
言語処理100本ノック

第3章: 正規表現

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

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

先に反省点

  • python3の引数、戻り値の型指定とかがおざなりになってるのが悔しい。
  • 一部の処理はもうちょっとwikiの仕様を把握して実装したほうがよかったかも

20. JSONデータの読み込み

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

import json
import gzip

with gzip.open("jawiki-country.json.gz", "r") as fi:
    for line in fi:
        article = json.loads(line)
        if article["title"] == "イギリス":
            print(article["text"])

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

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

import json
import gzip
import re

def search_country(country_name):
    with gzip.open("jawiki-country.json.gz", "r") as fi:
        for line in fi:
            article = json.loads(line)
            if article["title"] == country_name:
                return article
    return {}

def search_category(text):
    categories = []
    for line in text.split("\n"):
        if re.match("\[\[Category:(.+)?\]\]", line):
            categories.append(line)
    return categories

if __name__ == '__main__':
    article = search_country("イギリス")
    for category_line in search_category(article["text"]):
        print(category_line)

22. カテゴリ名の抽出

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

import json
import gzip
import re

def search_country(country_name):
    with gzip.open("jawiki-country.json.gz", "r") as fi:
        for line in fi:
            article = json.loads(line)
            if article["title"] == country_name:
                return article
    return {}

def search_category_names(text):
    categories = []
    for line in text.split("\n"):
        result = re.match("\[\[Category:([^|]+)(|.*)?\]\]", line)
        if result:
            categories.append(result.group(1))
    return categories

if __name__ == '__main__':
    article = search_country("イギリス")
    for category_name in search_category_names(article["text"]):
        print(category_name)

23. セクション構造

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

import json
import gzip
import re

def search_country(country_name):
    with gzip.open("jawiki-country.json.gz", "r") as fi:
        for line in fi:
            article = json.loads(line)
            if article["title"] == country_name:
                return article
    return {}

def search_sections(text):
    sections = []
    for line in text.split("\n"):
        if re.match("^[=]{2,}([^=]+)[=]{2,}$", line):
            sections.append(line)
    return sections

def get_section_level(section_line):
    if re.match("^==[^=]", section_line):
        return 1
    if re.match("^===[^=]", section_line):
        return 2
    if re.match("^====[^=]", section_line):
        return 3
    if re.match("^=====[^=]", section_line):
        return 4

def get_section_name(section_line):
    result = re.match("^[=]{2,}([^=]+)[=]{2,}$", section_line)
    return result.group(1)


if __name__ == '__main__':
    article = search_country("イギリス")
    for section_line in search_sections(article["text"]):
        print(get_section_name(section_line) + ":" + str(get_section_level(section_line)))

24. ファイル参照の抽出

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

import json
import gzip
import re

def search_country(country_name):
    with gzip.open("jawiki-country.json.gz", "r") as fi:
        for line in fi:
            article = json.loads(line)
            if article["title"] == country_name:
                return article
    return {}

def search_medias(text):
    medias = []
    for line in text.split("\n"):
        result = re.match(".*(\[\[)?ファイル:([^|]+)", line)
        if result:
            medias.append(result.group(2))
        else:
            result = re.match(".* = ([^.]+\.svg)", line)
            if result:
                medias.append(result.group(1))
    return medias

if __name__ == '__main__':
    article = search_country("イギリス")
    for media in search_medias(article["text"]):
        print(media)

25. テンプレートの抽出

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

import json
import gzip
from pprint import pprint
import re

def search_country(country_name):
    with gzip.open("jawiki-country.json.gz", "r") as fi:
        for line in fi:
            article = json.loads(line)
            if article["title"] == country_name:
                return article
    return {}

def get_basic_info(text):
    basic_info = {}

    basic_info_start = False
    last_index = None
    for line in text.split("\n"):
        if basic_info_start:
            # 基礎情報の抽出完了
            if re.match("^}}$", line):
                break

            # 前の行の続きの場合(ここはちょっと雑い...今回のドキュメントが渡されると予めわかっていれば通用するが、汎用的にするならタグの開き閉じ判定で見るべきか?)
            if re.match("^[\*]{1,2}{{", line):
                basic_info[last_index] += "\n" + line
                continue

            # 基礎情報を辞書に詰める
            result = re.match("^\|([^=]+) = (.+)", line)
            if result:
                basic_info[result.group(1)] = result.group(2)
                last_index = result.group(1)
        else:
            # 基礎情報の抽出開始
            if re.match("^{{基礎情報 ", line):
                basic_info_start = True

    return basic_info

if __name__ == '__main__':
    article = search_country("イギリス")
    pprint(get_basic_info(article["text"]))

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

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

# 25.pyのdef get_basic_infoの定義が終わった後の記述を下記に変更

def remove_accent(basic_info):
    copy = {}
    for key, value in basic_info.items():
        value = re.sub("[']{2,4}", "", value)
        copy[key] = value
    return copy

if __name__ == '__main__':
    article = search_country("イギリス")
    basic_info = get_basic_info(article["text"])
    pprint(remove_accent(basic_info))

27. 内部リンクの除去

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

# 26.pyのdef remove_accentの定義が終わった後の記述を下記に変更

def remove_inner_link(basic_info):
    copy = {}
    pattern1 = "\[\[([^:\]|]+)\|([^\]]+)\]\]"
    pattern2 = "\[\[([^:\]|]+)\]\]"

    for key, value in basic_info.items():
        value = re.sub(pattern1, "\g<2>", value)
        value = re.sub(pattern2, "\g<1>", value)
        copy[key] = value
    return copy

if __name__ == '__main__':
    article = search_country("イギリス")
    basic_info = get_basic_info(article["text"])
    basic_info = remove_accent(basic_info)
    basic_info = remove_inner_link(basic_info)
    pprint(basic_info)

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

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

# 27.pyのdef remove_inner_linkの定義が終わった後の記述を下記に変更

def remove_markup(basic_info):
    copy = {}
    pattern1 = "\[\[ファイル:([^|]+)\|([^|\]]+)\|([^\]]+)\]\]"
    pattern2 = "\{\{lang\|[a-z]{2,}\|([^}]+)\}\}"
    pattern3 = "\[([^ ]+) ([^\]]+)\]"
    pattern4 = "<([a-z]+)( )?/>"
    pattern5 = "<([a-z]+)( [^>]+)?>"
    pattern6 = "</([a-z]+)>"
    for key, value in basic_info.items():
        value = re.sub(pattern1, "\g<3>", value)
        value = re.sub(pattern2, "\g<1>", value)
        value = re.sub(pattern3, "\g<2>", value)
        value = re.sub(pattern4, "", value)
        value = re.sub(pattern5, "", value)
        value = re.sub(pattern6, "", value)
        copy[key] = value
    return copy


if __name__ == '__main__':
    article = search_country("イギリス")
    basic_info = get_basic_info(article["text"])
    basic_info = remove_accent(basic_info)
    basic_info = remove_inner_link(basic_info)
    basic_info = remove_markup(basic_info)
    pprint(basic_info)

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

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

# 28.pyのdef remove_markupの定義が終わった後の記述を下記に変更

def call_image_info_api(file_name):
    queries = {
        "action": "query",
        "titles": "File:" + urllib.parse.quote(file_name),
        "prop": "imageinfo",
        "iiprop": "url",
        "format": "json",
    }
    url = "https://www.mediawiki.org/w/api.php?" + "&".join([("%s=%s" % (k, v)) for k, v in queries.items()])
    req = urllib.request.Request(url=url)
    f = urllib.request.urlopen(req)
    response = f.read().decode('utf-8')

    return json.loads(response)

if __name__ == '__main__':
    article = search_country("イギリス")
    basic_info = get_basic_info(article["text"])
    basic_info = remove_accent(basic_info)
    basic_info = remove_inner_link(basic_info)
    basic_info = remove_markup(basic_info)

    result = call_image_info_api(basic_info["国旗画像"])
    print(result["query"]["pages"]["-1"]["imageinfo"][0]["url"])