LoginSignup
1
2

More than 3 years have passed since last update.

言語処理100本ノック2020: 第三章(正規表現)

Last updated at Posted at 2020-04-24

(注意): 先に進めること優先で現状記事が汚いです。

言語処理100本ノック2020版が公開されたのでこの機会に解いていく。
GitHubに公開しているJupyterをmarkdown出力したものなので,一度読み込んだパッケージはその後に読み込んでいないことに注意。

第一章はここ

いつも調べて行っていた正規表現に対して,まさにノックのような反復練習を行い,今後の解析がスムーズに行えるようになりそうでよかった。
最後まで終わらせることを目標にやっている途中なので何か間違いがあるかもしれません,ご指摘していただけますと幸いです。
記事の体裁も解き終わったら整える予定です。

第3章:正規表現

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.
* 1行に1記事の情報がJSON形式で格納される
* 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
* ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ.

内容的には2015と変わらないらしい。
Wikiのマークアップはここにまとめられている。

20. JSONデータの読み込み

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

import json

def return_article(fname, article_title):
    with open(fname, 'rt') as data_file:
        for line in data_file:
            data_json = json.loads(line)
            if data_json['title'] == article_title:
                return data_json['text']

file_path = '../data/jawiki-country.json'
uk_article = return_article(file_path, 'イギリス')

