Chapter 3 Regular Expressions
Wikipedia の記事を以下のフォーマットで書き出したファイル jawiki-country.json.gz
がある.
- 1行に1記事の情報が JSON 形式で格納される
- 各行には記事名が “title” キーに,記事本文が “text” キーの辞書オブジェクトに格納され,そのオブジェクトが JSON 形式で書き出される
- ファイル全体は
gzip
で圧縮される
以下の処理を行うプログラムを作成せよ.
20. JSONデータの読み込み
Wikipedia 記事の JSON ファイルを読み込み,「イギリス」に関する記事本文を表示せよ.問題21-29 では,ここで抽出した記事本文に対して実行せよ.
>>> from gzip import GzipFile
>>> from io import BytesIO
>>> import json
>>> from pprint import pprint
>>> import requests
>>> GZIPPED_JSON_FILE_URL = 'http://www.cl.ecei.tohoku.ac.jp/nlp100/data/jawiki-country.json.gz'
>>> UK = 'イギリス'
>>> response = requests.get(GZIPPED_JSON_FILE_URL)
>>> buffered = BytesIO(response.content)
>>> with GzipFile(fileobj=buffered) as gf:
... articles = list(map(lambda x: json.loads(x.decode('utf-8')), gf.readlines()))
>>> wikipedia = {}
>>> for article in articles:
... title = article['title']
... text = article['text']
... wikipedia[title] = text
>>> pprint(wikipedia[UK])
{'text': '\n'
'<ref>英語以外での正式国名:<br/>\n'
'*([[スコットランド・ゲール語]])<br/>\n'
'*([[ウェールズ語]])<br/>\n'
'*([[アイルランド語]])<br/>\n'
'*([[コーンウォール語]])<br/>\n'
'*([[スコットランド語]])<br/>\n'
'**、(アルスター・スコットランド語)</ref>\n'
...
21. カテゴリ名を含む行を抽出
記事中でカテゴリ名を宣言している行を抽出せよ.
>>> CATEGORY = 'Category'
>>> splitted_text_uk = raw_text_uk.replace('\n\n', '\n').split('\n')
>>> category_rows = [line for line in splitted_text_uk if CATEGORY in row]
>>> pprint(category_rows)
['[[Category:イギリス|*]]',
'[[Category:英連邦王国|*]]',
'[[Category:G8加盟国]]',
'[[Category:欧州連合加盟国]]',
'[[Category:海洋国家]]',
'[[Category:君主国]]',
'[[Category:島国|くれいとふりてん]]',
'[[Category:1801年に設立された州・地域]]']
22. カテゴリ名の抽出
記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.
>>> import re
>>> PATTERN_FOR_CATEGORY = r'\[\[Category:(.*?)[\|\]].*?'
>>> category_names = []
>>> for category_row in category_rows:
... m = re.search(PATTERN_FOR_CATEGORY, category_row)
... if m:
... category_name = m.group(1)
... category_names.append(category_name)
>>> pprint(category_names)
['イギリス', '英連邦王国', 'G8加盟国', '欧州連合加盟国', '海洋国家', '君主国', '島国', '1801年に設立された州・地域']
23. セクション構造
記事中に含まれるセクション名とそのレベル(例えば “== セクション名 ==” なら1)を表示せよ.
>>> PATTERN_FOR_SECTIONS = r'(={2,})\s?(.*?)\s?={2,}'
>>> for line in splitted_text_uk:
... m = re.search(PATTERN_FOR_SECTIONS, line)
... if m:
... section_level = len(m.group(1)) - 1
... section_name = m.group(2)
... print(section_name, section_level, sep='\t')
国名 1
歴史 1
地理 1
気候 2
政治 1
...
24. ファイル参照の抽出
記事から参照されているメディアファイルをすべて抜き出せ.
>>> PATTERN_FOR_MEDIA_FILES = r'.*(ファイル|File):(.*?)(\|.*?)'
>>> media_files = []
>>> for line in splitted_text_uk:
... m = re.search(PATTERN_FOR_MEDIA_FILES, line)
... if m:
... media_file = m.group(2)
... media_files.append(media_file)
>>> pprint(media_files)
['Royal Coat of Arms of the United Kingdom.svg',
'Battle of Waterloo 1815.PNG',
'The British Empire.png',
'Uk topo en.jpg',
'BenNevis2005.jpg',
...
25. テンプレートの抽出
記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.
26. 強調マークアップの除去
25の処理時に,テンプレートの値から MediaWiki の強調マークアップ(弱い強調,強調,強い強調のすべて)を除去してテキストに変換せよ(参考: マークアップ早見表).
27. 内部リンクの除去
26の処理に加えて,テンプレートの値から MediaWiki の内部リンクマークアップを除去し,テキストに変換せよ(参考: マークアップ早見表).
28. MediaWiki マークアップの除去
27の処理に加えて,テンプレートの値から MediaWiki マークアップを可能な限り除去し,国の基本情報を整形せよ.
設問25-28の処理を以下にまとめる.
>>> PATTERN_FOR_MAIN = r"\|(.+?) = (.+)"
>>> PATTERN_FOR_EMPHASIS = r"\'{2,5}"
>>> PATTERN_FOR_BRACES = r".*\{\{.+\|(.+)\|(.+)\}\}"
>>> PATTERN_FOR_SINGLE_BLOCKS = r".*\[\[(.+)\]\].*"
>>> PATTERN_FOR_DOUBLE_BLOCKS = r"\[\[(.+?)\]\].*\[\[(.+?)\]\]"
>>> PATTERN_FOR_REF = r"(.+?)<ref.+>$"
>>> PATTERN_FOR_BR = r"(.+?)<br.+"
>>> PATTERN_FOR_MEDIA_FILES = r"(ファイル|File):(.+?)\|.+"
>>> basic_info = {}
>>> start = splitted_text_uk.index('{' + '{基礎情報 国') + 1 # Avoid Liquid syntax error when generating HTML files from markdown for this blog article
>>> end = splitted_text_uk.index('}' + '}') - 2 # Avoid Liquid syntax error when generating HTML files from markdown for this blog article
>>> for line in splitted_text_uk[start:end+1]:
... line = re.sub(PATTERN_FOR_EMPHASIS, '', line)
... if line[0] == '|':
... if line[1:5] != '公式国名':
... m = re.search(PATTERN_FOR_MAIN, line)
... key, value = m.group(1), m.group(2)
... if 'ref' in value:
... m = re.search(PATTERN_FOR_REF, value)
... value = m.group(1)
... if 'br' in value:
... m = re.search(PATTERN_FOR_BR, value)
... value = m.group(1)
... if '[[' in value:
... if key not in ('確立形態1', '確立年月日1', 'ccTLD'):
... m = re.search(PATTERN_FOR_SINGLE_BLOCKS, value)
... value = m.group(1)
... if '|' in value:
... if value[:5] in ('ファイル:', 'File:'):
... m = re.search(PATTERN_FOR_MEDIA_FILES, value)
... value = m.group(2)
... else:
... value = list(value.split('|'))
... else:
... m = re.search(PATTERN_FOR_DOUBLE_BLOCKS, value)
... value = list(m.groups())
... basic_info[key] = value
... else:
... m = re.search(PATTERN_FOR_BRACES, line)
... key, value = m.group(1), m.group(2)
... basic_info['公式国名'] = {key: value}
... elif line[:3] == '*{' + '{': # Avoid Liquid syntax error when generating HTML files from Markdown for this blog article
... m = re.search(PATTERN_FOR_BRACES, line)
... key, value = m.group(1), m.group(2)
... if key != 'sco':
... basic_info['公式国名'].update({key: value})
... else:
... basic_info['公式国名'].update({key: [value]})
... elif line[:3] == '**{':
... for substr in line.split('、'):
... m = re.search(PATTERN_FOR_BRACES, substr)
... key, value = m.group(1), m.group(2)
... basic_info['公式国名'][key].append(value)
>>> pprint(basic_info)
{'GDP/人': '36,727',
'GDP値': '2兆3162億',
'GDP値MER': '2兆4337億',
'GDP値元': '1兆5478億',
'GDP統計年': '2012',
...
29. 国旗画像の URL を取得する
テンプレートの内容を利用し,国旗画像の URL を取得せよ.(ヒント: MediaWiki API の imageinfo を呼び出して,ファイル参照を URL に変換すればよい)
>>> MEDIAWIKI_API_ENDPOINT = 'https://en.wikipedia.org/w/api.php'
>>> payload = {
... "action": "query",
... "format": "json",
... "prop": "imageinfo",
... "titles": f"File:{basic_info['国旗画像']}",
... "iiprop": "url"
... }
>>> response = requests.get(url=MEDIAWIKI_API_ENDPOINT, params=payload)
>>> page_info = list(response.json()['query']['pages'].values())[0]
>>> url = page_info['imageinfo'][0]['url']
>>> pprint(url)
'https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg'
References
- Okazaki, N. (2015). 言語処理100本ノック 2015 [Natural Language Processing 100 Exercises 2015]. Retrieved from http://www.cl.ecei.tohoku.ac.jp/nlp100/
- Okazaki, N. (2020). 言語処理100本ノック 2020 [Natural Language Processing 100 Exercises 2020]. Retrieved from https://nlp100.github.io/ja/