おそらく彼はピッツァが大好きで、
その愛を抑えきれずに大金をはたいて、
わざわざピッツァ屋の近くに引っ越してきたのだろう。
しかし何の因果か、引っ越した瞬間にそのピッツァ屋が潰れてしまったというのだ。
彼にとってのエルサレムは失われてしまった。なんとも悲しい現実だ。
---- どうかこの方には同じ過ちを犯してほしくない。 ----
そう思って、私に何ができるかを考えた結果、
"ピッツァ屋を取り巻く環境を予め把握できれば、閉店のリスクを予測し、悲劇を回避できるだろう"
という結論に至った。
つまり、ピッツァ屋の周辺の賑わい度合い(=飲食店数)や同業態の競合数を把握できれば、
閉店のリスクがある環境かどうかを分析し、予め閉店のリスクを把握できるかもしれない。
そこで、「指定した駅周辺の飲食店情報」を食べログから取得するプログラムを作成してみた。
#具体的に何が取得できるの?
適当な"店舗のID"と"最寄り駅名で検索した際のURL"を渡せば、
最寄り駅周辺の全飲食店に関する、以下の情報が取得できる。
- 飲食店の最寄り駅名
- 飲食店の最寄り駅からの距離
- 食べログ上で登録されている、その飲食店の"ジャンルタグ"
#データの準備(手作業)
1.店舗の最寄り駅で検索した際のurlをコピーする。
1.1. 食べログで最寄り駅名で検索する。
1.2. 検索した際のurlをコピーする。
(参考)取得したい範囲を半径800mから変更したい場合は、サイト内下記の画面で距離を指定の上、生成されたurlをコピーする。
2."ID", "url" の2項目を記したcsvを用意する。
"url"は先程コピーしたもの。店舗"ID"は任意のものを記入。
(↑こんな感じ。)
複数の店舗を登録すれば、それぞれの最寄り駅周辺の飲食店データが取得できる。
作成したcsvファイルは適当な名前(一旦"店舗データ.csv"とする)で保存し、
スクリプトと同ディレクトリに配置する。
#実行環境
Python 3.7(Anaconda3系)
macOS Mojave 10.14.1
#処理の流れ
大まかな処理の流れとしては、
- 入力ファイルの1レコード目のURLを読み込む。
- 読み込んだURLにアクセスして、飲食店一覧ページに掲載されている飲食店情報を1店舗ずつ、一覧ページ&詳細ページから取得する。
- 次のページに移動して、2.のプロセスを実行する
- 全ページ分の飲食店情報を取得したら、入力データの2レコード目のURLを読み込み、2、3を実行。
- 入力ファイルで記入した全てのレコードで、上記の処理を完了したら、データをエクスポートして終了。
と言った感じ。
まず、必要なライブラリをインポートする。
from time import sleep
import requests
import re
import sys
import pandas as pd
from bs4 import BeautifulSoup
もし、スクレイピング用のライブラリ(BeautifulSoup4)が未インストールの場合
ターミナルからpipでインストールする。
$ pip install beautifulsoup4
入力ファイル名を、作成したcsvファイル名で設定するとともに、
任意の出力ファイル名を設定する。
#設定---------------------------------
fname = u'店舗データ.csv' # 入力ファイル名
outfile = u'食べログ飲食店データ.csv' # 出力ファイル名
#---------------------------------
飲食店詳細ページ記載情報を取得する関数を定義する。
### 関数1_飲食店詳細ページから競合店情報を取得
def syosai_scrape(syosai_url):
html = requests.get(syosai_url)
soup = BeautifulSoup(html.text, "html.parser")
data=[]
tables = soup.find_all("table",{"class":"c-table c-table--form rstinfo-table__table"})
for table in tables:
rows = table.find_all("tr")
for row in rows:
t_col1 = row.find("th").text.replace('\n',' ').replace('\t',' ').strip()
t_col2 = row.find("td").text.replace('\n',' ').replace('\t',' ').strip()
col1 = re.sub(r'\s+',u' ',t_col1) # 連続する空白を一つの空白に置換
col2 = re.sub(r'\s+',u' ',t_col2) # 連続する空白を一つの空白に置換
data.append([col1,col2])
result = [""]*24
for row in data:
if u"店名" == row[0]:
result[0] = row[1]
if u"ジャンル" == row[0]:
result[1] = row[1]
if u"住所" == row[0]:
result[2] = row[1].replace(' 大きな地図を見る 周辺のお店を探す','') # 不要なキーワードを削除
if u"交通手段" == row[0]:
result[3] = row[1]
if u"営業時間" == row[0]:
result[4] = row[1]
if u"定休日" == row[0]:
result[5] = row[1]
if u"予算" == row[0]:
result[6] = row[1]
if u"予算(口コミ集計)" == row[0]:
result[7] = row[1]
if u"席数" == row[0]:
result[8] = row[1]
if u"個室" == row[0]:
result[9] = row[1]
if u"禁煙・喫煙" == row[0]:
result[10] = row[1]
if u"駐車場" == row[0]:
result[11] = row[1]
if u"空間・設備" == row[0]:
result[12] = row[1]
if u"飲み放題コース" == row[0]:
result[13] = row[1]
if u"コース" == row[0]:
result[14] = row[1]
if u"ドリンク" == row[0]:
result[15] = row[1]
if u"料理" == row[0]:
result[16] = row[1]
if u"利用シーン" == row[0]:
result[17] = row[1]
if u"サービス" == row[0]:
result[18] = row[1]
if u"オープン日" == row[0]:
result[19] = row[1]
if u"電話番号" == row[0]:
result[20] = row[1]
ll= soup.find("script",{"type" : "application/ld+json"}).string
lat_st = ll.find("latitude")+10
lat_ed = ll.find(",",lat_st)
latitude = ll[lat_st:lat_ed] # 店舗緯度取得
lon_st = ll.find("longitude")+11
lon_ed = ll.find("}",lon_st)
longitude = ll[lon_st:lon_ed] # 店舗経度取得
result[21]=latitude
result[22]=longitude
result[23] = soup.find("span",{"class" : "linktree__parent-target-text"}).string # 最寄り駅取得
return result
次に、飲食店情報を取得する関数を定義する。
### 関数2_飲食店一覧ページから競合店情報を取得
def scrape(html, tcode):
soup = BeautifulSoup(html, "html.parser") # 対象ページhtml全取得
data=[]
## 飲食店名、詳細ページリンク先詳細情報取得
info = soup.find_all("a",{"class":"list-rst__rst-name-target cpy-rst-name"}) # 対象タグ取得
syosai_df = pd.DataFrame()
shopname = []
for name in info:
hr = name.get("href") # 詳細ページリンク取得
name = name.text # タグ内テキスト部抽出
name = name.replace("\u3000","") # 不要文字列除去
name = name.replace(",","") # 不要文字列除去
shopname.append(name)
scr = syosai_scrape(hr) # 詳細ページ情報をリストで取得
s = pd.Series(scr, index=["店名", "ジャンル", "住所", "交通手段", "営業時間",
"定休日", "予算", "予算(口コミ集計)", "席数", "個室",
"禁煙・喫煙", "駐車場", "空間・設備", "飲み放題コース",
"コース", "ドリンク", "料理", "利用シーン", "サービス",
"オープン日", "電話番号", "緯度", "経度", "最寄駅"])
syosai_df = syosai_df.append(s, ignore_index = True)
syosai_df = syosai_df[2:22] # 必要列のみ抽出
## 飲食店詳細ページURL取得
info = soup.find_all("a",{"class":"list-rst__rst-name-target cpy-rst-name"}) # 対象タグ取得
## 最寄り駅、駅からの距離、ジャンル取得
info = soup.find_all("span",{"class":"list-rst__area-genre cpy-area-genre"})# 対象タグ取得
station_name = []
station_distance = []
jenre1 = []
jenre2 = []
jenre3 = []
for sj in info:
sj = sj.text # タグ内テキスト部抽出
station_name.append(sj[:sj.find('駅')+1]) # 駅名取得
station_distance.append(sj[sj.find('駅')+1: sj.find('m')]) # 駅からの距離取得
if sj.count('、') == 0: # ジャンルが1つの場合
jenre1.append(sj[sj.find('/')+2:]) # ジャンル1取得
jenre2.append('') # ジャンル2取得
jenre3.append('') # ジャンル3取得
else:
jenre1.append(sj[sj.find('/')+2: sj.find('、')]) # ジャンル1取得
if sj.count('、') == 1: # ジャンルが2つの場合
jenre2.append(sj[sj.find('、')+1:]) # ジャンル2取得
jenre3.append('') # ジャンル3取得
elif sj.count('、') == 2: # ジャンルが3つ以上の場合
jenre2.append(sj[sj.find('、')+1: sj.find('、',sj.find('、')+1)]) # ジャンル2取得
jenre3.append(sj[sj.find('、',sj.find('、')+1)+1:]) # ジャンル3取得
## 夜予算取得
info = soup.find_all("span",{"class":"c-rating__val list-rst__budget-val cpy-dinner-budget-val"}) # 対象タグ取得
budget_min = []
budget_max = []
for bud in info:
bud = bud.text #タグ内テキスト部抽出
bud = bud.replace('¥', '').replace(',', '').replace('-', '') # 不要文字列除去
budget_min.append(bud[:bud.find('~')]) # 最低予算取得
budget_max.append(bud[bud.find('~')+1:]) # 最高予算取得
## 取得情報マージ
ten_code = [tcode]*len(shopname) #店舗ID付与
data = pd.DataFrame({'ID' : ten_code, '店名' : shopname, '最寄駅名' : station_name, '駅からの距離' : station_distance, 'ジャンル1' : jenre1, 'ジャンル2' : jenre2, 'ジャンル3' : jenre3, '夜最低予算' : budget_min, '夜最高予算' : budget_max})
data = pd.concat([data, syosai_df], axis=1)
return data
さいごに、全ページ分のURLを取得する関数を定義する。
### 関数3_1駅に対する店舗一覧全ページのURL取得
def get_write_data(url, tcode):
PageNum = 99999 # 取得する一覧ページ数
sleep_time_list = 1 # 店舗一覧ページURLを取得する間隔(秒)
sleep_time_detail = 1 # スクレイピングを実施する間隔(秒)
dataset = pd.DataFrame() # スクレイピング結果格納用のデータフレーム
url_list_all = [url] # 全商品一覧ページURL格納リスト
for i in range(PageNum):
try:
html = requests.get(url) # 店舗一覧ページのHTML取得
except ChunkedEncodingError:
print(u"エラー:URL取得時にエラーが発生しました。60秒ほど時間をおいて再度実行してください。")
## 商品一覧ページのHTML取得可否確認
if html.status_code == requests.codes.ok:
soup = BeautifulSoup(html.text, 'html.parser') # Soupオブジェクト取得
print(u"一覧ページのURL取得 成功: "+str(i)+"/"+str(PageNum))
else:
print(u"一覧ページのURL取得 失敗: "+str(i)+"/"+str(PageNum)+" error code:"+str(html.status_code))
sys.exit()
## NextPageのURL取得
nextpage = soup.find("a", {"class": "c-pagination__arrow c-pagination__arrow--next"})
if nextpage:
#url = urljoin(web_url,nextpage.get("href"))
url = nextpage.get("href")
url_list_all.append(url)
else:
break
sleep(sleep_time_list)
print(u"取得したURL数: "+str(len(url_list_all)))
## 商品一覧ページのHTML取得とスクレイピング
for i in range(len(url_list_all)):
html = requests.get(url_list_all[i]) # ページのHTML取得
if html.status_code == requests.codes.ok:
print(u"ページのHTML取得 成功: "+str(i)+"/"+str(len(url_list_all)))
data = scrape(html.text, tcode) # スクレイピング実行
dataset = pd.concat([dataset, data], ignore_index=True)
else:
print(u"ページのHTML取得 失敗: "+str(i)+"/"+str(len(prod_url))+" error code:"+str(html.status_code))
sys.exit()
sleep(sleep_time_detail) # 処理間隔
return dataset
予め用意したcsvを読み込み、関数を実行、ファイルを出力する。
###【主処理】ファイルの読み込み、スクレイピング、ファイル出力
print(u'インプットファイル読み込み:開始')
## 取得対象駅URLが記されたファイル読み込み
df = pd.read_csv(fname)
tenpo_code = df['ID'] # 読み込み対象フィールド名(店舗番号)
tabelog_url = df['url'] # 読み込み対象フィールド名(食べログURL)
code = tenpo_code.values.tolist() # 店舗ID格納リスト
stations = tabelog_url.values.tolist() # URL格納リスト
print(u'インプットファイル読み込み:終了')
## 関数実行
cnt = 0 #ループ回数カウンター
dataframe = pd.DataFrame() # スクレイピング結果格納用のデータフレーム
for i in range(len(stations)):
cnt += 1
print(cnt, u'レコード目クローリング、スクレイピング:開始---------------------------------')
url = stations[i]
tcode = code[i]
df = get_write_data(url, tcode) #関数②実行
dataframe = pd.concat([dataframe, df], ignore_index=True) #データフレームへ追加
print(cnt, u'レコード目クローリング、スクレイピング:終了---------------------------------')
## データセット出力
print(u'全レコード分スクレイピング終了')
# ファイル出力
dataframe.to_csv(outfile, encoding="utf-8", index=False)
print(u'ファイル出力完了')
#取得したデータをどう活用するか?
駅周辺の飲食店の数や、競合店数(分析対象のお店のジャンル"イタリアン"と、同じジャンルをもつ飲食店数)、価格帯、ジャンルの傾向から、分析対象の店がどれほど熾烈な競争に揉まれているか、周辺は賑わっているかが基礎統計を取るだけでもわかるだろう。
その他にも、各変数を用いて、生存予測モデルの説明変数として組み込むでも良し。
また、飲食店の緯度経度と自店舗の緯度経度から「自店舗から50m以内に競合店はいくつあるか?」といった変数も作成できる。(※要日本測地系→世界測地系変換)
#さいごに
彼にこのTipsが届くように祈っている。
また、Pythonを触り始めてからまだ日が浅いので、稚拙なコードが散見されると思います。
質問・指摘等ありましたら、ぜひ気軽コメントください。
LOVE コウメ。