この記事では、言語処理100本ノック第2章の解説に引き続き、言語処理100本ノック第3章の解説を行っていきます。 第3章では、正規表現ライブラリやメタ文字を使用して、wikipediaの記事を処理していきます。 それでは、 第3章の問題を解きましたので、処理の流れとポイントを踏まえて、解説していきます。
問題と解説
Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.
- 1行に1記事の情報がJSON形式で格納される
- 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される
- ファイル全体はgzipで圧縮される
以下の処理を行うプログラムを作成せよ.
- jawiki-country.json.gzをダウンロードし、解凍する。
1 2 3 4 |
#ファイルをダウンロード !wget https://nlp100.github.io/data/jawiki-country.json.gz #ファイルの解凍 !gunzip ./jawiki-country.json.gz |
- 正規表現のメタ文字一覧:以下の正規表現の問題で使用するメタ文字の一覧。この一覧を参照して、問題を解く。
JSONデータの読み込み
Wikipedia記事のJSONファイルを読み込み,「イギリス」に関する記事本文を表示せよ.問題21-29では,ここで抽出した記事本文に対して実行せよ.
【コード】
1 2 3 4 5 6 7 8 |
import pandas as pd filename = "jawiki-country.json" j_data = pd.read_json(filename, lines =True) df = j_data uk_df = df[df["title"]=="イギリス"] uk_df = uk_df["text"].values print(uk_df) |
【出力結果】

【処理の流れ】
- pandasをimportする。
- jsonファイルを読み込み、DataFrameに変換する。
- DataFrameの”title”列データが”イギリス”である行だけを取得する。
- uk_df[“text”]列のデータを配列(numpy.ndarray)に変換し出力する。
【ポイント】
- pd.read_json()関数:第一引数に、Json形式の文字列を渡すことで、文字列がpd.DataFrameに変換される。JSON Linesで書かれたファイルを読み込みたい場合は、lines=Trueを指定する。
- DataFrameに比較演算子を使用する:DataFrameに比較演算子を使用すると、データをbool型として出力する。Tureとなる場所をDataFrameに指定することで、その行のデータを取得することができる。
- DataFrame.values属性(Series.values):pandasのDataFrameとSeriesオブジェクトに対して、values属性を指定すると、NumPy配列(ndarry)に変換できる。
カテゴリ名を含む行を抽出
記事中でカテゴリ名を宣言している行を抽出せよ.
【コード】
1 2 3 4 5 6 7 8 9 10 |
import re #uk_dfの要素数を確認 #print(len(uk_df)) #uk_dfのデータ型を確認 #print(type(uk_df[0]), "\n") for text in uk_df[0].split("\n"): if re.search("Category", text): print(text) |
【出力結果】

【処理の流れ】
- reをimportする。
- 20.で取得したデータの要素数と、データ型を確認する。(コメント)
- split(“\n”)で、uk_dfの要素を改行文字で区切り、re.search(“Category”, text)で、”Category”が書かれている行を出力する。
【ポイント】
カテゴリ名の抽出
記事のカテゴリ名を(行単位ではなく名前で)抽出せよ.
【コード】
1 2 3 4 |
for text in uk_df[0].split("\n"): if re.search("Category", text): text = text.replace("[[Category:", "").replace("|*]]", "").replace("]]", "") print(text) |
【出力結果】

【処理の流れ】
- 前半は、問21と同じなので説明を省略する。
- replace()を使用して、カテゴリ名以外の余計な文字列を削除して、出力する。
【ポイント】
- str.replace()メソッド:指定した文字列を別の文字列に置き換える。
セクション構造
記事中に含まれるセクション名とそのレベル(例えば”== セクション名 ==”なら1)を表示せよ.
【コード】
1 2 3 4 |
for text in uk_df[0].split("\n"): if re.search("^=+.*=+$", text): num = text.count("=") / 2 - 1 print(text.replace("=", ""), int(num)) |
【出力結果】

【処理の流れ】
- イギリスデータの要素を空白文字で区切り、反復させる 。
- 正規表現のメタ文字を使用して、セクション名をサーチする
- “=”の数ごとのレベルを取得し、セクション名とレベルを出力する
【ポイント】
- シーケンス型共通のcount()メソッド:引数に指定した要素をカウントする
ファイル参照の抽出
記事から参照されているメディアファイルをすべて抜き出せ.
【コード】
1 2 |
m_file = re.findall("ファイル:(.+?)\|", uk_df[0]) print("\n".join(m_file)) |
【出力結果】

【処理の流れ】
- イギリスデータを見ると、「ファイル:」の形でメディアファイルが記述されているので、findall(“ファイル:(.+?)|”, t_df[0])を使用する。
- 最後は、m_fileリストの要素を改行文字で、連結させる。
【ポイント】
- メディアファイル
- re.findall():マッチするすべての文字列をlist型にして返す。リストの要素は、strオブジェクト(matchオブジェクトではない事に注意)。
テンプレートの抽出
記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.
【コード】
1 2 3 4 5 6 |
dic={} for text in uk_df[0].split("\n"): if re.search("\|(.+?)\s=\s*(.+)", text): match_txt = re.search("\|(.+?)\s*=\s*(.+)", text) dic[match_txt[1]] = match_txt[2] print(dic) |
【出力結果】

