【言語処理100本ノック解いてみた】 第3章:正規表現 No.25~No.29

プログラミング

はじめに

今回は第3章の問題の後半部分を解いていきたいと思います.
前半をまだ解いていない方はこちらからどうぞ.

第3章前半記事

本章はGoogle Colaboraroryでの実行を想定したコードになっていますので,コマンドを試しながら進めたい方は環境を揃えるようによろしくお願いします.
また,前回作成したuk_textが必要になりますのでご注意ください.

では,早速解いていきたいと思います.

前準備

uk_textの作成までのコードを再喝いたします.
下記コードを実行してから問25以降を解いていきましょう.

コマンド
#Wikiデータのダウンロードとunzipによる解凍
!wget https://nlp100.github.io/data/jawiki-country.json.gz
!gunzip jawiki-country.json.gz
import json
filepath = "/content/jawiki-country.json"
# ファイルを開く
with open(filepath) as f:
    for line in f:
        json_data = json.loads(line)
        if json_data["title"] == "イギリス":
            uk_text = json_data["text"]

前準備ができたところで本編の方に移っていきたいと思います.

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

問題文: 記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.
コマンド
basic_info = re.findall(r'^\{\{基礎情報 国\n(.+?)$\n\}\}', uk_text, re.MULTILINE+re.DOTALL)
print(basic_info)
#辞書オブジェクトの格納
object_dict = dict(re.findall(r'^\|(.+?)\s*=\s*(.+?)(?:(?=\n\|)|(?=$))', basic_info[0], re.MULTILINE+re.DOTALL))
for k, v in object_dict.items():
    print(k,v)
#出力(一部省略)
['|略名  =イギリス\n|日本語国名 = グレートブリテン及び北アイルランド連合王国\n|公式国名 = {{lang|en|United Kingdom of Great Britain and Northern Ireland}}・・・|注記 = ']
略名 イギリス
日本語国名 グレートブリテン及び北アイルランド連合王国
公式国名 {{lang|en|United Kingdom of Great Britain and Northern Ireland}}英語以外での正式国名:
  ・
  ・
  ・
国際電話番号 44
注記 
解説

今回の問題は扱う正規表現が多く,少し苦戦しました.
まずは,基礎情報がどのような形で記事に入っているかを確認しましょう.
基礎情報のコンテンツ部分を抽出するためには下記を知っておく必要があります.

re.DOTALL : “.”を改行やスペースを含めたあらゆる文字列にマッチするようにする.

上記によって改行を含む一連の取得したい文字列部分をひとまとまりに抜き出すことができます.
続いて,辞書化オブジェクトとしての格納部分に関してです.
まず,基礎情報内のコンテンツ部分の形式に着目すると,”|フィールド名=値”という形式で入ってきています.
つまり,開始タグとして”|”を使い,終了タグとして”\n(改行)”を指定すればフィールドと値を取得できそうです.
ただし,一つのフィールドの値が複数の改行を含めたものも存在しているので,そこの部分をマッチさせないようにする必要があります.
一つのフィールドと値の終わり方は 改行+”|” or 改行+後ろに何もない状態という所に着目します.
ここで使うのが,下記の二つです.

(?:…) : 普通の丸括弧の、キャプチャしない版。
(?=…) : …が次に続くものにマッチすればマッチするが、文字列をまったく消費しない。

一つ目に関しては,マッチさせたい部分はフィールド名と値の部分なのでそれ以外の部分を抽出しないようにするために利用しています.
二つ目に関しては,開始と終了タグの一部分が被ってしまっているため,文字列を消費してしまうとマッチした時に情報が損なわれ,一部のフィールドと値が抽出できなくなってしまうため利用しています.
上記で開始と終了を指定することができたので,後はフィールドと値を的確に取得できるようにします.
“フィールド=値”という形式では入っているため,”=”の左右でそれぞれマッチさせれば良さそうですね.
“=”の左右には空白が入っていることもあるので,空白を含まないようにするため”\s*”をつけています.
最後に,抽出してきた二つの値をdict()で辞書型にすることで,目的の形式を取得することができます.

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

