言語処理100本ノック-29:国旗画像のURLを取得する


言語処理100本ノック 2015「第3章: 正規表現」29本目「国旗画像のURLを取得する」記録です。
今回は今まで正規表現を使って作成した辞書から特定項目の値を抜き出してWebサービスに投げます。

参考リンク

リンク 備考
029.国旗画像のURLを取得する.ipynb 回答プログラムのGitHubリンク
素人の言語処理100本ノック:29 多くのソース部分のコピペ元
ゼロから覚えるPython正規表現の基本とTips 当ノックで学習した内容を整理しました
正規表現 HOWTO Python公式の正規表現How To
re --- 正規表現操作 Python公式のreパッケージ説明
Help:早見表 Wikipediaの代表的なマークアップの早見表

環境

種類 バージョン 内容
OS Ubuntu18.04.01 LTS 仮想で動かしています
pyenv 1.2.15 複数Python環境を使うことがあるのでpyenv使っています
Python 3.6.9 pyenv上でpython3.6.9を使っています
3.7や3.8系を使っていないことに深い理由はありません
パッケージはvenvを使って管理しています

上記環境で、以下のPython追加パッケージを使っています。通常のpipでインストールするだけです。requestsパッケージを使おうか迷ったのですが、使うまでもないシンプルな内容だったので使っていません。使えば、もう少しコードが短くなったはずです。

種類 バージョン
pandas 0.25.3

第3章: 正規表現

学習内容

Wikipediaのページのマークアップ記述に正規表現を適用することで,様々な情報・知識を取り出します.

正規表現, JSON, Wikipedia, InfoBox, ウェブサービス

ノック内容

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

  • 1行に1記事の情報がJSON形式で格納される
  • 各行には記事名が"title"キーに,記事本文が"text"キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
  • ファイル全体はgzipで圧縮される

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

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

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

課題補足(「MediaWiki API」について)

MediaWiki APIに関して以下の2リンク先を参考にしました。
MediaWiki API:APIのメインページ
imageinfo:サンプルやパラメータの説明

回答

回答プログラム 029.国旗画像のURLを取得する.ipynb

from collections import OrderedDict
import json
import re
from urllib import request, parse

import pandas as pd

def extract_by_title(title):
    df_wiki = pd.read_json('jawiki-country.json', lines=True)
    return df_wiki[(df_wiki['title'] == title)]['text'].values[0]

wiki_body = extract_by_title('イギリス')

basic = re.search(r'''
                    ^\{\{基礎情報.*?\n  #検索語句(\はエスケープ処理)、非キャプチャ、非貪欲
                    (.*?)              #任意の文字列
                    \}\}               #検索語句(\はエスケープ処理)
                    $                  #文字列の末尾
                    ''', wiki_body, re.MULTILINE+re.VERBOSE+re.DOTALL)

templates = OrderedDict(re.findall(r'''
                          ^\|         # \はエスケープ処理、非キャプチャ
                          (.+?)       # キャプチャ対象(key)、非貪欲
                          \s*         # 空白文字0文字以上
                          =           # 検索語句、非キャプチャ
                          \s*         # 空白文字0文字以上
                          (.+?)       # キャプチャ対象(Value)、非貪欲
                          (?:         # キャプチャ対象外のグループ開始
                            (?=\n\|)  # 改行(\n)+'|'の手前(肯定の先読み)
                          | (?=\n$)   # または、改行(\n)+終端の手前(肯定の先読み)
                          )           # キャプチャ対象外のグループ終了
                         ''', basic.group(1), re.MULTILINE+re.VERBOSE+re.DOTALL))

# マークアップ除去
def remove_markup(string):

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

    # 内部リンク・ファイルの除去 
    # 除去対象:[[記事名]]、[[記事名|表示文字]]、[[記事名#節名|表示文字]]、[[ファイル:Wi.png|thumb|説明文]]
    replaced = re.sub(r'''
        \[\[             # '[['(マークアップ開始)
        (?:              # キャプチャ対象外のグループ開始
            [^|]*?       # '|'以外の文字0文字以上、非貪欲
            \|           # '|'
        )*?              # グループ終了、このグループが0以上出現、非貪欲(No27との変更点)
        (                # グループ開始、キャプチャ対象
          (?!Category:)  # 否定の先読(含んだ場合は対象外としている)
          ([^|]*?)    # '|'以外が0文字以上、非貪欲(表示対象の文字列)
        )
        \]\]        # ']]'(マークアップ終了)        
        ''', r'\1', replaced, flags=re.MULTILINE+re.VERBOSE)

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

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

    # HTMLタグの除去
    # 除去対象 <xx> </xx> <xx/>
    replaced = re.sub(r'''
        <           # '<'(マークアップの開始)
        .+?         # 1文字以上、非貪欲
        >           # '>'(マークアップの終了)
        ''', '', replaced, flags=re.MULTILINE+re.VERBOSE)

    return replaced

for i, (key, value) in enumerate(templates.items()):
    replaced = remove_markup(value)
    templates[key] = replaced

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

# MediaWikiのサービスへリクエスト送信
connection = request.urlopen(request.Request(url))

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

print(response['query']['pages']['-1']['imageinfo'][0]['url'])

回答解説

今回のメインは以下の部分です。
URLのパラメータに対して、正規表現で作成した辞書から「国旗画像」の値を取得しています(単純に取得するとスペースなどが混じってしまっているのでurllib.parse.quote関数を使ってエンコードしています)。
認証がないと楽勝です。

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

# MediaWikiのサービスへリクエスト送信
connection = request.urlopen(request.Request(url))

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

print(response['query']['pages']['-1']['imageinfo'][0]['url'])

出力結果(実行結果)

プログラム実行すると以下の結果が出力されます。

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

この画像です。