言語処理100本ノック2020(python)備忘録20-29

20

import gzip
import json
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
print(england_data)

出力は
dict_values(['イギリス', '{{redirect|UK}}
で始まり
[[Category:1801年に成立した国家・領域]]'])
で終わる。
json
{"title": "エジプト", "text": "長い文章"}
{"title": "長い文章...

となっており、辞書としてjline['title']とjline['text']でそれぞれの内容を取り出せるようにcountry_dict_listに追加していく。

openのオプションがrtであり、rと比べるとテキストとしてprintでき便利。
titleがイギリスである要素は1個であると仮定し、「england_data = line_dict['text']」の直後でbreakをしてもよい。

21

import gzip
import json
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()
for line in attr_split:
    if line[:11] == '[[Category:' or line[:7] == '[[カテゴリ:':
        print(line)

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

である。
#####より上は問題20のコードの表示以外と同じである。以降本記事内では同様。
カテゴリ名の宣言には、「[[Category:」以外にも「[[カテゴリ:」で始まるが、イギリスの記事には存在しなかった。

22

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()
for line in attr_split:
    if re.fullmatch(r'^\[\[Category:.*$|^\[\[カテゴリ:.*\]\]$', line):
        line = re.sub(r'^\[\[Category:', '', line)
        line = re.sub(r'^\[\[カテゴリ:', '', line)
        line = re.sub(r'\|[^|]+\]\]$', '', line)
        line = re.sub(r'\]\]$', '', line)
        print(line)

出力は
イギリス
イギリス連邦加盟国
英連邦王国
G8加盟国
欧州連合加盟国
海洋国家
現存する君主国
島国
1801年に成立した国家・領域

正規表現を用いた。
それぞれの行に対して、[[Category:で始まって]]で終わるか、[[カテゴリ:で始まって]]で終わるかをre.fullmatch()で判定し、
先頭と末尾から余分なものを順に削除。
if文のように|でつなげば一行で4処理を同時に書けるが、可読性に難あり。

23

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()
for line in attr_split:
    if re.fullmatch(r'^==.*',line):
        print(re.sub(r'^==+ ?| ?=+$', '', line), len(re.match(r'^==+', line).group())-1)

出力は
国名 1
歴史 1
地理 1
主要都市 2
気候 2
政治 1
元首 2
法 2
内政 2
地方行政区分 2
外交・軍事 2
経済 1
鉱業 2
農業 2
貿易 2
不動産 2
エネルギー政策 2
通貨 2
企業 2
通信 3
交通 1
道路 2
鉄道 2
海運 2
航空 2
科学技術 1
国民 1
言語 2
宗教 2
婚姻 2
移住 2
教育 2
医療 2
文化 1
食文化 2
文学 2
哲学 2
音楽 2
ポピュラー音楽 3
映画 2
コメディ 2
国花 2
世界遺産 2
祝祭日 2
スポーツ 2
サッカー 3
クリケット 3
競馬 3
モータースポーツ 3
野球 3
カーリング 3
自転車競技 3
脚注 1
関連項目 1
外部リンク 1

==で始まる行をfullmatch()で見つけ、
前後の=と半角スペースを除去し、==で始まる=の数-1で階層を計算。

24

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####    
attr_split = england_data.splitlines()
pattern = re.compile(r"^\[\[ファイル:.*")
for line in attr_split:
    if re.fullmatch(r'^\[\[ファイル:.*', line):
        print(line.split('|')[0].replace('[[ファイル:',''))

出力は
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
Leeds CBD at night.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
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}}英語以外での正式国名:
',」などになる。

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()
template_dict = dict()
mode = 0 # if in 基礎情報 mode=1, else mode=0

for line in attr_split:
    if re.search(r'^\{\{基礎情報', line):
        mode = 1
    if mode == 1:
        if line == '}}':
            break;
        m = re.findall(r'[^=]+=.*', line)
        for item in m:
            mm = re.findall(r'[^=]+', item)
            template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()

出力は
{'GDP/人': '36,727',
'GDP値': '2兆3162億',
'GDP値MER': '2兆4337億',

で始まり
'首相等氏名': '[[ボリス・ジョンソン]]',
'首相等肩書': '[[イギリスの首相|首相]]',
'首都': '[[ロンドン]](事実上)'}

で終わる。
基礎情報である間は1、それ以外で0となる、modeを使った。
文に「{{基礎情報」があればmode=1、文全体が「}}」ならばmode=0とした。
=でつながれた行において、
左辺から先頭の「|」と末尾の空白を除いたものをキー、右辺の末尾の空白を除いたものを値として辞書にした。
wikipediaの記法について詳しければ更に正しくパースできると思う。
問題26では{{と}}の数を数えたものも実装したが、結局行ごとの処理である。

26

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()    

template_dict = dict()
mode = 0 # if in 基礎情報 mode=1, else mode=0
depth = 0
for line in attr_split:
    if re.search(r'^\{\{基礎情報',line):
        mode = 1
    if mode == 1:
        if re.findall(r'\{\{', line): #{{でdepthを{{の個数だけ増やす
            depth += len(re.findall(r'\{\{', line))
        
        if 0 < depth: #もし{{の内部なら
            m = re.findall(r'[^=]+=.*', line) 
            for item in m: # hoge = fuga があれば
                mm = re.findall(r'[^=]+', item) # 「=」を含まない場所を配列で取得
                if re.findall(r'\}\}', line) and (depth == len(re.findall(r'\}\}', line))): #この行で終わる場合}}を除く
                        template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()[:-2]
                else:
                    template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()

        if re.findall(r'\}\}', line):
            depth -= len(re.findall(r'\}\}',line))
            if depth <= 0:
                mode = 0
for key, value in template_dict.items():
    value = value.replace('\'\'\'','').replace('\'\'','') #「強調」の削除。
    print(key, value)

出力は
略名 イギリス
日本語国名 グレートブリテン及び北アイルランド連合王国
公式国名 {{lang|en|United Kingdom of Great Britain and Northern Ireland}}英語以外での正式国名:

で始まり
ccTLD [[.uk]] / [[.gb]]使用は.ukに比べ圧倒的少数。
国際電話番号 44
注記

で終わる。

modeに関しては問題25と同じ。
depthによって{{が何個あったか?を意味し、対応する個数}}を受け取ったら基礎情報の終わりとした。
強調マークアップアポストロフィ3個で囲んだものと2個で囲んだものがある。
先に2個で囲んだものを置換(=replace)すると、3個で囲んだものが1個で囲んだものになってしまうため、先に3個で囲んだものを置換している。(正確には、囲んでいるか?は気にしていない)

27

下から3行以外は問題26と同様。

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
    
attr_split = england_data.splitlines()    

template_dict = dict()
mode = 0 # if in 基礎情報 mode=1, else mode=0
depth = 0
for line in attr_split:
    if re.search(r'^\{\{基礎情報',line):
        mode = 1
    if mode == 1:
        if re.findall(r'\{\{', line): #{{でdepthを{{の個数だけ増やす
            depth += len(re.findall(r'\{\{', line))
        
        if 0 < depth: #もし{{の内部なら
            m = re.findall(r'[^=]+=.*', line) 
            for item in m: # hoge = fuga があれば
                mm = re.findall(r'[^=]+', item) # 「=」を含まない場所を配列で取得
                if re.findall(r'\}\}', line) and (depth == len(re.findall(r'\}\}', line))): #この行で終わる場合、}}を除くため別処理
                        template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()[:-2]
                else:
                    template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()

        if re.findall(r'\}\}', line):
            depth -= len(re.findall(r'\}\}',line))
            if depth <= 0:
                mode = 0
for key, value in template_dict.items():
    value = value.replace('\'\'\'','').replace('\'\'','') # マークアップ「強調」の削除。
    value = re.sub(r'\[\[([^\[]*?)\|([^\]]*)\]\]',r'\2', value) #リンク記法削除1 [[hoge|fuga]]->fuga
    value = value.replace('[[','').replace(']]','') #リンク記法削除2 [[hoge]]->hoge
    print(key, value)

出力は
略名 イギリス
日本語国名 グレートブリテン及び北アイルランド連合王国
公式国名 {{lang|en|United Kingdom of Great Britain and Northern Ireland}}英語以外での正式国名:

で始まり
ccTLD .uk / .gb使用は.ukに比べ圧倒的少数。
国際電話番号 44
注記

で終わる。

リンク記法を削除した。
#リンク記法削除1 の
\[\[([^\[]*?)\|([^\]]*)\]\]
は、[[リンク先|表示名]]なので表示名だけを残す。
\2で2個目の丸括弧内を取得し、sub()の第2引数(置換後の文字列)とした。
その後、単なるリンク[[リンク先]](表示名も同じ)のリンク記法を[[と]]の削除によって削除。
正確な処理では、「|」を含むリンクやそのエスケープ手法があるかもしれないが、妥協。

28

下から8行以外は問題26と同様。

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
#####
attr_split = england_data.splitlines()    

template_dict = dict()
mode = 0 # if in 基礎情報 mode=1, else mode=0
depth = 0
for line in attr_split:
    if re.search(r'^\{\{基礎情報',line):
        mode = 1
    if mode == 1:
        if re.findall(r'\{\{', line): #{{でdepthを{{の個数だけ増やす
            depth += len(re.findall(r'\{\{', line))
        
        if 0 < depth: #もし{{の内部なら
            m = re.findall(r'[^=]+=.*', line) 
            for item in m: # hoge = fuga があれば
                mm = re.findall(r'[^=]+', item) # 「=」を含まない場所を配列で取得
                if re.findall(r'\}\}', line) and (depth == len(re.findall(r'\}\}', line))): #この行で終わる場合、}}を除くため別処理
                        template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()[:-2]
                else:
                    template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()

        if re.findall(r'\}\}', line):
            depth -= len(re.findall(r'\}\}',line))
            if depth <= 0:
                mode = 0
for key, value in template_dict.items():
    value = value.replace('\'\'\'','').replace('\'\'','') # マークアップ「強調」の削除。
    value = re.sub(r'\[\[([^\[]*?)\|([^\]]*)\]\]',r'\2', value) #リンク記法削除1 [[hoge|fuga]]->fuga
    value = value.replace('[[','').replace(']]','') #リンク記法削除2 [[hoge]]->hoge
    value = value.replace('{{en icon}}', '') # {{en icon}}削除
    value = re.sub(r'\d+px\|', '', value) # 画像サイズを表す 「85px|」 を削除(汎用的に 「[数字]px|」を削除
    value = value.replace('{{0}}',' ') # {{0}}は文字「0」と同じ幅の空白なので{{}}の削除のみではないとした
    value = re.sub(r'\{\{.*?([^\|\}]*?)\}\}',r'\1', value)   # {{}}の削除(最後の要素のみ残す
    value = re.sub(r'<ref>.*?(</ref>|$)', '', value) #<ref>~</ref> と <ref>~で終わるものを削除。
    value = re.sub(r'<ref name".*?(" />|</ref>)', '', value)
    value = value.replace('<br />',' ') #改行コードの削除
    print(key, value)

出力は
略名 イギリス
日本語国名 グレートブリテン及び北アイルランド連合王国
公式国名 United Kingdom of Great Britain and Northern Ireland

で始まり
ccTLD .uk / .gb
国際電話番号 44
注記

で終わる。
コメントに書いたとおり、それぞれのMediaWikiマークアップなどを削除した。
公式国名の抽出が不適格なため、が閉じないまま行末に至るものも削除している。

29

#######までは問題28と同様。

import gzip
import json
import re
country_dict_list = []
with gzip.open('jawiki-country.json.gz', 'rt') as r:
    data = r.readlines()
for line in data:
    country_dict_list.append(json.loads(line))
for line_dict in country_dict_list:
    if line_dict['title'] == 'イギリス':
        england_data = line_dict['text']
    
attr_split = england_data.splitlines()    

template_dict = dict()
mode = 0 # if in 基礎情報 mode=1, else mode=0
depth = 0
for line in attr_split:
    if re.search(r'^\{\{基礎情報',line):
        mode = 1
    if mode == 1:
        if re.findall(r'\{\{', line): #{{でdepthを{{の個数だけ増やす
            depth += len(re.findall(r'\{\{', line))
        
        if 0 < depth: #もし{{の内部なら
            m = re.findall(r'[^=]+=.*', line) 
            for item in m: # hoge = fuga があれば
                mm = re.findall(r'[^=]+', item) # 「=」を含まない場所を配列で取得
                if re.findall(r'\}\}', line) and (depth == len(re.findall(r'\}\}', line))): #この行で終わる場合、}}を除くため別処理
                        template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()[:-2]
                else:
                    template_dict[mm[0].lstrip('|').rstrip()] = ''.join(mm[1:]).lstrip()

        if re.findall(r'\}\}', line):
            depth -= len(re.findall(r'\}\}',line))
            if depth <= 0:
                mode = 0
#######
import requests
S = requests.Session()
URL = "https://en.wikipedia.org/w/api.php"
PARAMS = {
    "action": "query",
    "format": "json",
    "prop": "imageinfo",
    "titles": 'File:'+template_dict['国旗画像'],
    'iiprop': 'url'
}
R = S.get(url=URL, params=PARAMS)
DATA = R.json()
PAGES = DATA["query"]["pages"]
for k, v in PAGES.items():
    print(v['imageinfo'][0]['url'])

出力は
https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg:image=https://upload.wikimedia.org/wikipedia/en/a/ae/Flag_of_the_United_Kingdom.svg

参考:API:Imageinfo - MediaWiki
ほぼ参考リンクのまま書くだけである。
呼び出し方は「v["imageinfo"][0]["user"]」の例のuserをurlに変更した。
「Gives URL to the file and the description page. If the file has been revision deleted, a filehidden property will be returned.」と書かれているため、このurlという要素を呼び出すとそのままurlである。