前書き
詳細は第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の書式でした。
{"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 の操作
- gzip 解凍後、197 行目、
"title": "イギリス"
のデータを引っ張ってくれば良さそうです -
gzip.open(filename, mode='rb', compresslevel=9, encoding=None, errors=None, newline=None)
で gzip 読込み -
json.loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
で JSON を dict 型に変換 -
dict.get(key[, default])
で要素取得
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 正規表現によるマッチング
- データ後半の
[[Category:イギリス|*]]\n[[Category:英連邦王国|*]]...
のあたりを抽出すればよさそうです -
re.search(pattern, string, flags=0)
で 正規表現によるマッチング
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 による複数行マッチング
-
re.findall(pattern, string, flags=0)
による複数行マッチング - リストの内包表記
[x for x in list]
を使って一行で print してみる -
(...)
は マッチ後に回収され、参照可能とする
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 非貪欲マッチ(最短マッチ)
-
(?:...)
は マッチ後に取り込まれない -
*?
,+?
,??
は非貪欲マッチ(最短マッチ)
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 失われる可読性
- リスト内包表記、書けるけど読みにくい…
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|イギリスの地形図]]
等の要素を抽出すればよさそうです
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型 に変換
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': "現在の国号「グレートブリテン及び北アイルランド連合王国」に変更"
- 例:
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年))'
- 例:
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 綺麗になるまでがんばる
- 特に基準なく、見栄えが良くなるまで置換する
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
-
urllib.request
で WikiMedia の API を実行 - WikiMedia の最新の API 仕様を確認、以下API(svfファイル名を指定)で取得できそう
https://commons.wikimedia.org/w/api.php?action=query&titles=File:Royal%20Coat%20of%20Arms%20of%20the%20United%20Kingdom.svg&prop=imageinfo&iiprop=url
-
API:Imageinfo - MediaWiki
- Parameters:
- iiprop:Which file information to get:
- url:Gives URL to the file and the description page.
"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"
}
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'