En regardant l'URL de chaque boutique, dans le cas de Tokyo, elle se présente sous la forme "//tabelog.com/tokyo/A .... / A ...... / ........ /" Il est devenu. Par exemple, dans le cas d'une boutique à Shibuya, l'URL est "//tabelog.com/tokyo/A1303/A130301/......../" Il peut être interprété comme "Shibuya / boutique spécifique /". En regardant la page supérieure du journal des aliments, il y a des données jusqu'à "// tabelog.com/tokyo/A .... /" à la place de "Recherche par zone", donc je vais les obtenir en premier. Comme prévu, nous n'avons pas besoin de toutes les données pour l'ensemble du pays, donc après avoir réduit dans une certaine mesure une grande zone, nous n'obtiendrons que les données que nous voulons.
import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup
import time
root_url = 'https://tabelog.com/'
res = requests.get(root_url)
soup = BeautifulSoup(res.content, 'html.parser')
Je l'ai analysé avec Beautiful Soup comme ça, et quand j'ai regardé à l'intérieur,
<h2 class="rsttop-heading1 rsttop-search__title">
Recherche par zone
</h2>
</div>
<ul class="rsttop-area-search__list">
<li class="rsttop-area-search__item">
<a class="rsttop-area-search__target js-area-swicher-target" data-swicher-area-list='[{"areaName":"Ginza / Shimbashi / Yurakucho","url":"/tokyo/A1301/"},{"areaName":"Tokyo / Nihonbashi","url":"/tokyo/A1302/"},{"areaName":"Shibuya / Ebisu / Daikanyama","url":"/tokyo/A1303/"},...
↑ Les données que vous voulez ici! !!
a = soup.find_all('a', class_='rsttop-area-search__target js-area-swicher-target')
a[0].get('data-swicher-area-list')
Si tu le fais comme ça
'[{"areaName":"Ginza / Shimbashi / Yurakucho","url":"/tokyo/A1301/"},{"areaName":"Tokyo / Nihonbashi","url":"/tokyo/A1302/"},{"areaName":"Shibuya / Ebisu / Daikanyama","url":"/tokyo/A1303/"},...
Etc. Cependant, je pensais que c'était une belle liste de dictionnaires, mais c'était complètement une chaîne de caractères. .. .. J'ai donc essayé de trouver comment le faire, mais je n'ai pas pu le trouver, alors je vais le fixer de force à la forme nécessaire bien que ce ne soit pas bon. Si vous savez comment gérer le processus ici en douceur, faites-le moi savoir!
splitted = a[0].get('data-swicher-area-list').split('"')
area_dict = {}
for i in range(int((len(splitted)-1)/8)):
area_dict[splitted[i*8+3]] = splitted[i*8+7]
Avec cela, j'ai réussi à obtenir le dictionnaire suivant.
{'Ueno / Asakusa / Nippori': '/tokyo/A1311/',
'Ryogoku, Kinshicho, Koiwa': '/tokyo/A1312/',
'Nakano-Nishiogikubo': '/tokyo/A1319/',...
Pour être honnête, Tokyo seul va bien, mais si vous voulez l'obtenir de manière exhaustive, c'est comme suit.
area_url = {}
for area in a:
area_dict = {}
splitted = area.get('data-swicher-area-list').split('"')
for i in range(int((len(splitted)-1)/8)):
area_dict[splitted[i*8+3]] = splitted[i*8+7]
area_url[area.get('data-swicher-city').split('"')[3]] = area_dict
Ce qui m'intéressait en chemin était len (area_url) = 47 pour len (a) = 53. Quand je l'ai recherché, la cause était que Tokyo, Kanagawa, Aichi, Osaka, Kyoto et Fukuoka sont apparus deux fois, mais le contenu était le même pour la partie que je voulais, alors j'ai dit que le code ci-dessus atteignait l'objectif. Cela a été jugé. L'URL peut être obtenue sous la forme suivante.
area_url
│
├──'Tokyo'
│ ├──'Ueno / Asakusa / Nippori' : '/tokyo/A1311/'
│ ├──'Ryogoku, Kinshicho, Koiwa' : '/tokyo/A1312/'
│ ⋮
│ └──'Ginza / Shimbashi / Yurakucho' : '/tokyo/A1301/'
│
├──'Kanagawa'
│ ├──'Autour d'Odawara' : '/kanagawa/A1409/'
│ ⋮
⋮
Maintenant que les zones principales ont été acquises, l'étape suivante consiste à acquérir les catégories mineures. De la même manière que pour obtenir une catégorie majeure,
url = '/tokyo/A1304/'
res = requests.get(root_url + url[1:])
soup = BeautifulSoup(res.content, 'html.parser')
a = soup.find_all('a', class_='c-link-arrow')
area_dict = {}
for area in a:
href = area['href']
if href[-21:-8]!=url:
continue
else:
area_dict[area.text] = href
Si tu fais
{'Yoyogi': 'https://tabelog.com/tokyo/A1304/A130403/',
'Okubo / Nouvel Okubo': 'https://tabelog.com/tokyo/A1304/A130404/',
'Shinjuku': 'https://tabelog.com/tokyo/A1304/A130401/',
'Shinjuku Gyoen': 'https://tabelog.com/tokyo/A1304/A130402/'}
Ça fait du bien. De plus, l'instruction if a été insérée car certaines publicités ont class = "c-link-arrow" lorsque soup.find_all ('a', class_ = 'c-link-arrow') est exécuté. Il s'agit de les éliminer.
Ensuite, spécifiez la zone dans laquelle vous souhaitez accéder et obtenez l'URL de cette zone.
visit_areas = ['Roppongi / Azabu / Hiroo', 'Harajuku / Omotesando / Aoyama', 'Yotsuya / Ichigaya / Iidabashi', 'Shinjuku / Yoyogi / Okubo',
'Tokyo / Nihonbashi', 'Shibuya / Ebisu / Daikanyama', 'Meguro / Shirokane / Gotanda', 'Akasaka / Nagatacho / Tameike', 'Ginza / Shimbashi / Yurakucho']
url_dict = {}
for visit_area in visit_areas:
url = area_url['Tokyo'][visit_area]
time.sleep(1)
res = requests.get(root_url + url[1:])
soup = BeautifulSoup(res.content, 'html.parser')
a = soup.find_all('a', class_='c-link-arrow')
for area in a:
href = area['href']
if href[-21:-8]!=url:
continue
else:
url_dict[area.text] = href
Nous avons réussi à obtenir l'URL de 34 zones sous la forme suivante!
{'Marunouchi / Otemachi': 'https://tabelog.com/tokyo/A1302/A130201/',
'9 marches plus bas': 'https://tabelog.com/tokyo/A1309/A130906/',...
Maintenant que vous avez l'URL qui pointe vers la zone ("// tabelog.com/tokyo/A .... / A ...... /"), l'étape suivante consiste à obtenir l'URL du restaurant individuel ("/". /tabelog.com/tokyo/A .... / A ...... / ........ / ").
url = 'https://tabelog.com/tokyo/A1302/A130201/'
res = requests.get(url)
soup = BeautifulSoup(res.content, 'html.parser')
L'URL ci-dessus affichera les 20 premiers magasins de cette zone. L'URL des 20 magasins suivants est "//tabelog.com/tokyo/A1302/A130201/rstLst/2/", et vous pouvez spécifier rstLst / 3,4,5, ... Tu peux voir ça. Étant donné que la valeur maximale de rstLst est requise pour le traitement en boucle ici, la valeur obtenue en divisant le nombre total de restaurants par 20 est arrondie à un entier comme indiqué ci-dessous.
import math
count = soup.find_all('span', class_='list-condition__count')
print(math.ceil(int(count[0].text)/20))
90
Il y a 1784 magasins au total, et s'il y a 20 magasins sur une page, la dernière page sera la 90e page. Cependant, lorsque j'ai essayé d'afficher la 90e page ...
Impossible d'afficher cette page Merci d'utiliser le journal des repas. Les pages 60 et suivantes ne peuvent pas être affichées. Veuillez affiner les conditions et effectuer une nouvelle recherche.
Est affiché! !! Pour le moment, il n'affiche que 60 pages. Ainsi, vous n'avez pas à vous soucier de savoir si vous souhaitez restreindre les conditions au-delà de la zone afin que le nombre de magasins soit de 1200 ou moins, puis exécutez le traitement en boucle, ou obtenez les 1200 premiers dans le nouvel ordre d'ouverture et soyez satisfait. Dans un mauvais sens. Alors, vérifiez une fois combien de magasins sont répertoriés dans chaque zone.
counts = {}
for key,value in url_dict.items():
time.sleep(1)
res = requests.get(value)
soup = BeautifulSoup(res.content, 'html.parser')
counts[key] = int(soup.find_all('span', class_='list-condition__count')[0].text)
print(sorted(counts.items(), key=lambda x:x[1], reverse=True)[:15])
[('Shinjuku', 5756),
('Shibuya', 3420),
('Shimbashi / Shiodome', 2898),
('Ginza', 2858),
('Roppongi / Nogizaka / Nishi Azabu', 2402),
('Marunouchi / Otemachi', 1784),
('Iidabashi / Kagurazaka', 1689),
('Ebisu', 1584),
('Nihonbashi / Kyobashi', 1555),
('Akasaka', 1464),
('Ningyocho / Kodenmacho', 1434),
('Gotanda / Takanawadai', 937),
('Yurakucho / Hibiya', 773),
('Tameike Sanno / Kasumigaseki', 756),
('Kayabacho / Hatchobori', 744)]
En conséquence, il est devenu clair que le nombre de publications dépassait 1 200 dans 11 domaines. À la suite de divers essais et erreurs, j'ai décidé d'être satisfait car j'ai limité le genre aux restaurants à la lumière de cet objectif et j'ai acquis les données des 1 200 premiers articles dans l'ordre de nouvelle ouverture. Pour le moment, obtenez les informations du restaurant affichées sur une page spécifique.
url = 'https://tabelog.com/tokyo/A1301/A130101/rstLst/RC/1/?Srt=D&SrtT=nod'
res = requests.get(url)
soup = BeautifulSoup(res.content, 'html.parser')
rc_list = []
for rc_div in soup.find_all('div', class_='list-rst__wrap js-open-new-window'):
rc_name = rc_div.find('a', class_='list-rst__rst-name-target cpy-rst-name').text
rc_url = rc_div.find('a', class_='list-rst__rst-name-target cpy-rst-name')['href']
rc_score = rc_div.find('span', class_='c-rating__val c-rating__val--strong list-rst__rating-val')
if rc_score is None:
rc_score = -1.
else:
rc_score = float(rc_score.text)
rc_review_num = rc_div.find('em', class_='list-rst__rvw-count-num cpy-review-count').text
rc_list.append([rc_name, rc_url, rc_score, rc_review_num])
J'ajouterai quelques explications. Tout d'abord, pour la première URL, «/ rstLst / RC» limite le genre aux restaurants. Le '/ 1' qui vient après cela signifie la première page, c'est-à-dire les 20 premiers cas. De plus, «/? Srt = D & SrtT = nod» est une nouvelle spécification d'ordre ouvert. Dans la déclaration for, 20 données de restaurant sont traitées dans l'ordre. C'est le score du journal alimentaire qui nécessite une attention. Vous pouvez obtenir le score avec la méthode de recherche ci-dessus, mais s'il n'y a pas de score, cette balise elle-même n'existe pas. Par conséquent, le cas a été divisé par Aucun dans l'instruction if, et s'il n'y avait pas de score, un processus a été ajouté pour définir temporairement le score à -1. En ce qui concerne le nombre d'avis, s'il n'y a pas d'avis, «-» sera retourné. Après cela, vous pouvez obtenir l'URL du restaurant en tournant la boucle pour chaque zone et chaque page! !!
Maintenant que vous pouvez obtenir l'URL de chaque restaurant, le but est d'obtenir des informations de bouche à oreille à partir de la page de chaque restaurant. Quand j'ai mis le code en premier, j'ai fait ce qui suit.
url = 'https://tabelog.com/tokyo/A1301/A130101/13079232/dtlrvwlst/COND-2/smp0/?smp=0&lc=2&rvw_part=all&PG=1'
res = requests.get(url)
soup = BeautifulSoup(res.content, 'html.parser')
station = soup.find_all('dl', class_='rdheader-subinfo__item')[0].find('span', class_='linktree__parent-target-text').text
genre = '/'.join([genre_.text for genre_ in soup.find_all('dl', class_='rdheader-subinfo__item')[1].find_all('span', class_='linktree__parent-target-text')])
price = soup.find_all('dl', class_='rdheader-subinfo__item')[2].find('p', class_='rdheader-budget__icon rdheader-budget__icon--dinner').find('a', class_='rdheader-budget__price-target').text
score = [score_.next_sibling.next_sibling.text for score_ in soup.find_all('span', class_='c-rating__time c-rating__time--dinner')]
print(station, genre, price, score)
J'ajouterai un commentaire. Tout d'abord, en ce qui concerne l'url, «/ dtlrvwlst» est une liste de révision, «/ COND-2» est une critique de nuit, «smp0» est un affichage simple, «lc = 2» est un affichage de 100 éléments, et «PG = 1» est 1 Cela signifie la page. La station, le genre et le budget les plus proches sont acquis dans l'ordre car il y a des données dans le'dl'tag dont le nom de classe est'rdheader-subinfo__item '. En ce qui concerne les genres, dans la plupart des cas, plusieurs genres sont attribués à un magasin, donc ici, tous les noms de genre sont combinés une fois avec '/'. Le budget et le score de chaque bouche à oreille sont un peu compliqués car je ne voulais que celui du soir.
Maintenant que nous pouvons obtenir les informations nécessaires individuellement, nous avons juste besoin d'obtenir les données que nous voulons par traitement en boucle! !!
import numpy as np
import pandas as pd
import requests
from bs4 import BeautifulSoup
import math
import time
from tqdm import tqdm
root_url = 'https://tabelog.com/'
res = requests.get(root_url)
soup = BeautifulSoup(res.content, 'html.parser')
a = soup.find_all('a', class_='rsttop-area-search__target js-area-swicher-target')
area_url = {}
for area in a:
area_dict = {}
splitted = area.get('data-swicher-area-list').split('"')
for i in range(int((len(splitted)-1)/8)):
area_dict[splitted[i*8+3]] = splitted[i*8+7]
area_url[area.get('data-swicher-city').split('"')[3]] = area_dict
visit_areas = ['Shibuya / Ebisu / Daikanyama']
url_dict = {}
for visit_area in visit_areas:
url = area_url['Tokyo'][visit_area]
time.sleep(1)
res = requests.get(root_url + url[1:])
soup = BeautifulSoup(res.content, 'html.parser')
a = soup.find_all('a', class_='c-link-arrow')
for area in a:
href = area['href']
if href[-21:-8]!=url:
continue
else:
url_dict[area.text] = href
max_page = 20
restaurant_data = []
for area, url in url_dict.items():
time.sleep(1)
res_area = requests.get(url)
soup_area = BeautifulSoup(res_area.content, 'html.parser')
rc_count = int(soup_area.find_all('span', class_='list-condition__count')[0].text)
print('There are ' + str(rc_count) + ' restaurants in ' + area)
for i in tqdm(range(1,min(math.ceil(rc_count/20)+1,max_page+1,61))):
url_rc = url + 'rstLst/RC/' + str(i) + '/?Srt=D&SrtT=nod'
res_rc = requests.get(url_rc)
soup_rc = BeautifulSoup(res_rc.content, 'html.parser')
for rc_div in soup_rc.find_all('div', class_='list-rst__wrap js-open-new-window'):
rc_name = rc_div.find('a', class_='list-rst__rst-name-target cpy-rst-name').text
rc_url = rc_div.find('a', class_='list-rst__rst-name-target cpy-rst-name')['href']
rc_score = rc_div.find('span', class_='c-rating__val c-rating__val--strong list-rst__rating-val')
if rc_score is None:
rc_score = -1.
else:
rc_score = float(rc_score.text)
rc_review_num = rc_div.find('em', class_='list-rst__rvw-count-num cpy-review-count').text
if rc_review_num != ' - ':
for page in range(1,math.ceil(int(rc_review_num)/100)+1):
rc_url_pg = rc_url + 'dtlrvwlst/COND-2/smp0/?smp=0&lc=2&rvw_part=all&PG=' + str(page)
time.sleep(1)
res_pg = requests.get(rc_url_pg)
soup_pg = BeautifulSoup(res_pg.content, 'html.parser')
if 'Je ne trouve pas la page que je recherche' in soup_pg.find('title').text:
continue
try:
station = soup_pg.find_all('dl', class_='rdheader-subinfo__item')[0].find('span', class_='linktree__parent-target-text').text
except:
try:
station = soup_pg.find_all('dl', class_='rdheader-subinfo__item')[0].find('dd', class_='rdheader-subinfo__item-text').text.replace('\n','').replace(' ','')
except:
station = ''
genre = '/'.join([genre_.text for genre_ in soup_pg.find_all('dl', class_='rdheader-subinfo__item')[1].find_all('span', class_='linktree__parent-target-text')])
price = soup_pg.find_all('dl', class_='rdheader-subinfo__item')[2].find('p', class_='rdheader-budget__icon rdheader-budget__icon--dinner').find('a', class_='rdheader-budget__price-target').text
score = [score_.next_sibling.next_sibling.text for score_ in soup_pg.find_all('span', class_='c-rating__time c-rating__time--dinner')]
restaurant_data.append([area, rc_count, rc_name, rc_url, rc_score, rc_review_num, station, genre, price, score])
En raison de la grande quantité de données, la zone a été temporairement limitée à visit_areas = ['Shibuya / Ebisu / Daikanyama']. De plus, puisque max_page = 20, seules 400 données de 20 éléments / page✖️20 (max_page) peuvent être acquises pour chaque emplacement. De plus, après avoir acquis le nombre d'avis, 100 avis / page sont acquis, mais le nombre d'avis acquis en premier inclut l'évaluation de jour et le traitement en boucle ne cible que l'évaluation de nuit. Par conséquent, dans le processus de traitement en boucle, il y avait une situation où il n'y avait pas autant d'examens que prévu initialement.
if 'Je ne trouve pas la page que je recherche' in soup_pg.find('title').text:
continue
Il a été traité en. De plus, la plupart des magasins indiquent la gare la plus proche, mais il y a quelques magasins qui répertorient la zone au lieu de la gare la plus proche, auquel cas les noms des balises sont différents, donc
try:
station = soup_pg.find_all('dl', class_='rdheader-subinfo__item')[0].find('span', class_='linktree__parent-target-text').text
except:
try:
station = soup_pg.find_all('dl', class_='rdheader-subinfo__item')[0].find('dd', class_='rdheader-subinfo__item-text').text.replace('\n','').replace(' ','')
except:
station = ''
Gère cela. En conséquence, nous avons pu obtenir 895 données. Parmi ceux-ci, 804 avaient même un avis noté.
Le diagramme de dispersion ci-dessus montre la relation entre l'évaluation moyenne du bouche-à-oreille et le score du journal alimentaire. Les magasins qui n'ont pas de score dans le journal alimentaire sont traités comme 2,9 points. Dans l'ensemble, si l'évaluation moyenne du bouche à oreille est élevée, vous pouvez voir que le score du journal alimentaire est également élevé. Bien que l'évaluation moyenne du bouche-à-oreille soit élevée, le score du journal de consommation était faible et il a également été constaté que certains magasins sont sous-estimés.
Alors, je vais aller dans l'une de ces boutiques sous-estimées la prochaine fois! !!
Recommended Posts