質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

Q&A

解決済

1回答

4040閲覧

食べログスクレイピング:店名、住所、評価点数ごとのデータフレームを作成したい

osssa

総合スコア1

スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

pandas

Pandasは、PythonでRにおけるデータフレームに似た型を持たせることができるライブラリです。 行列計算の負担が大幅に軽減されるため、Rで行っていた集計作業をPythonでも比較的簡単に行えます。 データ構造を変更したりデータ分析したりするときにも便利です。

0グッド

1クリップ

投稿2020/10/24 16:48

前提・実現したいこと

食べログで東京都内、星3.5以上のラーメン屋情報のスクレイピングをしています。
「店名、住所、評価点数」を1行としてデータフレームを作成し、csv書き出しをしたいです。

発生している問題・エラーメッセージ

「店名、住所、評価点数」を1行としたデータフレームが生成されません。
また、星3.67以上の情報はアウトプットできるのですが、3.67未満から動作がストップしてしまいます。

該当のソースコード

import requests
from bs4 import BeautifulSoup
import re
import pandas as pd
import time
import csv

class Tabelog:
"""
食べログスクレイピングクラス
test_mode=Trueで動作させると、最初のページの3店舗のデータのみを取得できる
"""
def init(self, base_url, test_mode=False, p_ward='東京都内', begin_page=1, end_page=30):

#### 変数宣言 self.store_id = '' self.store_id_num = 0 self.store_name = '' self.score = 0 self.address_name = "" self.columns = ['store_id', 'store_name', 'score','address_name'] self.df = pd.DataFrame(columns=self.columns) self.__regexcomp = re.compile(r'\n|\s') # \nは改行、\sは空白 page_num = begin_page # 店舗一覧ページ番号 if test_mode: list_url = base_url + str(page_num) + '/?Srt=D&SrtT=rt&sort_mode=1'

####食べログの点数ランキングでソートする際に必要な処理
self.scrape_list(list_url, mode=test_mode)
else:
while True:
list_url = base_url + str(page_num) + '/?Srt=D&SrtT=rt&sort_mode=1'
####食べログの点数ランキングでソートする際に必要な処理
if self.scrape_list(list_url, mode=test_mode) != True:
break

#### INパラメータまでのページ数データを取得する if page_num >= end_page: break page_num += 1 return def scrape_list(self, list_url, mode): """ 店舗一覧ページのパーシング """ r = requests.get(list_url) if r.status_code != requests.codes.ok: return False soup = BeautifulSoup(r.content, 'html.parser') soup_a_list = soup.find_all('a', class_='list-rst__rst-name-target') # 店名一覧 if len(soup_a_list) == 0: return False if mode: for soup_a in soup_a_list[:2]: item_url = soup_a.get('href') # 店の個別ページURLを取得 self.store_id_num += 1 self.scrape_item(item_url, mode) else: for soup_a in soup_a_list: item_url = soup_a.get('href') # 店の個別ページURLを取得 self.store_id_num += 1 self.scrape_item(item_url, mode) return True def scrape_item(self, item_url, mode): """ 個別店舗情報ページのパーシング """ start = time.time() r = requests.get(item_url) if r.status_code != requests.codes.ok: print(f'error:not found{ item_url }') return soup = BeautifulSoup(r.content, 'html.parser') #### 店舗名称取得 #### <h2 class="display-name"> #### <span> #### 麺匠 竹虎 新宿店 #### </span> #### </h2> store_name_tag = soup.find('h2', class_='display-name') store_name = store_name_tag.span.string print('{}→店名:{}'.format(self.store_id_num, store_name.strip()), end='') self.store_name = store_name.strip() #### ラーメン屋、つけ麺屋以外の店舗は除外 store_head = soup.find('div', class_='rdheader-subinfo') # 店舗情報のヘッダー枠データ取得 store_head_list = store_head.find_all('dl') store_head_list = store_head_list[1].find_all('span') #print('ターゲット:', store_head_list[0].text) if store_head_list[0].text not in {'ラーメン', 'つけ麺'}: print('ラーメンorつけ麺のお店ではないので処理対象外') self.store_id_num -= 1 return ####住所取得 ####<p class="rstinfo-table__address"> #### <span> #### <a href="/tokyo/" class="listlink"> #### 東京都 #### </a> #### </span> #### <span> #### <a href="/tokyo/C13104/rstLst/" class="listlink"> #### 新宿区 #### </a> #### <a href="/tokyo/C13104/C36218/rstLst/" class="listlink"> #### 歌舞伎町 #### </a> #### 1-9-5 #### </span> #### <span> #### 三経61ビル 2F #### </span> #### </p> try: address_name = soup.find("p", class_="rstinfo-table__address").text print(" 住所:{}".format(address_name), end="") self.address_name = address_name except AttributeError: href = '' #### 評価点数取得 ####<b class="c-rating__val rdheader-rating__score-val" rel="v:rating"> #### <span class="rdheader-rating__score-val-dtl">3.58</span> ####</b> rating_score_tag = soup.find('b', class_='c-rating__val') rating_score = rating_score_tag.span.string print(' 評価点数:{}点'.format(rating_score), end='') self.score = rating_score #### 評価点数が存在しない店舗は除外 if rating_score == '-': print(' 評価がないため処理対象外') self.store_id_num -= 1 return #### 評価が3.5未満店舗は除外 if float(rating_score) < 3.5: print(' 食べログ評価が3.5未満のため処理対象外') self.store_id_num -= 1 return #### データフレームの生成 self.make_df() return def make_df(self): self.store_id = str(self.store_id_num).zfill(8) #0パディング se = pd.Series([self.store_id, self.store_name, self.address_name, self.score], self.columns) # 行を作成 self.df = self.df.append(se, self.columns) # データフレームに行を追加 return

