事前に釈明
これは自分が夏休みにバイト戦士をやりながら寝る前の時間に眠い目を擦りながら作った物です。動きますが非効率的な部分、セキュアじゃない部分や未使用変数等が残ってるかもしれません。もし改善点を見つけた場合優しい言葉で教えてくれるとうれしいです。
今年の夏休み、うちのサークルの一回生の間で突然SlackBotを開発するムーブメントが起きました。その流行に便乗して作ったBotの機能の一つにWikipediaから575になっている部分を見つけ出してくれる機能を搭載しましたその実装の際に学んだことを備忘録程度に書いていきます。
ランダムなWikipediaページの本文取得
ランダムなWikipediaのページにアクセスするURL
先輩方が作ってたものでランダムなWikipediaのページを紹介するSlackBotがあったので方法があるのだろうと調べてみた結果以下のURLにアクセスすることでランダムなWikipediaの記事に飛ばされます。
http://ja.wikipedia.org/wiki/Special:Randompage
スクレイピング自体はC#で書いたことはあったのですがPythonで書くのは初めてだったので
https://qiita.com/poorko/items/9140c75415d748633a10
こちらのサイトを参考に、
import requests
import pandas as pd
from bs4 import BeautifulSoup
html=requests.get("http://ja.wikipedia.org/wiki/Special:Randompage").text
soup=BeautifulSoup(html,"html.parser")
for script in soup(["script", "style"]):
script.decompose()
と書きます。(引用元では改行ごとにリストに入れていますが575を検出する上で文を跨いで検出する必要はないのでこのリストのまま使いました)
575部分の検出
形態素解析
575を検出するということは言い換えると「文章の読みを見て単語単位で575に丁度良く区切れている」ことを検出することです。つまり、文章の読みと単語の区切れを見なければなりません。そこで活躍するのか形態素解析です。(厳密に言うと形態素と単語は同じではないが面倒なので深くは考えない)
ではまず文章の文字数を数える部分です。
def howmuch(moziyomi):
i = 0
for chara in moziyomi:
if chara == 'ー':
i = i + 1
for kana in [chr(i) for i in range(12449, 12532 + 1)]:
if chara == kana:
i = i + 1
if chara == 'ャ' or chara == 'ュ' or chara == 'ョ'or chara == 'ァ'or chara == 'ィ'or chara == 'ゥ'or chara == 'ェ'or chara == 'ォ':
i = i - 1
return (i)
Janomeで形態素解析を行うと読みは全角カタカナで返されます。なのでその返されたカタカナ文字列の文字数を数えます。伸ばし棒「ー」は一文字として数えまた「ッ」以外の小さいカタカナは無視します。
次に575判定部分です
fin = False
flag = False
for file in files:
# print(file)
s = file
if s.find('編集') > 0:
flag = True
if flag:
words = [token.surface for token in t.tokenize(s)]
hinsi = [token.part_of_speech.split(',')[0] for token in t.tokenize(s)]
yomi = [token.reading for token in t.tokenize(s)]
for i in range(len(words)):
if fin:
break
uta = ""
utayomi = ""
kami = ""
naka = ""
simo = ""
keyword = ""
if hinsi[i] == "名詞": # hinsi[i] == "動詞" or
keyword = words[i]
num = 0
utastat = 0
count = i
while num < 18 and count < len(yomi) and yomi[count].find("*") < 0:
num = num + howmuch(yomi[count])
uta = uta + words[count]
utayomi = utayomi + yomi[count]
if utastat == 0:
kami = kami + words[count]
if num > 5:
break
elif num == 5:
utastat = 1
elif utastat == 1:
naka = naka + words[count]
if num > 12:
break
elif num == 12:
utastat = 2
else:
simo = simo + words[count]
if num == 17:
if utayomi.find("。") >= 0:
continue
elif (utayomi.find("(") >= 0 and utayomi.find(")") >= 0) or (
utayomi.find("「") >= 0 and utayomi.find("」") >= 0) or (
utayomi.find("<") >= 0 and utayomi.find(">") >= 0) or (
utayomi.find("『") >= 0 and utayomi.find("』") >= 0):
fin = True
break
elif utayomi.find("(") >= 0 or utayomi.find(")") >= 0 or utayomi.find(
"「") >= 0 or utayomi.find("」") >= 0 or utayomi.find("<") >= 0 or utayomi.find(
">") >= 0 or utayomi.find("『") >= 0 or utayomi.find(
"』") >= 0:
continue
elif uta != "" and uta.find("リンク元") < 0:
fin = True
break
count = count + 1
ここでやっているのは
- 各行をチェックして「編集」という言葉が出てくるまでの行は無視する。
(そうしないと「メインページ」とか色々どのページでも共通する文字列を含んだりするので) - 名詞または動詞が来たらそこから文字列を数える。
(575は名詞か動詞スタートなら自然な川柳になるかなと思ったので) - 文字列の読みに「*」という記号が含まれているかどうか確認、含まれていたら次の名詞動詞を探してそこからやり直す。
(Janomeは数字などの読み方の分らない文字を「*」で返すので) - 単語で区切りながら見ていってちょうど575で区切れなかったら次の名詞や動詞を探してもう一度そこから数える。
- 「。」をまたぐと次の名詞や動詞を探してそこからやり直す
(575内で文をまたぐと不自然になるから) - カッコ記号の始まりがある場合575内に閉じるカッコが存在しているかの確認
(でもこの確認方法だと「」「とかは大丈夫になってしまい不充分) - 「リンク元」という文字列が575に含まれていたらやり直す
(「リンク元 関連ページ の更新」とか非特異的な川柳を返すので。)
さらにもし575が見つからなかった場合にはこれまでの操作をもう一度繰り返します。(もう一度ランダムなページにアクセスして同じことをする)
書いたクソコードの全容
def howmuch(moziyomi):
i = 0
for chara in moziyomi:
if chara == 'ー':
i = i + 1
for kana in [chr(i) for i in range(12449, 12532 + 1)]:
if chara == kana:
i = i + 1
if chara == 'ャ' or chara == 'ュ' or chara == 'ョ':
i = i - 1
return (i)
hujubun = True
while hujubun:
html = requests.get("http://ja.wikipedia.org/wiki/Special:Randompage").text
soup = bs4.BeautifulSoup(html, "html.parser")
for script in soup(["script", "style"]):
script.decompose()
text = soup.get_text()
# print(text)
t = Tokenizer()
files = text.split("\n")
fin = False
flag = False
for file in files:
# print(file)
s = file
if s.find('編集') > 0:
flag = True
if flag:
words = [token.surface for token in t.tokenize(s)]
hinsi = [token.part_of_speech.split(',')[0] for token in t.tokenize(s)]
yomi = [token.reading for token in t.tokenize(s)]
for i in range(len(words)):
if fin:
break
uta = ""
utayomi = ""
kami = ""
naka = ""
simo = ""
keyword = ""
if hinsi[i] == "名詞": # hinsi[i] == "動詞" or
keyword = words[i]
num = 0
utastat = 0
count = i
while num < 18 and count < len(yomi) and yomi[count].find("*") < 0:
num = num + howmuch(yomi[count])
uta = uta + words[count]
utayomi = utayomi + yomi[count]
if utastat == 0:
kami = kami + words[count]
if num > 5:
break
elif num == 5:
utastat = 1
elif utastat == 1:
naka = naka + words[count]
if num > 12:
break
elif num == 12:
utastat = 2
else:
simo = simo + words[count]
if num == 17:
if utayomi.find("。") >= 0:
continue
elif (utayomi.find("(") >= 0 and utayomi.find(")") >= 0) or (
utayomi.find("「") >= 0 and utayomi.find("」") >= 0) or (
utayomi.find("<") >= 0 and utayomi.find(">") >= 0) or (
utayomi.find("『") >= 0 and utayomi.find("』") >= 0):
fin = True
break
elif utayomi.find("(") >= 0 or utayomi.find(")") >= 0 or utayomi.find(
"「") >= 0 or utayomi.find("」") >= 0 or utayomi.find("<") >= 0 or utayomi.find(
">") >= 0 or utayomi.find("『") >= 0 or utayomi.find(
"』") >= 0:
continue
elif uta != "" and uta.find("リンク元") < 0:
fin = True
break
count = count + 1
if uta != "" and uta.find("リンク元") < 0 and uta.find("の下で利用") < 0:
hujubun = False
print(kami + "\n" + naka + "\n" + simo)
これで多分動くと思います。
コード自体はしっかりとした見直しもしていないので未使用変数とか明らかに非効率的な部分とかあるかもですが褒められて伸びる子なので指摘は本当にお手柔らかに……