作为标准的社畜吃货,一直有想着做一篇关于美食的数分内容,看看哪里工作的社畜最可怜,连吃美食的地方都没有,直到看到数据后我就又迷茫了,几百块的人均价堪比米其林,身边再多餐厅又有何用(手动狗头)。 言归正传,分析的第一步是数据的获取,在爬大众点评的餐厅的时候我差点就放弃了这个项目,他们的反爬真的很厉害(丧心病狂的那种),字符偏移、SVG、文字随机图片化(具体的问题大家可以参考git、csdn等),代理IP都没用,在手机流量超了1个G后,终于搞到了5000+数据。 ![]() 大众美食首页 美食里面的分类都对应着网址上的g**值,每个类别最多可以显示50页,最下面上代码。 因为不是每个菜系都是50页,所以最后的数据在5500家餐厅左右 ![]() 共爬取:店铺名、店铺星级(空)、评论数、均价、店铺类型、商圈、地址、口味|服务|环境的评分共计10个字段。基于个人习惯,一般看到有详细地址的数据,我都会用百度地图api转换一下,最下面放转换地址的代码,篇幅原因,后续的分析代码就不上了,如果后续有需要我私发或者补上都可以。 调取百度地图的api,通过详细地址去获取经纬度。普通用户每天的逆向地理信息调用是6千次,调试几次就用完了,建议感兴趣的童鞋可以注册为开发者(身份证认证),每天有30万的额度,基本够用了。这里获取店铺的经纬度、所属区和门址信息,同大众点评上的商圈字段不同的是,百度的门址信息是划分信息,如(门牌号、车站、机场、商务楼……): ![]() 接下来就是一堆数据预处理的骚操作,删除重复数据、转变类型、填充或者删除无效信息…… 然后,然我们看一下大众点评的餐厅都分布在哪里吧: 如图下图所示,基本上分布在以“人民广场”为中心的辐射半径为6公里的区域内(蓝色圆圈),各行政区内的店铺用不同颜色的店标记出来了,果然市区贵是有原因的稀疏密集程度和上海的房价几乎一致,难道是餐厅导致的上海高房价?大家赶紧去抵制吃饭,饭店关门了上海房价就跌了(再次狗头),黄浦和徐汇几乎是全区高密。 ![]() 市区范围外,还有些零散的聚地点,基本在川沙(张江码农集聚地)、紫藤路(高新科技园)、车站、机场(虹桥的聚集效果更强)、迪士尼(景区): ![]() 五角场 上海各区域内的商圈数量,金山、青浦和奉贤这样的大郊区的商圈真的是屈指可数,市区虽然面积小但是商圈数据碾压郊区(浦东除外,浦东实在是太大了,还有陆家嘴这样的金融区),市区的商务办公地也多,办公——人多——商圈多——人多 大家互相成就,正向相关。 ![]() 上海各区商圈数 浦东的餐厅最多,黄浦其次,但想想两者的面积差,真的是恐怖(黄浦有226家咖啡店,小资情调MAX) ![]() 各区各类型餐厅数 餐厅类型有日料、烧烤、咖啡厅等,我们探究下各个商圈下,每个商圈的餐厅类型有多少(该商圈所拥有的餐厅类型占整个上海餐厅类型的百分比),该指标反映商圈的繁荣度,餐厅类型越繁多,该商圈的人流量越大、越是繁华。 前六名分别是:淮海路(8.25%)、静安寺(5.39%)、人民广场(4.87%)、陆家嘴(4.64%)、虹桥(4.33%)、徐家汇(4.24%),的确是大家耳熟能详的地标商圈。敲下黑板,外地来旅游的朋友可以去这几个地点,繁华热闹,饮食上也绝对能满足您的口味。 ![]() 各商圈内各类型餐厅的占比 各个商圈的店铺数,各商圈数据均由下滑。但比较恐怖的是淮海路商圈,一条淮海路拥有整个上海地区5.12%的餐厅,一骑绝尘。怕不是整条路上全是餐厅?可怜我从小在淮海中路长大,也没吃几口好的(穷人在家吃) ![]() 各区商圈店铺数 ![]() 根据50以下、50-100、100-150这样的逻辑对人均消费分段,商圈大致以低价段-高价段递减的趋势,且淮海路商圈的100以下的人均店铺最多。值得一提的是陆家嘴商圈,其500以上人均消费的店铺比50-100人均的店铺还要多,不愧是金融凤凰男,我发四,以后再也不敢叫他们金融民工了。 ![]() 综上:从商圈角度而言,如果想吃的好、选择多,那么淮海路你一定不能错过,但您要是为了见识“魔都”,体验番一掷千金的“壕爽”,看看这灯红酒绿的十里洋场,那么2号线陆家嘴站欢迎您(呸,劳斯莱斯幻影、迈巴赫去接您)。 说了那么久,那么大众上到底哪些餐厅收录最多呢? ![]() 东北菜占有量接近5%,说明不少同学来自东北,可以想象下,海南的东北菜占比会不会高达50%(手动狗头)。 咖啡厅占比12.73%,面馆占比7.43%,难道大家都是吃饭时扒拉两口面,然后赶紧买杯咖啡继续加班么?果然上海是座工作型城市。为了验证设想,我们通过商圈对比面馆和咖啡馆的数量,两者成完美的正相关 ![]() 从百度的地址分类来看,收录的这五千多家餐厅,多以门址、购物、商务大厦和宾馆为主(依次递降)。 ![]() 门址和购物的对比,基本分布一致,比较有意思的是门址的500+人家的餐厅远远大于其他类型地址。证明门址地点比较特殊,两级分化严重,要么是低端低消费的苍蝇馆子,要么就是高逼格、豪装的特色餐厅和外国料理。 ![]() 购物地址和门址地址的人均价格段 商务大厦和宾馆的对比同上图的人均分布比较大体一致,比较特殊的是,商务大厦人均最多的在50-100的价格段,宾馆人均消费最短的价格段在150-200。以写字楼白领为主商务大厦主打中低端商务餐,而宾馆的住宿人群出手较为豪气(无论出差还是旅游,肯定是舍得花钱的)。 ![]() 商务大厦地址和宾馆地址的人均价格段 而餐厅类型最多的咖啡厅和面馆在百度地址上却无明显差役,分布几乎一致(在此不套路较小餐厅类型,烧烤餐饮肯定在商场的少啊) ![]() 大众点评上对于餐厅有三个关键的评分指标: 服务、环境、口味,我们将每家店铺的三个指标分想加求平均分,获取店铺的平均分,较为平均: ![]() 各类型餐厅评分 各家店铺都会有大众劵、买单打折活动,但是不是每个人都会去写点评。无论好坏,评论数能够代表一家店铺的人气,而高评论店铺一定是优质店铺,不然差评会让店铺没有人气,同时高评也能够代表该类型的店铺人均消费较低,以实惠著称。 ![]() 人均消费和评论的对比 南京菜、烤鸭、粤式茶点、火锅、日式自助是评论均榜的前五,基本以本地菜系为主,唯一入选的日料还是自助餐。南京菜的评论多,让我再次明白一个道理:没有一个鸭子能活着游过秦淮河,没有一个人能错过南京大牌档…… ![]() 各类型餐厅评论数 食品保健类餐厅的人均价遥遥领先,人均价要到3000元,抛掉保健特色不谈,人均价从高到低依次为:特色菜、寿司、法国菜、日本铁板烧、江湖河海鲜。印象中奢华的法国菜反倒只排到了第三(671元/人),就是没有想到寿司人均能到达795,不都是几十块一盒么。。。 ![]() 各类型餐厅均价(前20) 写了很多,对于我们这样想尝鲜的小伙伴来说,选择什么类型的餐厅最不容易采坑呢? 此处通过人均得分+餐厅评论得分+三指标(环境、服务、口味)得分【对各类型餐厅均分排序,赋予1-n分,然后三项指标得分累计计算出总分】,计算出更优质的餐厅类型,不易让大家采坑。 ![]() ![]() 日料、粤菜馆、自助餐、西餐的综合评分最高,口味也更能迎合大部分人,也不易踩坑,本帮菜的得分也较高,外地朋友也值得一尝。 再次对人均消费价进行划段,100元以内为经济型餐厅(应该去除咖啡厅的),100-150为普通型餐厅,150-250为小资型餐厅、250-350为轻奢类餐厅、350-500为昂贵型餐厅,500元以上为奢侈型餐厅。如下图所示,各区域内基本低消到高消递降,无论哪个城市,普罗大众才是最广泛的群体,最值得敬佩的群体。 ![]() 这是第二次追加文章了,数据表、图太多,细分下来篇幅实在恐怖。毕竟兴趣使然,没有确切分析目标,文章到此告一段落,代码下复。 爬虫代码: import requestsfrom requests.exceptions import RequestException from bs4 import BeautifulSoup import json import time import random from numba import jit #获取一页内容 def get_one_page(url): headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cache-Control': 'max-age=0', 'Connection': 'keep-alive', 'Cookie': '你的浏览器cookie ', 'Host': 'www.dianping.com', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' } proxiess=['http://39.105.104.1:3128', 'http://47.113.121.239:3128', 'http://58.220.95.54:9400', 'http://61.135.185.92:80', 'http://163.177.151.76:80', 'http://210.26.49.89:3128', 'http://117.185.17.151:80', 'http://117.185.17.177:80', 'http://111.13.100.91:80', 'http://47.98.162.91:6666', 'http://117.57.85.249:4216'] print(random.choice(proxiess)) try: response = requests.get(url, headers=headers,proxies={'http':random.choice(proxiess)}) if response.status_code == 200: return response.text return None except RequestException: return None #解析一页内容 def parse_one_page(html): soup = BeautifulSoup(html, 'lxml') infos = soup.select('#shop-all-list ul li div.txt') print(infos) # time.sleep(random.uniform(1,3)) for info in infos: yield { '商铺': info.h4.get_text(), '星级': info.find(class_="comment").span.get('title'), '评论数': info.select('.review-num b')[0].string if info.select('.review-num b') else None, '均价': info.select('.mean-price b')[0].string if info.select('.mean-price b') else None, '类型': info.select('.tag')[0].string if info.select('.tag') else None, '商区': info.select('.tag')[1].string if info.select('.tag') else None, '地址': info.find(class_="addr").string, '口味': info.select('.comment-list b')[0].string if info.select('.comment-list b') else None, '环境': info.select('.comment-list b')[1].string if info.select('.comment-list b') else None, '服务': info.select('.comment-list b')[2].string if info.select('.comment-list b') else None, } #保存结果数据 def write_to_file(content): with open('result_shop.txt', 'a', encoding='utf-8') as f: f.write(json.dumps(content, ensure_ascii=False) + '\n') f.close() def main(start): http = { 'http://www.dianping.com/shanghai/ch10/g101p', 'http://www.dianping.com/shanghai/ch10/g113p', 'http://www.dianping.com/shanghai/ch10/g112p', 'http://www.dianping.com/shanghai/ch10/g117p', 'http://www.dianping.com/shanghai/ch10/g110p', 'http://www.dianping.com/shanghai/ch10/g116p', 'http://www.dianping.com/shanghai/ch10/g111p', 'http://www.dianping.com/shanghai/ch10/g103p', 'http://www.dianping.com/shanghai/ch10/g114p', 'http://www.dianping.com/shanghai/ch10/g102p', 'http://www.dianping.com/shanghai/ch10/g118p', 'http://www.dianping.com/shangahi/ch10/g132p', 'http://www.dianping.com/shanghai/ch10/g219p', 'http://www.dianping.com/shanghai/ch10/g508p', 'http://www.dianping.com/shanghai/ch10/g115p', 'http://www.dianping.com/shanghai/ch10/g34236p', 'http://www.dianping.com/shanghai/ch10/g34014p', 'http://www.dianping.com/shanghai/ch10/g215p', 'http://www.dianping.com/shanghai/ch10/g106p', } for url in http: time.sleep(random.uniform(1,2)) html = get_one_page(url + str(start)) for data in parse_one_page(html): print(data) write_to_file(data) if __name__ == '__main__': for i in range(50): time.sleep(random.uniform(5,10)) main(i + 1) 经纬度地址转换代码: import pandas as pdimport json import requests import json import math import collections import openpyxl from pyecharts.charts import * from pyecharts.components import Table from pyecharts import options as opts from pyecharts.commons.utils import JsCode import random import datetime from numba import jit from pyecharts.globals import CurrentConfig CurrentConfig.ONLINE_HOST = "https://cdn.kesci.com/lib/pyecharts_assets/" # pyecharts的js动态有些问题,有时图显现不出来,加上面两行代码 # 读取文章,存贮的数据是txt文件里的json,使用过read_json方法,并不可以,遂在此逐行读存 papers=[] file = open('result_shanghai.txt', 'r',encoding='utf-8') for lin in file.readlines(): dic = json.loads(lin) papers.append(dic) print(papers) df = pd.DataFrame(papers) # 因为详细地址的数据是路名,为了能够精确定位经纬度,需要在地址前加上上海市,不然无法识别 list3=[] for i in df['地址'].values: i=i.replace('地址:','') print(i) if i.startswith('上海') : i=i else: i="上海市"+i list3.append(i) print(list3) list2=[] list4=[] list5=[] list6=[] for line in list3: # 去除换行符 line = line.strip('\n').replace('#',' ') # 去除特殊字符 line1 = line.replace('#', ' ').replace('/',' ').replace("'",'"') try: # 地址获取经纬度 baiduUrl="http://api.map.baidu.com/geocoding/v3/?address=%s&output=json&ak=kuRGjpEIfmc5qt8gKQjI5EwLSEzcKkB3&callback=showLocation"% ( line1) req = requests.get(baiduUrl) # print(req) content = req.text # print(content) content = content.replace("showLocation&&showLocation(", "") # print(content) content = content[:-1] baiduAddr = json.loads(content) # print(baiduAddr) level=baiduAddr["result"]["level"] print(level) list4.append(level) except: level='无数据' list4.append(level) print(level) try: lng = baiduAddr["result"]["location"]["lng"] lat = baiduAddr["result"]["location"]["lat"] list5.append(lng) list6.append(lat) # 经纬度获取城市 baiduUrl="http://api.map.baidu.com/reverse_geocoding/v3/?ak=你的ak&output=json&coordtype=wgs84ll&location=%s,%s"% ( lat, lng) req = requests.get(baiduUrl) content = req.text print('hahahhahahah') print(content) baiduAddr = json.loads(content) print('hehehhehehehhe') print(baiduAddr) province = baiduAddr["result"]["addressComponent"]["province"] city = baiduAddr["result"]["addressComponent"]["city"] district = baiduAddr["result"]["addressComponent"]["district"] print(district) list2.append(district) except: district='无法解析' print(district) list2.append(district) list5.append('无') list6.append('无') # print(district) print(list2) print(list4) print(list5) print(list6) df['区']=list2 df['level']=list4 df['经度']=list5 df['维度']=list6 df |
万奢网手机版
官网微博:万奢网服务平台