クローラーとはWeb上のデータを自動的に収集するための道具です。クローラーを活用することで、担当者が手動で行っていたWeb情報収集の効率化、また自社だけでは入手できないさまざまなデータを取得し自社データと結合することで新たな示唆を得ることが可能になります。
今回のセミナーでは初心者を対象にクローラーを作成し対象サイトのデータを収集、テキスト解析を行い、分析結果を得るまでの一連の流れについて、Python で使用するライブラリ、解析手法を交えて解説いたします。
※本発表は所属する組織とは一切関係がありません
今回は対象ページとして、日本酒物語 日本酒ランキング(人数) とそれに紐づく各銘柄の詳細を収集し、各種分析を行います。本解析の内容として次の項目を含みます。
(スライド参照)
(スライド参照)
まずは形態素解析ツール MeCab のインストールを行います。ここでは Mac (OSX Sierra) を仮定して進めています。
!brew install mecab mecab-ipadic
!pip install mecab-python3
Warning: mecab-0.996 already installed Warning: mecab-ipadic-2.7.0-20070801 already installed Requirement already satisfied: mecab-python3 in /Users/tojima/anaconda3/lib/python3.5/site-packages
次にクローラー関係のライブラリもインストールします。ここでは以下の3つのライブラリを導入しています。
!conda install -y html5lib
!conda install -y requests
!conda install -y BeautifulSoup4
Fetching package metadata ......... Solving package specifications: . # All requested packages already installed. # packages in environment at /Users/tojima/anaconda3: # html5lib 0.999 py35_0 Fetching package metadata ......... Solving package specifications: . # All requested packages already installed. # packages in environment at /Users/tojima/anaconda3: # requests 2.13.0 py35_0 Fetching package metadata ......... Solving package specifications: . # All requested packages already installed. # packages in environment at /Users/tojima/anaconda3: # beautifulsoup4 4.5.3 py35_0
ここではこの解析に使用する各種ライブラリを読み込んでいます。
# ファイル操作
import glob
import csv
# データ処理・視覚化
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# クローラー
import time
from datetime import datetime
from bs4 import BeautifulSoup
import requests
# テキスト解析
import MeCab
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.cluster import AgglomerativeClustering
ここでは取得対象となっているページのURLやクローラーの待ち時間、各種データの出力先を定義しています。
# 日本酒物語 日本酒ランキング(人数)の URL
FOLLOWRANK_URL = "http://www.sakeno.com/followrank/"
# クロール時の待ち時間
WAIT_TIME = 5
# 銘柄マスタの出力先
MEIGARA_MASTER_PATH = "../data/meigara_maseter.csv"
# 銘柄評価スコアの出力先ディレクトリ
MEIGARA_SCORES_DIR = "../data/meigara_scores/"
# 銘柄コメントの出力先ディレクトリ
MEIGARA_COMMENTS_DIR = "../data/meigara_comments/"
# TFIDF スコア算出後の結果出力先
TFIDF_PATH = "../data/tfidf.csv"
# クラスタリング結果の結果出力先
CLUSTER_PATH = "../data/cluster.csv"
# ページの HTML を取得
response = requests.get(FOLLOWRANK_URL)
# 正しく取得できたかどうか HTTP ステータスコードで確認
if not response.status_code == 200:
raise ValueError("Invalid response")
else:
print("OK.")
OK.
正しくランキングページが取得されていれば OK.
と出力されます。
次は取得してきた HTML の先頭 1000 件を見てみると…
response.text[:1000]
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\r\n<html lang="ja">\r\n<head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">\r\n<meta http-equiv="Content-Style-Type" content="text/css">\r\n<meta http-equiv="Content-Script-Type" content="text/javascript">\r\n<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=4">\r\n<meta name="keywords" content="ÆüËܼò,¥é¥ó¥\xad¥ó¥°,¸ý¥³¥ß,ɾ²Á">\r\n<title>ÆüËܼò¥é¥ó¥\xad¥ó¥°¡Ê¿Í¿ô¡Ë¡ÝÆüËܼòʪ¸ì</title>\r\n<link rel="stylesheet" href="http://www.sakeno.com/incfiles/sakeno.css">\r\n<link rel="stylesheet" media="screen and (max-width:800px)" href="http://www.sakeno.com/incfiles/sakeno_sp.css">\r\n</head>\r\n\r\n<body><a id="top" name="top"></a>\r\n<div id="bigbox">\r\n\r\n\t<div id="header"><div id="headertitle"><h1><a href="http://www.sakeno.com/"><img border="0" src="http://www.sakeno.com/images/logo_new.gif" width="246" height="54" alt="ÆüËܼòʪ¸ì"></a></h1></div><div id="headerlog'
このように対象ページの HTML が文字列として取得できたが、文字化けしている。文字列の先頭の方を見てみると、charset=euc-jp
という文字列が見えるので、euc-jpということを考慮して扱ってみる。
response.encoding = 'euc_jp'
print(response.text[:1000])
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=euc-jp"> <meta http-equiv="Content-Style-Type" content="text/css"> <meta http-equiv="Content-Script-Type" content="text/javascript"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=4"> <meta name="keywords" content="日本酒,ランキング,口コミ,評価"> <title>日本酒ランキング(人数)−日本酒物語</title> <link rel="stylesheet" href="http://www.sakeno.com/incfiles/sakeno.css"> <link rel="stylesheet" media="screen and (max-width:800px)" href="http://www.sakeno.com/incfiles/sakeno_sp.css"> </head> <body><a id="top" name="top"></a> <div id="bigbox"> <div id="header"><div id="headertitle"><h1><a href="http://www.sakeno.com/"><img border="0" src="http://www.sakeno.com/images/logo_new.gif" width="246" height="54" alt="日本酒物語"></a></h1></div><div id="headerlogin"><a href="http://www.sakeno.com/l
正しく出力された。
今度はこのページから find
メソッドを利用し、 <table> 〜 </table>
の部分を抜き出します。この処理には BeautifulSoup
を利用します。
soup = BeautifulSoup(response.text, "lxml")
table = soup.body.find("table")
次はテーブルの中の行要素を全て取得します。<tr> 〜 </tr>
の部分が各行に該当します。find_all
メソッドを利用すれば各行が1要素となったリスト構造として取得可能です。
trs = table.find_all("tr")[2:] # 先頭のゴミをカット
各行から必要な数値、文字列を抜き出します。
# ある行の情報をパースし以下の要素を取得する。
#
# [ 順位, 銘柄, 銘柄の読み,
# 蔵元, 蔵元の県, 蔵元の市町村,
# 銘柄詳細ページのURL ]
#
def parse_tr(tr):
# 順位
tds = tr.find_all("td")
rank = int(tds[0].get_text().split("位")[0])
# 銘柄
a = tds[1].find("a")
meigara = a.get_text()
detail_url = a.get("href")
yomi = tds[1].find("div").string
# 酒造
location = tds[2].find_all("a")
kuramoto = location[0].string
prefecture = location[1].string
city = location[2].string
tr_l = [
rank, meigara, yomi,
kuramoto, prefecture, city,
detail_url
]
return tr_l
ranking_list = [parse_tr(tr) for tr in trs]
ranking_list[:5]
[[1, '獺祭', 'だっさい', '旭酒造(山口県)', '山口県', '岩国市', 'http://www.sakeno.com/meigara/931'], [2, '醸し人九平次', 'かもしびとくへいじ', '萬乗醸造', '愛知県', '名古屋市', 'http://www.sakeno.com/meigara/735'], [3, '出羽桜', 'でわざくら', '出羽桜酒造', '山形県', '天童市', 'http://www.sakeno.com/meigara/219'], [4, '田酒', 'でんしゅ', '西田酒造店', '青森県', '青森市', 'http://www.sakeno.com/meigara/11'], [5, '黒龍', 'こくりゅう', '黒龍酒造', '福井県', '吉田郡', 'http://www.sakeno.com/meigara/667']]
あとの処理のためにデータフレームに変換します。この際にユニークな連番IDを付加します。
meigara_master_df = pd.DataFrame(
ranking_list,
columns=["rank", "meigara", "yomi",
"kuramoto", "prefecture", "city",
"detail_url"]
)
# ユニークな連番 ID を追加
meigara_master_df["meigara_id"] = meigara_master_df.index.to_series() + 1
meigara_master_df = meigara_master_df[
["meigara_id",
"rank", "meigara", "yomi",
"kuramoto", "prefecture", "city",
"detail_url"]
]
# 銘柄マスタデータの出力
meigara_master_df.to_csv(
MEIGARA_MASTER_PATH,
encoding="utf-8",
sep=",",
index=False
)
meigara_master_df.head()
meigara_id | rank | meigara | yomi | kuramoto | prefecture | city | detail_url | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 獺祭 | だっさい | 旭酒造(山口県) | 山口県 | 岩国市 | http://www.sakeno.com/meigara/931 |
1 | 2 | 2 | 醸し人九平次 | かもしびとくへいじ | 萬乗醸造 | 愛知県 | 名古屋市 | http://www.sakeno.com/meigara/735 |
2 | 3 | 3 | 出羽桜 | でわざくら | 出羽桜酒造 | 山形県 | 天童市 | http://www.sakeno.com/meigara/219 |
3 | 4 | 4 | 田酒 | でんしゅ | 西田酒造店 | 青森県 | 青森市 | http://www.sakeno.com/meigara/11 |
4 | 5 | 5 | 黒龍 | こくりゅう | 黒龍酒造 | 福井県 | 吉田郡 | http://www.sakeno.com/meigara/667 |
ここまでで、ランキング一覧の結果が無事取得できました。
次は先程取得したランキング一覧のデータを利用して、詳細ページ(detail_url
)から以下の情報を取得します。
これらのデータを集めるために、以下に次の3つの関数を記述しています。
# 数値による評価データ取得関数
def parse_scores_table(soup, meigara_id):
scores_table = soup.body.find_all("form")[1]
trs = scores_table.find_all("tr")
# 味
aji = trs[2].find_all("span")
aji_good = int(aji[0].string)
aji_bad = int(aji[1].string)
# 香り
kaori = trs[3].find_all("span")
kaori_good = int(kaori[0].string)
kaori_bad = int(kaori[1].string)
# 濃さ
kosa = trs[4].find_all("span")
kosa_good = int(kosa[0].string)
kosa_bad = int(kosa[1].string)
# 価格
kakaku = trs[5].find_all("span")
kakaku_good = int(kakaku[0].string)
kakaku_bad = int(kakaku[1].string)
# デザイン
design = trs[6].find_all("span")
design_good = int(design[0].string)
design_bad = int(design[1].string)
score_li = [
[meigara_id, "味", aji_good, aji_bad],
[meigara_id, "香り", kaori_good, kaori_bad],
[meigara_id, "濃さ", kosa_good, kosa_bad],
[meigara_id, "価格", kakaku_good, kakaku_bad],
[meigara_id, "デザイン", design_good, design_bad]
]
score_df = pd.DataFrame(
score_li,
columns=["meigara_id", "name", "good_score", "bad_score"]
)
# (index) name good_score bad_score
# 0 味 1123 260
# 1 香り 1095 250
# 2 濃さ 978 304
# 3 価格 950 344
# 4 デザイン 975 249
return score_df
# コメント一覧取得関数
def parse_comments_table(soup, meigara_id):
reviews_table = soup.body.find_all("table")[-1]
# 以下のような構造になっているため、dtのみ、ddのみで処理し、
# 最後に concat で横方向に単純結合する
#
# <dt>〜</dt><dd>〜</dd>
# <dt>〜</dt><dd>〜</dd>
# <dt>〜</dt><dd>〜</dd>
# ...
#
# <dt>〜</dt> の処理
dts = [
[meigara_id,
int(dt.contents[0].get("name").replace("voice", "")),
dt.contents[3].string]
for dt
in reviews_table.find_all("dt")
]
dts_df = pd.DataFrame(
dts,
columns=["meigara_id", "toukou_id", "title"]
)
# <dd>〜</dd> の処理
dds = [
[dd.contents[-1].text.split("(")[1].split(")")[0],
dd.contents[-1].find("a").string,
dd.contents[-2].replace("\n", " ")]
for dd
in reviews_table.find_all("dd")
]
dds_df = pd.DataFrame(
dds,
columns=["created_at", "user_name", "text"]
)
dds_df["created_at"] = dds_df["created_at"].apply(
lambda x: datetime.strptime(x, '%Y年%m月%d日 %H時%M分%S秒')
)
# 結合
comments_df = pd.concat([dts_df, dds_df], axis=1)
# (index) meigara_id toukou_id title created_at user_name text
# 0 1 6193 すっきりして飲みやすい 2016-10-20 11:57:54 あいうそん おいしいです
# ...
return comments_df
# すべての詳細ページからデータを取得するための関数
def parse_maigara_detail_page(row):
print(
datetime.now().isoformat(sep=" "),
row["meigara_id"],
row["meigara"]
)
# 連続アクセス時の負荷軽減
time.sleep(WAIT_TIME)
# クローリング
response = requests.get(row["detail_url"])
if not response.status_code == 200:
raise ValueError("Invalid response")
response.encoding = 'euc_jp'
# ゴミとなる文字群を除去
preprocessed_html_string = response.text.replace("<br>", "\n")
preprocessed_html_string = preprocessed_html_string.replace("\r", "")
preprocessed_html_string = preprocessed_html_string.replace(" ", " ")
soup = BeautifulSoup(preprocessed_html_string, "lxml")
# 評価スコアの取得 & 出力
score_df = parse_scores_table(soup, row["meigara_id"])
scores_path = MEIGARA_SCORES_DIR + str(row["meigara_id"]) + ".csv"
score_df.to_csv(
scores_path,
encoding="utf-8",
sep=",",
index=False,
quoting=csv.QUOTE_NONNUMERIC
)
# 評価コメントの取得 & 出力
comments_df = parse_comments_table(soup, row["meigara_id"])
comments_path = MEIGARA_COMMENTS_DIR + str(row["meigara_id"]) + ".csv"
comments_df.to_csv(
comments_path,
encoding="utf-8",
sep=",",
index=False,
quoting=csv.QUOTE_NONNUMERIC
)
return
次にこれらのコードを全ての銘柄に対して実行します。
for idx, row in meigara_master_df.iterrows():
parse_maigara_detail_page(row)
2017-03-12 17:58:02.529907 1 獺祭 2017-03-12 17:58:09.126493 2 醸し人九平次 2017-03-12 17:58:15.797209 3 出羽桜 2017-03-12 17:58:22.873022 4 田酒 2017-03-12 17:58:29.302859 5 黒龍 2017-03-12 17:58:35.890476 6 飛露喜 2017-03-12 17:58:42.338474 7 新政 2017-03-12 17:58:49.018110 8 雪の茅舎 2017-03-12 17:58:55.609204 9 鳳凰美田 2017-03-12 17:59:01.490036 10 風の森 2017-03-12 17:59:07.875526 11 鍋島 2017-03-12 17:59:14.489016 12 くどき上手 2017-03-12 17:59:20.826484 13 十四代 2017-03-12 17:59:27.388108 14 菊姫 2017-03-12 17:59:34.022284 15 天狗舞 2017-03-12 17:59:40.585017 16 神亀 2017-03-12 17:59:46.673921 17 浦霞 2017-03-12 17:59:52.898387 18 鶴齢 2017-03-12 17:59:59.212296 19 楯野川 2017-03-12 18:00:05.593475 20 八海山 2017-03-12 18:00:12.830346 21 雁木 2017-03-12 18:00:19.296840 22 開運 2017-03-12 18:00:25.956883 23 大七 2017-03-12 18:00:32.476801 24 〆張鶴 2017-03-12 18:00:38.731137 25 久保田 2017-03-12 18:00:45.247792 26 酔鯨 2017-03-12 18:00:51.643924 27 手取川 2017-03-12 18:00:58.161813 28 東一 2017-03-12 18:01:07.472091 29 陸奥八仙 2017-03-12 18:01:14.158923 30 写楽(寫樂) 2017-03-12 18:01:20.226436 31 仙禽 2017-03-12 18:01:26.551723 32 東洋美人 2017-03-12 18:01:33.177094 33 花陽浴 2017-03-12 18:01:39.551707 34 磯自慢 2017-03-12 18:01:46.262988 35 梵 2017-03-12 18:01:52.706725 36 悦凱陣 2017-03-12 18:01:58.956448 37 真澄 2017-03-12 18:02:05.618642 38 秋鹿 2017-03-12 18:02:11.940649 39 奥播磨 2017-03-12 18:02:18.542844 40 王祿 2017-03-12 18:02:24.676691 41 臥龍梅 2017-03-12 18:02:31.741734 42 豊盃 2017-03-12 18:02:38.143491 43 黒牛 2017-03-12 18:02:44.458652 44 伯楽星 2017-03-12 18:02:50.960992 45 義侠 2017-03-12 18:02:57.403678 46 上喜元 2017-03-12 18:03:03.850065 47 日高見 2017-03-12 18:03:10.586871 48 玉川(京都府) 2017-03-12 18:03:16.942453 49 小左衛門 2017-03-12 18:03:23.533112 50 一ノ蔵 2017-03-12 18:03:29.888834 51 作 2017-03-12 18:03:36.620805 52 奈良萬 2017-03-12 18:03:43.160130 53 屋守 2017-03-12 18:03:49.590359 54 村祐 2017-03-12 18:03:55.606912 55 白瀑 2017-03-12 18:04:02.068721 56 南部美人 2017-03-12 18:04:08.546669 57 三芳菊 2017-03-12 18:04:16.465088 58 七田 2017-03-12 18:04:22.600221 59 雨後の月 2017-03-12 18:04:28.704886 60 紀土 KID 2017-03-12 18:04:34.915424 61 亀齢(広島県) 2017-03-12 18:04:41.046368 62 天明 2017-03-12 18:04:47.481380 63 満寿泉 2017-03-12 18:04:53.624541 64 勝駒 2017-03-12 18:04:59.531139 65 蓬莱泉 2017-03-12 18:05:05.942901 66 東光 2017-03-12 18:05:12.123451 67 雅山流 2017-03-12 18:05:18.514607 68 緑川 2017-03-12 18:05:24.588177 69 正雪 2017-03-12 18:05:30.825473 70 まんさくの花 2017-03-12 18:05:37.373877 71 飛良泉 2017-03-12 18:05:43.456885 72 七本槍 2017-03-12 18:05:49.441395 73 澤乃井 2017-03-12 18:05:55.667049 74 遊穂 2017-03-12 18:06:01.888673 75 あさ開 2017-03-12 18:06:08.607785 76 姿 2017-03-12 18:06:15.474695 77 南 2017-03-12 18:06:21.992489 78 宝剣 2017-03-12 18:06:28.424933 79 大那 2017-03-12 18:06:35.182849 80 越乃寒梅 2017-03-12 18:06:41.723710 81 春鹿 2017-03-12 18:06:48.032324 82 常きげん 2017-03-12 18:06:54.589442 83 五橋 2017-03-12 18:07:00.893189 84 加賀鳶 2017-03-12 18:07:07.456926 85 国権 2017-03-12 18:07:14.117180 86 刈穂 2017-03-12 18:07:20.776054 87 松の司 2017-03-12 18:07:27.020833 88 ロ万 2017-03-12 18:07:33.502105 89 剣菱 2017-03-12 18:07:39.757611 90 綿屋 2017-03-12 18:07:45.877950 91 山間 2017-03-12 18:07:52.074591 92 秀鳳 2017-03-12 18:07:58.190948 93 越乃景虎 2017-03-12 18:08:04.403371 94 菊水 2017-03-12 18:08:10.592417 95 亀泉 2017-03-12 18:08:17.113787 96 銀嶺立山 2017-03-12 18:08:23.281406 97 三千盛 2017-03-12 18:08:30.810961 98 尾瀬の雪どけ 2017-03-12 18:08:37.117417 99 ひこ孫 2017-03-12 18:08:43.595980 100 庭のうぐいす 2017-03-12 18:08:49.925061 101 麒麟山 2017-03-12 18:08:56.533390 102 来福 2017-03-12 18:09:04.490698 103 乾坤一 2017-03-12 18:09:10.272546 104 墨廼江 2017-03-12 18:09:16.541895 105 石鎚 2017-03-12 18:09:22.834053 106 初亀 2017-03-12 18:09:29.015368 107 初孫 2017-03-12 18:09:35.386027 108 早瀬浦 2017-03-12 18:09:41.710258 109 羽根屋 2017-03-12 18:09:47.951677 110 天の戸 2017-03-12 18:09:54.279195 111 佐久乃花 2017-03-12 18:10:00.824845 112 幻 2017-03-12 18:10:06.994982 113 郷乃誉 2017-03-12 18:10:13.265061 114 諏訪泉 2017-03-12 18:10:19.696445 115 李白 2017-03-12 18:10:25.903834 116 酔心 2017-03-12 18:10:32.087111 117 長珍 2017-03-12 18:10:38.637781 118 呉春 2017-03-12 18:10:44.877142 119 一白水成 2017-03-12 18:10:50.838438 120 不動 2017-03-12 18:10:56.848904 121 繁桝 2017-03-12 18:11:03.375572 122 蒼空 2017-03-12 18:11:09.275653 123 雪中梅 2017-03-12 18:11:16.715891 124 喜久酔 2017-03-12 18:11:23.209904 125 泉川 2017-03-12 18:11:28.979427 126 車坂 2017-03-12 18:11:35.139811 127 梅乃宿 2017-03-12 18:11:41.624577 128 いづみ橋 2017-03-12 18:11:47.942934 129 大信州 2017-03-12 18:11:54.041809 130 龍力 2017-03-12 18:12:00.506518 131 嘉美心 2017-03-12 18:12:07.910173 132 残草蓬莱 2017-03-12 18:12:14.389312 133 栄光冨士 2017-03-12 18:12:20.607095 134 貴 2017-03-12 18:12:26.494997 135 水芭蕉 2017-03-12 18:12:32.842182 136 萩の鶴 2017-03-12 18:12:39.052504 137 篠峯 2017-03-12 18:12:45.484481 138 日置桜 2017-03-12 18:12:51.802755 139 龍神丸 2017-03-12 18:12:58.025424 140 北雪 2017-03-12 18:13:04.397964 141 鷹勇 2017-03-12 18:13:10.444564 142 上善如水 2017-03-12 18:13:16.541637 143 たかちよ 2017-03-12 18:13:22.887476 144 宗玄 2017-03-12 18:13:30.389642 145 小鼓 2017-03-12 18:13:37.043704 146 鯉川 2017-03-12 18:13:43.258559 147 不老泉 2017-03-12 18:13:49.701382 148 磐城壽 2017-03-12 18:13:55.527370 149 群馬泉 2017-03-12 18:14:01.875800 150 龍勢 2017-03-12 18:14:08.031016 151 鏡山 2017-03-12 18:14:14.378424 152 花垣 2017-03-12 18:14:20.753708 153 白岳仙 2017-03-12 18:14:27.017937 154 笑四季 2017-03-12 18:14:33.638816 155 萩乃露 2017-03-12 18:14:39.690084 156 醴泉 2017-03-12 18:14:46.411015 157 花泉 2017-03-12 18:14:52.805909 158 大山 2017-03-12 18:14:59.338377 159 鳴海 2017-03-12 18:15:07.233578 160 帰山 2017-03-12 18:15:13.895577 161 賀茂金秀 2017-03-12 18:15:20.145517 162 奥の松 2017-03-12 18:15:26.563100 163 宮寒梅 2017-03-12 18:15:32.422080 164 るみ子の酒 2017-03-12 18:15:38.670114 165 甲子 2017-03-12 18:15:44.928295 166 誠鏡 2017-03-12 18:15:51.241363 167 旭日 2017-03-12 18:15:57.226133 168 あぶくま 2017-03-12 18:16:03.706618 169 町田酒造 2017-03-12 18:16:10.099683 170 春霞 2017-03-12 18:16:16.087101 171 賀儀屋 2017-03-12 18:16:22.474515 172 川鶴 2017-03-12 18:16:28.903583 173 明鏡止水 2017-03-12 18:16:35.099438 174 洗心 2017-03-12 18:16:41.198932 175 出雲富士 2017-03-12 18:16:47.213342 176 会津娘 2017-03-12 18:16:52.938671 177 住吉 2017-03-12 18:16:59.059578 178 天吹 2017-03-12 18:17:06.178848 179 会津中将 2017-03-12 18:17:12.259170 180 豊賀 2017-03-12 18:17:18.051022 181 美丈夫 2017-03-12 18:17:24.358635 182 菊正宗 2017-03-12 18:17:30.542959 183 富久長 2017-03-12 18:17:37.288783 184 れいざん 2017-03-12 18:17:43.221305 185 一本義 2017-03-12 18:17:49.443888 186 竹鶴 2017-03-12 18:17:55.976943 187 丹沢山 2017-03-12 18:18:02.464572 188 奥 2017-03-12 18:18:08.285727 189 十九 2017-03-12 18:18:14.013907 190 長陽福娘 2017-03-12 18:18:20.125712 191 白龍(福井県) 2017-03-12 18:18:26.727557 192 美寿々 2017-03-12 18:18:32.445372 193 三連星 2017-03-12 18:18:38.574551 194 天寿 2017-03-12 18:18:45.343555 195 天覧山 2017-03-12 18:18:51.417105 196 酔右衛門 2017-03-12 18:18:57.799544 197 大倉 2017-03-12 18:19:04.069903 198 高千代 2017-03-12 18:19:10.391022 199 相模灘 2017-03-12 18:19:16.763779 200 七賢
ここまでで、銘柄のマスタデータ、詳細ページの評価、詳細ページのコメントの情報が手に入りました。
ここからは先程クローラーで収集したデータを利用して、TFIDFによるレビュー中の特徴的な形容詞の抽出と単語ベースのクラスタリングを行っていきます。
この解析では、あるドキュメント中における特徴的な単語(特徴語)の抽出を行います。集めたデータを各種統計処理で扱えるようにするためには、行列形式に変換する必要があります。今回は Bag-of-Words モデルを用いて、単語を行列の形に変換します。Bag-of-Words とは、文章に単語が含まれているかどうかのみを考え、単語の並び方などは考慮しないモデルのことです。一番シンプルなモデルは単語があれば 1、なければ 0 となります。また、単語の出現回数をそのまま使う (Term Frequency) という方法もあります。これは文書中にある単語が含まれている回数をそのまま値として用います。
すもももももももものうち (1)
↓
[すもも, も, もも, も, もも, の, うち] (2)
↓
{すもも: 1, も:2, もも: 2, の: 1, うち:1} (3)
そして各ドキュメントに含まれる単語を列に、文書を行とすると単語の出現回数を要素とした行列形式に変換できます。例として、 (a) 「すもももももももものうち」、(b) 「料理も景色もすばらしい」、(c) 「私の趣味は写真撮影です」という3つの文書を考えます。列のラベルは単語の出現の早い順に [すもも, も, もも, の, うち, 料理, 景色, 素晴らしい, 私, 趣味, は, 写真撮影, です] とすると、文書行列は下記のようになります。
[[1,2,2,1,1,0,0,0,0,0,0,0,0], # (a)
[0,2,0,0,0,1,1,1,0,0,0,0,0], # (b)
[0,0,0,1,0,0,0,0,1,1,1,1,1]] # (c)
今回は数値に Term Frequency を用います。この変換を行うためには、元のテキストデータを単語単位に分割する必要があります。これを行うためには形態素解析ツールのMeCabを利用します。
次に特徴量の計算には TFIDF を用います。TFIDF は TF と IDF というの2つの値を掛けあわせた指標のことです。TF は文書内における単語の出現頻度を表します。これは「ある文書中である単語が何回出現したか」で定義されます。1つの文書に多く出現する単語ほど重要度が高くなります。IDF は多数の文書に出現する単語ほど重要度が低くなるようなスコアです。「ある単語が含まれている文書数を全ての文書数で割ったものの逆数」で定義されます。つまり、TFIDF が大きな値になるということは、「文書内である特定の単語が多く出現し、かつその単語は他の文書ではほとんど出現しない」ということを表します。例えば、「私」という単語は、各文書内における出現回数は多いですが、多くの文書に出現するので重要度は下がります。逆に「特許」という単語は、「特許」を話題の中心にしている特定の文書には文書内には多く現れ、一般的な文書には現れない単語なので重要度は上がります。
これらの解析を行うためのコードを見ていきましょう。まずは単語の分割を行うための関数を定義します。ここでは対象の品詞のみに絞り込んで、原形のみを抽出するようにしています。
def split_text(text, target_pos=["形容詞"]):
tagger = MeCab.Tagger()
text_str = text
tagger.parse('')
node = tagger.parseToNode(text_str)
words = []
while node:
l = node.feature.split(",")
pos = l[0]
if pos in target_pos:
# unicode 型に戻す
if l[6] == "*":
word = node.surface # 変化しない語は表層形をそのまま使う
else:
word = l[6] # 動詞や形容詞は原形を使う
words.append(word)
node = node.next
return " ".join(words) # スペース区切りで単語を結合し返す
では実際に全銘柄のコメントを読み込んで、単語単位に分割し必要な単語のみを取り出してみましょう。
# 全ファイル読み込み
comment_files = glob.glob("../data/meigara_comments/*.csv")
# 縦方向に単純結合
comment_df = pd.concat([pd.read_csv(f) for f in comment_files])
comment_df = comment_df.reset_index()
# 全ての text に対して形容詞の抽出を行う
comment_df["split"] = comment_df["text"].map(split_text)
comment_df.head()
index | meigara_id | toukou_id | title | created_at | user_name | text | split | |
---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 6193 | すっきりして飲みやすい | 2016-10-20 11:57:54 | あいうそん | おいしいです | おいしい |
1 | 1 | 1 | 6126 | 獺祭 等外23 | 2016-08-08 22:20:28 | 富牟谷欠 | 獺祭 等外23 山田錦23 生酒 27BY ライチ様な立ち香、含み香。抜ける香りはやや甘く。... | 甘い 強い 淡い ない 濃い |
2 | 2 | 1 | 5992 | 獺祭50 | 2016-04-26 19:24:02 | 季がらし | 獺祭と言えば高精米、磨きが強調され、50%はその最低ランクである しかし全国の銘酒蔵もこの5... | 美味い 美味い |
3 | 3 | 1 | 5946 | 獺祭等外 | 2016-03-18 20:37:29 | 季がらし | 旭酒造蔵本の売店で買った普通酒! 山田錦は栽培時、5%以上の等外米(規格外)が出てしまい純米... | 美味しい 悪い |
4 | 4 | 1 | 5940 | スパークリング、うすにごり | 2016-03-13 23:16:16 | nomuyoshi | 抜栓直後、瓶から上る香りは若々しく青いような香り。 上る香りはさっぱりとした果物のよう。 口... | 若々しい 青い 鋭い |
銘柄別に全単語を結合します。
meigara_comments_df = comment_df\
.groupby("meigara_id")["split"]\
.apply(lambda x: "%s" % ' '.join(x))\
.reset_index()
meigara_comments_df.head()
meigara_id | split | |
---|---|---|
0 | 1 | おいしい 甘い 強い 淡い ない 濃い 美味い 美味い 美味しい 悪い 若々しい 青い 鋭い... |
1 | 2 | ない ない 美味い 美味い ない 早い 早い 若い 美味い 高い たまらない いい 甘い ... |
2 | 3 | 甘い 柔らかい 無い 美味い イイ やすい 力強い 鋭い 美味い 甘い くどい 旨い 甘い ... |
3 | 4 | いい 若い 深い 無い 新しい 甘い 濃い 旨い 美味い 高い 物足りない 甘酸っぱい 良... |
4 | 5 | 軽い 良い ほしい 強い うまい 不味い 苦い 良い 良い 広い 美味しい ない 美味しい... |
今回は数値に Term Frequency を用います。scikit-learn に CountVectorizerというものがあり、単語と列番号の対応付けなどの作業をまとめて行うことが出来ます。この次に TFIDF の計算を行う場合、さらに簡易化したライブラリが存在します。
TFIDF の計算は scikit-learn の TfidfVectorizer を用いれば、前述の CountVectorizer による行列化と TFIDF の計算を同時に行うことが出来るので、今回はこれを用いて TFIDF の計算を行います。では実際に TfidfVectorizer を利用して、TFIDF の計算を行ってみましょう。
vectorizer = TfidfVectorizer()
tfidfs = vectorizer.fit_transform(meigara_comments_df["split"])
tfidfs
<200x292 sparse matrix of type '<class 'numpy.float64'>' with 4099 stored elements in Compressed Sparse Row format>
では計算結果から、各銘柄におけるスコアの上位から5単語ずつ取り出してみましょう。
## TFIDF の結果からi 番目のドキュメントの特徴的な上位 n 語を取り出す
def extract_feature_words(terms, tfidfs, i, n):
tfidf_array = tfidfs[i]
top_n_idx = tfidf_array.argsort()[-n:][::-1]
words = [terms[idx] for idx in top_n_idx]
return words
# index 順の単語のリスト
terms = vectorizer.get_feature_names()
top_n = 5 # 上位5件
highscore_words = [
extract_feature_words(terms, tfidfs.toarray(), i, top_n)
for i
in range(len(meigara_comments_df.index))
]
highscore_words_str = [" ".join(l) for l in highscore_words]
meigara_comments_df["highscore_words"] = highscore_words_str
meigara_comments_df.head()
meigara_id | split | highscore_words | |
---|---|---|---|
0 | 1 | おいしい 甘い 強い 淡い ない 濃い 美味い 美味い 美味しい 悪い 若々しい 青い 鋭い... | 良い 美味しい 悪い ない うまい |
1 | 2 | ない ない 美味い 美味い ない 早い 早い 若い 美味い 高い たまらない いい 甘い ... | 美味い 不味い ない 甘い 旨い |
2 | 3 | 甘い 柔らかい 無い 美味い イイ やすい 力強い 鋭い 美味い 甘い くどい 旨い 甘い ... | 素晴らしい 甘い やすい イイ 美味しい |
3 | 4 | いい 若い 深い 無い 新しい 甘い 濃い 旨い 美味い 高い 物足りない 甘酸っぱい 良... | 良い 旨い 無い うまい ない |
4 | 5 | 軽い 良い ほしい 強い うまい 不味い 苦い 良い 良い 広い 美味しい ない 美味しい... | 良い うまい おいしい ない いい |
銘柄マスタと結合して、結果を確認してみましょう。
# マスタデータの JOIN
master_df = pd.read_csv(MEIGARA_MASTER_PATH)
result_df = master_df.merge(meigara_comments_df, on="meigara_id", how="inner")
result_df.head()
meigara_id | rank | meigara | yomi | kuramoto | prefecture | city | detail_url | split | highscore_words | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 獺祭 | だっさい | 旭酒造(山口県) | 山口県 | 岩国市 | http://www.sakeno.com/meigara/931 | おいしい 甘い 強い 淡い ない 濃い 美味い 美味い 美味しい 悪い 若々しい 青い 鋭い... | 良い 美味しい 悪い ない うまい |
1 | 2 | 2 | 醸し人九平次 | かもしびとくへいじ | 萬乗醸造 | 愛知県 | 名古屋市 | http://www.sakeno.com/meigara/735 | ない ない 美味い 美味い ない 早い 早い 若い 美味い 高い たまらない いい 甘い ... | 美味い 不味い ない 甘い 旨い |
2 | 3 | 3 | 出羽桜 | でわざくら | 出羽桜酒造 | 山形県 | 天童市 | http://www.sakeno.com/meigara/219 | 甘い 柔らかい 無い 美味い イイ やすい 力強い 鋭い 美味い 甘い くどい 旨い 甘い ... | 素晴らしい 甘い やすい イイ 美味しい |
3 | 4 | 4 | 田酒 | でんしゅ | 西田酒造店 | 青森県 | 青森市 | http://www.sakeno.com/meigara/11 | いい 若い 深い 無い 新しい 甘い 濃い 旨い 美味い 高い 物足りない 甘酸っぱい 良... | 良い 旨い 無い うまい ない |
4 | 5 | 5 | 黒龍 | こくりゅう | 黒龍酒造 | 福井県 | 吉田郡 | http://www.sakeno.com/meigara/667 | 軽い 良い ほしい 強い うまい 不味い 苦い 良い 良い 広い 美味しい ない 美味しい... | 良い うまい おいしい ない いい |
最後に結果をCSVファイルとして出力します。
target_cols = ["rank", "meigara", "kuramoto", "detail_url", "highscore_words"]
result_df[target_cols].to_csv(TFIDF_PATH, index=False)
ここまでが特徴語抽出を行うまでの一連の流れとなります。
次は評価が似た銘柄同士をまとめる方法を見ていきたいと思います。サンプルデータが大量にある場合、似た者同士をまとめることで、新たな知見が得られる可能性があります。ここではその一連の流れを見ていきます。
まず最初行わなければならないのは、特徴語の抽出の場合と同じく各文書に含まれる単語を行列形式に変換することです。今回も Term Frequency を用いた Bag-of-Words モデルで変換します。
次にクラスタリングを行う前に、行列に対していくつか前処理を行う必要があります。まずレビューの件数が大きく異なるので、数値を標準化してやる必要があります。今回は「Zスコア」という標準化を行います。これは各要素から平均を引いて、標準偏差で割ったものです。この変換を行うと、平均が 0 で標準偏差・分散が 1 になります。この変換を行うためのライブラリとして scikit-learn には StandardScaler があります。また、行列は疎な状態となっています。このような場合は次元圧縮を行うことで、より効率が良く、直感に近いクラスタリング結果を得られます。今回は主成分分析:PCAを利用して次元を圧縮しています。
最後にクラスタリングを行います。今回はコサイン類似度を距離の基準とした階層型クラスタリングを行いクラスタを決定しています。
では実際のコードを確認していきましょう。まずは対象の単語の抽出です。今回は名詞、動詞、形容詞を抽出しています。
noun_verb_adj_words = comment_df["text"]\
.apply(lambda x: split_text(x, target_pos=["名詞", "動詞", "形容詞"]))
comment_df["split_noun_verb_adj"] = noun_verb_adj_words
meigara_comments_df = comment_df\
.groupby("meigara_id")["split_noun_verb_adj"]\
.apply(lambda x: "%s" % ' '.join(x))\
.reset_index()
meigara_comments_df.head()
meigara_id | split_noun_verb_adj | |
---|---|---|
0 | 1 | おいしい 獺 祭 等外 2 3 山田 錦 2 3 生酒 2 7 BY ライチ 様 立ち 香 ... |
1 | 2 | Wow very oishii Sake ! 米 吟醸 aka 醸す 人 九 平次 彼 地 ... |
2 | 3 | 上 立つ 仄か 甘い 香り 含み 柔らかい 入る 派手 さ 無い 純大 吟 旨み 特徴 個 ... |
3 | 4 | 好き 酒 一つ する コク ある いい 感じ 田 酒 特 純生 飲む 開 栓 直後 含む す... |
4 | 5 | 甘み 感じる する 辛口 。 後味 軽い 酸味 ある 。 ラベル 飲む 方 「 冷やす 」 ... |
次に行列への変換です。前述の CountVectorizer を利用することで、簡単に変換できます。
vectorizer = CountVectorizer()
word_counts = vectorizer.fit_transform(meigara_comments_df.split_noun_verb_adj)
wca = word_counts.toarray()
wca
array([[0, 0, 0, ..., 0, 0, 1], [0, 0, 0, ..., 0, 1, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], dtype=int64)
StandardScaler を利用することで標準化も簡単に行えます(int 型から float 型への変換は意図通りなので警告内容については問題ない)。
sds = StandardScaler()
X = sds.fit_transform(wca)
/Users/tojima/anaconda3/lib/python3.5/site-packages/sklearn/utils/validation.py:429: DataConversionWarning: Data with input dtype int64 was converted to float64 by StandardScaler. warnings.warn(msg, _DataConversionWarning)
次は次元圧縮を行います。今回は30次元に圧縮しています。この圧縮された行列の各行は、それぞれの文書が表す概念を表現しているものとなり、概念ベクトルと呼べるものとなります。
model = PCA(n_components=30)
X_decomp = model.fit_transform(X)
X_decomp
array([[ 2.28167681e+02, -1.16255034e+02, -4.89383813e+01, ..., 9.14023707e-02, -8.60942515e-01, -6.03637929e-01], [ 1.12847840e+02, 1.94123781e+02, -6.26221348e+01, ..., 3.46364954e-01, -1.24565097e+00, -6.24311590e-01], [ 2.25193159e+01, 6.46982151e+00, 1.69020459e+01, ..., 1.00232518e+00, 6.03836210e-02, -5.87677278e-02], ..., [ -8.47630942e+00, -2.13891471e+00, -4.14508268e+00, ..., -8.44277795e-02, -4.82077079e-01, -4.45857641e-01], [ -4.62522339e+00, -3.79801541e-01, -3.06852519e+00, ..., 4.29486114e-01, -1.49074427e+00, 2.41093999e-01], [ -6.23976436e+00, -1.28528158e+00, -2.87711006e+00, ..., 1.53063625e-01, 9.22628033e-02, -9.44972731e-01]])
さて、ここまでで下準備が終わったのでクラスタリングを実行しましょう。今回はコサイン類似度を基準とした6つのクラスタに分けています。
model = AgglomerativeClustering(n_clusters=6, linkage="average", affinity="cosine")
y = model.fit_predict(X_decomp)
y
array([2, 2, 2, 2, 2, 5, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 1, 3, 0, 0, 1, 2, 3, 3, 3, 3, 0, 0, 0, 3, 0, 2, 0, 2, 1, 3, 3, 2, 1, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 3, 3, 1, 2, 0, 3, 3, 3, 3, 0, 0, 3, 2, 3, 3, 3, 3, 3, 3, 0, 0, 3, 2, 3, 3, 0, 3, 2, 3, 3, 3, 3, 3, 0, 4, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 2, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3, 3])
クラスタ番号が算出できたので、銘柄マスタデータと結合し結果を確認してみましょう。
# マスタデータの JOIN
master_df = pd.read_csv(MEIGARA_MASTER_PATH)
result_df = master_df.merge(meigara_comments_df, on="meigara_id", how="inner")
result_df["cluster"] = y
result_df.sort_values(by=["cluster", "rank"])
meigara_id | rank | meigara | yomi | kuramoto | prefecture | city | detail_url | split_noun_verb_adj | cluster | |
---|---|---|---|---|---|---|---|---|---|---|
6 | 7 | 7 | 新政 | あらまさ | 新政酒造 | 秋田県 | 秋田市 | http://www.sakeno.com/meigara/55 | 冷蔵 する いる 気 つける 開 栓 する プシュー 吹き出す しまう 半年 寝かせる いる... | 0 |
8 | 9 | 7 | 鳳凰美田 | ほうおうびでん | 小林酒造(栃木県) | 栃木県 | 小山市 | http://www.sakeno.com/meigara/95 | 鳳凰 美田 濾過 本 生 2 0 1 7 / 2 頂く アル 添 フルーティー 香り ラベル... | 0 |
9 | 10 | 10 | 風の森 | かぜのもり | 油長酒造 | 奈良県 | 御所市 | http://www.sakeno.com/meigara/898 | 風 森 ALPHA TYPE 3 米 吟醸 八 錦 5 0 2 7 BY 軽い 吟醸 香 含... | 0 |
10 | 11 | 10 | 鍋島 | なべしま | 富久千代酒造 | 佐賀県 | 鹿島市 | http://www.sakeno.com/meigara/1482 | 春先 購入 する 半年 寝かせる もの タッチ チリ する 白 ワイン 的 ブドウ シャープ... | 0 |
16 | 17 | 17 | 浦霞 | うらかすみ | 佐浦 | 宮城県 | 塩竈市 | http://www.sakeno.com/meigara/47 | 素っ気 ラベル 純 米 事 原酒 事 わかる メチャクチャ ぶっきらぼう 強面 塩釜 漁師 ... | 0 |
17 | 18 | 18 | 鶴齢 | かくれい | 青木酒造(新潟県) | 新潟県 | 南魚沼市 | http://www.sakeno.com/meigara/583 | 鶴 齢 特別 米 越 淡い 麗 5 5 % 濾過 生 原酒 2 8 BY 綺麗 果実 香 含... | 0 |
18 | 19 | 18 | 楯野川 | たてのかわ | 楯の川酒造 | 山形県 | 酒田市 | http://www.sakeno.com/meigara/229 | 楯 野川 純 米 吟醸 山田 5 0 汲む 夏 熟 2 7 BY 好い 熟れる 甘い 果実 ... | 0 |
21 | 22 | 21 | 開運 | かいうん | 土井酒造場 | 静岡県 | 掛川市 | http://www.sakeno.com/meigara/729 | 酒屋 特 本 思う 特 純 特 純 包み 紙 オレンジ ん 思い返す 飲 購入 する みる ... | 0 |
22 | 23 | 21 | 大七 | だいしち | 大七酒造 | 福島県 | 二本松市 | http://www.sakeno.com/meigara/381 | 全体 的 濃い 醇 いう もと 生まれる コシ 力強い さ ある いい ん じん わり 感じ... | 0 |
29 | 30 | 30 | 写楽(寫樂) | しゃらく | 宮泉銘醸 | 福島県 | 会津若松市 | http://www.sakeno.com/meigara/1804 | 一 回 火入れ 米 酒 バランス いい 言う の 特徴 感じる られる の 甘い さ 旨み ... | 0 |
30 | 31 | 30 | 仙禽 | せんきん | 株式会社せんきん | 栃木県 | さくら市 | http://www.sakeno.com/meigara/457 | 米 栃木 県 さくら 市 山田 錦 仕込 水 地域 地下 水 使用 ドメーヌ 化 する 米 ... | 0 |
31 | 32 | 32 | 東洋美人 | とうようびじん | 澄川酒造場 | 山口県 | 萩市 | http://www.sakeno.com/meigara/1094 | シリーズ 使用 米 ラベル 記載 する れる いる 筈 ん コレ 米 品種 名 書く れる ... | 0 |
33 | 34 | 32 | 磯自慢 | いそじまん | 磯自慢酒造 | 静岡県 | 焼津市 | http://www.sakeno.com/meigara/725 | 磯 自慢 特別 醸造 山田 5 5 6 0 2 7 BY メロン 様 優しい 清楚 香り 柔... | 0 |
35 | 36 | 36 | 悦凱陣 | よろこびがいじん | 丸尾本店 | 香川県 | 仲多度郡 | http://www.sakeno.com/meigara/325 | 悦 凱陣 山 廃 米 ろ過 生 赤磐 雄 町 6 8 2 6 BY 微か 酸 感じる させる... | 0 |
45 | 46 | 45 | 上喜元 | じょうきげん | 酒田酒造 | 山形県 | 酒田市 | http://www.sakeno.com/meigara/224 | 上 喜 米 吟醸 仕込 五 五 号 濾過 生 原酒 杜氏 佐藤 正一 渾身 山田 白玉 5 ... | 0 |
46 | 47 | 47 | 日高見 | ひたかみ | 平孝酒造 | 宮城県 | 石巻市 | http://www.sakeno.com/meigara/184 | うすい にごる フレッシュ テクスチャー 林檎 的 香味 マッチング 素晴らしい 生原 酒 ... | 0 |
56 | 57 | 57 | 三芳菊 | みよしきく | 三芳菊酒造 | 徳島県 | 三好市 | http://www.sakeno.com/meigara/1456 | グラス 注ぐ 瞬間 立ちあがる 華やか 芳香 含む 甘酸っぱい パイナップル 系 甘味 口 ... | 0 |
61 | 62 | 61 | 天明 | てんめい | 曙酒造 | 福島県 | 河沼郡 | http://www.sakeno.com/meigara/404 | 天明 通常 飲む こと ない 本来 ノーマル 飲む 比較 する タイプ の 情報 なる 思う... | 0 |
62 | 63 | 61 | 満寿泉 | ますいずみ | 桝田酒造店 | 富山県 | 富山市 | http://www.sakeno.com/meigara/621 | 米 酒 生酒 生酒 言う フレッシュ 甘い イメージ ある の 違う 印象 若干 香り 高い... | 0 |
71 | 72 | 70 | 七本槍 | しちほんやり | 冨田酒造 | 滋賀県 | 長浜市 | http://www.sakeno.com/meigara/1211 | 何 気 飲む スペック 飲む 瞬間 器 大きい さ 感じる 精白 ら する さ イメージ す... | 0 |
72 | 73 | 70 | 澤乃井 | さわのい | 小澤酒造 | 東京都 | 青梅市 | http://www.sakeno.com/meigara/112 | 淡い 麗 辛口 飲む 口 いい クセ ない 素直 味 旨み する いる 呑む やすい コスト... | 0 |
77 | 78 | 77 | 宝剣 | ほうけん | 宝剣酒造 | 広島県 | 呉市 | http://www.sakeno.com/meigara/1020 | 含む 意外 熟れる 果実 香 ムン 感じる 生 フレッシュ さ イチゴ マスカット 様 甘味... | 0 |
85 | 86 | 77 | 刈穂 | かりほ | 刈穂酒造 | 秋田県 | 大仙市 | http://www.sakeno.com/meigara/198 | 含む フレッシュ 直球 的 香味 感じる 日ハム 大谷 投手 オーラ ある 味わい バスンバ... | 0 |
102 | 103 | 101 | 乾坤一 | けんこんいち | 大沼酒造店 | 宮城県 | 柴田郡 | http://www.sakeno.com/meigara/1153 | 含む ハチミツ よう する 甘味 口 中 フワ ッ 波打つ よう 広がる いく 酸 弱める ... | 0 |
112 | 113 | 113 | 郷乃誉 | さとのほまれ | 須藤本家(茨城県) | 茨城県 | 笠間市 | http://www.sakeno.com/meigara/1081 | 香り 控え目 特有 フレッシュ 甘味 ある する 口当たり ドライテイスト 余韻 する 酸 ... | 0 |
118 | 119 | 113 | 一白水成 | いっぱくすいせい | 福禄寿酒造 | 秋田県 | 南秋田郡 | http://www.sakeno.com/meigara/1753 | 目 とまる 一 白水 成 四 合 瓶 見る ラベル 左上 plus +」 ある 店員 さん ... | 0 |
129 | 130 | 128 | 龍力 | たつりき | 本田商店 | 兵庫県 | 姫路市 | http://www.sakeno.com/meigara/871 | ひる 燻蒸 香 太い すぎる よい うまい さ 燗 する 燻蒸 クセ 和らぐ よう メロン ... | 0 |
133 | 134 | 128 | 貴 | たか | 永山本家酒造場 | 山口県 | 宇部市 | http://www.sakeno.com/meigara/1085 | オリ 甘い さ 中 バナナ 感じる 甘味 中盤 活性 にごる 来る 炭酸 押し寄せる 強い ... | 0 |
153 | 154 | 144 | 笑四季 | えみしき | 笑四季酒造 | 滋賀県 | 甲賀市 | http://www.sakeno.com/meigara/784 | 笑 四季 特別 米 Polynesia 生 アル 原酒 2 7 BY 熟れる 果実 濃厚 甘... | 0 |
19 | 20 | 20 | 八海山 | はっかいさん | 八海醸造 | 新潟県 | 南魚沼市 | http://www.sakeno.com/meigara/593 | 八海山 言う こと 味 薄い そう 思う いる 米 原酒 。 する 旨み コク ある これ ... | 1 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
170 | 171 | 168 | 賀儀屋 | かぎや | 成龍酒造 | 愛媛県 | 西条市 | http://www.sakeno.com/meigara/1536 | 伊予 賀 儀 屋 純大 しずく 媛 4 5 生原 2 6 BY 控えめ 香り 甘い 穏やか ... | 3 |
171 | 172 | 168 | 川鶴 | かわつる | 川鶴酒造 | 香川県 | 観音寺市 | http://www.sakeno.com/meigara/337 | 川鶴 特別 米 Wisdom 限定 生 原酒 山田 / オオセト ・ 5 0 5 5 2 8... | 3 |
172 | 173 | 168 | 明鏡止水 | めいきょうしすい | 大澤酒造 | 長野県 | 佐久市 | http://www.sakeno.com/meigara/525 | これ 当たり 旨味 濃い フルーティー 色 黄み かう 1 日 目 尖る 3 日 目 マイル... | 3 |
173 | 174 | 168 | 洗心 | せんしん | 朝日酒造(新潟県) | 新潟県 | 長岡市 | http://www.sakeno.com/meigara/1171 | 私 1 2 年 前 プロデュース する 下田 富士 米 吟醸 富士 錦 洗心 飲む 比べる ... | 3 |
174 | 175 | 168 | 出雲富士 | いずもふじ | 富士酒造 | 島根県 | 出雲市 | http://www.sakeno.com/meigara/971 | 出雲 市 契約 農家 さん 特別 栽培 山田 錦 100 % 使用 35 % 贅沢 磨く 上... | 3 |
175 | 176 | 168 | 会津娘 | あいづむすめ | 高橋庄作酒造店 | 福島県 | 会津若松市 | http://www.sakeno.com/meigara/80 | 含む リンゴ パイナップル 感じる ドライ フルーツ よう 上品 果実 的 香味 バランス ... | 3 |
176 | 177 | 168 | 住吉 | すみよし | 樽平酒造 | 山形県 | 東置賜郡 | http://www.sakeno.com/meigara/247 | かすか 杉 香り ある 酒 自体 冷 辛口 角 トンガッタ よう 感じ 痛い なる よう 味... | 3 |
177 | 178 | 168 | 天吹 | あまぶき | 天吹酒造 | 佐賀県 | 三養基郡 | http://www.sakeno.com/meigara/288 | 精米 歩合 60 % 米 酒 酸味 ある 辛口 うまい 。 温める うまみ 消える 酸味 残... | 3 |
178 | 179 | 168 | 会津中将 | あいづちゅうじょう | 鶴乃江酒造 | 福島県 | 会津若松市 | http://www.sakeno.com/meigara/396 | うまい すごい きれい 酒 食 中 うまい 美味しい 香り 穏やか 甘味 酸味 旨味 バラン... | 3 |
179 | 180 | 168 | 豊賀 | とよか | 高沢酒造 | 長野県 | 上高井郡 | http://www.sakeno.com/meigara/1601 | 豊 賀 米 吟醸 天女 しずく 中 取る 濾過 生 原酒 美山 錦 5 9 長野 酵母 2 ... | 3 |
180 | 181 | 168 | 美丈夫 | びじょうぶ | 浜川商店 | 高知県 | 安芸郡 | http://www.sakeno.com/meigara/375 | 間違い ない 上品 酒 質 値段 安い たまらない 辛口 おとなしい 酒 味 辛口 クリア ... | 3 |
181 | 182 | 168 | 菊正宗 | きくまさむね | 菊正宗酒造 | 兵庫県 | 神戸市 | http://www.sakeno.com/meigara/842 | 何 よい の 菊 正 純 米 目 入る 買う みる 冷やす 書く ある 冷やす 飲む みる ... | 3 |
182 | 183 | 168 | 富久長 | ふくちょう | 今田酒造本店 | 広島県 | 東広島市 | http://www.sakeno.com/meigara/1040 | 甘み ある 有名 米 吟醸 みたい 女性 勧める 地元 広島 大手 スーパー 配置 する れ... | 3 |
184 | 185 | 168 | 一本義 | いっぽんぎ | 一本義久保本店 | 福井県 | 勝山市 | http://www.sakeno.com/meigara/1083 | 文句 ない 爽快 感 至高 廉価 (^∇^) 淡い 麗 辛口 香り 昔 ある 日本 酒 ( ... | 3 |
185 | 186 | 168 | 竹鶴 | たけつる | 竹鶴酒造 | 広島県 | 竹原市 | http://www.sakeno.com/meigara/1036 | 竹 鶴 純 米 熱 燗冷まし 新た 飲む 方 教える くれる にごり酒 純 米 にごる 冷 ... | 3 |
186 | 187 | 168 | 丹沢山 | たんざわさん | 川西屋酒造店 | 神奈川県 | 足柄上郡 | http://www.sakeno.com/meigara/498 | すう 私 蔵 訪問 する 吟 旨い さ 惚れ込む 販売 上 策 名 する その後 当時 2 ... | 3 |
187 | 188 | 168 | 奥 | おく | 山崎合資 | 愛知県 | 西尾市 | http://www.sakeno.com/meigara/1253 | さわやか ラムネ 香 しつこい 甘味 酸味 する 炭酸 夏 酒 する ドライ 仕上がり ソフ... | 3 |
188 | 189 | 168 | 十九 | じゅうく | 尾澤酒造場 | 長野県 | 長野市 | http://www.sakeno.com/meigara/1106 | 最初 感じる の かすか 吟醸 香 中 甘い さ 味 濃い 酒 味 強い すぎる 寿司 一緒... | 3 |
189 | 190 | 168 | 長陽福娘 | ちょうようふくむすめ | 岩崎酒造 | 山口県 | 萩市 | http://www.sakeno.com/meigara/1742 | 日本 酒 度 甘い ほう 夏みかん よう 香り のどごし いい 甘い 酒 飲む 進む くどい... | 3 |
190 | 191 | 168 | 白龍(福井県) | はくりゅう | 吉田酒造(福井県) | 福井県 | 吉田郡 | http://www.sakeno.com/meigara/1288 | 米 吟醸 頂く 米 吟醸 飲める 味 辛口 飲める すぎる ちゃう 気 する 値段 する 満足 | 3 |
192 | 193 | 168 | 三連星 | さんれんせい | 美冨久酒造 | 滋賀県 | 甲賀市 | http://www.sakeno.com/meigara/1653 | 含む 生らす いる フレッシュ 風味 綺麗 質感 中盤 余韻 甘酸っぱい 酸味 印象 的 酸... | 3 |
193 | 194 | 168 | 天寿 | てんじゅ | 天寿酒造 | 秋田県 | 由利本荘市 | http://www.sakeno.com/meigara/197 | 原酒 香り 少ない 旨味 アルコール 感 強い 涼 冷え 常温 非常 旨い ここ 酒 近い ... | 3 |
194 | 195 | 168 | 天覧山 | てんらんざん | 五十嵐酒造 | 埼玉県 | 飯能市 | http://www.sakeno.com/meigara/1120 | 美山 錦 精米 歩合 65 %、 日本 酒 度 - 5 酸度 1 . 8 アルコール 15 ... | 3 |
195 | 196 | 168 | 酔右衛門 | よえもん | 川村酒造店 | 岩手県 | 花巻市 | http://www.sakeno.com/meigara/1269 | アルコール 度数 17 18 精米 歩合 50 % 発泡 炭酸 これ 華やか フレッシュ 吟... | 3 |
196 | 197 | 168 | 大倉 | おおくら | 大倉本家 | 奈良県 | 香芝市 | http://www.sakeno.com/meigara/1273 | 雄 町 ひる ひかり オオセト 3 種類 米 作る 酒 責め ところ ブレンド 責め 責め ... | 3 |
197 | 198 | 168 | 高千代 | たかちよ | 高千代酒造 | 新潟県 | 南魚沼市 | http://www.sakeno.com/meigara/1442 | 2 年 前 飲む 感動 する 日本 酒 香り メロン 味 重い 飲める いる かちよ 濃厚 ... | 3 |
198 | 199 | 168 | 相模灘 | さがみなだ | 久保田酒造(神奈川県) | 神奈川県 | 相模原市 | http://www.sakeno.com/meigara/1241 | 開 栓 直後 含む ピリリ ガス 感 原酒 濃厚 フレッシュ バター 甘味 口 中 一 杯 ... | 3 |
199 | 200 | 168 | 七賢 | しちけん | 山梨銘醸 | 山梨県 | 北杜市 | http://www.sakeno.com/meigara/506 | 今 日本 酒 苦手 敬遠 する 毎年 お世話 なる いる 山中湖 民宿 主人 すすめ 試す ... | 3 |
86 | 87 | 77 | 松の司 | まつのつかさ | 松瀬酒造 | 滋賀県 | 蒲生郡 | http://www.sakeno.com/meigara/793 | 松 司 純 米 吟醸 楽 しぼる たて 山田 錦 吟 吹雪 6 0 2 8 BY 僅か セメ... | 4 |
5 | 6 | 6 | 飛露喜 | ひろき | 廣木酒造本店 | 福島県 | 河沼郡 | http://www.sakeno.com/meigara/409 | 今宵 息子 家 飲む こと 飛 露 喜 特別 純 米 セレクト 予想 する れる 飲む 飽き... | 5 |
200 rows × 10 columns
最後に結果を出力します。
target_cols = ["rank", "meigara", "kuramoto", "detail_url", "cluster"]
result_df[target_cols].to_csv(CLUSTER_PATH, index=False)
このような流れで単語ベースのクラスタリングが行えます。
今回の解析はまだまだ不足している点があります。例えば以下のような点を考慮しませんでした。
これらの点などを改善していくことにより、より我々の感覚と近い結果を得られるようになります。