☰
美食数据智能分析系统:从爬虫采集到Django与大模型应用
2026/10/14 4:21:45 网站建设 项目流程

1. 这个系统到底解决什么问题:先看场景,再做架构

做美食数据这个方向,最尴尬的不是没数据,而是数据躺在那里完全用不起来。某平台上餐厅的评分、人均消费、用户评论、菜系分布、地理位置,这些信息单独看没什么,放在一起就能回答很多实际的问题:一个商圈里什么菜系竞争最激烈、某个区域的用户对人均价格的真实承受区间、用户评论里高频出现的"服务差"到底占比多少。这些问题靠人工翻网页是回答不了的,所以才有了这套系统的雏形——用爬虫把数据采下来,用Django做成业务系统,再用数据可视化和大模型把结论"讲"出来。

整个项目从立项到跑通,大概花了三周时间。技术栈很清晰:Python负责爬虫和数据处理,Django做后端框架和接口,MySQL存结构化数据,Redis做缓存和异步任务队列,ECharts做可视化展示,最后接DeepSeek大模型做智能分析层。我见过不少类似的项目,很多卡在中间某个环节就烂尾了——要么爬虫被封到怀疑人生,要么数据量大一点查询慢得没法用,要么可视化图表做出来了但根本解释不了业务含义。这套系统的价值就在于,它把"数据采集→数据治理→业务接口→可视化→大模型洞察"这条链路完整打通了,每个环节都有能落地的方案。

适合谁来参考?如果你是做Python全栈开发的,或者是想做数据类毕设、个人项目的,又或者公司正好要做类似的美食/本地生活数据分析产品,这篇文章应该能让你少踩几个坑。我会把设计取舍、爬虫策略、数据清洗规则、大模型接入方式都讲清楚,包括哪些地方是我试过之后发现行不通、最后换掉的方案。

2. 爬虫采集:从目标平台拿数据的三道关口

2.1 数据源分析和字段设计:先想清楚要什么,再动手写代码

做爬虫最容易犯的错就是上来就写代码。我前几次吃过的亏都在这里:爬到一半才发现评论时间字段没采,补采要重跑一遍;或者店铺地址采到了完整字符串,但后面做地图可视化的时候需要经纬度,又得回头补。这个项目的数据源是某旅游平台的美食板块,最终的字段设计我定了这几类:

  • 店铺基本信息:店名、评分、人均价格、菜系、地址、评论数
  • 位置信息:经度、纬度、所在区域
  • 评论内容:评论文本、打分、评论时间
  • 聚合指标:点评数量、收藏数、榜单类型

这里有一个关键决定:人均价格我在爬虫阶段就做了格式化处理,直接存数值。平台页面上展示的是"人均¥86"这种带货币符号的字符串,如果原样入库,后面做价格区间分析的时候还得清洗一遍。我直接在解析阶段就处理成整数,省了一步麻烦。

2.2 请求策略:不追求快,追求稳定

爬虫这部分我用了requests加协程的方式。协程数量控制在20个以内,每个请求之间加了随机延迟,延迟区间在0.5秒到1.5秒之间。目标平台有IP反爬机制,单IP请求频率过高会被临时限制访问,所以我的策略是给每个请求配置独立的UA,会定时轮换。同时维护了一个简单的代理池,来源是自己收集的免费代理——说实话免费代理质量参差不齐,真正能用的大概只有三成,所以我做了一个代理验证模块,每30分钟测一次可用性,不可用的直接剔除。

解析用的是BeautifulSoup加正则。页面结构分析清楚之后,数据抽取反而是整个项目里最简单的一步。比较麻烦的是翻页逻辑,平台的列表页翻到底之后会出现验证码,我的处理方式很直接:一级列表页每页25条,采集到第20页左右就切换排序条件,比如从"好评优先"换到"人气排序",这样拿到了相对更完整的数据集,同时也规避了连续翻页触发的风控。

2.3 断点续采:爬虫也要有"记忆"

