餐饮市场分析(上)

news2024/11/18 21:33:50

阅读原文

研究某一类餐饮产品的市场概况,并在不同地区和品牌之间进行对比

一、数据需求

使用美团搜索商品返回的数据。

首先进入美团首页,切换到对应城市,并搜索感兴趣的关键词。接下来尝试翻页获取更多数据,点击下一页时发现页面地址没变,并且浏览器发送了一批请求。选定对应的范围,容易找到下图中的数据就是加载出来的商家信息,以 .json 格式返回。

该数据的请求地址为: https://apimobile.meituan.com/group/v4/poi/pcsearch/30 ,参数有:

uuid=8639d6b8d8bf457691b9.1594807338.1.0.0
userid=-1
limit=32
offset=32
cateId=-1
q=%E5%A5%B6%E8%8C%B6

尝试在浏览器请求 API,发现美团会进行 IP 验证,而且 URL 参数 uuid 对请求不产生影响。如果一个 IP 首次访问该 API,会跳转到人机验证。对此,可以使用selenium 完成首次的验证,然后在一段时间内便可以进行 GET 请求。参考以下文章: https://www.cnblogs.com/test_home_c/p/9619542.html

又或者可以获取 Cookies,并传入详细的 headers 参数,经过尝试,在 Cookie 中传入 uuid 参数即可跳过验证。

二、数据获取

具体的脚本可以分成几个步骤。首先是根据给定的城市名称和搜索关键词,获取对应城市的入口链接和城市 ID,后者用于 API 的拼接。得到 API 链接后,再获取 Cookie 字段,组成 headers,然后便可以发起请求获得数据。

需要注意的问题:

  • 城市 ID 可以在网页源码中的 Script 脚本中得到,但是直接 GET 请求时不加载脚本,需要给定 headers 参数;
  • 获取 Cookies 时并不返回完整的 Cookies 字段,但是经过测试,仅有的字段可以起作用;
  • 当出现 Exception 时,可能是偏移量超出范围或者会话 Expired,需要跳出循环更新 Cookies 重新获取数据;
  • 返回的 json 数据层次较复杂,分成两个关系表储存。

主要代码速览:

class MtSpider:

    def __init__(self, cityname, keyword):

        self.name = cityname
        self.keyword = urllib.parse.quote(keyword)
        self.linux_headers = ...
        self.windows_headers = ...
        self.citylink = self.get_city_link()        # Searching URL
        self.host = self.citylink.split('/')[2]     # City Hostname
        self.cityid = self.get_city_id()
        self.cookies = self.get_cookies()

    def get_city_link(self):

        res = rq.get('https://www.meituan.com/changecity/', headers=self.windows_headers)
        soup = BeautifulSoup(res.text, features='lxml')
        cities = soup.find_all('a', {'class': 'link city'})
        for c in cities:
            if self.name in c.text or c.text in self.name:
                link = 'https:' + c.attrs['href'] + '/s/' + self.keyword

        return link

    def get_city_id(self):

        headers = dict(self.windows_headers, Host=self.host)
        res = rq.get(self.citylink, headers=headers)
        id = re.findall(r'{"id":(\d+),"name"', res.text)[0]

        return id

    def get_cookies(self):

        jar = http.cookiejar.CookieJar()
        processor = urllib.request.HTTPCookieProcessor(jar)
        _ = urllib.request.build_opener(processor).open(self.citylink)

        cookies = []
        for i in jar:
            cookies.append(i.name + '=' + i.value)
        
        return '; '.join(cookies)

    def get_json(self, page):
        '''Get data of one page'''

        url = 'https://apimobile.meituan.com/group/v4/poi/pcsearch/{}?userid=-1&limit=32&offset={}&cateId=-1&q={}'
        url = url.format(self.cityid, page*32, self.keyword)    # self.cityid
        headers = {
            'Cookie': self.cookies,             # self.cookies
            'Host': 'apimobile.meituan.com',
            'Origin': 'https://' + self.host,   # self.host
            'Referer': self.citylink,           # self.citylink
            'User-Agent': self.windows_headers['User-Agent']
            }
        res = rq.get(url, headers=headers)
        data = json.loads(res.text)

        return data['data']['searchResult']
    

    def parse_data(self, data):
        
        ...

    def main(self, pages):
        '''Entry'''

        ...
        for p in range(pages):
            try:
                df1, df2 = self.parse_data(self.get_json(p))
                ...
            except Exception as e:
                print('ERROR: ' + str(e))
                self.cookies = self.get_cookies()   # Update Cookie
                continue
        ...