【処理の流れ】
- 空のdictを作成し、イギリスデータの要素を改行文字で区切る。
- 「基本情報」テンプレートのフィールド名と値の形に注目して、正規表現のメタ文字を記述する。
- if re.search(“|(.+?)\s=\s*(.+)”, text):でTrueになった値にたいして、キーと値を指定し、dictに追加する。
【ポイント】
- 基礎情報テンプレートの「|フィールド名 = 値」で記述されている箇所を抽出する。
- re.search()メソッド:戻り値は、match型。インデックスを指定することで、このmatchオブジェクトから3種類の値を取得できる。インデックス0なら、マッチしたオブジェクトが存在する行、1ならマッチしたオブジェクトだけ、2ならマッチしたオブジェクト以外の値を返す。
強調マークアップの除去
25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)を除去してテキストに変換せよ(参考: マークアップ早見表).
【コード】
1 2 3 4 5 6 |
for text in uk_df[0].split("\n"): if re.search("\|(.+?)\s=\s*(.+)", text): match_txt = re.search("\|(.+?)\s=\s*(.+)", text) dic[match_txt[1]] = match_txt[2] match_sub = re.sub("\'{2,}(.+?)\'{2,}", "\\1", text) print(match_sub) |
【出力結果】

【処理の流れ】
- 前半は、問25と同じなので説明を省略する。
- re.sub(“‘{2,}(.+?)'{2,}”, “\1”, text)を使用して、マッチした文字列を(.+?)で置換する。
【ポイント】
- 例)「”’グレートブリテン及び北アイルランド連合王国”’」を「グレートブリテン及び北アイルランド連合王国」に変更する。
- re.sub():第一引数で、指定した正規表現とマッチした文字列を第二引数で、指定した文字列に置換する。戻り値は、str型。
- re.sub()メソッドの引数”\\1″:第一引数のメタ文字の一部を()で囲むと、マッチしたオブジェクトを()内の文字列で置換する。
内部リンクの除去
26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,テキストに変換せよ(参考: マークアップ早見表).
【コード】
1 2 3 4 5 6 7 |
for text in uk_df[0].split("\n"): if re.search("\|(.+?)\s=\s*(.+)", text): match_txt = re.search("\|(.+?)\s=\s*(.+)", text) dic[match_txt[1]] = match_txt[2] match_sub = re.sub("\'{2,}(.+?)\'{2,}", "\\1", text) match_sub = re.sub("\[\[(.+?)\]\]", "\\1", match_sub) print(match_sub) |
【出力結果】

【処理の流れ】
- 前半は、問26と同じなので説明を省略する。
- 26.で協調マークアップの除去を行ったように、内部リンクも除去して出力する。
【ポイント】
- ([[スコットランド・ゲール語]])を(スコットランド・ゲール語)に変更する。
- re.sub()を用いて、内部リンクマークを(.+?)に置換する。つまり、内部リンクマークを削除する。
MediaWikiマークアップの除去
27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.
【コード】
1 2 3 4 5 6 7 8 9 10 11 |
for text in uk_df[0].split("\n"): if re.search("\|(.+?)\s=\s*(.+)", text): match_sub = re.search("\|(.+?)\s=\s*(.+)", text) dic[match_sub[1]] = match_sub[2] match_sub = re.sub("\'{2,}(.+?)\'{2,}", "\\1", text) #強調マークアップの削除 match_sub = re.sub("\[\[(.+?)\]\]", "\\1", match_sub) #内部リンクマークアップの削除 match_sub = re.sub("\[(.+?)\]", "\\1", match_sub) #外部リンクマークアップの削除 match_sub = re.sub("\*+(.+?)", "\\1", match_sub) #*箇条書きの削除 match_sub = re.sub("\:+(.+?)", "\\1", match_sub)#定義の箇条書きの削除 match_sub = re.sub("\{\{(.+?)\}\}", "\\1", match_sub) #スタブなどを削除 print(match_sub) |
【出力結果】

【処理の流れ】
- 前半は、問27と同じなので説明を省略する。
- マークアップ早見表を確認し、該当するマークアップをre.sub()で削除していく。
【ポイント】
- マークアップ早見表でマークアップの一覧を確認する。
国旗画像のURLを取得する
テンプレートの内容を利用し,国旗画像のURLを取得せよ.(ヒント: MediaWiki APIのimageinfoを呼び出して,ファイル参照をURLに変換すればよい)
【コード】
1 2 |
#requestsライブラリのインストール !pip install requests |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import requests S = requests.Session() URL = "https://en.wikipedia.org/w/api.php" PARAMS = { "action": "query", "format": "json", "prop": "imageinfo", "titles": f"File:{dic['国旗画像']}", "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"]) |
【出力結果】

【処理の流れ】
- S.get(url=URL, params=PARAMS)にURLとparamsを指定して、URLの情報を取得する。
- R.json()で、取得した情報をJSON形式に変換する。
- [“query”][“pages”]の要素に国旗画像のURLが含まれているため、[“imageinfo”]のキーを指定して、値(国旗画像のURL)を取得する。
【ポイント】
- imageinfoのPythonコードを参照する。
- requestsモジュール:HTTP通信を行うためのライブラリ。
- requests.get()メソッド:URL先の情報を取得する。戻り値は、Response型。
- get()のparamsを指定すると、URLにクエリストリングとして指定される。今回は、URLを取得したいので、”iiprop”:”url”を指定。”titles”:”File(国旗画像のファイル名)”を指定することで、指定したファイル名を取得する。
- Response.json()メソッド:取得した情報をJSON形式で取得する。
おわりに
以上が、言語処理100本ノック第3章の解説になります。正規表現のメタ文字については、最初は理解する事に時間がかかるかもしれませんが、第3章を通して徐々に理解していくと思います。実装方法は、一通りではないので、自分なりのコードで挑戦してみてはいかかでしょうか。次は、言語処理100本ノック第4章の解説を行っていきます。
K.Y