采集过程中最容易崩溃的不是代码逻辑,而是网络中断、服务器响应超时、代理失效这些问题。如果从零重跑,前面几小时的时间就白费了。我做了两个防护机制:

第一个是数据去重。数据库里对店铺ID建了唯一索引,插入的时候用INSERT IGNORE语法,重复记录自动跳过。第二个是断点续采,在Redis里存一个"已完成页码"的集合,每成功解析一页就把页码推进去,重启之后可以从Redis里读取进度,接着上次中断的位置继续。

提示:爬虫一定要考虑失败重试的代价。单条数据请求失败后,如果立即重试,很可能再次失败;我把失败请求写进日志,统一放到任务队列尾部延迟30分钟再重试一次。第二次还失败就人工检查一下是不是页面结构变了。

3. 数据治理:从原始数据到可用数据集的ETL细节

3.1 清洗规则:别让脏数据毁掉你的图表

爬下来的数据直接入库是能跑,但做分析的时候就露馅了。这个项目里最典型的脏数据问题有三个:

第一个是价格字段的异常值。有些店铺的人均价格显示的是"¥86/人",有些是"¥86",还有些店铺没填直接是空值。清洗规则是把货币符号、单位全部去掉,空值一律填0,后面分析价格分布的时候把0值排除掉。

第二个是评分区间的统一。平台店铺评分有的是4.5分制,有的评价标签显示的是"超赞"这类文本描述。我写了一个映射表,把文本描述映射成分数:超赞→9.6,非常好→9.0,不错→8.0,以此类推。这个映射不一定精准,但保证了数据口径统一。

第三个是菜系标签的归一化。同一个菜系在不同店铺下面可能叫"川菜"也可能叫"四川菜",还有"湘菜"和"湖南菜"这种同义表达。我维护了一份菜系别名映射表,所有数据入库前做一次标准化转换。

3.2 经纬度补全:没有坐标的地图可视化是空谈

地址字符串可以让人看懂,但机器画地图需要的是经纬度。平台页面源码里其实埋了经纬度字段,大部分店铺能直接拿到。约有一成左右的店铺缺这个字段,原因是数据源在不同页面展示的信息不完整。补全方案用到了高德地图的地理编码API,拿地址字符串换坐标。这个方案有两个坑:一是API有每日调用限额,所以我做了一个本地缓存表,已经查询过的地址直接命中缓存;二是有些地址格式不规范,比如"XX路与XX街交汇处"这种描述,编码出来的坐标会偏。处理方式是人工抽检比例,统计误差在50米以内的占比,低于八成的地段单独标记。

3.3 数据入库的库表设计:维度建模比直觉建表更省心

库表设计我直接用了星型模型:一张店铺的事实表,关联维度表有区域维度、菜系维度、时间维度。事实表存店铺ID、人均价格、评分、评论数、经纬度、采集时间。这样做的好处是查询统计的时候不需要在店铺主表里做一堆条件过滤,通过JOIN维度表就能完成按菜系、按区域的聚合分析。MySQL的索引也只做了两组最核心的:(区域ID, 菜系ID) 联合索引、评分降序索引。后面跑大范围数据筛选的时候,查询基本都在毫秒级返回。

4. Django后端设计:不只是写接口,还要管好数据流

4.1 项目结构和数据模型

Django部分我用的是标准的MTV模式加Django REST Framework。项目拆成了几个核心应用:accounts负责用户登录注册,crawler负责爬虫任务的配置和管理,analysis负责数据统计接口,recommend负责大模型推荐逻辑。各应用之间通过数据库和Redis解耦,爬虫应用通过Celery提交异步任务,不在请求线程里跑耗时操作。

数据模型有一个地方特别值得说:爬虫任务本身也建了模型。每次跑采集任务,会记录采了多少条、失败多少条、耗时多少。这样做的目的是便于观测——调爬虫是个反复的过程,没有任务历史记录,出了问题就只能靠猜。

4.2 API设计:接口要按照业务去设计,而不是按表去设计