print(uk_article)
    {{redirect|UK}}
    {{redirect|英国|春秋時代の諸侯国|英 (春秋)}}
    {{Otheruses|ヨーロッパの国|長崎県・熊本県の郷土料理|いぎりす}}
    {{基礎情報 
    |略名  =イギリス
    |日本語国名 = グレートブリテン及び北アイルランド連合王国

    <<以下略>>

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

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

上記結果の中身を見るとカテゴリは以下のように記載されていた。

[[Category:イギリス|*]]
[[Category:イギリス連邦加盟国]]
[[Category:英連邦王国|*]]
[[Category:G8加盟国]]
[[Category:欧州連合加盟国|元]]
[[Category:海洋国家]]
[[Category:現存する君主国]]
[[Category:島国]]
[[Category:1801年に成立した国家・領域]]

形式は[[Category:カテゴリ名|ソートキー]]という形をとっている。
特殊文字を,その特殊な意味を発動させずに使うには,バックスラッシュを使う必要がある。自分はr'^\[+Category\:.+\]+$'のように記載した。
re.MULTILINEとfindallを使うことで,改行ごとにforループを回さなくても検索することができる。

import re

def extract_category_row(wiki_text):
    p = re.compile(r'^\[+Category\:.+\]+$', re.MULTILINE)
    return p.findall(wiki_text)

category_rows = extract_category_row(uk_article)
for line in category_rows:
    print(line)
    [[Category:イギリス|*]]
    [[Category:イギリス連邦加盟国]]
    [[Category:英連邦王国|*]]
    [[Category:G8加盟国]]
    [[Category:欧州連合加盟国|元]]
    [[Category:海洋国家]]
    [[Category:現存する君主国]]
    [[Category:島国]]
    [[Category:1801年に成立した国家・領域]]

22. カテゴリ名の抽出

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

()で囲んだ部分だけを抽出することができる。
\wでUnicodeの単語文字にマッチさせている。

def extract_category_name(wiki_text):
    p = re.compile(r'^\[+Category\:(\w+).+$', re.MULTILINE)
    return p.findall(wiki_text)

category_name = extract_category_name(uk_article)
for line in category_name:
    print(line)
    イギリス
    イギリス連邦加盟国
    英連邦王国
    G8加盟国
    欧州連合加盟国
    海洋国家
    現存する君主国
    島国
    1801年に成立した国家

23. セクション構造

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

==歴史==
こういうのがセクションらしい。

def extract_section(wiki_text):
    result = {}
    p = re.compile(r'^(={2,})(\w+)\1$', re.MULTILINE)
    section_content =  p.findall(wiki_text)
    for item in section_content:
        result[item[1]] = len(item[0])
    return result

section_dict = extract_section(uk_article)

for k,v in section_dict.items():
    print('レベル:',v, k)
    レベル: 2 国名
    レベル: 2 歴史
    レベル: 2 地理
    レベル: 3 主要都市
    レベル: 3 気候
    レベル: 2 政治
    レベル: 3 元首

24. ファイル参照の抽出

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

[[ファイル:Wikipedia-logo-v2-ja.png|thumb|説明文]]というのがファイルの記載マークアップらしい。

def extract_file(wiki_text):
    p = re.compile(r'\[\[ファイル\:(.+?)\|')
    file_name = p.findall(wiki_text)
    return file_name

file_reference = extract_file(uk_article)
print(file_reference)
    ['Royal Coat of Arms of the United Kingdom.svg', 'Descriptio Prime Tabulae Europae.jpg', "Lenepveu, Jeanne d'Arc au siège d'Orléans.jpg", 'London.bankofengland.arp.jpg', 'Battle of Waterloo 1815.PNG', 'Uk topo en.jpg', 'BenNevis2005.jpg', 'Population density UK 2011 census.png', '2019 Greenwich Peninsula & Canary Wharf.jpg', 'Birmingham Skyline from Edgbaston Cricket Ground crop.jpg', 'Leeds CBD at night.jpg', 'Glasgow and the Clyde from the air (geograph 4665720).jpg', 'Palace of Westminster, London - Feb 2007.jpg', 'Scotland Parliament Holyrood.jpg', 'Donald Trump and Theresa May (33998675310) (cropped).jpg', 'Soldiers Trooping the Colour, 16th June 2007.jpg', 'City of London skyline from London City Hall - Oct 2008.jpg', 'Oil platform in the North SeaPros.jpg', 'Eurostar at St Pancras Jan 2008.jpg', 'Heathrow Terminal 5C Iwelumo-1.jpg', 'Airbus A380-841 G-XLEB British Airways (10424102995).jpg', 'UKpop.svg', 'Anglospeak.svg', "Royal Aberdeen Children's Hospital.jpg", 'CHANDOS3.jpg', 'The Fabs.JPG', 'Wembley Stadium, illuminated.jpg']

25. テンプレートの抽出

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

基礎情報は以下のようになっている。

{{基礎情報 国
|略名  =イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
|公式国名 = {{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}}([[スコットランド・ゲール語]])
*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd Iwerddon}}([[ウェールズ語]])
*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na hÉireann}}([[アイルランド語]])
*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon Glédh}}([[コーンウォール語]])
*{{lang|sco|Unitit Kinrick o Great Breetain an Northren Ireland}}([[スコットランド語]])
**{{lang|sco|Claught Kängrick o Docht Brätain an Norlin Airlann}}、{{lang|sco|Unitet Kängdom o Great Brittain an Norlin Airlann}}(アルスター・スコットランド語)</ref>
|国旗画像 = Flag of the United Kingdom.svg
|国章画像 = [[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]

<<中略>>

}}

|を頼りに検索するため,先読みアサーション系統を用いた(pythonのドキュメントより以下引用)

(?=...)
... が次に続くものにマッチすればマッチしますが、文字列をまったく消費しません。これは 先読みアサーション (lookahead assertion) と呼ばれます。例えば、Isaac (?=Asimov) は 'Isaac ' に、その後に 'Asimov' が続く場合にのみ、マッチします。

ここに関しては,素人の言語処理を参考にさせていただいた。
全く同じになってもつまらないので,{{基礎情報}}の部分を抽出しないで書いてみた。ただし,他の例においての応用生が低いので,きちんと抽出してからやったほうがいいと思う。具体的には,直接抽出した際に邪魔になるのが`|style=`という部分だけだったので,それを除去する形で抽出した。

def extract_basic_info(wiki_text):
    result = {}
    p = re.compile(r'^\|(?!style)(\w+?)\s*\=\s*(.+?)(?:(?=\n\|))', re.MULTILINE)
    basics = p.findall(wiki_text)
    for item in basics:
        result[item[0]] = item[1]
    return result

