Edited at

素人の言語処理100本ノック:29

More than 1 year has passed since last update.

言語処理100本ノック 2015の挑戦記録です。環境はUbuntu 16.04 LTS + Python 3.5.2 :: Anaconda 4.1.1 (64-bit)です。過去のノックの一覧はこちらからどうぞ。


第3章: 正規表現


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

・1行に1記事の情報がJSON形式で格納される

・各行には記事名が"title"キーに,記事本文が"text"キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される

・ファイル全体はgzipで圧縮される

以下の処理を行うプログラムを作成せよ.



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


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



出来上がったコード:


main.py

# coding: utf-8

import gzip
import json
import re
import urllib.parse, urllib.request
fname = 'jawiki-country.json.gz'

def extract_UK():
'''イギリスに関する記事本文を取得

戻り値:
イギリスの記事本文
'''

with gzip.open(fname, 'rt') as data_file:
for line in data_file:
data_json = json.loads(line)
if data_json['title'] == 'イギリス':
return data_json['text']

raise ValueError('イギリスの記事が見つからない')

def remove_markup(target):
'''マークアップの除去
MediaWikiマークアップを可能な限り除去する

引数:
target -- 対象の文字列
戻り値:
マークアップを除去した文字列
'''

# 強調マークアップの除去
pattern = re.compile(r'''
(\'{2,5}) # 2〜5個の'(マークアップの開始)
(.*?) # 任意の1文字以上(対象の文字列)
(\1) # 1番目のキャプチャと同じ(マークアップの終了)
'''
, re.MULTILINE + re.VERBOSE)
target = pattern.sub(r'\2', target)

# 内部リンク、ファイルの除去
pattern = re.compile(r'''
\[\[ # '[['(マークアップの開始)
(?: # キャプチャ対象外のグループ開始
[^|]*? # '|'以外の文字が0文字以上、非貪欲
\| # '|'
)*? # グループ終了、このグループが0以上出現、非貪欲
([^|]*?) # キャプチャ対象、'|'以外が0文字以上、非貪欲(表示対象の文字列)
\]\] # ']]'(マークアップの終了)
'''
, re.MULTILINE + re.VERBOSE)
target = pattern.sub(r'\1', target)

# Template:Langの除去 {{lang|言語タグ|文字列}}
pattern = re.compile(r'''
\{\{lang # '{{lang'(マークアップの開始)
(?: # キャプチャ対象外のグループ開始
[^|]*? # '|'以外の文字が0文字以上、非貪欲
\| # '|'
)*? # グループ終了、このグループが0以上出現、非貪欲
([^|]*?) # キャプチャ対象、'|'以外が0文字以上、非貪欲(表示対象の文字列)
\}\} # '}}'(マークアップの終了)
'''
, re.MULTILINE + re.VERBOSE)
target = pattern.sub(r'\1', target)

# 外部リンクの除去 [http://xxxx] 、[http://xxx xxx]
pattern = re.compile(r'''
\[http:\/\/ # '[http://'(マークアップの開始)
(?: # キャプチャ対象外のグループ開始
[^\s]*? # 空白以外の文字が0文字以上、非貪欲
\s # 空白
)? # グループ終了、このグループが0か1出現
([^]]*?) # キャプチャ対象、']'以外が0文字以上、非貪欲(表示対象の文字列)
\] # ']'(マークアップの終了)
'''
, re.MULTILINE + re.VERBOSE)
target = pattern.sub(r'\1', target)

# <br>、<ref>の除去
pattern = re.compile(r'''
< # '<'(マークアップの開始)
\/? # '/'が0か1出現(終了タグの場合は/がある)
[br|ref] # 'br'か'ref'
[^>]*? # '>'以外が0文字以上、非貪欲
> # '>'(マークアップの終了)
'''
, re.MULTILINE + re.VERBOSE)
target = pattern.sub('', target)

return target

# 基礎情報テンプレートの抽出条件のコンパイル
pattern = re.compile(r'''
^\{\{基礎情報.*?$ # '{{基礎情報'で始まる行
(.*?) # キャプチャ対象、任意の0文字以上、非貪欲
^\}\}$ # '}}'の行
'''
, re.MULTILINE + re.VERBOSE + re.DOTALL)

# 基礎情報テンプレートの抽出
contents = pattern.findall(extract_UK())