接口层的设计原则是"一次请求拿到完整视图"。前端要做价格分布饼图,我就提供一个聚合接口返回所有菜系的价格区间分布;前端要做区域热力图,我就提供一个按区域聚合的店铺数和平均分接口。而不是让前端自己去查数据库、自己算分布——那样前端代码会很复杂,而且图表数据源不统一,后期排查问题也难。

一个典型的接口示例,获取菜系价格分布:

def cuisine_price_distribution(request): data = ( CuisineDim.objects .filter(shop_count__gt=0) .values("cuisine_name", "avg_price", "shop_count") .order_by("-shop_count")[:50] ) result = [ { "name": item["cuisine_name"], "value": round(item["avg_price"], 1), "shop_count": item["shop_count"], } for item in data ] return JsonResponse({"code": 0, "data": result})

有两点经验:一是所有统计类的接口都用Redis做了缓存,缓存时间一小时,避免每次页面刷新都重跑一遍SQL聚合;二是分页参数统一走DRF的page_size和page,不要让前端传入limit 1000这种不可控的数字。

4.3 性能优化:大数据量下的三个必做动作

项目跑到后面,店铺数据累积到数万条之后,接口开始出现明显卡顿。我做了三个优化动作:

第一个是MySQL慢查询日志全开,把执行时间超过1秒的SQL抓出来。一查发现主要瓶颈是视图里做了多层子查询,后来改成用物化视图定期生成中间结果表,查询直接从中间表取值。

第二个是接口层加Redis缓存,这个前面提到了。再加了一层Django caching,模板片段的缓存粒度精确到每个图表卡片。

第三个是分页场景改用了keyset分页,不再用offset。原因很简单,offset翻到后面页,MySQL要扫描大量无用行,keyset分页基于索引定位,深度翻页速度快了十倍以上。

5. 可视化展示:让数据自己说话,而不是堆图表

5.1 技术选型和页面布局

可视化用的是ECharts,主要考虑是地图支持好、图表类型覆盖全、社区文档丰富。页面布局我设计了四个核心视图:

  • 总览看板:核心指标卡片 + 数据趋势图 + 今日更新数据
  • 区域分析:地图热力图展示不同商圈店铺密度和平均评分
  • 菜系分析:雷达图 + 价格区间分布图 + 菜品关键词词云
  • 店铺排行:Top榜单 + 评分/人均价格散点图

页面不追求花哨,所有图表都围绕"能回答业务问题"来设计。比如地图热力图回答的是"哪里的餐饮最集中",雷达图回答的是"各个菜系在哪几个维度上有优势"。

5.2 图表与服务端的数据契约

前后端数据交互格式提前定了规范:所有图表接口统一返回{ "code": 0, "data": { ... } },data结构里按ECharts的series格式组织。比如饼图的data就是[{ "name": "川菜", "value": 234 }, ...],地图热力图的data就是[{ "name": "商圈A", "value": [lng, lat, 12] }, ...]。这样前端直接拿数据渲染,不需要做二次转换。这个契约很重要——目录多、页面多的时候,接口格式不一致会导致前端代码到处特判,维护成本指数上升。

5.3 交互过滤的联动逻辑

页面上的筛选器我做了联动设计:点击"川菜"这个菜系,地图、排行榜、价格分布图都会同步过滤到川菜数据。实现方式不是前端做筛选,而是通过URL参数把筛选条件带上,每次筛选变化重新请求接口。这样做得益于接口层全部参数化,一个filter_by=cuisine&value=川菜就能搞定,而且刷新页面后状态还能保留。

6. DeepSeek大模型接入:从"知道数据长什么样"到"理解数据在说什么"

6.1 为什么选大模型来做分析层

传统的数据分析系统做到可视化这一步就停了。出来的图表能描述"发生了什么",但回答不了"为什么发生"和"应该怎么办"。比如价格分布图显示人均80到120元的区间店铺最多,这是一眼能看见的结论;但用户评论里高频出现的"环境好"和"口味偏咸"这些信息,靠图表是挖掘不出来的。大模型的价值在于把非结构化的评论数据变成结构化的洞察结论,然后进一步生成可读的自然语言分析。