###実行
tokyo_ramen_address = Tabelog(base_url="https://tabelog.com/tokyo/rstLst/ramen/",test_mode=False)

tokyo_ramen_address.df.to_csv("Users/〜〜〜〜/Desktop/tokyo_ramen_address.csv")

試したこと

https://qiita.com/toshiyuki_tsutsui/items/f143946944a428ed105b
上記URLの方のブログのコードを元に、住所情報をスクレイピングするためにコードをアレンジしました。
住所情報を取り出すことは出来たのですが、それが綺麗にデータフレームとしてアウトプットできず、また星3.5まで取り出しきれない状況です。
(上記URLのコードをそのまま試した際は、データフレームに収まり上手くいったのですが。。)

補足情報(FW/ツールのバージョンなど)

Python、スクレイピングを勉強し始めたばかりの初心者です。mac OS、Jupyter Notebookで取り組んでいます。
皆さま恐れ入りますがご教示いただけますと幸いです。何卒よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

meg_

2020/10/24 18:01

問題と関係しているかは分かりませんが、食べログはスクレイピング禁止かと思います。
guest

回答1

0

ベストアンサー

利用規約及びurllib.robotparser.RobotFileParser()で調べた所、食べログの一部ページはスクレイピングが禁止されている様ですが、掲題のコード動作に関する禁止はされていない様でした。

また掲題のコードを確認致しましたが、特には不具合などはなく正常に動作し
データフレーム、CSV共に正しく出力する事が出来ておりました。
(一部インデントがずれていたり、__init__initとなってしまっていたなど
細かい所でおかしい部分はありましたが、そういった点のみの修正で動作出来ております。)
大きな修正点としては以下の1点です。

diff

1- tokyo_ramen_address.df.to_csv("tokyo_ramen_address.csv") 2+ tokyo_ramen_address.df.to_csv("tokyo_ramen_address.csv", encoding='utf_8_sig')

python

