この記事でやりたいこと
- 東方作中のセリフをまとめているサイトRadical Discoveryからセリフをいい感じに抽出する。
- いい感じとは、
- dialoguesディレクトリを作成する。
- その中に、dialogue_n.txt(nは整数)を作成する。
- そのファイルに、[キャラ名:セリフ]の形式で一文ずつ書き出す。
- 話題の分かれ目は何もない行を挿入する。
- こうすると他のプログラムからデータを扱いやすい。
- です。
- いい感じとは、
かなり雑に組まれているのでコード自体は汎用性は低いです。
まずはソースから
vacuum_data.py
import requests
from bs4 import BeautifulSoup
def main_soup_from_url(url):
response = requests.get(url)
response.encoding = response.apparent_encoding
main_soup = BeautifulSoup(response.text, 'html.parser')
return main_soup
def get_urls(original_url):
main_soup = main_soup_from_url(original_url)
urls = []
a_tag = main_soup.find_all('a')
for a in a_tag:
url = a.attrs['href']
if url[:7] != 'http://' and url.find('toho/serihu/') == 0 and url != 'toho/serihu/serihu.htm':
url = original_url + url
urls.append(url)
return urls
def rsplit_characters(text, rsp_chrs):
for rsp_chr in rsp_chrs:
text = text.rsplit(rsp_chr)[0]
return text
def replace_characters(text,rep_chrs):
for rep_chr in rep_chrs:
text = text.replace(rep_chr[0],rep_chr[1])
return text
def delete_characters(text, del_chrs):
for del_chr in del_chrs:
text = text.replace(del_chr, '')
return text
def all_delete(text, all_dels):
for all_del in all_dels:
sta_n = text.find(all_del[0])
end_n = text.find(all_del[1])
while sta_n > -1 and end_n > -1:
text = text[:sta_n] + text[end_n + 1:]
sta_n = text.find(all_del[0])
end_n = text.find(all_del[1])
return text
def end_find(text, end_chrs):
end_n = len(text)
for end_chr in end_chrs:
find_n = text.find(end_chr)
if find_n > -1 and find_n < end_n:
end_n = find_n
if end_n < len(text):
return end_n
else:
return -1
def match_characters(aim_chr,mat_chrs):
for i in range(len(mat_chrs)):
if aim_chr == mat_chrs[i]:
return i
return -1
def get_dialogue(url):
main_soup = main_soup_from_url(url)
td_tag = main_soup.find_all('td', class_='word')
if len(td_tag) > 0:
return get_dialogue_float(main_soup)
else:
return get_dialogue_int(main_soup)
def get_dialogue_int(main_soup):
font_tag = main_soup.find_all('font')
texts = []
blank = True
sep_chr = ':'
del_chrs = ['\n', ' ']
sta_chrs = ['(']
fin_chrs = [')']
end_chrs = '。!?〜~)♪'
for value in font_tag:
text = delete_characters(value.text, del_chrs)
name_n = text.find(':')
if(name_n > -1):
name = text[:name_n]
text = text[name_n + 1:]
end_n = end_find(text, end_chrs)
while end_n > -1:
while end_n < len(text) and match_characters(text[end_n], end_chrs) > -1:
if match_characters(text[end_n], fin_chrs) > -1:
end_n += 1
if match_characters(text[0], sta_chrs) > -1:
break
else:
end_n += end_find(text[end_n:], end_chrs)
end_n += 1
texts.append(name + sep_chr + text[:end_n])
text = text[end_n:]
end_n = end_find(text, end_chrs)
if len(text) > 0:
texts.append(name + sep_chr + text)
blank = False
elif(not blank):
texts.append('')
blank = True
return texts
def get_dialogue_float(main_soup):
td_tag = main_soup.find_all('td', class_='word')
texts = []
blank = True
new_sentence = True
sep_chr = ':'
rep_chrs = [['\n\n','\t'],['〜','~']]
del_chrs = ['\n', '\u3000', '\xa0', ' ', ' ']
all_dels = [['【','】']]
sta_chrs = ['「','(']
fin_chrs = ['」',')']
end_chrs = ['。', '!', '?', '~', '♪'] + fin_chrs
sentence_sta = 0
for value in td_tag:
text = value.text
text = replace_characters(text, rep_chrs)
text = delete_characters(text, del_chrs)
text = all_delete(text, all_dels)
if end_find(text,sta_chrs) > -1:
end_n = end_find(text, end_chrs)
while end_n > -1:
name_n = end_find(text, sta_chrs)
if name_n > 0 and (name_n < end_find(text, end_chrs) or new_sentence):
sentence_sta = match_characters(text[name_n],sta_chrs)
name = text[:name_n]
if name[0] == '\t':
texts.append('')
name = name.replace('\t','')
text = text[name_n + 1:]
end_n = end_find(text, end_chrs)
new_sentence = False
try:
while match_characters(text[end_n], end_chrs) > -1:
if text[end_n] == fin_chrs[sentence_sta]:
new_sentence = True
if match_characters(text[end_n - 1], end_chrs) > -1:
text = text.replace(text[end_n], '', 1)
else:
text = text.replace(text[end_n], '。', 1)
end_n += 1
break
end_n += 1
except:
global urls,i
print('error:',urls[i])
texts.append(name + sep_chr + text[:end_n])
text = text[end_n:]
end_n = end_find(text, end_chrs)
if len(text) > 0:
texts.append(name + sep_chr + text)
blank = False
if(not blank):
texts.append('')
blank = True
return texts
# メインのコード
url = 'http://radical-d.extrem.ne.jp/'
urls = get_urls(url)
for i in range(len(urls)):
texts = get_dialogue(urls[i])
with open('dialogues/dialogue_' + str(i) + '.txt', 'w',encoding = 'utf-8') as file:
print(urls[i])
for text in texts:
file.write(text + '\n')
print('finish:',i,'/',len(urls))
内容解説
関数
-
get_urls(url)
- Radical Discovery上のaタグからセリフが入っているものだけを抽出する。
- 旧作は入れてないです。
-
get_dialogue(url)
- URLから会話を抽出する。
- ここで問題なのが、セリフの表記方法で、
- [キャラ名「セリフ」]
- たまに[キャラ名(セリフ)]になる。
- [キャラ名:セリフ]
- [キャラ名「セリフ」]
- が混在している。
- 後から気づいたのですが、新版のサイトが公開されていて、そちらでは表記がちゃんと揃っています。ちゃんと下調べしてから実装していきましょう。
- しかも、そのセリフがあるタグがfontタグかtdタグかも異なる。
- なので、実際の処理はHTML全体でのtdタグの有無でget_dialogue_floatとget_dialogue_intに分けている。
- これらにはURLではなく、main_soup_from_url(url)を使ってHTMLの構造を渡している。
-
main_soup_from_url(url)
- URLからHTMLの構造を出力する。
- requests.get → 文字コードを合わせる → BeautifulSoupという流れで取得できる。
- URLからHTMLの構造を出力する。
-
get_dialogue_float(main_soup)
- [キャラ名「セリフ」]から一文ずつ[キャラ名:セリフ]にしてまとめたテキストを出力する。
- ここでいう一文は[。]や[!]が出てきたら終わるものを指します。
- 元のセリフでは文が繋がっているものが多いので分けていく。
- !?など、終止符が一つで終わらない場合もあるのでwhileで終止符以降を1文字ずつ判断していく。
- ただし、[」]で終わる場合は。がない場合があるのでちゃんと文末と判断して[。]をつけてあげる処理が必要。
- 制御文字がたくさんあるので適切に排除していく。
- \u3000や\xa0がある。何用かはよくわからない。
- 話題の分け目の判断に必要な\n\nはいったん\tに変換しておき、\nを消してから\tを\nに変換する。
- 一般的ではない伸ばし棒[〜(波ダッシュ)]を[~(全角チルダ)]に変換しておく。
- Qiita上では同じに見えるが、違う文字コード。
- 使用用途としては[〜(波ダッシュ)]が正しいが、キーボードで一発で打てないので、[~(全角チルダ)]を使う……
- ここでいう一文は[。]や[!]が出てきたら終わるものを指します。
-
- 非整数番台の作品の一部に[キャラ名「セリフ」]の表記が見られたのでfloatと名付けた。
- [キャラ名「セリフ」]から一文ずつ[キャラ名:セリフ]にしてまとめたテキストを出力する。
-
get_dialogue_int(main_soup)
- [キャラ名:セリフ]から一文ずつ[キャラ名:セリフ]にしてまとめたテキストを出力する。
- ここでの[~]は普通の[~]なのでスルーしてよい。
- floatより幾分か処理がすっきりしている。実装も楽だった。
- hrタグで対戦前後のセリフが分かれているが、これをデータに反映させるのは面倒なのでパスした。
-
それ以外の関数
- 中身が簡素なので、どんな処理かはご自身で確かめてみてください……(説明が面倒になっただけ)
実行について
- サーバへの負荷もありますし、時間がかかるのでお勧めできません。
- こちら(Github)のdialogues.zipが実行後のデータを圧縮したものなので、何かに使いたい場合はこっちがおすすめです。
まとめ
自然言語処理などで、大量のコーパス(テキスト)が必要になったときに、Webスクレイピングは非常に有効な手段です。
BeautifulSoupがあれば簡単にWebスクレイピングできるので、是非皆さんも試してみてね~!