『仕事に使えるクチコミ分析[テキストマイニングと統計学をマーケティングに活用する]』を拝読いたしました。
本書の中でAmazonのレビューを使って、クチコミ分析を行う、という解説が面白かったので、Pythonで実践していきたいと思います。
今回のターゲット
今回はAK48のシングル全53曲を見てみたいと思います。 リストは次の通りです。
- 会いたかった
- 制服が邪魔をする
- 軽蔑していた愛情
- BINGO!
- 僕の太陽
- 夕陽を見ているか?
- ロマンス、イラネ
- 桜の花びらたち2008
- Baby! Baby! Baby!
- 大声ダイヤモンド
- 10年桜
- 涙サプライズ!
- 言い訳Maybe
- RIVER
- 桜の栞
- ポニーテールとシュシュ
- ヘビーローテーション
- Beginner
- チャンスの順番
- 桜の木になろう
- Everyday、カチューシャ
- フライングゲット
- 風は吹いている
- GIVE ME FIVE!
- 真夏のSounds good !
- ギンガムチェック
- UZA
- 永遠プレッシャー
- So long !
- さよならクロール
- 恋するフォーチュンクッキー
- ハート・エレキ
- 鈴懸の木の道で……
- 前しか向かねえ
- ラブラドール・レトリバー
- 心のプラカード
- 希望的リフレイン
- Green Flash
- 僕たちは戦わない
- ハロウィン・ナイト
- 唇にBe My Baby
- 君はメロディー
- 翼はいらない
- LOVE TRIP/しあわせを分けなさい
- ハイテンション
- シュートサイン
- 願いごとの持ち腐れ
- 好きなんだ
- 11月のアンクレット
- ジャーバージャ
- Teacher Teacher
- センチメンタルトレイン
取得するためのコードを作る
いつも通りテキストの取得にはScrapyを使いたいと思います。 Scrapyの使い方はこちら。
dailytextmining.hatenablog.com
シェル上で取得できるテキストを確認していきます。 まずはレビューのテキストから取得していきます。
text = response.xpath('//*[@class="a-size-base ○○"]').extract()
元のAmazonのソースがこんな感じなので、テキストだけを取得すると、改行を挟むごとにリストの要素が増えてしまうようです。
元のテキスト。
“中西里菜が、好きだったら買うべき。彼女の” <br> “過去を知ることができる良い作品。”
取得するとこんな感じになります。
['中西里菜が、好きだったら買うべき。彼女の', '過去を知ることができる良い作品。',
要素がバラバラになってしまい、これだと使いづらそうなので、タグごと取得してみます。
text = response.xpath('//span[@class="a-size-base ○○"]').extract()
text
'[\'"<span data-hook=review-body class=a-size-base ○○t>中西里菜が、好きだったら買うべき。彼女の<br>過去を知ることができる良い作品。</span>"'
これを47日目で学んだBeautifull Soupでタグを取り除けば良さそうです。
dailytextmining.hatenablog.com
from bs4 import BeautifulSoup bt = "<span data-hook=review-body class=a-size-base ○○>中西里菜が、好きだったら買うべき。彼女の<br>過去を知ることができる良い作品。</span>" example = BeautifulSoup(bt) print(example.get_text())
こんな感じで綺麗にテキストだけ取得できます。
中西里菜が、好きだったら買うべき。彼女の過去を知ることができる良い作品。
レビューのタイトルは簡単に取得できます。
text = response.xpath('//*[@class="a-size-base a-link-normal ○○"]/text()').extract()
実行結果。
'会いたかった!', '人それぞれ。', '神曲の1つ'
星の数はそのまま取得して、あとで処置したいと思います。
text = response.xpath('//*[@class="a-icon a-icon-star ○○"]//span/text()').extract()
こんな感じです。
'5つ星のうち5.0'
どの曲かわからなくなると困るので、曲のタイトルも取得します。
text = response.xpath('//*[@class="a-size-large ○○"]//a/text()').extract()
[‘会いたかった']
自動的に次のページに行くにはこちらですね。
review_pages = response.xpath('//*[@class="a-○○"]//a/@href').extract()
これで、とりあえず欲しいデータは取得できそうです。
実装
spiderに落とし込んでいきます。 その前にデータベースでテーブルを次のように作っておきます。
create database akb_amazon; use akb_amazon; create table songs( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, song_title varchar(60) NOT NULL, star varchar(60) NOT NULL, review_title varchar(60) UNIQUE NOT NULL, review text, datesp DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
spiderはほぼ42日目と同じです。
dailytextmining.hatenablog.com
$ scrapy crawl amazon_akb
実行結果。
id,song_title,star,review_title,review,datesp 1,['会いたかった'],"['5つ星のうち5.0', '5つ星のうち5.0', '5つ星のうち5.0', '5つ星のうち5.0', '5つ星のうち","['買うべき。', '会いたかった です...(⌒-⌒) / ニコニコ...', 'この曲が一番好きです', '子供のプ","['<span data-hook="review-body"(以下、省略)
Oh, 全部のデータが1つのリストに入っているかなり使えなさそうなデータができました。 ちょっとpandasで読み込んでみます。
id,song_title,star,review_title,review,datesp 0 1,['会いたかった'],"['5つ星のうち5.0', '5つ星のうち5.0', '5つ星の... 1 2,['会いたかった'],"['5つ星のうち5.0', '5つ星のうち5.0', '5つ星の... 2 3,['会いたかった'],"['5つ星のうち5.0', '5つ星のうち5.0']","['元...
分析に全く使えなさそうですね。
とは言え、テキストは取得できていますので、方法としてはリストとして取得した要素を1つずつitemに入れれば良さそうです。 レビュータイトル・星の数・レビューテキストは全て同じ要素数のはずなので、49日目で学んだforの書き方が使えそうです。
for cluster in range(0,20):
これですね。これを参考に次のように書き直します。
song_title = response.xpath('//*[@class="a-size-large ○○"]//a/text()').extract() star = response.xpath('//i[@data-hook="review-star-rating"]//text()').extract() review_title = response.xpath('//a[@class="a-size-base ○○"]/text()').extract() review = response.xpath('//span[@class="a-size-○○"]').extract() for i in range(0, len(review_title)): item['song_title'] = str(song_title) item['star'] = star[i] item['review_title'] = review_title[i] item['review'] = review[i] items.append(item) yield item
まず、テキスト要素をリストに代入し、[i]の場所だけをループにしてitemに代入します。 それで次のページに行く前にDBに保存する必要がありますので、forの中にsql文を入れればできるはずです。結局こんな感じになりました。
def parse(self, response): sel = Selector(response) items = [] item = AmazonItem() song_title = response.xpath('//*[@class="a-size-large○○"]//a/text()').extract() star = response.xpath('//i[@data-hook="review-star-rating"]//text()').extract() review_title = response.xpath('//a[@class="a-size-base ○○"]/text()').extract() review = response.xpath('//span[@class="a-size-base ○○"]').extract() for i in range(0, len(review_title)): item['song_title'] = str(song_title) item['star'] = star[i] item['review_title'] = review_title[i] item['review'] = review[i] items.append(item) yield item connect = { 'user': ‘xxxxx’, 'password': ‘xxxxxx’, 'host': ‘xxxxxxxx’, 'database': 'akb_amazon', } db=mysql.connector.connect(**connect) crsr = db.cursor() db=mysql.connector.connect(**connect) crsr = db.cursor() sql = ( "INSERT IGNORE INTO songs (song_title, star, review_title, review)" "VALUES (%s, %s, %s, %s)" ) data = (item['song_title'], item['star'], item['review_title'], item['review']) crsr.execute('SET NAMES utf8mb4') crsr.execute("SET CHARACTER SET utf8mb4") crsr.execute("SET character_set_connection=utf8mb4") crsr.execute(sql, data) db.commit() crsr.close() review_pages = response.xpath('//*[@class="a-○○"]//a/@href').extract() #response.css('li.next a::attr(href)').extract_first() for review_page in review_pages: if review_page is not None: review_page = response.urljoin(review_page) yield scrapy.Request(review_page, callback=self.parse)
これで、DBにテキストをきれいに保存できました。DBから出力したcsvをpandasで読み込むとこんな感じになりました。
id song_title star review_title review datesp 0 66 ['49th Single「#好きなんだ」【Type D】通常盤'] 5つ星のうち4.0 良かったです。 <span data-hook=review-body" class="a-size-bas... 2018-08-18 01:36:12 1 67 ['49th Single「#好きなんだ」【Type D】通常盤'] 5つ星のうち5.0 このシングルも良いですね。 <span data-hook=review-body" class="a-size-bas... 2018-08-18 01:36:12
これで、分析ができそうです。 明日はレビューを集めて分析をしたいと思います。
今日の結果
今日のAKBメンバーによる呟きは67件でした。
{'楽しい': 6, '嬉しい': 5, '可愛い': 4, 'ない': 3, 'すごい': 3, 'よい': 2, '幅広い': 2, '面白い': 2, 'いい': 2, 'やばい': 2, '素晴らしい': 1, 'くい': 1, 'いたい': 1, '良い': 1, '近い': 1, '早い': 1, '甘い': 1, '大きい': 1}) 'アクシュカイ': 12, '公演': 11, '日': 10, 'さん': 10, 'ちゃん': 9, '中': 8, '方': 8, 'ん': 7, '月': 7, '楽しい': 6, '笑': 6, '人': 6, 'お願い': 6, 'センチメンタルトレイン': 6, '受付': 6, '写真': 6, '皆さん': 6, 'よう': 6, 'する': 35, 'なる': 18, 'いる': 15, 'くださる': 13, 'アクシュカイ': 12, 'てる': 12, '公演': 11, '日': 10, 'さん': 10, 'ちゃん': 9, '中': 8, '方': 8, '来る': 8, 'ん': 7, '月': 7, 'くる': 7, 'くれる': 7, '会う': 7, 'みる': 7,