# 抽出結果からのフィールド名と値の抽出条件コンパイル
pattern = re.compile(r'''
^\| # '|'で始まる行
(.+?) # キャプチャ対象(フィールド名)、任意の1文字以上、非貪欲
\s* # 空白文字0文字以上
=
\s* # 空白文字0文字以上
(.+?) # キャプチャ対象(値)、任意の1文字以上、非貪欲
(?: # キャプチャ対象外のグループ開始
(?=\n\|) # 改行+'|'の手前(肯定の先読み)
| (?=\n$) # または、改行+終端の手前(肯定の先読み)
) # グループ終了
'''
, re.MULTILINE + re.VERBOSE + re.DOTALL)

# フィールド名と値の抽出
fields = pattern.findall(contents[0])

# 辞書にセット
result = {}
for field in fields:
result[field[0]] = remove_markup(field[1])

# 国旗画像の値を取得
fname_flag = result['国旗画像']

# リクエスト生成
url = 'https://www.mediawiki.org/w/api.php?' \
+ 'action=query' \
+ '&titles=File:' + urllib.parse.quote(fname_flag) \
+ '&format=json' \
+ '&prop=imageinfo' \
+ '&iiprop=url'

# MediaWikiのサービスへリクエスト送信
request = urllib.request.Request(url,
headers={'User-Agent': 'NLP100_Python(@segavvy)'})
connection = urllib.request.urlopen(request)

# jsonとして受信
data = json.loads(connection.read().decode())

# URL取り出し
url = data['query']['pages'].popitem()[1]['imageinfo'][0]['url']
print(url)



実行結果:


端末

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


Webブラウザで得られたURLにアクセスしてみました。

Kobito.4Q8ynS.png

正しいURLが取得できているようです。


Webサービスの利用

Webサービスを使うにはHTTPをしゃべる必要があります。今回はお手軽そうなurllib.requestを使いました。

ただ、この解説に、


参考:より高水準の HTTP クライアントインターフェイスとしてはRequests package がお奨めです。


とあるので、そちらを使った方が良いのかもしれません。


MediaWiki API

プログラムの前半は前問のままです。前問で作った辞書から国旗画像のファイル名を取り出して、MediaWiki APIを使ってURLを取得しています。MediaWiki APIの使い方はAPI:メイン ページで調べられます。単純な例を参考に、HTTP GETで実装してみました。


アクセスするURL(エンドポイント)

エンドポイントに説明があります。とりあえずMediaWiki APIのURLhttps://www.mediawiki.org/w/api.phpを使っています。


User-Agentの指定

クライアントを識別するに説明されているように、User-Agentを省略することができません。正確なフォーマットは分かりませんが、クライアントを識別するための文字列を指定すれば良いようなので、NLP100_Python(@segavvy)としてみました。


パラメーター

今回は次のものを指定しています。

パラメーター
指定値
詳細

action
query

単純な例をそのまま真似しました。

titles
File:(国旗画像のファイル名)

API:Imageinfoで取得したい対象のファイルを指定します。なお、特殊文字のエスケープが必要なため、urllib.parse.quote()を使いました。

format
json

単純な例をそのまま真似して、json形式にしました。

prop
imageinfo

API:Imageinfoを使うことを示しています。

iiprop
url

API:Imageinfoで取得したい項目を指定します。今回はURLが欲しいのでurlを指定しました。


APIの戻り値

json形式を指定しているので、以前の問題で使用したjson.loads()で変換させれば、簡単に扱えるようになります。なお、受信結果はバイト列のため、json.loads()する前にbytes.decode()で文字列への変換が必要です。

以下、json.loads()したdataを、print(json.dumps(data, indent=4))した結果です。


端末

{

"continue": {
"continue": "||",
"iistart": "2007-09-03T09:51:34Z"
},
"query": {
"pages": {
"-1": {
"imagerepository": "shared",
"imageinfo": [
{
"url": "https://upload.wikimedia.org/wikipedia/commons/a/ae/Flag_of_the_United_Kingdom.svg",
"descriptionshorturl": "https://commons.wikimedia.org/w/index.php?curid=347935",
"descriptionurl": "https://commons.wikimedia.org/wiki/File:Flag_of_the_United_Kingdom.svg"
}
],
"missing": "",
"title": "File:Flag of the United Kingdom.svg",
"ns": 6
}
}
}
}

目的のurlが結構深いところにあるので、取り出しはちょっと面倒でした。

 

30本目のノックは以上です。誤りなどありましたら、ご指摘いただけますと幸いです。


実行結果には、100本ノックで用いるコーパス・データで配布されているデータの一部が含まれます。この第3章で用いているデータのライセンスはクリエイティブ・コモンズ 表示-継承 3.0 非移植日本語訳)です。