if __name__ == "__main__":
    
    ...

这里选择“奶茶”作为关键词,获取主要的省会城市的数据。获取的数据分成两个表,一个是商店列表,一个是推荐的热销商品,两表以 shop_id 作为外键连接。下图为某一个城市的数据:

三、数据清洗

在 MtSpider 中的 parse_data 环节已经进行简单的清洗,主要根据返回的 json 文档的数据结构,将其分割成两个表,方便处理。

获取数据时选择每个城市的数据作为单独的 csv 文件保存,这样出现问题容易处理。因此首先要对数据进行合并汇总。又由于数据较多较复杂,只进行简单的清理,保留大部分的有用信息。在后续进行分析时在根据具体需要重整数据。

df = pd.read_csv(f'{PATH}{city}{KEYWORD}_shops.csv')
# 删除店铺标题中的括号内容,例如:一点点(XXX店)
df['title'] = df.title.map(lambda s: s.split('(')[0])
# 替换店铺标题中的特殊字符
df['title'] = df.title.str.replace(S, '·')
df['title'] = df.title.str.replace('一点点', '1點點')
# 提取地址中的区号生成新字段(先大致提取,有些城市可能会出现脏数据,无法全部兼顾)
df['region'] = df.address.map(lambda s: s.split('区')[0] + '区')
# 发现有些不是主营茶饮的,例如深圳的尊宝披萨;但是它又有茶饮产品,不好直接去掉
# 于是增加一个布尔型字段区分一下是否主营奶茶
df['isMain'] = df.backCateName.map(lambda cat: KEYWORD in cat)

# 然后将所有城市的数据汇总得到总的 shops 和 deals 两个表

下一步进行可视化分析前还需要对表格某些数据进行重构处理:

# 商品标题字段,通过分词得到高频词列表
text = ' '.join(deals.title)
words = pd.Series(jieba.cut(text, cut_all=False))
stopwords = ...
wc = wordcloud.WordCloud(..., stopwords=stopwords)
wc.generate_from_text(' '.join(words))
# 借助 WordCloud 对象生成的高频词筛选源列表
level = ' '.join(list(wc.words_.keys())).split(' ')
new_words = words.loc[words.isin(level)]

# 联合两表计算每个店铺的推荐商品平均销量(作为该店铺的销量指数)
sales = deals.groupby('shop_id').sales.mean().reset_index().rename({'shop_id': 'id'}, axis=1)
shops.merge(sales, on='id', inplace=True)

四、可视化分析

对获得的数据进行初步的简单分析,看是否能得到有用的结论。接下来就是全国各地奶茶大赏!

  • 下面图表如无说明,均为全国范围的数据
  • 仅包含已入驻美团的商家数据
  • 关于价格的信息仅包含部分热销商品,仅做参考
  • 最后更新日期为 2022.08.03

(1)CoCo都可 分布最广、口碑最佳

评分这一点,众所周知,水分很大。不过也是可以反映一些信息的,毕竟哪怕是刷的分,也是要成本的。

(2)郑州 —— 杀出来的黑马

具体到每个城市的分布如下,可以看到郑州(2019 GDP 排名 16)的主要品牌奶茶店分布数量领先绝大部分城市。从图中还可以清楚看到每个品牌青睐哪个城市,例如郑州是蜜雪冰城的天下,而天津则被沪上阿姨占领。

(3)奈雪的茶 —— 高端大气

接下来看价格的分布,图中大小表示店铺的数量,颜色表示产品均价。从图中可以看到哪些品牌价格比较亲民,哪些比较高端。奈雪的茶荣登榜首(忽略部分分布数量较少的品牌)。明显的趋势是:价格亲民的品牌,有遍地开花的趋势,反之价格高的品牌店铺数量较少。

(4)沿江沿海地区消费水平较高

这个趋势还是比较明显的,深色数据点主要围绕着北上广和长江沿岸。按照这个结论,有两个省会城市就脱离了趋势,一个是中西部的陕西西安,一个是西南的贵州贵阳。

