23
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Pythonで駅周辺の飲食店データを食べログから取得して、彼の悲劇を救いたい。

Posted at

突然だが先日、このようなツイートを拝見した。
スクリーンショット 2019-03-21 20.47.42.png

おそらく彼はピッツァが大好きで、
その愛を抑えきれずに大金をはたいて、
わざわざピッツァ屋の近くに引っ越してきたのだろう。

しかし何の因果か、引っ越した瞬間にそのピッツァ屋が潰れてしまったというのだ。
彼にとってのエルサレムは失われてしまった。なんとも悲しい現実だ。

---- どうかこの方には同じ過ちを犯してほしくない。 ----

そう思って、私に何ができるかを考えた結果、
"ピッツァ屋を取り巻く環境を予め把握できれば、閉店のリスクを予測し、悲劇を回避できるだろう"
という結論に至った。

つまり、ピッツァ屋の周辺の賑わい度合い(=飲食店数)や同業態の競合数を把握できれば、
閉店のリスクがある環境かどうかを分析し、予め閉店のリスクを把握できるかもしれない。

そこで、「指定した駅周辺の飲食店情報」を食べログから取得するプログラムを作成してみた。

#具体的に何が取得できるの?
適当な"店舗のID"と"最寄り駅名で検索した際のURL"を渡せば、
最寄り駅周辺の全飲食店に関する、以下の情報が取得できる。

  • 飲食店の最寄り駅名
  • 飲食店の最寄り駅からの距離
  • 食べログ上で登録されている、その飲食店の"ジャンルタグ"
スクリーンショット 2019-03-19 21.48.03.png * 夜最高予算 * 夜最低予算 * 住所 * 交通手段 * 営業時間 * 定休日 * 予算 * 席数 * 個室 * 禁煙・喫煙 * 駐車場 * 空間・設備 * 飲み放題コース * 利用シーン * オープン日 * 電話番号 * 緯度(日本測地系) * 経度(日本測地系) 他

#データの準備(手作業)
1.店舗の最寄り駅で検索した際のurlをコピーする。
1.1. 食べログで最寄り駅名で検索する。
スクリーンショット 2019-03-19 21.59.10.png
1.2. 検索した際のurlをコピーする。
スクリーンショット 2019-03-19 22.13.25.png
(参考)取得したい範囲を半径800mから変更したい場合は、サイト内下記の画面で距離を指定の上、生成されたurlをコピーする。
スクリーンショット 2019-03-19 21.59.50.png

2."ID", "url" の2項目を記したcsvを用意する。
"url"は先程コピーしたもの。店舗"ID"は任意のものを記入。
スクリーンショット 2019-03-19 22.53.06.png
(↑こんな感じ。)

複数の店舗を登録すれば、それぞれの最寄り駅周辺の飲食店データが取得できる。

作成したcsvファイルは適当な名前(一旦"店舗データ.csv"とする)で保存し、
スクリプトと同ディレクトリに配置する。

#実行環境
Python 3.7(Anaconda3系)
macOS Mojave 10.14.1

#処理の流れ
大まかな処理の流れとしては、

  1. 入力ファイルの1レコード目のURLを読み込む。
  2. 読み込んだURLにアクセスして、飲食店一覧ページに掲載されている飲食店情報を1店舗ずつ、一覧ページ&詳細ページから取得する。
  3. 次のページに移動して、2.のプロセスを実行する
  4. 全ページ分の飲食店情報を取得したら、入力データの2レコード目のURLを読み込み、2、3を実行。
  5. 入力ファイルで記入した全てのレコードで、上記の処理を完了したら、データをエクスポートして終了。

と言った感じ。

まず、必要なライブラリをインポートする。

scrape.py
from time import sleep
import requests
import re
import sys
import pandas as pd
from bs4 import BeautifulSoup

もし、スクレイピング用のライブラリ(BeautifulSoup4)が未インストールの場合
ターミナルからpipでインストールする。

$ pip install beautifulsoup4

入力ファイル名を、作成したcsvファイル名で設定するとともに、
任意の出力ファイル名を設定する。

scrape.py
#設定---------------------------------
fname = u'店舗データ.csv' # 入力ファイル名
outfile = u'食べログ飲食店データ.csv' # 出力ファイル名
#---------------------------------

飲食店詳細ページ記載情報を取得する関数を定義する。

scrape.py
### 関数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

次に、飲食店情報を取得する関数を定義する。

scrape.py
### 関数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を取得する関数を定義する。

scrape.py
### 関数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を読み込み、関数を実行、ファイルを出力する。

scrape.py
###【主処理】ファイルの読み込み、スクレイピング、ファイル出力
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 コウメ。

23
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?