LoginSignup
3
3

More than 3 years have passed since last update.

言語処理100本ノックをNLP屋がやってみる: 第3章 20~29

Posted at

はじめに

だいぶ間が空いてしまいました。お久しぶりです。
言語処理100本ノックの続き、今回は第3章をやっていきます。

過去分はこちら↓
第1章 00~09 - 第2章 10~19

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

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

ということで、人類が手にした最強兵器、正規表現に入門する章です。
あと、gzipファイルを読み込んだりjsonを読み込んだりすることに慣れていきます。
題材のgzファイルは本家から落とせますので手元にご用意を。

20. JSONデータの読み込み

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

この章は doctest がちょっと書きづらいので、ターミナルで確認する形にしておきます。
読み込むフォーマットは上にも引用したとおり、{"title": "タイトル", "text": "本文"} という感じです。各行がこのフォーマットのjsonになっているファイルが、gzipで圧縮されているファイルです。これを読み込むコードはこんな感じ。

nlp20.py
import gzip
import json
import re  # あとで使うのでimportしておきます


def load_json_gz(filename):
    """
    指定されたファイルを読み込みます。
    """
    with gzip.open(filename, 'rt', encoding='utf-8') as f:
        return {item['title']: item['text'] for item
                in [json.loads(line) for line in f]}


articles = load_json_gz('jawiki-country.json.gz')
england = articles['イギリス']
print(england)

gzip.open を使うと、普通にファイルを open するのと同じインターフェースで gz ファイルを読み込むことができます。楽ちん楽ちん。
各行をjsonとしてパースするのは json.loads メソッドを使います。
リスト内包表記で辞書にしてロード結果を返して、その結果から「イギリス」に関する内容をprintしています。
実行結果がこちら。