basic_info = extract_basic_info(uk_article)
print(json.dumps(basic_info, sort_keys=True, indent=4, ensure_ascii=False))
    {
        "GDP値": "2兆3162億<ref name=\"imf-statistics-gdp\" />",
        "GDP値MER": "2兆4337億<ref name=\"imf-statistics-gdp\" />",
        "GDP値元": "1兆5478億<ref name=\"imf-statistics-gdp\">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a=IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom]</ref>",
        "GDP統計年": "2012",
        "GDP統計年MER": "2012",
        "GDP統計年元": "2012",
        "GDP順位": "6",

        <<中略>>

    }

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

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

''強調''のように,二つ以上の'に囲まれているところが強調マークアップである。処理時ということなので新しく重複する処理を行う関数を定義した。

def extract_basic_removed_reinforce(wiki_text):
    result = {}
    ps = re.compile(r'\'{2,}') # 加えた部分
    p = re.compile(r'^\|(?!style)(\w+?)\s*\=\s*(.+?)(?:(?=\n\|))', re.MULTILINE)
    removed_text = ps.sub('', wiki_text) # 加えた部分
    basics = p.findall(removed_text)
    for item in basics:
        result[item[0]] = item[1]
    return result

basic_info = extract_basic_removed_reinforce(uk_article)
print(json.dumps(basic_info, sort_keys=True, indent=4, ensure_ascii=False))
    {
        "GDP値": "2兆3162億<ref name=\"imf-statistics-gdp\" />",
        "GDP値MER": "2兆4337億<ref name=\"imf-statistics-gdp\" />",
        "GDP値元": "1兆5478億<ref name=\"imf-statistics-gdp\">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a=IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom]</ref>",
        "GDP統計年": "2012",
        "GDP統計年MER": "2012",
        "GDP統計年元": "2012",
        "GDP順位": "6",
        "GDP順位MER": "6",
        "ccTLD": "[[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>",
        "人口値": "6643万5600<ref>{{Cite web|url=https://www.ons.gov.uk/peoplepopulationandcommunity/populationandmigration/populationestimates|title=Population estimates - Office for National Statistics|accessdate=2019-06-26|date=2019-06-26}}</ref>",
        "人口大きさ": "1 E7",
        "人口密度値": "271",
        "人口統計年": "2018",
        "人口順位": "22",
        "他元首等氏名1": "[[:en:Norman Fowler, Baron Fowler|ノーマン・ファウラー]]",
        "他元首等氏名2": "{{仮リンク|リンゼイ・ホイル|en|Lindsay Hoyle}}",
        "他元首等氏名3": "[[:en:Brenda Hale, Baroness Hale of Richmond|ブレンダ・ヘイル]]",
        "他元首等肩書1": "[[貴族院 (イギリス)|貴族院議長]]",
        "他元首等肩書2": "[[庶民院 (イギリス)|庶民院議長]]",
        "他元首等肩書3": "[[連合王国最高裁判所|最高裁判所長官]]",
        "位置画像": "United Kingdom (+overseas territories) in the World (+Antarctica claims).svg",
        "元首等氏名": "[[エリザベス2世]]",
        "元首等肩書": "[[イギリスの君主|女王]]",
        "公用語": "[[英語]]",
        "国旗画像": "Flag of the United Kingdom.svg",
        "国歌": "[[女王陛下万歳|{{lang|en|God Save the Queen}}]]{{en icon}}<br />神よ女王を護り賜え<br />{{center|[[ファイル:United States Navy Band - God Save the Queen.ogg]]}}",
        "国章リンク": "([[イギリスの国章|国章]])",
        "国章画像": "[[ファイル:Royal Coat of Arms of the United Kingdom.svg|85px|イギリスの国章]]",
        "国際電話番号": "44",
        "地図画像": "Europe-UK.svg",
        "夏時間": "+1",
        "建国形態": "建国",
        "日本語国名": "グレートブリテン及び北アイルランド連合王国",
        "時間帯": "±0",
        "最大都市": "ロンドン",
        "標語": "{{lang|fr|[[Dieu et mon droit]]}}<br />([[フランス語]]:[[Dieu et mon droit|神と我が権利]])",
        "水面積率": "1.3%",
        "略名": "イギリス",
        "確立年月日1": "927年/843年",
        "確立年月日2": "1707年{{0}}5月{{0}}1日",
        "確立年月日3": "1801年{{0}}1月{{0}}1日",
        "確立年月日4": "1927年{{0}}4月12日",
        "確立形態1": "[[イングランド王国]]/[[スコットランド王国]]<br />(両国とも[[合同法 (1707年)|1707年合同法]]まで)",
        "確立形態2": "[[グレートブリテン王国]]成立<br />(1707年合同法)",
        "確立形態3": "[[グレートブリテン及びアイルランド連合王国]]成立<br />([[合同法 (1800年)|1800年合同法]])",
        "確立形態4": "現在の国号「グレートブリテン及び北アイルランド連合王国」に変更",
        "通貨": "[[スターリング・ポンド|UKポンド]] (£)",
        "通貨コード": "GBP",
        "面積値": "244,820",
        "面積大きさ": "1 E11",
        "面積順位": "76",
        "首相等氏名": "[[ボリス・ジョンソン]]",
        "首相等肩書": "[[イギリスの首相|首相]]",
        "首都": "[[ロンドン]](事実上)"
    }