具体到不同城市的分布如下图。注意到箱形图部分城市的四分位已经到零点,主要是部分商家首页没有推荐的折扣商品,导致计算产品的参考均值的时候得到缺失值。这个后续再进一步处理,目前仅作简单的分析。可以重点看一下北上广深的分布情况,感觉越看越有房价内味儿~

### **(5)各品牌销量均值**

这里是对每个店铺的推荐商品销量求均值,然后再对该品牌的店铺求均值,作为销量的参考指标。这里在此前名不见经传的天御皇茶扳下一城,领先大部分品牌。但是,这个销量数据同样还是水分满满,可能需要另找一个替代的指标。

(6)舔屏 —— 推荐商品中出现最多的元素

(7)具体看一下深圳的数据

首先看一下各区的分布(绘制这个图的时候都没发现,现在才惊觉有个逻辑上的错误,影响不大,懒得改了)。

最后来个热力图压压惊。可以看到深圳湾“店满为患”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699896.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

跨越时空限制,酷暑天气用VR看房是一种什么体验?

近年来,全球厄尔尼诺现象越来越频繁,夏季温度不断创下新高,持续大范围的高温天气让人们对出门“望而生畏”。很多购房者也不愿意在如此酷暑期间,四处奔波看房,酷暑天气让带看房效率大大降低,更有新闻报道&a…

Linux:LAMP-phpmyadmin

LAMP环境 (1条消息) Linux:LAMP搭建(全源码包安装)_鲍海超-GNUBHCkalitarro的博客-CSDN博客 phpmyadminphpMyAdminhttps://www.phpmyadmin.net/ 传进Linux tar xfz phpMyAdmin-5.2.1-all-languages.tar.gz 这个是解出来的包 mv phpMyAdmin-5.2.1-all-languages /…

【NOSQL数据库】Redis数据库的配置与优化一

目录 一、关系型数据库与非关系型数据库1.1关系型数据库1.2非关系型数据库1.3关系型数据库与非关系型数据库的区别1.3.1数据存储方式不同1.3.2扩展方式不同1.3.3对事务性的支持不同 1.4非关系型数据库产生的背景1.5总结 二、Redis简介2.1Redis的优点2.2使用场景2.3哪些数据适合…

大二网页设计实训-豆瓣首页(html+css)

免费开源一个前端网页,豆瓣首页,可以用来当实训等

探索神奇的甲方需求:提出异常要求的背后逻辑

在IT行业,每个人都可能遇到“神奇的甲方”和他们提出的匪夷所思甚至无厘头的需求。虽然这些要求可能让人摸不着头脑,但背后通常隐藏着某种逻辑和需求。让我们来探索一下这些“无理需求”背后的心理和可能的应对策略。 首先,为什么会出现这些…

Maven安装与配置详解

安装JDK JDK1.8所有版本官网下载链接: https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html 所有JDK下载地址: https://www.oracle.com/java/technologies/oracle-java-archive-downloads.html 可参照我的另一篇博客 安…

Unity | HDRP高清渲染管线学习笔记:HDRP Custom Pass