选DeepSeek的理由很直接:中文本语义理解能力强,对评论这类口语化文本的处理效果好,而且推理成本远低于通用大模型。做一个全量评论的情感分析,如果用贵的大模型API,成本能占到整个项目开销的七成以上;用DeepSeek的API,同样的任务成本大约能压到原来的五分之一。

6.2 应用程序构建:评论分析的几类任务

这个系统里大模型参与了三类任务:

第一类是单店评论摘要。从某家店的评论里抽样出100条,让大模型总结出"这家店最受认可的三个维度"和"最有争议的三个维度"。输出格式我做成JSON:

{ "positive_points": ["食材新鲜", "菜品分量足", "上菜速度快"], "negative_points": ["等位时间过长", "高峰期服务响应慢"], "summary": "该店口碑整体较好,但高峰期服务能力需要提升" }

第二类是评分纠偏。平台上有些店铺的评分偏离实际水平,比如评分很高但评论里负面信息占比也高。让大模型把评论文本转成一个"真实推荐指数",和平台评分做对比,差值大的店铺单独标记出来,在系统里用橙色警示标识提醒用户。

第三类是趋势洞察。系统会定期把一周内新增评论做一次主题聚类,找出新增的高频话题。比如某个区域一周内突然多了很多"停车方便"的评论,就自动生成一条趋势提示。

6.3 调用链路和稳定性设计

大模型调用我封装了一个独立的service模块,避免业务代码里到处直接调API。调用链路是这样:Django的视图函数拿到店铺ID,从数据库取评论样本,拼装成Prompt,调用DeepSeek的聊天补全接口,拿到返回结果后用response_format参数要求JSON结构返回,解析成功后再做一次字段校验,校验不通过就抛异常走重试队列。

稳定性有两个问题值得单独说。第一个是超时控制:大模型接口有时候会慢,最长的等待可能有30秒。方案是设置超时时间是20秒,超时之后降级到基础统计结果,不阻塞用户页面。第二个是输出不稳定的问题:虽然要求了JSON格式,偶尔还是会出现多余的说明文字或者json代码块包裹。我的解析函数里做了一个容错:先尝试标准解析,失败就正则抽取{...}部分再解析,还失败就返回空结果并在日志里记录。

Prompt设计经验:把样例放进去比规则讲一万遍都管用

最开始我写的Prompt是一堆规则:请你从以下评论中提取正面和负面观点,要求返回JSON格式。效果不稳定,有时候输出的JSON嵌套层级不对,有时候"正面观点"变成了"优点"。后来改成少样本策略:先放一条示例评论和对应的输出结果,再放真正要分析的评论。效果立刻稳定了下来。这是实践下来最有价值的一个经验——大模型的指令遵循能力很依赖示例格式。还有一点,评论样本的选择不要贪多,一次给100条和给200条,对摘要质量的提升非常有限,反而增加了API的token开销和响应延迟。测试下来的合理区间是80到150条。

7. 部署和后续扩展:从Demo到真正可用的系统还差几步

7.1 部署环境与运维保障

部署用的是最常规的方案:一台云服务器,Nginx + Gunicorn + MySQL + Redis + Celery。系统里有一个cron定时任务,每周一凌晨跑一次增量数据采集,采集完成之后自动触发大模型分析任务。整个链路是串行的,采集结束才做分析,分析结束才刷新缓存。运维上我加了两个监控点:

一是Celery任务队列积压监控,队列任务数超过1000就报警,因为采集任务或者大模型调用大批量失败的时候,队列会瞬间塞满。二是数据采集的增量监控,如果当天采到的数据量比上周同日下降超过50%,说明爬虫可能出了问题,需要人工介入检查。

7.2 后续可以扩展的方向

这套系统的架构本身不挑行业。美食数据换成酒店数据,爬虫的解析规则改一下,数据清洗的规则表换一下,大模型的分析维度调整一下,就能变成一套酒店数据智能分析系统。如果进一步要做推荐功能,可以把用户的历史浏览记录喂给大模型做个性化推荐,但这个功能涉及用户行为数据的埋点,工作量会大不少。

