Posted at

言語処理100本ノックに挑戦 / 第3章: 正規表現

More than 1 year has passed since last update.


:muscle: 前書き

詳細は第1章参照、引き続き言語処理100本ノックの第3章を回答していきます。本章では 正規表現を中心に json 操作、辞書型、API 呼出を学びました。正規表現を大体理解していたので、サクサク進めることができました。

これはどうやら、JupyterNotebookを使ったほうが効率よく回答できるやつだ、、、とやりながら気づきましたが、引き続きローカルの生 python でやっています。


第3章: 正規表現


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

1行に1記事の情報がJSON形式で格納される

各行には記事名が"title"キーに,記事本文が"text"キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される

ファイル全体はgzipで圧縮される

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


gzip解凍した後のデータのサンプル、UTF-8/LFでした。日本語のWikiPediaの「国」カテゴリが、改行区切りのJSON形式で連なっているようです(LDJSONと呼ぶそう)。1行が長いですが、"text"に本文、"title"がタイトルのようで、値はWikiPediaの書式でした。


jawiki-country.json

{"text": "{{otheruses|主に現代のエジプト・アラブ共和国|古代|古代エジプト}}\n{{基礎情報 国\n|略名 =エジプト\n|日本語国名 =エジプト・アラブ共和国\n|公式国名 ='''{{lang|ar|جمهورية مصر العربية}}'''\n|国旗画像 =Flag of Egypt.svg\n|国章画像 =[[ファイル:Coat_of_arms_of_Egypt.svg|100px|エジプトの国章]]\n|国章リンク =([[エジプトの国章|国章]])\n|標語 =なし\n|位置画像 =Egypt (orthographic projection).svg\n|公用語 =[[アラビア語]]\n|首都 =[[カイロ]]\n|最大都市 =カイロ\n|元首等肩書 =[[近代エジプトの国家元首の一覧|大統領]]\n|元首等氏名 =[[アブドルファッターフ・アッ=シーシー]]\n|首相等肩書 =[[エジプトの首相|首相]]\n|首相等氏名 =[[イブラヒーム・メフレブ]]\n|面積順位 =29\n|面積大きさ =1 E12\n|面積値 =1,001,450\n|水面積率 =0.6%\n|人口統計年 =2011\n|人口順位 =\n|人口大きさ =1 E7\n|人口値 =81,120,000\n|人口密度値 =76\n|GDP統計年元 =2008\n|GDP値元 =8,965億<ref name=\"economy\">IMF Data and Statistics 2009年4月27日閲覧([http://www.imf.org/external/pubs/ft/weo/2009/01/weodata/weorept.aspx?pr.x=77&pr.y=19&sy=2008&ey=2008&scsm=1&ssd=1&sort=country&ds=.&br=1&c=469&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a=])</ref>\n|GDP統計年MER =2008\n|GDP順 

(以下略)


⚾ 20. JSONデータの読み込み


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



解1 gzip/json/dict の操作


answer

import gzip,json

with gzip.open('./data/jawiki-country.json.gz',mode='rt',encoding='utf=8') as gz_temp:
jsonlines = gz_temp.read()

lst_country = []
for line in jsonlines.split('\n'):
if len(line) > 1: # 空行がエラーになるため
lst_country.append(json.loads(line))

for dct_country in lst_country:
if dct_country.get('title') == 'イギリス':
print(dct_country.get('text'))
break

# {{redirect|UK}}
# {{基礎情報 国
# |略名 = イギリス
# |日本語国名 = グレートブリテン及び北アイルランド連合王国
# |公式国名 = {{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br/>
# *{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu Thuath}}([[スコットランド・ゲール語]])<br/>
# *{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}([[ウェールズ語]])<br/>
# (以下略)



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


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



解1 正規表現によるマッチング


answer

import re

# dct_country は Q20 にて取得済みとする
for text in dct_country.get('text').split('\n'):
match = re.search('\[\[Category.*',text )
if match != None:
print(match.group(0))

# [[Category:イギリス|*]]
# [[Category:英連邦王国|*]]
# [[Category:G8加盟国]]
# [[Category:欧州連合加盟国]]
# [[Category:海洋国家]]
# [[Category:君主国]]
# [[Category:島国|くれいとふりてん]]
# [[Category:1801年に設立された州・地域]]



解2 re.findall による複数行マッチング


answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('(\[\[Category:.*\]\])',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match] # リスト内包表記,devnullはprintの戻り値[Null]格納用
# [[Category:イギリス|*]]
# [[Category:英連邦王国|*]]
# [[Category:G8加盟国]]
# [[Category:欧州連合加盟国]]
# [[Category:海洋国家]]
# [[Category:君主国]]
# [[Category:島国|くれいとふりてん]]
# [[Category:1801年に設立された州・地域]]


⚾ 22. カテゴリ名の抽出


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



解1 非貪欲マッチ(最短マッチ)


answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('\[\[Category:(.*?)(?:\|.*)?\]\]',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match] # リスト内包表記,devnullはprintの戻り値[Null]格納用
# イギリス
# 英連邦王国
# G8加盟国
# 欧州連合加盟国
# 海洋国家
# 君主国
# 島国
# 1801年に設立された州・地域


⚾ 23. セクション構造


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



解1 失われる可読性


  • リスト内包表記、書けるけど読みにくい…


answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('(==+)(.*?)==+',dct_country.get('text'),re.MULTILINE)
devnull = [print(v[1] + ' is Level ' + str(v[0].count('='))) for v in match]
# 国名 is Level 2
# 歴史 is Level 2
# 地理 is Level 2
# 気候 is Level 3
# 政治 is Level 2
# 外交と軍事 is Level 2
# (以下略)


⚾ 24. ファイル参照の抽出


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



解1 特に新しい要素はなし



  • [[File:Uk topo en.jpg|thumb|200px|イギリスの地形図]] 等の要素を抽出すればよさそうです


answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('File:(.*?)\|',dct_country.get('text'),re.MULTILINE)
devnull = [print(v) for v in match]
# Battle of Waterloo 1815.PNG
# The British Empire.png
# Uk topo en.jpg
# BenNevis2005.jpg
# (以下略)


⚾ 25. テンプレートの抽出


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



解1 改行を含むマッチング


  • データ前半の {{基礎情報 国\n|略名 = イギリス\n|日本語国名 = グレートブリテン及び... を見ると、 =でフィールド名と値が設定されているようです


  • re.DOTALL で改行を含むマッチング




  • dict() で list型 を dict型 に変換




answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
print (match_dct)
# {'略名': 'イギリス', '日本語国名': 'グレートブリテン及び北アイルランド連合王国', '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern Ireland}}<ref>英語以外での正式国名:<br/>', '国旗画像': 'Flag of the United Kingdom.svg', '国章画像': '[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]', '国章リンク': '(
# (以下略)


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


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



解1 特に新しい要素はなし


  • マークアップ早見表の記載が変わっていましたが、過去のバージョン では ''弱い強調'' '''強調''' '''''強い強調''''' でした


    • 例: '確立形態4': "現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更"'確立形態4': "現在の国号「グレートブリテン及び北アイルランド連合王国」に変更"




answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_dct2 = {}
for key,value in match_dct.items():
match_dct2[key] = re.sub('\'\'+','',value)

print (match_dct2)
# (中略)
# '確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更'
# (後略)



⚾ 27. 内部リンクの除去


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



解1 マッチした文字列を置換に利用する



  • \1 でマッチした文字列へアクセス




  • [[記事名]] [[記事名|表示文字]] [[記事名#節名|表示文字]] を除去すればよさそうです


    • 例: '確立形態2': '[[グレートブリテン王国]]建国<br />([[連合法 (1707年)|1707年連合法]])''確立形態2': 'グレートブリテン王国建国<br />(連合法 (1707年))'




answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_tmp = ''
match_tmp2 = ''
match_dct2 = {}
for key,value in match_dct.items():
match_tmp = re.sub('\'\'+','',value) # 強調を除去
match_tmp = re.sub('\[\[(.*?)([|\|].*)?\]\]','\\1',match_tmp) # リンクを除去
match_dct2[key] = match_tmp

print (match_dct2)
# (中略)
# '確立形態2': 'グレートブリテン王国建国<br />(連合法 (1707年))',
# (後略)



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


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



解1 綺麗になるまでがんばる


  • 特に基準なく、見栄えが良くなるまで置換する


answer

import re

# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)
match_tmp = ''
match_dct2 = {}
for key,value in match_dct.items():
match_tmp = re.sub('\'\'+','',value) # 強調を除去
match_tmp = re.sub('\[\[(.*?)([|\|].*)?\]\]','\\1',match_tmp) # リンクを除去
match_tmp = re.sub('\{\{lang\|.*?\|(.*?)\}\}','\\1',match_tmp) # {{Lang 要素を除去
match_tmp = re.sub('<ref.*?( />|</ref>)','',match_tmp) # ref 要素を除去
match_tmp = re.sub('<br(\s|)/>','',match_tmp) # br 要素を除去
match_dct2[key] = match_tmp

print (match_dct2)
# {'略名': 'イギリス', '日本語国名': 'グレートブリテン及び北アイルランド連合王国', '公式国名': 'United Kingdom of Great Britain and Northern Ireland<ref>英語以外での正式国名:', '国旗画像': 'Flag of the United Kingdom.svg', '国章画像': 'ファイル:Royal Coat of Arms of the United Kingdom.svg', '国章リンク': '(イギリスの国章)', '標語': 'Dieu et mon droit(フランス語:神と私の権利)', '国歌': '女王陛下万歳', '位置画像': 'Location_UK_EU_Europe_001.svg',
# (以下略)



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


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



解1 urllib.request で API Call


imageinfo_api_result

                "imageinfo": [

{
"url": "https://upload.wikimedia.org/wikipedia/commons/9/98/Royal_Coat_of_Arms_of_the_United_Kingdom.svg",
"descriptionurl": "https://commons.wikimedia.org/wiki/File:Royal_Coat_of_Arms_of_the_United_Kingdom.svg",
"descriptionshorturl": "https://commons.wikimedia.org/w/index.php?curid=21101265"
}


answer

import re

import urllib.request
# dct_country は Q20 にて取得済みとする
match = re.findall('^\{\{基礎情報(.*?)\}\}$',dct_country.get('text'),re.MULTILINE+re.DOTALL)
match2 = re.findall('\|(.*?) = (.*?)\n',match[0])
match_dct = dict(match2)

image_name = re.sub('\[\[ファイル:(.*?)\|.*\]\]','\\1',match_dct['国章画像']) # ファイル名を抽出
param = {
'format': 'json',
'action': 'query',
'titles': 'File:Royal%20Coat%20of%20Arms%20of%20the%20United%20Kingdom.svg',
'prop': 'imageinfo',
'iiprop': 'url'
}
query = '&'.join("%s=%s" % (k, v) for k, v in param.items())
url = 'http://en.wikipedia.org/w/api.php?%s' % (query)
request = urllib.request.urlopen(url)
jsonData = request.read().decode('utf-8')
data = json.loads(jsonData)
print(data.get('query').get('pages').get('-1').get('imageinfo')[0].get('url'))

# 'https://upload.wikimedia.org/wikipedia/commons/9/98/Royal_Coat_of_Arms_of_the_United_Kingdom.svg'


あれ、間違えて国旗じゃなく国章のURLを取得してた、まあいいやおしまい!