LoginSignup
2
1

More than 5 years have passed since last update.

Python100本ノック(25/100)

Last updated at Posted at 2018-02-17

はじめに

Pythonを行き当たりばったりで触ってきたので
何処までPythonできているのかを見つめ直すために、自然言語処理100本ノックしていきます。
python2ばっかり使っていましたので、慣れるためにpython3でやっていきます。

Python100本ノック(25/100)の続きをやっていきます。

不慣れな章に入って苦戦しました。

一度終えてから、素人の言語処理100本ノックで答え合わせをしたのですが、ひどいありさまでした。
解答を見てからの修正したコードを追記します。


第3章: 正規表現

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

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

20. JSONデータの読み込み

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

import json
import gzip

path = 'jawiki-country.json.gz'
# wiki_jsons = [json.loads(line) for line in gzip.open(path, 'r').readlines()]
# filter(lambda x: x['title'] == 'イギリス', wiki_jsons)
for line in gzip.open(path, 'rt').readlines():
    page = json.loads(line)
    if page['title'] == 'イギリス':
        break
with open('england.json', 'w') as f:
    f.write(page['text'])
# print(page['text'])

途中でjson読み込みがうまくできなくて、困って検索すると100本ノックの解答を見てしまいました。
gzipというライブラリがあることがわかったので、pythonの中で完結させることにしました。
readlinesで、一行ごとにjson.loadsで読み込みました。
そして、titleがイギリスの行を抽出しました。

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

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

import gzip
import json
import re

path = 'jawiki-country.json.gz'
wiki_jsons = [json.loads(line) for line in gzip.open(path, 'r').readlines()]
text = list(filter(lambda x: x['title'] == 'イギリス', wiki_jsons))[0]['text']

# [line for line in text.split('\n') if 'Category:' in line]
# なにかあるはず……うまいことできるはず……
print([line for line in text.split('\n') if re.match(r'\[\[Category:.+\]\]', line) is not None])
['[[Category:イギリス|*]]', '[[Category:英連邦王国|*]]', '[[Category:G8加盟国]]', '[[Category:欧州連合加盟国]]', '[[Category:海洋国家]]', '[[Category:君主国]]', '[[Category:島国|くれいとふりてん]]', '[[Category:1801年に設立された州・地域]]']

jsonで戸惑いすぎて、3章が正規表現のことを完全に忘れていて苦戦しました。
行抽出なら、まだif inで抽出できるので、さらに混乱してました。
なんとか正規表現のことを思い出し, pythonの正規表現メモを参考にして解決しました。

print('\n'.join(re.findall(r'(^.*\[\[Category:.+\]\].*$)', text, re.MULTILINE)))

re.MULTILINEを指定することで、マルチラインへ対応
findallで結果のグループを配列で返すように変更

22. カテゴリ名の抽出

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

import gzip
import json
import re
path = 'jawiki-country.json.gz'
wiki_jsons = [json.loads(line) for line in gzip.open(path, 'r').readlines()]
text = list(filter(lambda x: x['title'] == 'イギリス', wiki_jsons))[0]['text']
reg = re.compile(r'\[\[Category:(?P<value>.+)\]\]')
print([reg.match(line).group('value') for line in text.split('\n') if reg.match(line) is not None])

# [line.strip('[[]]').split(':')[1] for line in text.split('\n') if 'Category:' in line]
# わからん……なんもわからん……
# パースする方法があるんちゃうんか?
# それともここからパースするんか…?

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

ここが、正規表現を使わないと簡単に解決できなくて悩んだ所です。
また、re.groupで特定箇所が抽出できること・名前付きグループができることを初めて知りました。
すごい!っと思いつつ、使いました。

reg = re.compile(r'\[\[Category:(?P<value>.+?)(?:\|.*)?\]\]')
print('\n'.join(reg.findall(text)))

findallを使うことで、ループを削除
|以降についていた文言を正規表現を修正して削除

23. セクション構造

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

import gzip
import json
import re
path = 'jawiki-country.json.gz'
wiki_jsons = [json.loads(line) for line in gzip.open(path, 'r').readlines()]
text = list(filter(lambda x: x['title'] == 'イギリス', wiki_jsons))[0]['text']
reg = re.compile(r'(?P<level>=+)\s?(?P<name>.+?)\s?=+')
# print([len(reg.match(line).group('level')) - 1, reg.match(line).group('name') for line in text.split('\n') if reg.match(line) is not None])
for line in text.split('\n'):
    result = reg.match(line)
    if result is None:
        continue
    print(len(result.group('level')) - 1, result.group('name'))
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 外部リンク

正規表現を変更して、セクション名の箇所を抽出しました。
そして、=の数でセクションレベルを判断しました。
セクション名のスペースの有無での違いがわからない。

内包表記を使って、1行で書けないことはないです。
ですが、re.matchを2回しなければいけないことが好きになれず、for文で書いています

reg = re.compile(r'^(?P<level>={2,})\s*(?P<name>.+?)\s*={2,}', re.MULTILINE)
print('\n'.join(['{0}: {1}'.format(n, len(l) - 1) for l, n in reg.findall(text)]))

正規表現のバグを修正
・複数の空白や行頭にない場合に取得するバグ
・=が1個しかない場合に発見するバグ

24. ファイル参照の抽出

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

import gzip
import json
import re
path = 'jawiki-country.json.gz'
wiki_jsons = [json.loads(line) for line in gzip.open(path, 'r').readlines()]
text = list(filter(lambda x: x['title'] == 'イギリス', wiki_jsons))[0]['text']
reg = re.compile(r'\[\[File:(?P<fname>.+?)\|')
# print([reg.match(line).group('fname') for line in text.split('\n') if reg.match(line) is not None])
for line in text.split('\n'):
    result = reg.match(line)
    if result is None:
        continue
    print(result.group('fname'))
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

正規表現を使い、File:がメディアファイルと思い、抽出しました。

reg = re.compile(r'(?:ファイル|File):(?P<fname>.+?)\|')
print('\n'.join(reg.findall(text)))

ファイル:galleryタグに存在するメディアファイルを取得できるように正規表現を修正


スーパー銭湯はいい。
でっかい風呂はいい。

2
1
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
2
1