简介:本资源是一套面向高校计算机专业学生与初阶Python开发者的大众点评评论数据采集与分析实战项目,聚焦毕业设计、课程设计及爬虫技术入门实践。项目完整覆盖从网页评论抓取、多线程图片下载、OCR文本识别、MongoDB数据存储,到清洗去重、情感分析、SHAP可解释性建模及消费行为可视化报告的全流程,兼具工程规范性与教学实用性。压缩包共61个文件,含20个核心Python脚本(如scrape_comment.js、data_preprocess.py、customer_shap.py)、9个JS爬虫逻辑文件、4个Markdown文档(含项目介绍与配置说明)、4个JSON/YAML配置文件及配套bat/sh启动脚本,总大小仅90KB,轻量易部署。已有151人学习下载,提供完整可运行代码、结构化目录设计、全局变量与日志管理模块,并支持在本地快速复现数据获取→清洗→分析→报告生成全链路,特别适合毕设选题参考与爬虫进阶实践。
1. 项目缘起:从数据焦虑到实战洞察
最近在复盘一个本地生活服务相关的项目时,我遇到了一个典型的数据困境:手头有一些零散的消费记录,但想了解某个商圈或品类的整体消费趋势、用户评价偏好时,却缺乏系统性的数据支撑。市面上的行业报告要么太宏观,要么收费昂贵,且不一定能精准匹配我的分析需求。这时,一个直接的想法就是:为什么不自己动手,从大众点评这样的生活服务平台上获取第一手数据呢?
这个想法催生了今天要分享的这个实战项目。它不仅仅是一个简单的“Python爬虫”,而是一个从数据采集、清洗到分析、可视化的完整数据流水线。项目最终打包成了一个名为“Python大众点评评论爬虫源码+数据清洗+消费分析报告+实战案例.zip”的压缩包,里面包含了可运行的代码、处理好的示例数据、详细的分析报告以及一个完整的实战案例。无论你是想学习爬虫技术、掌握数据清洗的“脏活累活”,还是希望获得一份能直接用于商业分析或学术研究的消费洞察报告,这个项目都能提供一个清晰的路径和可复现的模板。
大众点评作为本地生活领域的头部平台,积累了海量的商户信息和用户评价,这些数据是理解消费市场、分析用户行为、评估商户竞争力的宝贵资源。通过Python技术栈,我们可以自动化地获取这些数据,并从中提炼出有价值的信息。接下来,我将拆解这个项目的核心模块,分享其中的技术选型、实战步骤以及我踩过的一些坑,希望能帮你绕过弯路,直接上手。
2. 技术栈选型与核心原理拆解
在开始动手之前,选择合适的工具至关重要。这个项目涉及数据采集、处理、存储和分析多个环节,我选择的是一套成熟、高效且社区活跃的技术组合。
2.1 爬虫框架:Requests + 自定义会话管理
对于大众点评这类反爬机制较为复杂的网站,直接使用Scrapy这样重量级的框架有时反而会因特征明显而被快速封禁。我选择了更轻量、更灵活的Requests库作为核心,配合Session对象来维持会话状态。大众点评的页面加载和内容呈现严重依赖JavaScript,尤其是商户列表和评论数据,很多都是通过XHR(Ajax)请求动态加载的。
这里的关键在于模拟浏览器行为,并正确解析这些动态请求。通过浏览器开发者工具的“网络”(Network)面板,我们可以捕获到获取评论数据的真实API接口。通常,这些接口会返回JSON格式的数据,里面包含了评论内容、用户评分、人均消费、推荐菜等结构化信息。使用Requests发起对这类接口的请求,比解析完整的HTML页面要高效和稳定得多。
注意:直接请求API接口虽然高效,但接口参数往往包含加密的令牌(token)或签名(sign),这些需要从首页或列表页的JavaScript代码或网络请求中逆向分析得出。这是本项目的一个技术难点,也是反爬对抗的核心。
2.2 数据解析与提取:JsonPath 与 正则表达式
由于目标数据是JSON格式,使用JsonPath来提取数据比传统的XPath或BeautifulSoup更为直观和方便。例如,要提取所有评论的用户昵称,JsonPath表达式可能类似于$.data.reviews[*].userName。对于少量嵌套在HTML片段中的信息(如评论正文中可能包含的标签),则辅以正则表达式进行清洗。
2.3 数据清洗与处理:Pandas 为核心
这是将“原始数据”转化为“可用数据”的关键一步。Pandas是Python数据分析的事实标准,其DataFrame结构非常适合处理表格型数据。在本项目中,数据清洗主要包括以下几个方面:
- 缺失值处理:用户可能未填写“人均消费”,或者“推荐菜”信息为空。需要根据分析目标,决定是填充(如用均值)、插值还是删除。
- 格式标准化:从网页爬取的“人均消费”可能是字符串“¥150”,需要提取数字并转换为整型;“评分”可能是字符串“4.5”,需转为浮点型。
- 文本清洗:评论正文可能包含无关的HTML标签、特殊字符、表情符号(Emoji)、多余的空格和换行符。需要使用字符串方法和正则表达式进行清理。
- 数据去重:由于网络重试或分页逻辑可能导致的重复记录,需要根据唯一标识(如评论ID)进行去重。
- 异常值检测与处理:例如,出现人均消费为0元或99999元的明显异常数据,需要结合业务逻辑进行筛选或修正。
2.4 数据存储:CSV 与 SQLite
对于中小规模的数据分析和项目演示,CSV文件是最简单通用的交换格式。使用Pandas的to_csv()方法可以轻松将DataFrame保存为CSV文件。如果数据量较大或需要更复杂的查询,我会将数据存入SQLite数据库。SQLite是一个轻量级的文件数据库,无需安装服务器,通过Python标准库sqlite3即可操作,非常适合作为桌面级数据分析项目的存储后端。
2.5 分析报告与可视化:Matplotlib/Seaborn 与 Jupyter Notebook
数据分析的结果需要直观地呈现。Matplotlib是基础的绘图库,Seaborn基于其上,提供了更美观的统计图形和更简洁的API。两者结合可以制作出专业的图表,如消费价格分布直方图、评分趋势折线图、菜系占比饼图等。
整个分析过程,我强烈推荐在Jupyter Notebook中完成。Notebook支持交互式编程,能将代码、运行结果、图表和富文本注释(Markdown)整合在一个文档中,是生成可复现分析报告的绝佳工具。最终的分析报告可以导出为HTML或PDF,与源码和数据一并打包。
3. 爬虫实战:突破反爬与稳健数据采集
有了技术栈的蓝图,我们来深入爬虫部分的具体实现。爬取大众点评的核心挑战在于其多层次的反爬策略,我们的代码必须足够“友好”以模拟真人行为,同时又要足够“健壮”以应对各种异常。
3.1 请求头(Headers)的精细化伪装
这是最基础也是最重要的一步。你的请求头需要看起来完全像一个真实的浏览器。这不仅仅是添加一个User-Agent那么简单。我通常会从浏览器中复制一次完整请求的所有Headers,包括:
User-Agent: 标识浏览器和操作系统。Accept/Accept-Language/Accept-Encoding: 声明客户端接受的内容类型。Connection: 保持连接。Host: 目标主机。- 特别重要的
Cookie: 大众点评的登录状态和风控标识大多存在于Cookie中。首次访问可能需要处理验证码或扫码登录来获取初始Cookie,之后在Session中维护。对于公开数据采集,有时也可以使用未登录状态,但可能会遇到频次限制。 Referer: 表明请求来源,对于按顺序翻页的请求,正确设置Referer至关重要。X-Requested-With: 如果是Ajax请求,可能需要设置为XMLHttpRequest。
一个配置得当的Headers字典,能大幅降低被直接拒绝访问的概率。
3.2 会话(Session)管理与Cookie持久化
使用requests.Session()对象,它可以自动处理Cookies,使得多次请求之间保持状态,就像浏览器一样。在爬虫启动时,可以先访问一次大众点评首页,让Session获取必要的初始Cookie。之后的所有请求都通过这个Session发出。
为了应对可能的中断和重启,我们需要实现Cookie的持久化。可以将Session的cookies通过pickle库保存到本地文件,下次启动时再加载进来,从而延续之前的会话状态,避免重复登录或从头开始爬取。
3.3 核心API接口的逆向与参数构造
如前所述,直接抓取动态加载的数据是关键。以获取某商户的评论列表为例:
- 打开目标商户页面,在开发者工具Network面板中筛选XHR请求。
- 滚动页面触发评论加载,找到返回评论数据的请求(通常包含“review”或“comment”字样)。
- 分析这个请求的URL、方法(GET/POST)以及参数(Query String或Form Data)。
- 你会发现关键参数如
shopId(商户ID)、page(页码)、pageSize(每页数量),以及一个可能随时间或会话变化的_token或sign参数。 sign参数通常是其他参数加上一个密钥(salt)通过某种加密算法(如MD5, SHA1)生成的,用于防止请求被篡改。你需要找到生成这个sign的JavaScript代码,并用Python实现相同的算法,或者更简单地,直接复用浏览器运行时生成的值(这要求爬虫与浏览器环境联动,复杂度较高)。对于学习和小规模采集,有时可以观察到短时间内token的变化规律,进行模拟。
3.4 频率控制与异常处理
毫无节制地高速请求是导致IP被封的最快途径。必须在请求间加入随机延时。
import time import random def safe_request(session, url, **kwargs): time.sleep(random.uniform(1, 3)) # 随机等待1-3秒 try: resp = session.get(url, **kwargs) resp.raise_for_status() # 检查HTTP状态码是否为200 return resp except requests.exceptions.RequestException as e: print(f"请求失败: {url}, 错误: {e}") # 这里可以加入重试逻辑 return None此外,代码必须包含完善的异常处理(try...except),对网络超时、连接错误、HTTP错误状态码(如403禁止访问、429请求过多)等情况进行捕获和记录,并根据策略决定是重试、跳过还是终止爬取。
3.5 数据解析与存储
成功获取到JSON响应后,使用json.loads()解析,然后用JsonPath或直接通过字典键值对提取所需字段。将每一页的每条评论数据整理成一个字典,添加到列表中。每爬完一定数量(比如一页)或每隔一段时间,将数据列表通过Pandas转换为DataFrame,并追加写入到CSV文件或SQLite数据库中,实现增量存储,防止程序意外崩溃导致数据全部丢失。
4. 数据清洗实战:从杂乱到规整
爬取下来的原始数据就像刚从矿场挖出的原石,充满了杂质。数据清洗的目的就是将其打磨成可供分析的璞玉。这个过程通常在Jupyter Notebook中进行,便于一步步观察数据变化。
4.1 加载与初步观察
首先,用Pandas加载爬取到的CSV文件。
import pandas as pd df = pd.read_csv('dianping_reviews_raw.csv') print(df.head()) # 查看前几行 print(df.info()) # 查看列信息和缺失值 print(df.describe()) # 查看数值型列的统计摘要4.2 处理缺失值
使用df.isnull().sum()统计各列缺失值数量。对于“人均消费”这样的数值列,如果缺失比例不高,可以考虑用中位数进行填充,因为中位数对异常值不敏感。
if df['avg_cost'].isnull().sum() > 0: median_cost = df['avg_cost'].median() df['avg_cost'].fillna(median_cost, inplace=True)对于“推荐菜”这类文本列,缺失可以直接填充为空字符串''。
4.3 格式转换与标准化
- 价格处理:
df['avg_cost']可能是字符串“¥128”。我们需要提取数字。
df['avg_cost_num'] = df['avg_cost'].str.extract(r'(\d+)').astype(float) # 或者更稳健的方式,先替换掉非数字字符 df['avg_cost_num'] = df['avg_cost'].replace(r'[^\d.]', '', regex=True).astype(float)- 评分处理:
df['taste_score']等评分可能是字符串“4.5”,直接转换。
score_columns = ['taste_score', 'env_score', 'service_score'] for col in score_columns: df[col] = pd.to_numeric(df[col], errors='coerce') # 转换失败设为NaN- 时间处理:评论时间
review_time可能是“2023-08-15”或“昨天 19:30”等格式。需要统一解析为datetime类型。对于相对时间(如“昨天”),需要编写函数进行转换,或者如果分析不依赖精确日期,可以暂时忽略这部分复杂处理。
4.4 文本评论深度清洗
评论正文content的清洗最为繁琐,也最影响后续的文本分析(如情感分析、关键词提取)。
import re def clean_text(text): if not isinstance(text, str): return '' # 1. 移除HTML标签 text = re.sub(r'<[^>]+>', '', text) # 2. 移除URL链接 text = re.sub(r'https?://\S+', '', text) # 3. 移除表情符号(简单版,复杂表情需更全面的范围) text = re.sub(r'[\U00010000-\U0010ffff]', '', text, flags=re.UNICODE) # 4. 移除多余空白字符(包括全角空格) text = re.sub(r'\s+', ' ', text) text = re.sub(r' +', ' ', text) # 全角空格 # 5. 移除首尾空格 text = text.strip() return text df['content_cleaned'] = df['content'].apply(clean_text)4.5 去重与异常值处理
- 去重:根据评论唯一ID去重。
df.drop_duplicates(subset=['review_id'], inplace=True, keep='first')- 异常值:对于人均消费,可以设定一个合理的范围(如10元到2000元),超出范围的数据视为异常,可以剔除或标记。
reasonable_min, reasonable_max = 10, 2000 df = df[(df['avg_cost_num'] >= reasonable_min) & (df['avg_cost_num'] <= reasonable_max)]清洗完成后,将干净的数据保存到新的文件,如dianping_reviews_cleaned.csv,供后续分析使用。
5. 消费分析报告生成:从数据到洞察
数据清洗完毕,就进入了最有意思的部分——探索数据背后的故事。我们围绕“消费分析”这个主题,可以从多个维度展开。
5.1 基础统计分析
首先对整体情况有一个把握。
# 基本统计量 print(f"共分析 {len(df)} 条有效评论。") print(f"人均消费平均值为:{df['avg_cost_num'].mean():.1f} 元") print(f"人均消费中位数为:{df['avg_cost_num'].median():.1f} 元") print(f"口味评分均分:{df['taste_score'].mean():.2f}") print(f"环境评分均分:{df['env_score'].mean():.2f}") print(f"服务评分均分:{df['service_score'].mean():.2f}") # 评分分布 import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize=(15, 4)) for i, col in enumerate(['taste_score', 'env_score', 'service_score'], 1): plt.subplot(1, 3, i) sns.histplot(df[col], bins=10, kde=True) plt.title(f'{col} Distribution') plt.tight_layout() plt.show()通过直方图,我们可以直观看到评分是呈正态分布还是严重偏态(例如,用户倾向于打高分或低分)。
5.2 价格区间与消费水平分析
将人均消费划分为几个区间,观察不同价位段的商户数量、平均评分等。
# 定义消费区间 bins = [0, 50, 100, 150, 200, 300, 500, 1000, df['avg_cost_num'].max()] labels = ['<50', '50-100', '100-150', '150-200', '200-300', '300-500', '500-1000', '>1000'] df['cost_bin'] = pd.cut(df['avg_cost_num'], bins=bins, labels=labels, right=False) # 统计各区间商户数量及平均评分 cost_analysis = df.groupby('cost_bin').agg( review_count=('review_id', 'count'), avg_taste_score=('taste_score', 'mean'), avg_cost=('avg_cost_num', 'mean') ).round(2).reset_index() print(cost_analysis)这个分析可以揭示“性价比”区间。例如,可能发现人均100-150元区间的商户不仅数量多,而且平均口味评分最高。
5.3 评分与消费的关联性分析
人们通常认为“贵的就是好的”,那么数据是否支持这个观点?我们可以计算人均消费与各项评分的相关系数,并绘制散点图观察趋势。
# 计算相关系数 correlation = df[['avg_cost_num', 'taste_score', 'env_score', 'service_score']].corr() print(correlation['avg_cost_num']) # 查看消费与各评分的相关性 # 绘制消费 vs 口味评分散点图 plt.figure(figsize=(10, 6)) sns.scatterplot(data=df, x='avg_cost_num', y='taste_score', alpha=0.5) sns.regplot(data=df, x='avg_cost_num', y='taste_score', scatter=False, color='red') # 添加回归线 plt.title('Relationship between Average Cost and Taste Score') plt.xlabel('Average Cost (CNY)') plt.ylabel('Taste Score') plt.show()如果回归线呈轻微上升趋势,说明有一定正相关,但通常这个关系不会很强,因为影响评分的因素很多。
5.4 文本评论的情感与主题挖掘
对于清洗后的评论内容content_cleaned,可以进行简单的词频统计,找出高频词汇。
from collections import Counter import jieba # 中文分词库 # 将所有评论拼接并分词 all_text = ' '.join(df['content_cleaned'].dropna().tolist()) words = jieba.lcut(all_text) # 过滤掉停用词(如“的”、“了”、“和”等)和短词 stopwords = set(['的', '了', '在', '是', '我', '有', '和', '就', ...]) # 需要自定义停用词表 filtered_words = [w for w in words if len(w) > 1 and w not in stopwords] word_freq = Counter(filtered_words).most_common(20) # 取前20个高频词 print(word_freq)高频词往往能反映用户关注的焦点,如“味道”、“服务”、“环境”、“价格”、“不错”、“一般”等。更进一步,可以使用snownlp或paddlehub等库进行情感分析,给每条评论打上情感倾向分(正面/负面),然后分析不同消费区间、不同评分段的情感分布差异。
5.5 时间趋势分析
如果数据中包含准确的评论日期,可以分析消费趋势、评分趋势随时间(如月份、季度)的变化。例如,是否在节假日前后人均消费更高?夏季对冷饮店的评分是否有提升?
6. 实战案例:分析某商圈火锅店消费特征
为了将上述流程串起来,我选取了“XX市YY商圈”的所有火锅店作为分析案例。这个案例完整地走了一遍从目标定义到报告呈现的全过程。
6.1 目标定义与数据采集
目标:分析该商圈内火锅店的整体竞争格局、价格分布、口碑差异,并找出潜在的高性价比或特色店铺。采集:使用爬虫,以商圈名为关键词搜索,筛选品类为“火锅”的商户,共获取了45家有效商户。然后针对每家商户,爬取其最近300条评论(如果不足则全部爬取),最终获得约1.2万条评论数据。
6.2 数据清洗重点
针对火锅店场景,清洗时特别关注了“推荐菜”字段。原始数据中推荐菜可能是一个用逗号或顿号分隔的字符串。我们将其拆分为列表,便于后续分析哪些菜品最受欢迎。
df['recommended_dishes'] = df['recommended_dishes'].fillna('').apply(lambda x: [dish.strip() for dish in re.split(r'[,、,]', x) if dish.strip()]) # 将所有推荐菜展开,统计词频 from itertools import chain all_dishes = list(chain.from_iterable(df['recommended_dishes'])) dish_freq = Counter(all_dishes).most_common(15)6.3 分析发现与可视化
- 价格带分析:发现该商圈火锅店人均消费主要集中在80-150元区间,属于大众消费水平。其中100-120元区间的店铺数量最多,且综合评分均值最高,可视为“黄金价格带”。
- 口碑两极分化:有3家店口味评分平均在4.8分以上(满分5分),但人均消费也高于180元;另有5家店评分低于3.8分,且多集中在低价位(<80元)。中间档位的店铺评分差异不大。
- 菜品关联:通过分析推荐菜词频,发现“毛肚”、“虾滑”、“牛肉”、“鸭血”是提及率最高的菜品。进一步交叉分析发现,高评分店铺的推荐菜中,“鲜切牛肉”、“特级毛肚”等高品质食材关键词出现频率显著更高。
- 服务与环境的影响:通过相关性分析,发现对于火锅店,“服务评分”与“口味评分”的相关系数最高(0.65),远高于“环境评分”与“口味评分”的相关系数(0.3)。这说明在消费者感知中,火锅店的服务质量(如加汤及时性、响应速度)极大地影响了他们对整体口味体验的评价。
6.4 报告输出与建议
基于以上分析,在Jupyter Notebook中制作了包含图表和文字说明的完整报告,并导出为HTML。报告的核心结论和建议包括:
- 对消费者:建议在100-120元价位选择,性价比最高;关注推荐菜中包含“鲜切”、“特级”等关键词的店铺,口味更有保障;特别留意服务评价。
- 对商户(竞品分析):低价并非竞争优势,低于80元价位的店铺口碑普遍较差。建议中等价位店铺在保证核心菜品质量的同时,重点提升服务水平,这是拉升口碑的关键杠杆。高价位店铺需明确其高端食材或独特体验的价值主张,以支撑其溢价。
这个案例的完整代码、清洗后的数据和分析报告,都包含在项目压缩包中。通过这个端到端的项目,你不仅能学到技术,更能掌握一套从数据获取到商业洞察的完整方法论。数据爬取只是起点,如何清洗、如何提问、如何从数据中找到故事的线索,才是更有价值的技能。
本文还有配套的精品资源,点击获取