確認
$ python nlp20.py 
{{redirect|UK}}
{{基礎情報 国
|略名 = イギリス
|日本語国名 = グレートブリテン及び北アイルランド連合王国
...()

うん、イギリスの情報が取れていそうですね。

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

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

カテゴリ名の書き方については、 https://ja.wikipedia.org/wiki/Help:カテゴリ によると以下の通りだそうです。

同じカテゴリに属すページをまとめるには、[[Category:カテゴリ名]]あるいは、[[Category:カテゴリ名|ソートキー]]と記述します。

というわけで、「[[Category:」を含む行を探してあげれば良さそうです。
先程のコードの続きでカテゴリ名がある行を抽出してみましょう。

nlp20.py-つづき
category_lines = [line for line in england.split('\n') if '[[Category:' in line]
print(category_lines)

england の中には先程取り出した「イギリス」の記事本文が入っています。
リスト内包表記内ではこれを改行コードで分割し、if 文で「[[Category:」を含む行だけを取り出しています。
実行結果がこちら。

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

こちらも正しく取れていそうです。

22. カテゴリ名の抽出

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

いよいよ真面目に正規表現を使います。
先程引用したとおり、カテゴリ名のフォーマットは「[[Category:カテゴリ名]]あるいは、[[Category:カテゴリ名|ソートキー]]」です。
これをどちらもまとめて引っ掛けられる正規表現は \[\[Category:(.*?)(\|.*?)\]\] という感じでしょうか。
一個目のカッコ内が求めるカテゴリ名に当たります。
二個目のカッコはソートキーが指定されている場合にそれをカテゴリ名に含めないための指定です。

では、これで抽出できるか試してみましょうか。
今度も先程のコードの続きに書きます。

nlp20.py-さらにつづき
categories = [t[0] for t in re.findall(r'\[\[Category:(.*?)(\|.*?)?\]\]', england)]
print(categories)

re.findall を使って、指定した正規表現に適合する箇所をすべて取得しています。
t[0] には一個目のカッコの内容、t[1] には二個目のカッコの内容が入ってきます。
結果がこちら。

確認
$ python nlp20.py 
['イギリス', '英連邦王国', 'G8加盟国', '欧州連合加盟国', '海洋国家', '君主国', '島国', '1801年に設立された州・地域']

さきほどの 21 で抽出された各行のカテゴリ名が取れていますね。

23. セクション構造

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

nlp20.py-さらにつづき
for section in re.findall(r'(=+)([^=]+)\1\n', england):
    print('{}\t{}'.format(section[1].strip(), len(section[0])-1))

ここでは正規表現内でカッコに括った範囲を「\1」を利用して参照しています。

# 正規表現 意味 ===外交と軍事===\nでマッチする部分
1 (=+) 「=」1文字以上が続くだけ全部マッチ 最初の "==="
2 ([^=]+) 「=」以外の文字(i.e. [^=])が1文字以上続くだけ全部マッチ 外交と軍事
3 \1 #1の内容と同じものがここでマッチする。 二回目の "==="
4 \n 改行 \n

re.findall が返してくれる各マッチ箇所のデータには、[0] に1つ目のカッコ内の内容、つまり '=' の連続が、 [1] には 2つ目のカッコ内の内容すなわちセクション名が入っています。
なお、セクションのレベルは「=」の数-1になるので、 len(section[0])-1 としてレベルを算出しています。

確認
国名    1
歴史    1
地理    1
気候    2
政治    1
外交と軍事      1
地方行政区分    1
主要都市        2
科学技術        1
経済    1
鉱業    2
農業    2
貿易    2
通貨    2
企業    2
交通    1
道路    2
鉄道    2
海運    2
航空    2
通信    1
国民    1
言語    2
宗教    2
婚姻    2
教育    2
文化    1
食文化  2
文学    2
哲学    2
音楽    2
イギリスのポピュラー音楽        3
映画    2
コメディ        2
国花    2
世界遺産        2
祝祭日  2
スポーツ        1
サッカー        2
競馬    2
モータースポーツ        2
脚注    1
関連項目        1
外部リンク      1

24. ファイル参照の抽出

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

メディアファイルの書き方ですが、[[File:ファイル名]] あるいは [[ファイル:ファイル名]] が基本のようです。[[File:ファイル名|オプション]] の形式でファイル名の後にパイプ(|)区切りでオプション情報を付与することも出来ます。詳細はこちらを。

nlp20.py-つづき
for file in re.findall(r'\[\[(ファイル|File):([^]|]+?)(\|.*?)+\]\]', england):
    print(file[1])

そこで、こんな感じに正規表現を書いてあげれば、前項と同様に2つ目のカッコの中を取り出すことでファイル名だけを取り出すことが出来ます。

結果
Royal Coat of Arms of the United Kingdom.svg
Battle of Waterloo 1815.PNG
The British Empire.png
Uk topo en.jpg
BenNevis2005.jpg
Elizabeth II greets NASA GSFC employees, May 8, 2007 edit.jpg
Palace of Westminster, London - Feb 2007.jpg
David Cameron and Barack Obama at the G20 Summit in Toronto.jpg
Soldiers Trooping the Colour, 16th June 2007.jpg
Scotland Parliament Holyrood.jpg
London.bankofengland.arp.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 T5.jpg
Anglospeak.svg
CHANDOS3.jpg
The Fabs.JPG
Wembley Stadium, illuminated.jpg

25. テンプレートの抽出

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

テンプレートについてはこちらを見ていただければと思いますが、書式はさきほどのファイルの場合と似ていて、{{テンプレート名|オプション|オプション...}} といった書式です。
ただ実際のデータを見てみると、これまでとちがって{{から}}までの間に改行が入っていたりするようですね。
あと、前の24でも本当は気にすべきだったのですが、「オプション」の位置で更に {{テンプレート名}} の形式でテンプレートが入っていたりするようです。つまり、{{}} のカッコが入れ子になってしまっているんですね。

これは難題です。

というのも、正規表現は基本的に入れ子構造が苦手なんですね。事実上入れ子は標準的な正規表現では扱えません1

そこで、簡易的なパーサーを作って、ちゃんと入れ子構造も考慮した形にしてみました。
説明は・・・長くなるので省きます。

nlp20.py-つづき
def parse_article(article):
    """
    渡されたarticleの内容をパースします。
    基本的には文字列のリストになって帰ってきますが、
    テンプレート、内部リンクの場合はリストが一段深くネストされて帰ってきます。
    下の doctest を見たほうがわかりやすいかと。

    >>> parse_article('foo{{bar|opt1=value1|opt2={{template|opt1=value100|opt2=value101}}hoge[[link|printed]]|opt3=huga}}')
    ['foo', ['{{', 'bar', '|', 'opt1', '=', 'value1', '|', 'opt2', '=', ['{{', 'template', '|', 'opt1', '=', 'value100', '|', 'opt2', '=', 'value101', '}}'], 'hoge', ['[[', 'link', '|', 'printed', ']]'], '|', 'opt3', '=', 'huga', '}}']]
    """
    parsed = [[]]
    for s in re.findall(r'{{|}}|\[\[|\]\]|\||=|[^{}\[\]|=]+', article):
        if s in ('{{', '[['):
            parsed[-1].append([s])
            parsed.append(parsed[-1][-1])
        elif s in ('}}', ']]'):
            parsed[-1].append(s)
            parsed.remove(parsed[-1])
        else:
            parsed[-1].append(s)
    return parsed[0]


def join_all(nested_list):
    """
    リストを文字列として連結します。
    リスト内の要素にリストが有った場合、再帰的に連結を行います。

    >>> join_all(['a', ['b', ['c', 'd'], 'e'], 'f'])
    'abcdef'
    """
    return ''.join([join_all(i) if type(i) is list else i for i in nested_list])


result = {}
for item in parse_article(england):
    # 基礎情報〜という名前のテンプレートを探す
    if type(item) is list and item[0] == '{{' and item[1].startswith('基礎情報'):
        # テンプレート内の '|' のインデックスを全部見つける。
        option_indices = [i for i, s in enumerate(item) if s == '|']
        for i in option_indices:
            # '|' の次の要素がオプション名
            key = item[i+1]
            # オプションの値は、'|' の3つ先以降、'|' か '}}' が出てくるまで。
            value = itertools.takewhile(lambda s: s not in ('|', '}}'), item[i+3:])
            # 辞書に突っ込む。
            result[key.strip()] = join_all(value).strip()

pprint.pprint(result)

doctestは出力が長いときに改行が入れられないのが若干イマイチですね。
(doctestでそこまで頑張ったテストを書くなということかも)

これを実行すると、しっかりパースできたことがわかります。

結果
{'GDP/人': '36,727<ref name="imf-statistics-gdp" />',
 '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': '5',
 'ISO 3166-1': 'GB / GBR',
 'ccTLD': '[[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>',
 '人口値': '63,181,775<ref>http://esa.un.org/unpd/wpp/Excel-Data/population.htm '
        'United Nations Department of Economic and Social Affairs>Population '
        'Division>Data>Population>Total Population</ref>',
 '人口大きさ': '1 E7',
 '人口密度値': '246',
 '人口統計年': '2011',
 '人口順位': '22',
 '位置画像': 'Location_UK_EU_Europe_001.svg',
 '元首等氏名': '[[エリザベス2世]]',
 '元首等肩書': '[[イギリスの君主|女王]]',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern '
         'Ireland}}<ref>英語以外での正式国名:<br/>\n'
         '*{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu '
         'Thuath}}([[スコットランド・ゲール語]])<br/>\n'
         '*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd '
         'Iwerddon}}([[ウェールズ語]])<br/>\n'
         '*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na '
         'hÉireann}}([[アイルランド語]])<br/>\n'
         '*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon '
         'Glédh}}([[コーンウォール語]])<br/>\n'
         '*{{lang|sco|Unitit Kinrick o Great Breetain an Northren '
         'Ireland}}([[スコットランド語]])<br/>\n'
         '**{{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|イギリスの国章]]',
 '国際電話番号': '44',
 '夏時間': '+1',
 '建国形態': '建国',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '時間帯': '±0',
 '最大都市': 'ロンドン',
 '標語': '{{lang|fr|Dieu et mon droit}}<br/>([[フランス語]]:神と私の権利)',
 '水面積率': '1.3%',
 '注記': '<references />',
 '略名': 'イギリス',
 '確立年月日1': '[[927年]]/[[843年]]',
 '確立年月日2': '[[1707年]]',
 '確立年月日3': '[[1801年]]',
 '確立年月日4': '[[1927年]]',
 '確立形態1': '[[イングランド王国]]/[[スコットランド王国]]<br />(両国とも[[連合法 (1707年)|1707年連合法]]まで)',
 '確立形態2': '[[グレートブリテン王国]]建国<br />([[連合法 (1707年)|1707年連合法]])',
 '確立形態3': '[[グレートブリテン及びアイルランド連合王国]]建国<br />([[連合法 (1800年)|1800年連合法]])',
 '確立形態4': "現在の国号「'''グレートブリテン及び北アイルランド連合王国'''」に変更",
 '通貨': '[[スターリング・ポンド|UKポンド]] (&pound;)',
 '通貨コード': 'GBP',
 '面積値': '244,820',
 '面積大きさ': '1 E11',
 '面積順位': '76',
 '首相等氏名': '[[デーヴィッド・キャメロン]]',
 '首相等肩書': '[[イギリスの首相|首相]]',
 '首都': '[[ロンドン]]'}

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

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

ここで言う「弱い強調」「強調」「強い強調」は、現在のリンク先では「他との区別」「強調」「車体と強調」と名前が変わっているようです。
''他との区別'' '''強調''' '''''斜体と強調''''' と、シングルクォート 2 / 3 / 5個連続している場合に、除去してしまえばいいわけですね。25がめんどかったのに、急に簡単。

辞書に突っ込むところで、これらをリプレースしてしまいましょう。

nlp20.py-途中を差し替え
result = {}
for item in parse_article(england):
    # 基礎情報〜という名前のテンプレートを探す
    if type(item) is list and item[0] == '{{' and item[1].startswith('基礎情報'):
        # テンプレート内の '|' のインデックスを全部見つける。
        option_indices = [i for i, s in enumerate(item) if s == '|']
        for i in option_indices:
            # '|' の次の要素がオプション名
            key = item[i+1]
            # オプションの値は、'|' の3つ先以降、'|' か '}}' が出てくるまで。
            value = itertools.takewhile(lambda s: s not in ('|', '}}'), item[i+3:])
            # '' / ''' / ''''' を除去する。
            value_str = re.sub(r"'{2,3}|'{5}", '', join_all(list(value)))
            # 辞書に突っ込む。
            result[key] = value_str

pprint.pprint(result)

これで実行すると、強調が消えます。実際のデータでは ''' しかない気がしますが。

結果
{'GDP/人': '36,727<ref name="imf-statistics-gdp" />',
 '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': '5',
 'ISO 3166-1': 'GB / GBR',
 'ccTLD': '[[.uk]] / [[.gb]]<ref>使用は.ukに比べ圧倒的少数。</ref>',
 '人口値': '63,181,775<ref>http://esa.un.org/unpd/wpp/Excel-Data/population.htm '
        'United Nations Department of Economic and Social Affairs>Population '
        'Division>Data>Population>Total Population</ref>',
 '人口大きさ': '1 E7',
 '人口密度値': '246',
 '人口統計年': '2011',
 '人口順位': '22',
 '位置画像': 'Location_UK_EU_Europe_001.svg',
 '元首等氏名': '[[エリザベス2世]]',
 '元首等肩書': '[[イギリスの君主|女王]]',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern '
         'Ireland}}<ref>英語以外での正式国名:<br/>\n'
         '*{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu '
         'Thuath}}([[スコットランド・ゲール語]])<br/>\n'
         '*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd '
         'Iwerddon}}([[ウェールズ語]])<br/>\n'
         '*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na '
         'hÉireann}}([[アイルランド語]])<br/>\n'
         '*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon '
         'Glédh}}([[コーンウォール語]])<br/>\n'
         '*{{lang|sco|Unitit Kinrick o Great Breetain an Northren '
         'Ireland}}([[スコットランド語]])<br/>\n'
         '**{{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|イギリスの国章]]',
 '国際電話番号': '44',
 '夏時間': '+1',
 '建国形態': '建国',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '時間帯': '±0',
 '最大都市': 'ロンドン',
 '標語': '{{lang|fr|Dieu et mon droit}}<br/>([[フランス語]]:神と私の権利)',
 '水面積率': '1.3%',
 '注記': '<references />',
 '略名': 'イギリス',
 '確立年月日1': '[[927年]]/[[843年]]',
 '確立年月日2': '[[1707年]]',
 '確立年月日3': '[[1801年]]',
 '確立年月日4': '[[1927年]]',
 '確立形態1': '[[イングランド王国]]/[[スコットランド王国]]<br />(両国とも[[連合法 (1707年)|1707年連合法]]まで)',
 '確立形態2': '[[グレートブリテン王国]]建国<br />([[連合法 (1707年)|1707年連合法]])',
 '確立形態3': '[[グレートブリテン及びアイルランド連合王国]]建国<br />([[連合法 (1800年)|1800年連合法]])',
 '確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更',
 '通貨': '[[スターリング・ポンド|UKポンド]] (&pound;)',
 '通貨コード': 'GBP',
 '面積値': '244,820',
 '面積大きさ': '1 E11',
 '面積順位': '76',
 '首相等氏名': '[[デーヴィッド・キャメロン]]',
 '首相等肩書': '[[イギリスの首相|首相]]',
 '首都': '[[ロンドン]]'}

27. 内部リンクの除去

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

さっきよりは若干面倒な問題。
内部リンクには大きく二種類の書き方があるようで、[[記事名]] と記事に対してリンクしつつ、表示される文字も記事名そのものの場合と、[[記事名|表示文字]] と記事名と表示される文字が異なるパターン。
また、先に出てきた画像の場合は、[[画像ファイル名|オプション...|代替文]] というフォーマットになるため2、結局パイプ区切りの最後を取ればいいという結論になります。
[[Category:...]] という形式のカテゴリ表記に関してはここでは無視します。なぜならちょっと疲れてきたから。

nlp20.py-差し替え
result = {}
for item in parse_article(england):
    # 基礎情報〜という名前のテンプレートを探す
    if type(item) is list and item[0] == '{{' and item[1].startswith('基礎情報'):
        # テンプレート内の '|' のインデックスを全部見つける。
        option_indices = [i for i, s in enumerate(item) if s == '|']
        for i in option_indices:
            # '|' の次の要素がオプション名
            key = item[i+1]
            # オプションの値は、'|' の3つ先以降、'|' か '}}' が出てくるまで。
            value = itertools.takewhile(lambda s: s not in ('|', '}}'), item[i+3:])
            # '' / ''' / ''''' を除去する
            value_str = re.sub(r"'{2,3}|'{5}", '', join_all(value).strip())
            # 内部リンク(画像等も含む)を除去する。代替文があればそれを採用する。(つまりパイプ区切りの最後)
            value_str = re.sub(r"\[\[([^|\]]+\|)*(.*?)\]\]", "\\2", value_str)
            # 辞書に突っ込む。
            result[key.strip()] = value_str

pprint.pprint(result)

実行すると内部リンクが処理されていることがわかります。

結果
{'GDP/人': '36,727<ref name="imf-statistics-gdp" />',
 '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': '5',
 'ISO 3166-1': 'GB / GBR',
 'ccTLD': '.uk / .gb<ref>使用は.ukに比べ圧倒的少数。</ref>',
 '人口値': '63,181,775<ref>http://esa.un.org/unpd/wpp/Excel-Data/population.htm '
        'United Nations Department of Economic and Social Affairs>Population '
        'Division>Data>Population>Total Population</ref>',
 '人口大きさ': '1 E7',
 '人口密度値': '246',
 '人口統計年': '2011',
 '人口順位': '22',
 '位置画像': 'Location_UK_EU_Europe_001.svg',
 '元首等氏名': 'エリザベス2世',
 '元首等肩書': '女王',
 '公式国名': '{{lang|en|United Kingdom of Great Britain and Northern '
         'Ireland}}<ref>英語以外での正式国名:<br/>\n'
         '*{{lang|gd|An Rìoghachd Aonaichte na Breatainn Mhòr agus Eirinn mu '
         'Thuath}}(スコットランド・ゲール語)<br/>\n'
         '*{{lang|cy|Teyrnas Gyfunol Prydain Fawr a Gogledd '
         'Iwerddon}}(ウェールズ語)<br/>\n'
         '*{{lang|ga|Ríocht Aontaithe na Breataine Móire agus Tuaisceart na '
         'hÉireann}}(アイルランド語)<br/>\n'
         '*{{lang|kw|An Rywvaneth Unys a Vreten Veur hag Iwerdhon '
         'Glédh}}(コーンウォール語)<br/>\n'
         '*{{lang|sco|Unitit Kinrick o Great Breetain an Northren '
         'Ireland}}(スコットランド語)<br/>\n'
         '**{{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',
 '国歌': '神よ女王陛下を守り給え',
 '国章リンク': '(国章)',
 '国章画像': 'イギリスの国章',
 '国際電話番号': '44',
 '夏時間': '+1',
 '建国形態': '建国',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '時間帯': '±0',
 '最大都市': 'ロンドン',
 '標語': '{{lang|fr|Dieu et mon droit}}<br/>(フランス語:神と私の権利)',
 '水面積率': '1.3%',
 '注記': '<references />',
 '略名': 'イギリス',
 '確立年月日1': '927年/843年',
 '確立年月日2': '1707年',
 '確立年月日3': '1801年',
 '確立年月日4': '1927年',
 '確立形態1': 'イングランド王国/スコットランド王国<br />(両国とも1707年連合法まで)',
 '確立形態2': 'グレートブリテン王国建国<br />(1707年連合法)',
 '確立形態3': 'グレートブリテン及びアイルランド連合王国建国<br />(1800年連合法)',
 '確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更',
 '通貨': 'UKポンド (&pound;)',
 '通貨コード': 'GBP',
 '面積値': '244,820',
 '面積大きさ': '1 E11',
 '面積順位': '76',
 '首相等氏名': 'デーヴィッド・キャメロン',
 '首相等肩書': '首相',
 '首都': 'ロンドン'}

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

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

まあ、可能な限りきれいにしろってことですね。
ここでは、以下の3つを追加でやってみました。

というわけで再度差し替え版。

nlp20.py-差し替え
result = {}
for item in parse_article(england):
    # 基礎情報〜という名前のテンプレートを探す
    if type(item) is list and item[0] == '{{' and item[1].startswith('基礎情報'):
        # テンプレート内の '|' のインデックスを全部見つける。
        option_indices = [i for i, s in enumerate(item) if s == '|']
        for i in option_indices:
            # '|' の次の要素がオプション名
            key = item[i+1]
            # オプションの値は、'|' の3つ先以降、'|' か '}}' が出てくるまで。
            value = itertools.takewhile(lambda s: s not in ('|', '}}'), item[i+3:])
            # '' / ''' / ''''' を除去する
            value_str = re.sub(r"'{2,3}|'{5}", '', join_all(value).strip())
            # 内部リンク(画像等も含む)を除去する。代替文があればそれを採用する。(つまりパイプ区切りの最後)
            value_str = re.sub(r"\[\[([^|\]]+\|)*(.*?)\]\]", "\\2", value_str)
            # <ref> タグを除去。<ref ... /> の書き方と <ref ...>...</ref> の書き方がある。
            # (?s) は . を改行文字にもヒットさせるためのフラグ。
            value_str = re.sub(r"(?s)<ref[^>]*?/>|<ref[^>]*?>.*?</ref>", '', value_str)
            # {{lang|言語タグ|文字列}} を文字列部分だけにする
            value_str = re.sub(r"{{lang\|[^|]+\|(.*?)}}", '\\1', value_str)
            # <br> タグを \n に置換
            value_str = re.sub(r"<br\s*?/>", '\n', value_str)
            # 辞書に突っ込む。
            result[key.strip()] = value_str

pprint.pprint(result)

結果はこちら。

結果
{'GDP/人': '36,727',
 'GDP値': '2兆3162億',
 'GDP値MER': '2兆4337億',
 'GDP値元': '1兆5478億',
 'GDP統計年': '2012',
 'GDP統計年MER': '2012',
 'GDP統計年元': '2012',
 'GDP順位': '6',
 'GDP順位MER': '5',
 'ISO 3166-1': 'GB / GBR',
 'ccTLD': '.uk / .gb',
 '人口値': '63,181,775',
 '人口大きさ': '1 E7',
 '人口密度値': '246',
 '人口統計年': '2011',
 '人口順位': '22',
 '位置画像': 'Location_UK_EU_Europe_001.svg',
 '元首等氏名': 'エリザベス2世',
 '元首等肩書': '女王',
 '公式国名': 'United Kingdom of Great Britain and Northern Ireland',
 '公用語': '英語(事実上)',
 '国旗画像': 'Flag of the United Kingdom.svg',
 '国歌': '神よ女王陛下を守り給え',
 '国章リンク': '(国章)',
 '国章画像': 'イギリスの国章',
 '国際電話番号': '44',
 '夏時間': '+1',
 '建国形態': '建国',
 '日本語国名': 'グレートブリテン及び北アイルランド連合王国',
 '時間帯': '±0',
 '最大都市': 'ロンドン',
 '標語': 'Dieu et mon droit\n(フランス語:神と私の権利)',
 '水面積率': '1.3%',
 '注記': '',
 '略名': 'イギリス',
 '確立年月日1': '927年/843年',
 '確立年月日2': '1707年',
 '確立年月日3': '1801年',
 '確立年月日4': '1927年',
 '確立形態1': 'イングランド王国/スコットランド王国\n(両国とも1707年連合法まで)',
 '確立形態2': 'グレートブリテン王国建国\n(1707年連合法)',
 '確立形態3': 'グレートブリテン及びアイルランド連合王国建国\n(1800年連合法)',
 '確立形態4': '現在の国号「グレートブリテン及び北アイルランド連合王国」に変更',
 '通貨': 'UKポンド (&pound;)',
 '通貨コード': 'GBP',
 '面積値': '244,820',
 '面積大きさ': '1 E11',
 '面積順位': '76',
 '首相等氏名': 'デーヴィッド・キャメロン',
 '首相等肩書': '首相',
 '首都': 'ロンドン'}

だいぶ読みやすくなりましたね。

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

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

最後は急にAPI呼び出し。
上記のページを読むと、以下のようなリクエストを送ればAPIでURLが取得できるようです。

GET https://ja.wikipedia.org/w/api.php?action=query&format=json&prop=imageinfo&titles=File:Flag%20of%20the%20United%20Kingdom.svg&iiprop=url

実際これを vscode の REST client で流してみた結果がこちら。

{
  "continue": {
    "iistart": "2007-09-03T09:51:34Z",
    "continue": "||"
  },
  "query": {
    "normalized": [
      {
        "from": "File:Flag of the United Kingdom.svg",
        "to": "\u30d5\u30a1\u30a4\u30eb:Flag of the United Kingdom.svg"
      }
    ],
    "pages": {
      "-1": {
        "ns": 6,
        "title": "\u30d5\u30a1\u30a4\u30eb: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"
          }
        ]
      }
    }
  }
}

結果の query > pages > * > imageinfo[0] > url を取ればいいっぽいですね。
実際、pythonで呼び出してみましょう。
コードはさっきまでの続きです。result に記事「イギリス」の基礎情報テンプレートの内容のパース結果が入っています。

nlp20.py-つづき
api_url = 'https://ja.wikipedia.org/w/api.php?{}'
params = {
    'action': 'query',
    'format': 'json',
    'prop': 'imageinfo',
    'titles': 'File:{}'.format(result['国旗画像']),
    'iiprop': 'url'
}

req = urllib.request.Request(api_url.format(urllib.parse.urlencode(params)))
with urllib.request.urlopen(req) as res:
    j = json.loads(res.read())
print(list(j['query']['pages'].values())[0]['imageinfo'][0]['url'])

これでURLが表示されます。

結果
https://upload.wikimedia.org/wikipedia/commons/a/ae/Flag_of_the_United_Kingdom.svg

というわけで無事URLが表示されました。
第3章はこれでおしまい。25が難関でしたね。最初から真面目にパースしようとするのが結局一番早かった。
次回はいつになるかわかりませんが、また気が向いたら。


  1. .Netの正規表現などは拡張によって可能にしてるようです。 

  2. 本当はもう少しだけややこしいんですが、そのへんは wikipedia の説明を読んでみてください。 

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