目录 一、Custom Pass Volume组件介绍 1.Mode(模式) 2.Injection Point(注入点) 3.Priority 4.Fade Radius 5.custom passes 二、查看Custom Pass的渲染阶段 Custom Pass允许你执行以下操作(官方文档&#xff0…

Linux--在当前路径下创建目录/文件夹指令:mkdir

语法: mkdir [选项] 文件名 功能: 在当前目录下创建一个名为 “文件名”的目录 常用选项: -p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些不存在的目录,即一次可以建立…

HTML5 游戏开发实战 | 贪吃蛇

在该游戏中,玩家操纵一条贪吃的蛇在长方形场地里行走,贪吃蛇按玩家所按的方向键折行,蛇头吃到食物(豆)后,分数加10分,蛇身会变长,如果贪吃蛇碰上墙壁或者自身的话,游戏就结束了(当然也可能是减去…

开放式耳机漏音严重吗?开放式耳机会不会吵到别人?

​在了解开放式耳机是否漏音的时候,首先要知道什么是开放式耳机。 开放式耳机是一种不入耳,没有封闭耳朵的蓝牙耳机,可以听歌的同时接收来自外界声音,安全性高,也减少长期佩戴耳机带来的负担,更适合运动佩…

E. Singers‘ Tour(数学推导)

Problem - 1618E - Codeforces 将n个城镇按顺序排列成一个环。这些城镇按顺时针顺序编号为1到n。在第i个城镇里,有一名歌手,他的曲目列表中每首歌的演唱时间为ai分钟,其中i∈[1,n]。 每位歌手按顺时针顺序访问所有n个城镇,并在…

为什么可以直接使用简单名字访问java.lang包中的公开类、接口

java.lang包中有一些基本类,在代码中访问这些公开的类、接口可以直接用简单的名字访问,而不需要用import语句显式导入。 先看下面两段示例代码,这两段代码的执行效果是一样的。 示例代码1–访问Math类的时候直接写简单的名字,而且…

pwn学习day4——ret2text

文章目录 原理&#xff1a;例子x64x32 原理&#xff1a; 覆盖函数返回地址到程序内存在的gadgets获取shell 例子 #include<stdio.h> #include<stdlib.h>void hint(){system("$0"); } int main(){char buf[0x10];printf("input: ");scanf(&q…

1.5、JAVA 初识JAVA运算符

1 运算符 1.1 概述 运算符 用于连接 表达式 的 操作数&#xff0c;并对操作数执行运算。 例如&#xff0c;表达式num1num2&#xff0c;其操作数是num1和num2&#xff0c;运算符是””。 在java语言中&#xff0c;运算符可分为5种类型&#xff1a; 算术运算符、赋值运算符、关…

数据中心可视化合集 | 图扑数字孪生机房,解锁运维新境界

IDC&#xff08;Internet Data Center&#xff09;数据中心可视化是指通过可视化的方式对数据中心的运行状态、资源使用情况、安全监控等进行展示和管理。可以帮助管理员更好地了解数据中心的运行情况和趋势&#xff0c;及时发现问题并采取措施&#xff0c;提高数据中心的运行效…

libevent实践01:准备源码、搭建项目、编译脚本和入门例子

编译源码 libevent是一个Reactor事件库。 我的理解&#xff0c;就是封装了select、epoll、poll的函数库。有使用select&#xff0c;poll&#xff0c;epoll的需求就可以使用的。 官网地址&#xff1a;https://libevent.org/ 下载源码&#xff1a; https://github.com/libev…

2023亚马逊云科技中国峰会主题演讲,分享基础架构在AI浪潮的求新求变

2023亚马逊云科技中国峰会第二天精彩继续&#xff01;6月28日&#xff0c;亚马逊云科技大中华区产品总经理陈晓建在《专注创新&#xff0c;摆脱基础架构束缚》主题演讲中提出&#xff0c;云服务是支持数字创新的关键生产力。在当前生成式AI技术爆发的大环境下&#xff0c;亚马逊…

一种快速估算PCB走线电阻的方法:方块统计

我们通常需要快速地估计出印刷电路板上一根走线或一个平面的电阻值&#xff0c;而不是进行冗繁的计算。 虽然现在已有可用的印刷电路板布局与信号完整性计算程序&#xff0c;可以精确地计算出走线的电阻&#xff0c;但在设计过程中&#xff0c;我们有时候还是希望采取快速粗略…

通过BeautifulSoup获取【领域赛道--大数据与算法】top100用户

文章目录 前言介绍实现帖子地址请求地址引入模块提取代码程序入口提取到的数据 总结最后 前言 博主空空star主页空空star的主页 大家好&#xff0c;我是空空star&#xff0c;本篇给大家分享一下《通过BeautifulSoup获取【领域赛道--大数据与算法】top100用户》。 介绍 Beautif…

社区团购爆品怎么做?社区团购小程序有什么功能?

社区团购爆款可以通过拉新&#xff0c;打好用户基础。刚上线就推行低价爆款拉新&#xff0c;利用一些低成本的奶制品类和禽蛋类&#xff0c;蔬菜类来引流拉新。这一招非常有效&#xff0c;吸引源源不断的客流量汇集平台&#xff0c;庞大的用户流量是订单爆棚的保障。商家/运营方…