問題文:25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)を除去してテキストに変換せよ.
(参考: マークアップ早見表
コマンド
#'が二つ以上五つ以下の場合,対象箇所の強調を削除
for k,v in object_dict.items():
    before = v
    object_dict[k] = re.sub(r'\'{2,5}', '', v)
    if before != object_dict[k]:
        print(before)
        print(object_dict[k])
        print("---"*20)
#出力
[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}
''神よ女王を護り賜え''
{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}
[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}
神よ女王を護り賜え
{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}
------------------------------------------------------------
現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更
現在の国号「グレートブリテン及び北アイルランド連合王国」に変更
------------------------------------------------------------
解説

マークアップ早見表の中の斜体,太字,斜体+太字を削除していきます.
全ての強調の仕方に共通して「’」を使っており,この「’」が2以上,5以下の時に削除するようにre.sub()を記述していきます.
正規表現では”{a, b}”でaからbまでの範囲で繰り返されるものにマッチさせることができます.
解答例では変更があった部分だけ表示するようにしています.

27. 内部リンクの除去

問題文:26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,テキストに変換せよ(参考: マークアップ早見表 )
コマンド
#内部リンクを削除する
for k,v in object_dict.items():
    before = v
    object_dict[k] = re.sub(r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]', r'\1', v)
    if object_dict[k] != before:        
        print(before)
        print(object_dict[k])
        print("---"*20)
#出力(一部)
([[イギリスの国章|国章]])
(国章)
------------------------------------------------------------
{{lang|fr|[[Dieu et mon droit]]}}
([[フランス語]]:[[Dieu et mon droit|神と我が権利]])
{{lang|fr|Dieu et mon droit}}
(フランス語:神と我が権利)
------------------------------------------------------------
[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}
神よ女王を護り賜え
{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}
[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}
神よ女王を護り賜え
{{center|ファイル:United States Navy Band - God Save the Queen.ogg}}
------------------------------------------------------------
[[英語]]
英語
------------------------------------------------------------
[[ロンドン]](事実上)
ロンドン(事実上)
------------------------------------------------------------
[[イギリスの君主|女王]]
女王
                        ・
                        ・
                        ・
解説

今回の問題は”[[ ]]”を削除するだけかと思っていたのですが,内部リンクだけを的確に取り除き,表示名を取得することが問題の主旨のようです.
コードの解説をしていきます.
今回の正規表現を分解していくと,”(?:[^|]?|)”,”??”,”([^|]?)” この三つに分かれるかと思います.

#一つ目の正規表現部分
“?:”によってこのグループのキャプションをしないことを明示します.
“[^|]?|”によって”|”以外の文字列を”|”が出るまで最小マッチさせます.
#二つ目の正規表現部分
“??”は一つ目のグループの最小マッチを0,1回繰り返すという意味になります.
内部リンクの形式は”|”を一つ含むか,含まないかの二択なので,上記によって含まない場合は処理を行わず,含んでいたら,キャプションせずに文字列を消費するという風な処理にすることができます.
#三つ目の正規表現部分
二つ目の正規表現までに”|”の前部分は削除することができていますが,マークアップの中には”|”を複数含んでいるものもあります.
“([^|]
?)”によって,抽出する文字列に”|”を含まないように調整しています.

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

問題文:27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.
コマンド
for k,v in object_dict.items():
    before = v
    #URLを削除する
    #[]で囲われているURLの削除
    v = re.sub(r'\[https?://.+\]', '', v)
    #URLに使われることのない文字orタブ・改行に達する部分まで削除
    v = re.sub(r'https?://[^\{\}\|\\\^\t\n]+', '', v)
    #HTMLタグを削除
    v = re.sub(r'<.*>(.+)<\/.+>', r'\1', v)
    v = re.sub(r'<.*>', '', v)
    #langテンプレートの削除
    v = re.sub(r'\{\{lang\|.+?\|(.+?)\}\}', r'\1' ,v)
    #テンプレートの削除(ファイルなどはいらないと判断したのでテンプレート自体を削除しています.)
    v = re.sub(r'\{\{.*?\}\}', '' ,v)
    #一部内部リンクが再度出てきたので
    v = re.sub(r'\[\[(?:[^|]*?\|)??([^|]*?)\]\]', r'\1', v)
    object_dict[k] = v
    if before != object_dict[k]:        
        print(before)
        print(object_dict[k])
        print("---"*20)

#出力
{{lang|en|United Kingdom of Great Britain and Northern Ireland}}英語以外での正式国名:
United Kingdom of Great Britain and Northern Ireland
------------------------------------------------------------
{{lang|fr|Dieu et mon droit}}
(フランス語:神と我が権利)
Dieu et mon droit(フランス語:神と我が権利)
------------------------------------------------------------
[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}
神よ女王を護り賜え
{{center|ファイル:United States Navy Band - God Save the Queen.ogg}}
God Save the Queen
------------------------------------------------------------
{{仮リンク|リンゼイ・ホイル|en|Lindsay Hoyle}}

------------------------------------------------------------
                          ・
解説

ここまでのテンプレート削除の応用ですね.
URLに関しては”http(s):”で始まるという特徴を正規表現で捉えています.
HTMLタグに関しても”<xx>〇〇</xx>”のような表現がされるので〇〇の部分を取得できるよう正規表現を用いて抽出しています.
他のテンプレートに関しては早見表を参考にしながら,削除したり.一部残したりしています.
最終的に整形した結果の一例は下記になります.

#出力
{'GDP/人': '36,727',
 'GDP値': '2兆3162億',
 'GDP値MER': '2兆4337億',
 'GDP値元': '1兆5478億',
 'GDP統計年': '2012',
 'GDP統計年MER': '2012',
 'GDP統計年元': '2012',
      …
}

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

問題文:テンプレートの内容を利用し,国旗画像のURLを取得せよ.
(ヒント: MediaWiki APIのimageinfoを呼び出して,ファイル参照をURLに変換すればよい)
コマンド
import requests
uk_flag = object_dict["国旗画像"]
uk_flag = uk_flag.replace(" ", "_")
print(uk_flag)
S = requests.Session()
URL = "https://www.mediawiki.org/w/api.php"

PARAMS = {
    "action": "query",
    "format": "json",
    "prop": "imageinfo",
    "iiprop":"url",
    "titles": "File:"+uk_flag
}

R = S.get(url=URL, params=PARAMS)
DATA = R.json()
print(DATA)
uk_flag_url = DATA["query"]["pages"]["-1"]["imageinfo"][0]["url"]
print(uk_flag_url)
#出力
Flag_of_the_United_Kingdom.svg
{'continue': {'iistart': '2019-09-10T16:52:58Z', 'continue': '||'}, 'query': {'normalized': [{'from': 'File:Flag_of_the_United_Kingdom.svg', 'to': 'File:Flag of the United Kingdom.svg'}], 'pages': {'-1': {'ns': 6, 'title': 'File:Flag of the United Kingdom.svg', 'missing': '', 'known': '', 'imagerepository': 'shared', 'imageinfo': [{'url': 'https://upload.wikimedia.org/wikipedia/commons/a/ae/Flag_of_the_United_Kingdom.svg', 'descriptionurl': 'https://commons.wikimedia.org/wiki/File:Flag_of_the_United_Kingdom.svg', 'descriptionshorturl': 'https://commons.wikimedia.org/w/index.php?curid=347935'}]}}}}
https://upload.wikimedia.org/wikipedia/commons/a/ae/Flag_of_the_United_Kingdom.svg

解説

国旗画像のURLを取得する際にimageinfoを用いて取得してきます.
imageinfoのpythonのサンプルコードを参考にコードを作成しています.
iipropでurlの取得ができるので,パラメータとして追加してください.
あとは出力されたjsonの中身を探すとURLの記述があるので,それを文字列として取得すれば国旗画像のURLを取得できます.

最後に

今回は第3章の後半の問題を解いてきました.
正規表現の中でも応用的な使い方が多く,考える・理解するのに時間がかかりました…
正規表現の全てを理解しなくても公式のページを見ながら,抜き出したいオブジェクトが取得できるように自分でコードを作れるようになりたいですね.
では,また4章編でお会いしましょう.

コメント

タイトルとURLをコピーしました