アットコスメをスクレイプしてみた
要約
プログラミング初心者が、google先生を頼りになんとか口コミサイトのスクレイピングツールを作ってみたよ、というお話。
目的
たまに仕事でアットコスメ(化粧品口コミサイト)の口コミを集計したりすることがあるのですが、集計の準備として、ひたすら口コミ情報をエクセルにコピペするみたいなことをやっていました。
こんなのやってらんない!リロードの度に微妙に待たされるストレスで仕事が手に付かない!パソコンさんにやってもらおう!
と、いうことで、それまでほとんどプログラム言語を触ったことがなかったのですが、ググりながらなんとかコードを書いてみました。
やってみたこと
プログラミング言語といえば、高校時代のブログのHTMLで太字、文字色、文字サイズをいじっていたくらいのもので、エクセルの関数はできるけどVBAは挫折したレベル。
こんな私でも「なんかよくわからないけどpythonっていうのがすごいらしい」というレベルでpythonを知ってはいました。
さらに、何やらgoogle colaboratoryというのを使えばややこしいことをしなくてもプログラミングを開始することができるらしいことを知ったこともあり、じゃあやってみようか、と、google先生に手取り足取り教えてもらいながら骨子を書いてみたのが始まりです。
その後、簡単に基礎知識を勉強して、リファクタリングという言葉をしり、関数定義などをしてみたのですが、これで良いのかどうかもわからず、とりあえず動くからいっか、という感じで落ち着いています。
pythonコード
おそらくセンスのないコードなのでしょうが、せっかく書いたので公開してみます。
アドバイスやコメントなどいただけると嬉しいです。(スクレイピングは場合によっては違法になる可能性があります。アットコスメの利用規約にクローリングやスクレイピングに関する規則は見当たりませんでしたが、本記事及び筆者は、以下のプログラムを実行した場合に発生したいかなる不利益や損害について、その一切の責任を負いません。)
from bs4 import BeautifulSoup
import requests
import pandas as pd
import time
from datetime import datetime as dt
from google.colab import files
def get_10reviews_list(soup):
"""
10件ごとの一覧ページから、指定範囲の投稿日の口コミurlを取得
投稿日をkey、口コミurlをvalueとした辞書を出力
"""
read = soup.select("p.read")
review_urls = [s.a.get("href") for s in read]
#各口コミのURLはp class="read"内のaタグに格納されているので
#p class="read"をリスト取得して、それぞれのhrefを取得
for i in range(len(read)):
soup.select_one("p.reviewer-rating").decompose()
#投稿日はdiv class="rating clearfix"内のp class="date"もしくは"mobile-date"
#に格納されている。deteとmobiledateで2種類あるため、rating clearfixにある
#ほかの内容(p class="reviewer-rating)を削除して、div内に投稿日情報のみ残す。
div = soup.select("div.rating.clearfix")
review_dates = [dt.strptime(i.text.strip(),"%Y/%m/%d %H:%M:%S") for i in div]
#投稿日を上から順に、datetime情報に変換しながらリストとして取得
dic = dict(zip(review_dates,review_urls))
#投稿日をkey、口コミURLをvalueとした辞書を作成
return dic
def next_check(soup):
"""10件ごとの一覧ページに「次に」があるかどうかを判定
「次に」があればTrue、なければFalseを返す"""
next = soup.select_one("li.next span")
return next == None
#最終ページの「次へ」がspanになっていることを利用。
def get_review_data(url):
"""
指定のアットコスメ口コミurlからデータを抽出
投稿者情報などをリストで出力
"""
rev_response = requests.get(url).text
soup = BeautifulSoup(rev_response,"html.parser")
#レビューページのレスポンス
review = soup.select_one("div.body p.read").text
if soup.select_one("span.buy"):
buy = soup.select_one("span.buy").extract().text
#購入情報があれば抽出してテキスト化
else:
buy = "-" #購入情報がない場合はハイフン
#購入情報がないことがあるため、ifで分岐
if soup.select_one("span.repeat"):
repeat = soup.select_one("span.repeat").extract().text
#リピート情報があれば抽出してテキスト化
else:
repeat = "-"#購入情報がない場合はハイフン
#リピート情報がないことがあるため、ifで分岐
if soup.select_one("div.rating.clearfix p.reviewer-rating"):
score = soup.select_one( \
"div.rating.clearfix p.reviewer-rating").extract().text
else:
score = "-"
#スコア情報を取得。スコアなしのエラーを防ぐためif分岐
if soup.select_one("dl.item-status.clearfix ul"):
status = soup.select_one( \
"dl.item-status.clearfix ul").text
else:
status = "-"
#購入品、サンプル品、プレゼント情報を取得。スコアなしのエラーを防ぐためif分岐
if soup.select_one("div.rating.clearfix p.date"):
date = soup.select_one("div.rating.clearfix p.date").text
else:
date = soup.select_one("div.rating.clearfix p.mobile-date").text
#日付情報を取得
reviewer_name = soup.select_one( \
"div.reviewer-info span.reviewer-title").text
#投稿者名を取得
revewer_profs = soup.select("div.reviewer-info li")
age = revewer_profs[0].text.replace("歳","")
skin = revewer_profs[1].text
#年齢と肌情報が並列で並んでいるため、リストとして入手して分割
datalist = [date,reviewer_name,age,skin,score,buy,review,status,repeat]
return datalist
#ここからメイン
code = input("クチコミを読みたい商品コードを入力してください")
start = dt.strptime(input("開始日時 yyyy/mm/dd hh:mm:ss"),"%Y/%m/%d %H:%M:%S")
end = dt.strptime(input("終了日時 yyyy/mm/dd hh:mm:ss"),"%Y/%m/%d %H:%M:%S")
file_name = input("口コミリストを保存するファイル名を入力してください")
product_url = "https://www.cosme.net/product/product_id/" + str(code) + "/reviews/p/"
page_count = 0 #アットコスメは0ページ目スタート
all_urls = [] #ループの外側にてURLsをリストと宣言
craw_frag = True
while craw_frag:
print(f"{page_count + 1}ページ目") #進捗表示
list_response = requests.get(product_url+str(page_count)).text
soup = BeautifulSoup(list_response,'html.parser')
dic = get_10reviews_list(soup)
for date in dic.keys():
if start <= date <= end:
all_urls.append(dic.get(date))
elif date < start:
craw_frag = False
break
#投稿日が設定範囲に入る場合、その口コミurlをall_urlsリストに蓄積。
#アットコスメ口コミはデフォで新しい順に並ぶので収集開始日時よりも
#古い口コミが出てきた時点で一覧のクローリングをストップする。
if next_check(soup):
page_count += 1
time.sleep(3)
continue
else:
craw_frag = False
print(f"一覧読み込み完了 レビュー{len(all_urls)}件 各レビュー読み込みを開始します")
rev_count = 1
columns = ["date","name","age","skin","score","buy","review","status","repeat",]
df = pd.DataFrame(columns=columns)
error_url_list = []
for url in all_urls:
print(f"{rev_count}件目 / {len(all_urls)}件 / エラー{len(error_url_list)}件")
#口コミ取得の際にエラーが出るとこれまでの読み込みが止まってしまうので
#回避のtryブロックを追加。その場合は結果のエクセルとリストに対象urlを吐き出す。
try:
se = pd.Series(get_review_data(url),columns).str.strip()
except:
error_message_dl = ["Error",f"No.{rev_count}","-","-","-","-",url]
se = pd.Series(error_message_dl,columns).str.strip()
error_url_list.append(url)
df = df.append(se, columns)
rev_count += 1
time.sleep(3)
df.to_csv(file_name+".csv", index = False, encoding = "utf_8_sig")
files.download(file_name+".csv")
print("プログラムを終了します")
アットコスメでは、商品ごとに固有のIDが設定されていて、10件ごとの口コミが新しい順に並んだページから、個々の口コミに入っていくような構造をしています。
そこでコードとしては、以下の2段階に分けた設計にしました。
①
対象の商品IDを入力して口コミ一覧をクローリングし、個々の口コミURLと投稿日情報を取得。
あらかじめ入力しておいた対象範囲に入るかどうかをチェックして、収集対象の口コミのURLをリストに格納する。
②
①で収集したリストから個々のURLにアクセスし、必要情報をスクレイプする。
この時に何故かエラーが生じたことがあったので、try文でエラーを回避。
エラーで読み込めなかった口コミがあった場合はそのURLを吐き出す。
①、②を経て集まった口コミ情報をgoogleスプレッドシートとしてダウンロードしてプログラムは終了です。
なお、サーバー負荷対策として、各アクセス毎に3秒の待機時間を設けています。
まとめ
おそらくコード的にはスマートじゃないところが多いのだと思います…
しかし、プログラムを実行して、初めてきちんと動作し、欲しい口コミが勝手に集まってきた時は、少し感動するものがありました。
そして、これによって業務の効率化にも繋がったと感じています。
次は、集まった口コミの分析として自然言語処理とか機械学習に手を出したいと思っているのですが、どこから始めていいのかとっかかりが見えず、今はまたpythonの基礎勉強を再開しているところです。
KH coderで簡単なことはできるので、そこまで必要にかられていない、というのもあります。
これからも、何かできたものがあればnoteを使って共有したいと思います。
この記事が気に入ったらサポートをしてみませんか?