如果你学完了 Python 基础语法,也跑过几个 requests 爬虫小案例,但面试时被问到“你做过什么完整项目”还是会卡壳,那么这篇文章就是为你准备的。
很多人学 Python 遇到的问题不是“不会写代码”,而是“不知道怎么做项目”。爬虫会写,数据分析也会一点,Flask 能启动一个最简单的页面,ECharts 也看过官方示例,可一旦要把这些东西组合成一个能展示的作品,就会陷入混乱:数据从哪来?清洗到什么程度?后端接口怎么设计?前端图表怎么对接?
这篇文章要解决的,就是这件事。
我会用“泡泡玛特热搜评论数据分析”作为真实业务场景,从爬虫采集、数据清洗、情感词统计,到 Flask 接口封装、ECharts 可视化,完整带你把一个 Python3 数据分析小项目跑通。整条链路就像一条流水线,你要做的不是背诵每一段代码,而是理解“数据如何一步步变成图表”。
先给一个明确判断:这个项目的技术难度其实不高,难度在于工程组织和数据理解。但恰恰是这种“完整链路”的项目,才是毕业设计、简历项目、面试展示中最吃香的类型。因为它证明你不只会写单点代码,还具备把一个模糊需求拆解成完整系统的能力。
如果你想把它改成其他品牌、其他平台的评论分析,方法完全一样,替换数据源和分析维度即可。
1. 这篇文章真正要解决的问题
很多学习者的真实状态是:知识点会,作品集空。
写爬虫时,能抓着 requests 和 BeautifulSoup 跑通一个静态页面;写数据分析时,能对一份现成的 CSV 做 mean、sum、groupby;写 Flask 时,能复制官方文档的 Hello World;写 ECharts 时,能打开官方示例库对着改。但这些东西分开看都行,合在一起就不知道怎么串联了。
这个项目的价值,就是把“数据获取—数据清洗—数据分析—后端接口—前端可视化”这条完整链路,用最小成本跑通。
具体来说,读完本文你会得到:
- 一套可运行的 Python3 爬虫代码,能抓取公开的热搜评论数据,并保存为 CSV。
- 一套 pandas 数据清洗与分析方法,从原始评论中提取关键词、情感倾向、时间趋势等信息。
- 一个 Flask 后端服务,把分析结果封装成 JSON 接口。
- 一个 ECharts 可视化页面,用饼图、柱状图、折线图直观展示分析结果。
- 一套面试讲解思路,知道怎么把项目讲得既有技术深度,又有业务价值。
为什么选择泡泡玛特?因为这类潮玩产品的热搜评论天然带有强烈情绪:有人觉得“真香”,有人吐槽“太贵了”,有人在讨论隐藏款概率。情绪浓度高的数据,做情感分析和关键词统计时,可视化效果会非常明显,也更适合拿来当项目展示。
需要强调的一点是:本文的爬虫代码只面向“公开可访问的数据源”,并且用于学习演示。在实际项目中,是否允许爬取某个平台的数据,要以该平台的服务条款和数据合规要求为准。技术能力不是用来突破边界的,而是用来在边界内高效完成任务的。
2. 项目整体架构与技术选型思路
这个项目虽然是教学案例,但它的架构设计是贴近真实工程实践的。整条数据处理流程分为四层:
数据采集层 -> 数据处理层 -> 接口服务层 -> 可视化展示层对应到技术栈如下:
| 层次 | 技术选型 | 主要职责 |
|---|---|---|
| 数据采集层 | Python3 + requests | 请求公开数据源,获取热搜评论原始数据 |
| 数据处理层 | pandas + jieba(可选) | 数据清洗、去重、关键词提取、情感词频统计 |
| 接口服务层 | Flask + flask-cors | 将分析结果封装为 JSON API,供前端调用 |
| 可视化展示层 | HTML + ECharts(JavaScript) | 饼图、柱状图、折线图展示分析结果 |
技术选型上有几个判断需要说清楚。
首先是 Flask 而不是 Django。Flask 足够轻量,一个文件就能启动服务,非常适合课程设计和中小型项目。如果你用 Django,光是项目初始化的目录结构就会劝退很多新手。Flask 的重点放在接口设计上,而不是被框架配置消耗精力。
其次是 ECharts 而不是 Matplotlib。Matplotlib 适合生成静态图片,但 ECharts 生成的是交互式 Web 图表,可以直接嵌入网页,视觉效果好、中文文档丰富,在简历和答辩演示中更有优势。而且 ECharts 只需要引入一个 JavaScript 文件,不需要额外安装 Python 包。
第三是 pandas 而不是纯 Python 处理。评论数据里重复值、空值、无意义字符很多,pandas 的 DataFrame 操作把这些常见问题封装得很优雅。用纯 Python 列表推导式处理几百条数据还行,但数据量一旦上千,pandas 的数据结构会让你省下大量时间。
来看一下项目的目录结构。建议你从一开始就保持清晰,这本身就是工程能力的体现:
bubble-mart-analysis/ ├── spider/ │ └── comment_spider.py # 爬虫:采集评论数据 ├── analysis/ │ └── data_cleaner.py # 数据清洗与分析 ├── web/ │ ├── app.py # Flask 后端 │ └── templates/ │ └── index.html # ECharts 可视化页面 ├── data/ │ ├── raw_comments.csv # 爬虫采集的原始数据 │ └── analysis_result.json # 清洗分析后的结构化结果 ├── requirements.txt # 依赖清单 └── README.md # 项目说明(答辩/面试展示用)一开始就按“蜘蛛—分析—服务—页面”四个模块来组织代码,会让你的项目显得非常专业。面试官看你简历时,第一眼不是看你代码多复杂,而是看你的工程组织能力。
3. 环境准备与前置条件
在开始写代码之前,先把环境搭好。这部分操作比较常规,但建议你不要跳过,因为后面所有代码都依赖这里完成。
3.1 Python 版本
本文示例基于 Python3。建议使用 Python 3.8 及以上版本,代码在 3.10 环境中开发验证。如果你用的是 3.7,大部分代码也能运行,只是个别语法特性可能不兼容,遇到问题优先升级 Python 版本。
在终端验证当前版本:
python3 --version如果是 Windows 系统,命令可能是python --version。不同系统的差异只是命令前缀不同,后面的逻辑完全一致。
3.2 创建虚拟环境
虚拟环境是我强烈建议保留的习惯。它能把项目依赖和系统全局的 Python 包隔离,避免“昨天还能跑,今天启动就报错”的依赖冲突问题。
mkdir bubble-mart-analysis cd bubble-mart-analysis python3 -m venv venv source venv/bin/activate # Windows 使用 venv\Scripts\activate启动虚拟环境后,终端前面会出现(venv)提示符,说明已经进入隔离环境。
3.3 安装依赖包
创建requirements.txt,写入以下内容:
requests==2.31.0 pandas==2.0.3 flask==3.0.0 flask-cors==4.0.0版本号可以按需调整,关键是明确依赖关系。安装命令:
pip install -r requirements.txt如果你在国内网络环境,可以改用清华镜像源加速:
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,可以用一行命令验证关键依赖是否就绪:
python3 -c "import requests, pandas, flask; print('env ok')"看到env ok输出,说明环境准备完成。整个过程不超过十分钟。
4. 爬虫模块:先拿到能用的评论数据
爬虫是整个项目的数据源头。很多初学者写爬虫时最关心“怎么绕过限制”,但实际上,一个合格的爬虫首先应该考虑的是“数据源头是否允许访问”“请求频率是否礼貌”“拿到数据后如何结构化存储”。
4.1 确定数据源与字段设计
本项目的目标是“热搜评论”,也就是围绕某个热搜话题产生的用户评论。以公开可访问的数据源为例,评论通常包含以下字段:
| 字段名 | 含义 | 示例 |
|---|---|---|
| user_name | 评论用户昵称 | 泡泡爱好者 |
| comment | 评论正文 | 这个隐藏款也太好看了 |
| create_time | 评论发布时间 | 2025-06-01 12:30:00 |
| like_count | 点赞数 | 328 |
| topic | 所属话题或关键词 | 泡泡玛特 |
这个字段设计非常通用。不管后续你怎么扩展,用户、内容、时间、热度、主题这五类信息基本覆盖了评论分析的核心维度。
4.2 爬虫代码实现
在spider/comment_spider.py中实现核心爬虫逻辑:
# 文件路径:spider/comment_spider.py import csv import time import random import requests # 请求头:模拟浏览器访问,是爬虫的基本礼仪之一 HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", "Accept": "application/json, text/plain, */*", "Referer": "https://example.com/", # 请按实际数据源调整 } BASE_URL = "https://api.example.com/search/comments" # 示例接口,请替换为授权数据源 def fetch_comments(keyword: str, page_limit: int = 5) -> list: """ 抓取指定关键词下的公开评论数据 :param keyword: 热搜关键词 :param page_limit: 最多抓取页数 :return: 评论数据列表 """ all_comments = [] for page in range(1, page_limit + 1): params = { "keyword": keyword, "page": page, "page_size": 20, } try: resp = requests.get(BASE_URL, headers=HEADERS, params=params, timeout=10) resp.raise_for_status() data = resp.json() # 根据实际接口结构调整,这里假设返回结构为 data.items items = data.get("data", {}).get("items", []) for item in items: all_comments.append({ "user_name": item.get("user_name", ""), "comment": item.get("content", ""), "create_time": item.get("create_time", ""), "like_count": int(item.get("like_count", 0)), "topic": keyword, }) print(f"[info] 第 {page} 页抓取完成,累计 {len(all_comments)} 条评论") except requests.RequestException as e: print(f"[error] 第 {page} 页抓取失败:{e}") # 礼貌爬虫:随机休眠,避免高频请求给目标服务器带来压力 time.sleep(random.uniform(1.5, 3.5)) return all_comments def save_to_csv(comments: list, filepath: str = "data/raw_comments.csv") -> None: """保存评论数据到 CSV 文件""" if not comments: print("[warning] 没有可保存的数据") return fieldnames = ["user_name", "comment", "create_time", "like_count", "topic"] with open(filepath, mode="w", encoding="utf-8-sig", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(comments) print(f"[info] 数据已保存至 {filepath},共 {len(comments)} 条") if __name__ == "__main__": keyword = "泡泡玛特" comments = fetch_comments(keyword, page_limit=5) save_to_csv(comments)这段代码有几个关键设计值得你记住:
第一,请求头中的User-Agent是爬虫的基本礼仪,它告诉服务器“我是一个正常的浏览器”。缺少 UA 的请求容易被直接拒绝。
第二,random.uniform(1.5, 3.5)的随机休眠非常重要。爬虫不是越快越好,过度频繁的请求会给目标服务器造成压力,也可能导致 IP 被限制。
第三,encoding="utf-8-sig"是专门为 Excel 兼容设置的。如果你用utf-8保存 CSV,直接用 Excel 打开可能中文乱码,而utf-8-sig会写入 BOM 头,Excel 能正确识别。
4.3 运行爬虫并检查数据
python3 spider/comment_spider.py运行成功后会输出每页的抓取进度,最终在data/raw_comments.csv生成原始数据。
打开 CSV 文件,你应该能看到类似这样的数据:
user_name,comment,create_time,like_count,topic 用户A,这个联名款太好看了,2025-06-01 12:30:00,328,泡泡玛特 用户B,蹲一个隐藏款攻略,2025-06-01 12:28:00,156,泡泡玛特 用户C,价格越来越贵了...,2025-06-01 12:25:00,89,泡泡玛特如果你拿到了数据,爬虫模块就算完成。数据不在多,先拿到几百条就能支撑后续分析演示。
4.4 爬虫模块的边界提醒
这里有必要多说一句。爬虫技术本身是中性的,但使用它的边界必须清晰:
- 只采集公开可访问的数据,不绕过登录、验证码、付费墙等技术限制。
- 遵守目标网站的 robots 协议和服务条款。
- 控制请求频率,使用随机延时,不对目标服务器造成压力。
- 采集的数据仅用于学习研究,不用于商业目的。
在实际项目中,如果数据量需求很大,更推荐优先寻找官方 API 或数据合作渠道,而不是自己写爬虫。能用合法接口解决的,尽量不用爬虫。
5. 数据清洗与分析:从字符串到可视化图表
爬虫拿到的是原始数据,原始数据是不能直接可视化的。这一章,我们把“看起来乱糟糟的评论”变成“有分析价值的图表数据”。
5.1 数据读取与去重
用 pandas 读取 CSV,做基础清洗。新建analysis/data_cleaner.py:
# 文件路径:analysis/data_cleaner.py import pandas as pd import re from collections import Counter def load_raw_data(filepath: str = "data/raw_comments.csv") -> pd.DataFrame: """读取爬虫采集的原始数据""" df = pd.read_csv(filepath, encoding="utf-8-sig") print(f"[info] 原始数据共 {len(df)} 条") return df def clean_data(df: pd.DataFrame) -> pd.DataFrame: """ 数据清洗: 1. 去重 2. 去除空评论 3. 清理评论中的特殊符号和链接 4. 解析时间字段 """ df = df.drop_duplicates(subset=["comment"], keep="first") df = df.dropna(subset=["comment"]) def clean_comment(text: str) -> str: text = str(text) text = re.sub(r"http\S+", "", text) # 去掉链接 text = re.sub(r"@\w+", "", text) # 去掉 @用户 text = re.sub(r"#\w+#", "", text) # 去掉话题标签 text = re.sub(r"\s+", " ", text) # 合并空白 return text.strip() df["clean_comment"] = df["comment"].apply(clean_comment) df = df[df["clean_comment"] != ""] # 去掉清洗后为空的评论 df["create_time"] = pd.to_datetime(df["create_time"], errors="coerce") df = df.dropna(subset=["create_time"]) print(f"[info] 清洗后数据共 {len(df)} 条,去重去空完成") return df清洗的核心逻辑是从“展示原始内容”转向“提取分析价值”。删除重复评论是为了避免水军重复刷量影响统计;删除链接和 @ 用户是因为这些内容对情感分析没有帮助;解析时间字段是为了后续做时间趋势分析。
5.2 情感词频统计
评论分析最直观的产出是“大家都在讨论什么”。这里用关键词词频统计来实现:
def analyze_keywords(df: pd.DataFrame, top_n: int = 10) -> dict: """ 基于简单关键词库统计评论中提到的核心词 实际项目中可以引入 jieba 分词 + TF-IDF """ keyword_dict = { "好看": ["好看", "可爱", "颜值", "美"], "很贵": ["贵", "价格", "性价比"], "隐藏款": ["隐藏", "隐藏款", "端盒"], "收藏": ["收藏", "盲盒", "手办", "摆件"], "失望": ["失望", "踩雷", "丑", "不入"], "惊喜": ["惊喜", "喜欢", "开心", "好评"], } counter = Counter() for comment in df["clean_comment"]: for category, keywords in keyword_dict.items(): for kw in keywords: if kw in comment: counter[category] += 1 break top = counter.most_common(top_n) return { "categories": [item[0] for item in top], "counts": [item[1] for item in top], }这段代码用了一个非常“笨”但有效的方法:预置关键词库,逐个检查评论中是否出现关键词。这种方式对几百条数据完全够用。如果你想让项目更有深度,可以引入jieba分词加 TF-IDF 提取真正的热点词,代码会更复杂,但分析结果会更客观。
5.3 统计指标设计与 JSON 输出
为了让可视化层展示得更丰富,我们再计算几个指标:
def build_analysis_json(df: pd.DataFrame, output_path: str = "data/analysis_result.json") -> None: """生成可视化所需的结构化 JSON""" # 1. 基础统计 total_comments = len(df) # 2. 点赞 TOP 10 评论 top_liked = df.nlargest(10, "like_count")[ ["user_name", "clean_comment", "like_count"] ].to_dict(orient="records") # 3. 按小时统计评论量 df["hour"] = df["create_time"].dt.hour hour_count = df.groupby("hour").size().reset_index(name="count") hour_distribution = { "hours": hour_count["hour"].astype(str).tolist(), "counts": hour_count["count"].tolist(), } # 4. 关键词频次 keywords = analyze_keywords(df) # 5. 封装统一 JSON result = { "total_comments": total_comments, "top_liked_comments": top_liked, "hour_distribution": hour_distribution, "keywords_distribution": keywords, } with open(output_path, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"[info] 分析结果已保存至 {output_path}")5.4 运行数据清洗与分析
在analysis/data_cleaner.py末尾添加入口:
if __name__ == "__main__": raw_df = load_raw_data() cleaned_df = clean_data(raw_df) build_analysis_json(cleaned_df)运行:
python3 analysis/data_cleaner.py成功后,打开data/analysis_result.json,你就能看到结构化好的分析结果。比如:
{ "total_comments": 86, "top_liked_comments": [ { "user_name": "泡泡爱好者", "clean_comment": "这个隐藏款也太好看了", "like_count": 328 } ], "hour_distribution": { "hours": ["10", "11", "12", "21", "22"], "counts": [12, 15, 8, 20, 10] }, "keywords_distribution": { "categories": ["好看", "隐藏款", "很贵", "惊喜"], "counts": [40, 30, 25, 18] } }到这里,数据分析部分已经完成。下一步,我们通过 Flask 把这份 JSON 变成可访问的接口。
6. Flask后端:让数据通过接口统一对外提供
有人可能会问:我们已经有 JSON 文件了,前端直接读取不行吗?为什么还要加一层 Flask?
这个问题的答案是:在真实项目中,前端不应该直接依赖硬盘文件。文件系统是脆弱且不可控的。通过后端接口,你可以随时切换数据源(CSV、数据库、API),前端代码完全不需要改动。这就是接口层存在的意义。
6.1 编写 Flask 应用
在web/app.py中实现接口服务:
# 文件路径:web/app.py import json from flask import Flask, render_template, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app) # 允许跨域请求,前后端分离时必用 ANALYSIS_RESULT_PATH = "../data/analysis_result.json" def load_analysis_result(): """读取分析结果 JSON 文件""" with open(ANALYSIS_RESULT_PATH, "r", encoding="utf-8") as f: return json.load(f) @app.route("/") def index(): """首页:返回可视化页面""" return render_template("index.html") @app.route("/api/summary") def api_summary(): """接口1:基础统计信息""" data = load_analysis_result() return jsonify({ "total_comments": data["total_comments"], "message": "success", }) @app.route("/api/keywords") def api_keywords(): """接口2:关键词分布""" data = load_analysis_result() return jsonify(data["keywords_distribution"]) @app.route("/api/hour-distribution") def api_hour_distribution(): """接口3:评论时间分布""" data = load_analysis_result() return jsonify(data["hour_distribution"]) @app.route("/api/top-liked") def api_top_liked(): """接口4:点赞 TOP 评论""" data = load_analysis_result() return jsonify(data["top_liked_comments"]) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)三个接口分别对应三个维度的可视化需求:关键词分布用于柱状图、时间分布用于折线图、点赞 TOP 用于展示核心评论。注意接口返回的是jsonify包装的 Response,这是 Flask 的标准做法,前端可以直接通过res.json()获取数据。
6.2 启动 Flask 服务
cd web python3 app.py启动成功后,终端会显示:
* Running on http://127.0.0.1:5000用浏览器访问http://127.0.0.1:5000/api/keywords,如果看到 JSON 数据,说明后端接口已经通了。
7. ECharts前端可视化:一张图看懂产品口碑
这是整个项目最直观的产出环节。我们用一个 HTML 页面承载三张图表:关键词分布柱状图、评论时间分布折线图、点赞 TOP 评论列表。
7.1 页面结构
在web/templates/index.html中写入:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>泡泡玛特热搜评论数据分析</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> <style> body { font-family: "Microsoft YaHei", sans-serif; background: #f7f8fa; margin: 0; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; } h1 { text-align: center; color: #333; } .chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; } .chart-item { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); } .chart-title { font-size: 16px; font-weight: 600; color: #555; margin-bottom: 8px; } .chart { width: 100%; height: 350px; } #liked-list { list-style: none; padding: 0; margin: 0; } #liked-list li { padding: 8px 0; border-bottom: 1px solid #eee; color: #444; font-size: 14px; } </style> </head> <body> <div class="container"> <h1>泡泡玛特热搜评论数据分析</h1> <div class="chart-grid"> <div class="chart-item"> <div class="chart-title">关键词讨论热度 TOP</div> <div id="keyword-chart" class="chart"></div> </div> <div class="chart-item"> <div class="chart-title">评论发布时间分布</div> <div id="hour-chart" class="chart"></div> </div> </div> <div class="chart-item" style="margin-top: 20px;"> <div class="chart-title">点赞最高的评论</div> <ul id="liked-list"></ul> </div> </div> <script> // 初始化关键词柱状图 const kwChart = echarts.init(document.getElementById('keyword-chart')); // 初始化时间折线图 const hourChart = echarts.init(document.getElementById('hour-chart')); // 从后端接口获取数据 Promise.all([ fetch('/api/keywords').then(res => res.json()), fetch('/api/hour-distribution').then(res => res.json()), fetch('/api/top-liked').then(res => res.json()) ]).then(([kwData, hourData, likedData]) => { // 渲染柱状图 kwChart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: kwData.categories }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: kwData.counts, itemStyle: { color: '#f39c12' } }] }); // 渲染折线图 hourChart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: hourData.hours }, yAxis: { type: 'value' }, series: [{ type: 'line', data: hourData.counts, smooth: true, lineStyle: { color: '#3498db' }, areaStyle: { opacity: 0.2 } }] }); // 渲染点赞 TOP 列表 const ul = document.getElementById('liked-list'); likedData.forEach(item => { const li = document.createElement('li'); li.innerHTML = `<strong>${item.user_name}</strong>:${item.clean_comment}(点赞 ${item.like_count})`; ul.appendChild(li); }); }); </script> </body> </html>这段前端代码非常轻量,没有引入 Vue 或 jQuery,纯粹用原生 JavaScript 的fetch请求后端接口。这样做的好处是只要你理解了“前端拿数据—数据喂给图表”这个逻辑,迁移到 Vue、React 项目时也只是换个框架语法而已。
7.2 如果页面无法加载图表
最常见的失败原因有两个:一是后端没有启动,fetch请求失败,浏览器控制台能看到Failed to fetch;二是 ECharts 的 CDN 链接被本地网络拦截,导致echarts对象未定义。第一种情况,检查 Flask 终端是否还在运行;第二种情况,可以把echarts.min.js下载到本地,放到web/static/js/目录下,用相对路径引入。
8. 完整运行流程
从零开始跑通整个项目,只需要按顺序执行下面四条命令:
# 1. 进入项目目录并激活虚拟环境 cd bubble-mart-analysis source venv/bin/activate # Windows: venv\Scripts\activate # 2. 运行爬虫,采集评论数据 python3 spider/comment_spider.py # 3. 运行数据清洗与分析,生成 JSON python3 analysis/data_cleaner.py # 4. 启动 Flask 服务(注意保持终端开启) cd web python3 app.py然后打开浏览器,访问http://127.0.0.1:5000。
如果一切正常,你会看到:
- 页面顶部标题“泡泡玛特热搜评论数据分析”。
- 左侧柱状图展示不同的关键词讨论热度,比如“好看”“隐藏款”“很贵”等。
- 右侧折线图展示不同时间段评论发布量的变化。
- 底部列出点赞数最高的几条评论。
整个项目从爬虫到可视化页面,一个完整的闭环就形成了。
9. 常见问题与排查思路
无论你照着教程写得多仔细,运行时总会遇到一些“惊喜”。下面是这套环境里最高频的问题和排查思路:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
爬虫报错requests.exceptions.ConnectionError | 数据源接口地址错误,或本地网络无法访问 | 打印接口地址,用浏览器访问确认 | 修改BASE_URL为可访问的授权数据源 |
| 爬虫抓到 0 条数据 | 接口返回字段与代码解析不一致 | 先打印resp.text查看原始 JSON 结构,再调整字段取值 | 按实际返回结构调整item.get()的字段名 |
| CSV 文件中文乱码 | 写入时编码不是utf-8-sig | 检查open()中的encoding参数 | 使用encoding="utf-8-sig" |
运行data_cleaner.py报错找不到文件 | 工作目录不对 | 检查命令在哪个目录执行pwd | 统一在项目根目录执行python3 -m analysis.data_cleaner,而不是直接python3 analysis/data_cleaner.py,后者会把当前目录切换到 analysis 下造成相对路径错乱 |
| Flask 启动提示端口 5000 被占用 | 上一次服务未关闭 | lsof -i:5000查看占用进程 | 关闭旧进程或用app.run(port=5001)换端口 |
| 浏览器打开页面图表空白 | ECharts CDN 加载失败,或接口请求失败 | 按 F12 打开控制台看报错信息 | CDN 问题就改为本地引入;接口失败就检查 Flask 是否启动 |
接口返回中文显示\uXXXX | Flask 的jsonify默认转义中文 | 无影响,浏览器解析会正常显示 | 也可以在返回前用json.dumps(data, ensure_ascii=False)处理 |
这里特别说明一下第 4 条。很多初学者在项目根目录执行python3 analysis/data_cleaner.py后,代码里的相对路径data/raw_comments.csv会失效,因为此时 Python 的工作目录变成了analysis/,它会在analysis/下找data/文件夹,自然找不到。解决办法有两种:在代码里用os.path拼接绝对路径,或者统一用python3 -m analysis.data_cleaner命令以模块方式运行。这个问题在真实项目里非常常见,建议直接记住模块运行方式。
10. 最佳实践与面试亮点
项目跑通只是第一步,怎么在简历和面试中展示它,才是决定项目价值的关键。
10.1 工程层面的建议
第一,数据存储不要只依赖 CSV。当数据量超过几千条,CSV 的读写和查询性能会明显下降。你可以引入 SQLite,用 pandas 的to_sql写入,再用read_sql读取,改动成本非常低,但项目专业度会提升一个档次。
第二,爬虫代码一定要有日志。不要只用print,改用logging模块,按时间、级别分类记录。面试官看到你输出日志和异常捕获,会默认你已经有生产环境的工程意识。
第三,给爬虫加上“增量更新”概念。简单做法是:每次爬之前读一下已有数据的时间戳,只爬取比最新时间更晚的新评论。这样你的项目就不是一次性玩具,而是一个可持续更新的数据分析系统。
10.2 简历项目描述怎么写
简历上的项目描述不是“我用了 Flask”,而是“我完成了什么、解决了什么问题”。参考写法:
泡泡玛特热搜评论数据分析系统(Python3 + Flask + ECharts) - 基于 Python3 requests 实现公开评论数据采集,设计随机延时与异常重试机制,稳定采集 XX 条有效评论 - 使用 pandas 完成数据清洗、去重、关键词频次与时间分布统计,输出结构化分析结果 - 基于 Flask 设计 RESTful API,实现分析结果按维度接口化输出 - 基于 ECharts 构建可视化页面,以柱状图、折线图等图表直观展示用户口碑与热度变化注意其中的“XX 条有效评论”要填真实数字,不要虚构。面试官追问的时候,你要能准确说出这个数字是怎么来的。
10.3 面试时的讲解节奏
面试中讲项目,不要从代码第一行开始念。按照下面这个节奏讲:
- 背景:这个项目解决的是什么问题?—— 用户评论中隐藏着产品口碑和用户关注点,手动看评论效率太低。
- 方案:如何解决?—— 爬取公开评论,清洗分析,可视化展示。
- 挑战:过程中最大的难点是什么?—— 数据清洗规则怎么定?接口怎么设计?前端图表怎么对数据。
- 结果:最终效果如何?—— 输入关键词,输出图表化分析报告。
如果面试官追问“为什么用 Flask 不用 Django”?你要说清楚:Flask 更轻量,适合快速搭建小型接口服务;Django 自带 Admin、ORM、多应用模块,适合大型业务系统。技术选型永远要结合业务场景,而不是越重越好。
如果面试官问“爬虫被反爬怎么办”?正确的回答是:先判断目标网站是否允许爬取,再考虑限速、代理、浏览器渲染等更复杂的手段。但更稳妥的方案是优先寻找官方开放 API,或者直接使用网页版本的公开导出功能。这个回答既展示了技术认知,也体现了数据合规意识。
11. 总结与后续学习方向
最后来盘点一下,这个项目让你掌握了哪些核心能力:
从上到下,你已经能独立完成数据采集、数据清洗、统计分析、后端接口设计、前端可视化这条完整链路。这套能力不只是为了一项作业,而是你以后做任何“数据驱动决策”类项目的通用能力。
这个项目其实还有很大的进阶空间。如果你时间充裕,建议在下面四个方向里选一个继续深入:
- 情感分析:引入 SnowNLP 或预训练模型,对评论做正负向情感判断,把“好看”“贵”“失望”等关键词映射成情感得分,可视化效果会更有故事感。
- 词云图:用 jieba 分词加 wordcloud 库生成中文词云,放到页面上视觉冲击力很强。
- 数据入库:把 CSV 换成 SQLite 或 MySQL,用 SQL 查询替代 pandas 统计,工程量不大但项目完整性高出不少。
- 定时调度:用 GitHub Actions 或服务器 cron 每天定时跑一次爬虫和分析,这样你的项目就是一个持续运转的数据系统。
做项目和学习算法最本质的区别是:算法考你“会不会”,项目考你“能不能在真实约束下把事情做完”。这个项目虽然小,但它已经把你推到了“能完成一件事”的门口。
建议你把代码完整跑通之后,再花一个晚上把简历上的项目描述写好。项目本身是敲门砖,而把它讲清楚才是面试加分的关键。如果这篇文章对你有帮助,建议先收藏,回头实际搭建的时候对照着做。