1import requests 2from bs4 import BeautifulSoup 3import re 4import pandas as pd 5import time 6import csv 7 8class Tabelog: 9 def __init__(self, base_url, test_mode=False, p_ward='東京都内', begin_page=1, end_page=50): 10 #変数宣言 11 self.store_id = '' 12 self.store_id_num = 0 13 self.store_name = '' 14 self.score = 0 15 self.address_name = "" 16 self.columns = ['store_id', 'store_name', 'address_name','score'] 17 self.df = pd.DataFrame(columns=self.columns) 18 self.__regexcomp = re.compile(r'\n|\s') # \nは改行、\sは空白 19 20 page_num = begin_page # 店舗一覧ページ番号 21 22 if test_mode: 23 list_url = base_url + str(page_num) + '/?Srt=D&SrtT=rt&sort_mode=1' #食べログの点数ランキングでソートする際に必要な処理 24 self.scrape_list(list_url, mode=test_mode) 25 else: 26 while True: 27 list_url = base_url + str(page_num) + '/?Srt=D&SrtT=rt&sort_mode=1' #食べログの点数ランキングでソートする際に必要な処理 28 if self.scrape_list(list_url, mode=test_mode) != True: 29 break 30 31 #INパラメータまでのページ数データを取得する 32 if page_num >= end_page: 33 break 34 page_num += 1 35 # dfの確認 36 print(self.df) 37 38 39 def scrape_list(self, list_url, mode): 40 r = requests.get(list_url) 41 if r.status_code != requests.codes.ok: 42 return False 43 44 soup = BeautifulSoup(r.content, 'html.parser') 45 soup_a_list = soup.find_all('a', class_='list-rst__rst-name-target') # 店名一覧 46 47 if len(soup_a_list) == 0: 48 return False 49 50 if mode: 51 for soup_a in soup_a_list[:2]: 52 item_url = soup_a.get('href') # 店の個別ページURLを取得 53 self.store_id_num += 1 54 self.scrape_item(item_url, mode) 55 else: 56 for soup_a in soup_a_list: 57 item_url = soup_a.get('href') # 店の個別ページURLを取得 58 self.store_id_num += 1 59 self.scrape_item(item_url, mode) 60 61 return True 62 63 def scrape_item(self, item_url, mode): 64 start = time.time() 65 66 r = requests.get(item_url) 67 if r.status_code != requests.codes.ok: 68 #print(f'error:not found{ item_url }') 69 return 70 71 soup = BeautifulSoup(r.content, 'html.parser') 72 73 store_name_tag = soup.find('h2', class_='display-name') 74 store_name = store_name_tag.span.string 75 #print('{}→店名:{}'.format(self.store_id_num, store_name.strip()), end='') 76 self.store_name = store_name.strip() 77 78 # ラーメン屋、つけ麺屋以外の店舗は除外 79 store_head = soup.find('div', class_='rdheader-subinfo') # 店舗情報のヘッダー枠データ取得 80 store_head_list = store_head.find_all('dl') 81 store_head_list = store_head_list[1].find_all('span') 82 #print('ターゲット:', store_head_list[0].text) 83 84 if store_head_list[0].text not in {'ラーメン', 'つけ麺'}: 85 #print('ラーメンorつけ麺のお店ではないので処理対象外') 86 self.store_id_num -= 1 87 return 88 89 try: 90 address_name = soup.find("p", class_="rstinfo-table__address").text 91 #print(" 住所:{}".format(address_name), end="") 92 self.address_name = address_name 93 94 except AttributeError: 95 href = '' 96 97 rating_score_tag = soup.find('b', class_='c-rating__val') 98 rating_score = rating_score_tag.span.string 99 #print(' 評価点数:{}点'.format(rating_score), end='') 100 self.score = rating_score 101 102 #評価点数が存在しない店舗は除外 103 if rating_score == '-': 104 #print(' 評価がないため処理対象外') 105 self.store_id_num -= 1 106 return 107 108 #評価が3.5未満店舗は除外 109 if float(rating_score) < 3.5: 110 #print(' 食べログ評価が3.5未満のため処理対象外') 111 self.store_id_num -= 1 112 return 113 114 115 #データフレームの生成 116 self.make_df() 117 return 118 119 def make_df(self): 120 self.store_id = str(self.store_id_num).zfill(8) #0パディング 121 se = pd.Series([self.store_id, self.store_name, self.address_name, self.score], self.columns) # 行を作成 122 self.df = self.df.append(se, self.columns) # データフレームに行を追加 123 print(self.address_name) 124 print(self.score) 125 print(self.store_name) 126 print(self.store_id) 127 print('df appended!') 128 print('='*50) 129 time.sleep(0.4) 130 return 131 132if __name__ == '__main__': 133 tokyo_ramen_address = Tabelog(base_url="https://tabelog.com/tokyo/rstLst/ramen/",test_mode=False) 134 tokyo_ramen_address.df.to_csv("tokyo_ramen_address.csv", encoding='utf_8_sig')

追記

作成者が違う様で、質問者様に言っても仕方がない事では有ると思いますが
確認用のprintはscrape_item関数内で行うよりも
make_df関数内で確認を行った方が[抽出出来ているか]と[正しい値であるか]を
同時に確認する事ができて効率が良いでしょう。(雑な説明ですみません)

またif store_head_list[0].text not in {'ラーメン', 'つけ麺'}:の点についてですが
店舗情報のヘッダー枠のジャンルという項目の1個目が[ラーメン]ではない場合に
実際にラーメン屋であった場合でも除外されてしまっております。
この場合の処理をもう少し柔軟に対応してあげられると更に精度が高まると思います。
例:ジャンル: ラーメン▼担々麺▼ → 処理対象
ジャンル: 担々麺▼ラーメン▼ → 除外対象

投稿2020/10/25 08:57

編集2020/10/25 09:11
nto

総合スコア1438

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

osssa

2020/11/21 06:39

お返事が遅くなり大変申し訳ございません。上記で試していますが、やはり星3.67あたりでフリーズしてしまいますが、csv書き出しまで行えているとのことですので別PCでも試してみます。この度は誠にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問