データセットと声優動画の抽出

国立情報学研究所にあるニコニコデータセットをダウンロードする。JSON形式で圧縮されている約50GBの800万動画分コメントデータである。
圧縮されたファイルは解凍しないでそのまま処理することで、HDDが300GBなくても大丈夫なようにした。
ニコニコ大百科女性声優一覧から、いわゆる萌えアニメに出演している声優を選んで288人のリスト cvlist.txt を作る。
タイトルやタグなどの動画情報ファイルである meta.txt と、cvlist.txt に載っている声優がタグに出現する動画のコメントを抜き出したファイル nm...txt ができる。112958の動画が抽出された。
JSONの扱いがなぜかPython 2 ではうまくいかなかったので version 3 でやった。

### Python3 で!!!
import json
import re
import os
import tarfile
import gzip
import unicodedata
import MeCab
from multiprocessing import Pool

### parallel computing
def processor(files):
	wd = "/niconico/" # データのあるディレクトリ
	outwd = "/niconico/output/" # 処理したデータを出力するディレクトリ 
	voice = [v0.strip() for v0 in open("cvlist.txt", "rU")]
	labels = ["video_id", "upload_time", "title", "length", "view_counter", "comment_counter", "mylist_counter"]
	comment = ["date", "no", "vpos", "comment"]
	tmpf = files
	w0 = open(outwd + "meta/" + str(tmpf) + ".txt", "w") # 動画データはひとまとめにする
	w0.write("\t".join(labels + ["tag"])+"\n")
	f0 = gzip.open(wd + "meta/" + str(tmpf) + ".dat.gz", "rb")
	tmpg = f0.read().decode("utf-8").split("\n")
	p0 = tmpg.pop(-1) # 最後は \n のため "" になる
	map0 = list(map(lambda x: json.loads(x, "utf-8"), tmpg))
	tar0 = tarfile.open(wd + "comment/" + str(tmpf) + ".tar.gz", "r") # コメントデータは動画IDごとに出力する
	dat = tar0.getnames()
	for tmp in map0:
		tags = [t0["tag"] for t0 in tmp["tags"]] # tag を分解
		tags = list(map(lambda x: unicodedata.normalize('NFKC', x), tags)) # 半角文字に変換
		if any(map(lambda x: x in voice, tags)): # tag が声優をひとつでも含んでいるか
			a0 = list(map(lambda x: str(tmp[x]), labels)) # 動画情報
			if str(tmpf)+"/"+tmp["video_id"]+".dat" in dat: # 削除か何かですべての動画がダンプされているわけではなさそう
				w0.write("\t".join(a0 + [",".join(tags)]) + "\n")
				tmpcom = list(tar0.extractfile(tar0.getmember(str(tmpf) + "/" + tmp["video_id"] + ".dat")))
				tmpc = list(map(lambda y: json.loads(y, "utf-8"), list(map(lambda x: x.decode().rstrip(), tmpcom))))
				a1 = list(map(lambda y: list(map(lambda x: str(y[x]), comment)), tmpc))
				for i in range(len(a1)):  # 半角文字に変換
					a1[i][comment.index("comment")] = unicodedata.normalize('NFKC', a1[i][comment.index("comment")])
				
				w1 = open(outwd + "comment/" + tmp["video_id"] + ".txt", "w")
				w1.write("\t".join(comment)+"\n")
				for a2 in a1:
					w1.write("\t".join(a2) + "\n")
				
				w1.close() # コメントの終わり
	
	w0.close() # 動画データの終わり

p = Pool(4)
p.map(processor, files)

MeCab による品詞分解の実行
MeCabのデフォルト辞書にはNAIST、拡張としてはてなキーワードを追加し、声優やアニメなどの固有名詞にも対応できるようにした。しかしはてなキーワードは2010年で更新が止まっているので、これ以降に追加されたものについてはドンマイ。追加の仕方についてはこちら

# mecab python2
from multiprocessing import Pool
import os
mecabf = os.listdir(wd) # /niconico/output/comment/ など抽出した動画のディレクトリにある全動画

def processor(files):
	wd = "/niconico/output/comment/"
	outwd = "/niconico/output/mecab/"
	import unicodedata
	import codecs
	import MeCab
	tagger = MeCab.Tagger("-Ochasen")
	lex = ["名詞", "動詞", "形容詞", "副詞"]
	f0 = open(wd + str(files), "rU")
	header = f0.readline().strip().split("\t")
	w0 = open(outwd + str(files), "w")
	w0.write("\t".join(header) + "\n")
	for j in f0:
		tmp = j.rstrip().split("\t")
		if len(tmp) == len(header):
			mec0 = tagger.parse(tmp[header.index("comment")].replace('"', "")).split("\n") # mecab実行。" はEOFエラーとか言われてRで死ぬ
			gomi = [mec0.pop(-1) for i in range(2)]
			mec1 = map(lambda x: x.split("\t"), mec0)
			line = []
			for i in mec1:
				if any(map(lambda x: x in i[3], lex)): # 品詞のどれかがあれば
					line += [i[2]] # 品詞の基本形を取得する
			
			tmp[header.index("comment")] = " ".join(line)
			w0.write("\t".join(tmp) + "\n")
	
	w0.close()

p = Pool(4)
p.map(processor, mecabf)