还有一个可以扩展的方向是图像数据——平台上有大量菜品图片,可以用视觉模型做菜品识别,自动给店铺打上"招牌菜"标签。这个方向我在调研但还没有落地,成本和准确率还需要测试。

8. 踩坑记录:这些坑我替你们先踩了一遍

8.1 爬虫被封:不是因为频率高,而是因为TLS指纹

前期爬虫跑得非常顺,后来某一天突然所有请求全部返回403。我开始以为是频率太高,把协程数从20降到了5,延迟从1秒拉到了3秒,还是403。后来排查才发现问题不在频率,而在TLS指纹——服务器端通过TLS握手特征能识别出这是非浏览器的请求。解决方式是用了一个模拟浏览器TLS指纹的库,效果立竿见影,系统恢复稳定。这个坑很难从报错信息里看出来,因为403的常规思路就是频率和UA,提醒大家面对403的时候多考虑一层。

8.2 大模型调用的"幻觉"数据被当成真实数据分析

之前提到大模型输出JSON结构,但解析成功不代表内容可信。有一次分析结果里出现了"该店评分较低,建议优化菜品结构"的结论,但我查了一下这家店的平台评分其实不低。问题出在评论样本的取法上——平台评论默认按推荐排序,如果只看前几十条好评,大模型总结出来的"正面观点"就特别多,但反而不准确,因为真正有信息量的是中差评。后续我调整了抽样策略:好评、中评、差评按比例抽取,同时把评论对应的打分一并喂给大模型,最后输出的结论准确率高了很多。

8.3 MySQL深分页的性能陷阱:offset越大越慢

排行榜页面一开始用LIMIT 10 OFFSET 900这种写法,数据少没什么感觉,数据多了之后响应时间直线上升。MySQL的offset实现方式是扫描并丢弃前N行,所以翻页越深代价越大。我改成基于上次返回的店铺ID做条件过滤,客户端每次请求带上上一页最后一条数据的ID,服务端用WHERE id > 上页ID ORDER BY id LIMIT 10这个方式,响应时间从此稳定在100毫秒以内。

8.4 定时任务的时区问题:采集和分析串行引发的时间错乱

Celery的定时任务,服务器默认时区是UTC,我配置的时候用了北京时间CST,但Celery Beat读取定时表达式的时候还是会按UTC来解析。结果就是设定的"每周一凌晨3点"实际跑在了"北京时间凌晨3点"和"UTC凌晨3点"之间,数据采集的"上周一"区间判断出了偏差,导致增量数据统计口径错了一天。这个问题排查了很久,最后在配置里统一把时区设成Asia/Shanghai,并明确一下dtast变量名。这个坑不大,但很折磨人,建议一开始就把时区问题约定清楚。

9. 写在最后:从项目完成到真正能用的距离

整个系统从确定需求到能跑通,用了三周。但"能跑通"和"能用"之间还有很长的路。数据准确性的校验、异常数据的兜底、大模型输出的降级方案,这些都是在反复试错中补上的。

我个人对这类项目的一个体会是:技术选型上不要贪新贪贵。大模型这个环节很多人会纠结要不要微调一个专用模型,但实际测试下来,好的Prompt设计带来的效果提升已经足够用,微调的成本和风险对这个体量的项目完全没必要。同样,爬虫部分也不要试图把所有反爬机制都对抗一遍——平台更新了页面结构,你跟着更新解析规则,这是家常便饭,保持简单反而更容易维护。

最后分享一个小技巧:整个系统我留了一个"数据体检"入口,感觉数据哪里不对的时候,一键跑一下完整的数据质量检查,包括空值率、重复率、评分分布合理性、价格异常值占比。这个入口在项目上线初期帮了大忙,很多问题在用户发现之前就暴露了。如果你们也在做类似的数据系统,建议从一开始就加上这个工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询