### 27. 内部リンクの除去

> 26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,テキストに変換せよ

リンクは

[[記事名]]
[[記事名|表示文字]]
[[記事名#節名|表示文字]]


の3種類がある。処理する際に巻き込みそうな他のwikiマークアップとしては,カテゴリやファイル指定,リダイレクト要素がある。記事にredirectが見当たらなかったので,categoryとファイル指定の除去だけ考える。
ここまで,全て一つの関数で行っているが,本来なら基礎助用法の読み取りなど,一つ一つを別の関数に定義するべきである,と思う。

[[Category:ヘルプ|はやみひよう]]
[[ファイル:Wikipedia-logo-v2-ja.png|thumb|説明文]]
#REDIRECT [[記事名]]
#REDIRECT [[記事名#節名]]

?!:当てはまらない時に正
?::キャプチャしないところを指定

# リンク除去の関数
def remove_links(text):
    p = re.compile(r'\[\[(?!Category\:ファイル)(?:[^|]*?\|)?([^|]*?)\]\]')
    return p.sub(r'\1', text)

def extract_basic_not_link_reinforce(wiki_text):
    result = {}
    ps = re.compile(r'\'{2,}') 
    p = re.compile(r'^\|(?!style)(\w+?)\s*\=\s*(.+?)(?:(?=\n\|))', re.MULTILINE) 
    removed_text = remove_links(ps.sub('', wiki_text)) # 変更した部分
    basics = p.findall(removed_text)
    for item in basics:
        result[item[0]] = item[1]
    return result

basic_info = extract_basic_not_link_reinforce(uk_article)
print(json.dumps(basic_info, sort_keys=True, indent=4, ensure_ascii=False))

```json
    {
        "GDP値": "2兆3162億<ref name=\"imf-statistics-gdp\" />",
        "GDP値MER": "2兆4337億<ref name=\"imf-statistics-gdp\" />",
        "GDP値元": "1兆5478億<ref name=\"imf-statistics-gdp\">[http://www.imf.org/external/pubs/ft/weo/2012/02/weodata/weorept.aspx?pr.x=70&pr.y=13&sy=2010&ey=2012&scsm=1&ssd=1&sort=country&ds=.&br=1&c=112&s=NGDP%2CNGDPD%2CPPPGDP%2CPPPPC&grp=0&a=IMF>Data and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom]</ref>",
        "GDP統計年": "2012",
        "GDP統計年MER": "2012",
        "GDP統計年元": "2012",
        "GDP順位": "6",
        "GDP順位MER": "6",
        "ccTLD": ".uk / .gb<ref>使用は.ukに比べ圧倒的少数。</ref>",
        "人口値": "6643万5600<ref>{{Cite web|url=https://www.ons.gov.uk/peoplepopulationandcommunity/populationandmigration/populationestimates|title=Population estimates - Office for National Statistics|accessdate=2019-06-26|date=2019-06-26}}</ref>",
        "人口大きさ": "1 E7",
        "人口密度値": "271",
        "人口統計年": "2018",
        "人口順位": "22",
        "他元首等氏名1": "ノーマン・ファウラー",
        "他元首等氏名2": "{{仮リンク|リンゼイ・ホイル|en|Lindsay Hoyle}}",
        "他元首等氏名3": "ブレンダ・ヘイル",
        "他元首等肩書1": "貴族院議長",
        "他元首等肩書2": "庶民院議長",
    }

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

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

基本情報の抜き出しから改めて定義して行ってみる。cite webやcenterがまだ取り出せていないので修正する必要がある。10章までいったら帰ってくるつもり。

# 基礎情報の抽出
def extract_basic(text):
    p = re.compile(r'^\|(?!style)(\w+?)\s*\=\s*(.+?)(?:(?=\n\|))', re.MULTILINE)
    basics = p.findall(text)
    return basics

# 除去関数
def remove_emphasis(text):
    p = re.compile(r'\'{2,}')
    return p.sub(r'', text)
def remove_links(text):
    p = re.compile(r'\[\[(?:[^|]*?\|)*?([^|]*?)\]\]')
    return p.sub(r'\1', text)
def remove_tags(text):
    p = re.compile(r'<[^>]*?>')
    return p.sub(r'', text)
def remove_lang(text):
    p = re.compile(r'\{\{lang(?:[^|]*?\|)*?([^|]*?)\}\}')
    return p.sub(r'\1', text)
def remove_ex_link(text):
    p = re.compile(r'\[http:\/\/(?:[^\s]*?)\s([^]]*?)\]')
    return p.sub(r'\1', text)


def main():
    basic_dict = {}
    basic_list = extract_basic(uk_article)
    for target in basic_list:
        explanation = remove_emphasis(target[1])
        explanation = remove_links(explanation)
        explanation = remove_tags(explanation)
        explanation = remove_lang(explanation)
        explanation = remove_ex_link(explanation)
        basic_dict[target[0]] = explanation
    print(json.dumps(basic_dict, sort_keys=True, indent=4, ensure_ascii=False))

if __name__ == '__main__':
    main()
    {
        "GDP値": "2兆3162億",
        "GDP値MER": "2兆4337億",
        "GDP値元": "1兆5478億and Statistics>World Economic Outlook Databases>By Countrise>United Kingdom",
        "GDP統計年": "2012",
        "GDP統計年MER": "2012",
        "GDP統計年元": "2012",
        "GDP順位": "6",
        "GDP順位MER": "6",
        <<以下略>>
    }

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

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

28の時点で"国旗画像": "Flag of the United Kingdom.svg"が取得できている。

import requests

def extract_basic_dict(article):
    basic_dict = {}
    basic_list = extract_basic(article)
    for target in basic_list:
        explanation = remove_emphasis(target[1])
        explanation = remove_links(explanation)
        explanation = remove_tags(explanation)
        explanation = remove_lang(explanation)
        explanation = remove_ex_link(explanation)
        basic_dict[target[0]] = explanation
    return basic_dict

basic_dict = extract_basic_dict(uk_article)
fname_flag = basic_dict['国旗画像']

def obtain_url(basic_dict, title):
    fname_flag = basic_dict[title].replace(' ', '_')
    url = 'https://en.wikipedia.org/w/api.php?' \
        + 'action=query' \
        + '&titles=File:' + fname_flag \
        + '&prop=imageinfo' \
        + '&iiprop=url' \
        + '&format=json'
    data = requests.get(url)
    return re.search(r'"url":"(.+?)"', data.text).group(1)


def main():
    basic_dict = extract_basic_dict(uk_article)
    query_url = obtain_url(basic_dict, "国旗画像")
    print(query_url)

if __name__ == '__main__':
    main()
    https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg
1
2
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
1
2