3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

: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を取得してた、まあいいやおしまい!
image.png

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?