1. 为什么我盯上了这个"毕业设计题目"
每年到了毕业季,计算机相关专业的学生就开始集体焦虑:论文题目选什么?系统做什么?既要体现技术含量,又不能复杂到做不完。我见过太多人栽在"选题一时爽,实现火葬场"这个坑里——要么选题太空,落地时发现工作量爆表;要么选题太虚,答辩时被老师问两句就露馅。
前段时间在指导学生选题时,看到这个题目:
基于python的京东手机数据分析推荐系统:Flask框架 + pyecharts + requests爬虫 + 大数据分析
说实话,我第一反应是"这题有点东西"。原因有三:
第一,技术栈足够全。Python爬虫、Web开发、数据可视化、推荐算法、大语言模型接口调用,几乎覆盖了本科阶段能拿得出手的所有Python技能点。答辩时任何一条线都能展开讲半小时。
第二,数据来源真实可靠。京东手机频道的商品数据是公开的,字段维度丰富,包含价格、评价数、店铺、品牌、型号等信息,分析起来故事性很强。相比那些用假数据凑数的项目,这个题目自带"真实业务场景"的光环。
第三,有明确的"人无我有"亮点。在传统"爬虫+可视化大屏"的模板上,叠加了基于DeepSeek的智能推荐,直接把项目从"数据分析展示"拉升到了"智能应用"的层级。这个差异化非常关键,评阅老师看到"推荐系统"和"大模型"两个字,印象分会明显不同。
当然,题目好不等于好做。真正的坑在于:爬虫反爬怎么破?数据清洗怎么做?推荐怎么算才有说服力?DeepSeek接口怎么接入才稳定?这些问题我在实际验证过程中都踩了一遍,下面按一条完整可复现的路线拆开来讲。
2. 系统整体架构:从爬虫到推荐的四层链路
动手写代码之前,我建议先花半天把架构图在脑子里画清楚。这个项目本质上是一条数据流水线,从源头到展示总共四层,每一层都有明确输入输出:
2.1 四层结构拆解
| 层级 | 核心职责 | 关键技术 | 关键产物 |
|---|---|---|---|
| 数据采集层 | 从京东搜索页抓取手机商品信息 | requests + 随机UA + 代理池 | 原始JSON数据 |
| 数据治理层 | 清洗、去重、转换、入库 | Pandas + SQLite/MySQL | 结构化数据表 |
| 分析展示层 | 统计指标、生成图表,Web端输出 | Flask + pyecharts + Jinja2 | 可视化大屏页面 |
| 智能推荐层 | 根据用户偏好生成个性化推荐 | 内容相似度 + DeepSeek API | 推荐结果模块 |
这四层之间用清楚的数据接口解耦:爬虫只管把数据存到数据库,分析模块读库计算,Flask读取计算结果渲染图表,推荐模块独立运行。这样每条线都可以单独调试、单独测试,不用牵一发动全身。
2.2 为什么选择这套技术组合
选型不是拍脑袋,每个组件都有明确的理由:
requests做爬虫:相比Scrapy,requests上手成本低,单页抓取场景足够用。京东手机列表页返回的是内嵌JSON,直接解析比Scrapy维护管道更轻快。对于毕业设计而言,管好"能跑、能讲、能扩展"就够,没必要引入重型框架。
Flask做Web层:Flask足够轻,路由写在单文件里就能跑通全站。配合Jinja2模板渲染pyecharts生成的HTML片段,前后端分离程度适中,逻辑清晰。Django对这个项目明显过重了。
pyecharts做图表:pyecharts最大优势是生成的图表可以直接嵌入Flask模板,不需要写任何前端JS。对于Python技术栈的同学来说,这是最省力的可视化路线。
SQLite做存储:如果数据量在几万条以内,SQLite一个文件搞定,免安装、免配置、随项目打包。你甚至可以直接用Navicat打开看数据。等以后数据量大再换MySQL也不迟——因为SQLAlchemy已经把ORM层抽象好了。
DeepSeek做智能推荐:这是项目的加分项。通过调用大模型API,对商品信息进行语义理解,再结合评分算法生成推荐理由,比单纯"根据分类推荐同品牌"有说头得多。
有一点要提前想清楚:推荐系统不是炫技的舞台,而是讲故事的舞台。要在论文里讲清楚"为什么要推荐这个商品",就需要给推荐结果附加可读的推荐理由,DeepSeek恰好能自动生成诸如"这款手机在3000元价位段拥有最强影像模组,根据您的需求优先级,性价比排名第一"这类自然语言说明。
3. 数据采集层:requests爬虫的京东实战与反爬对策
爬虫是整个项目的数据源头,也是很多同学第一次接触真实反爬机制的地方。京东的搜索页面虽然公开,但直接裸奔requests请求,大概率会收到一个"滑动验证"或者直接进风控。这一节把完整破解链路写清楚。
3.1 确定目标接口:直接解析内嵌JSON
京东手机搜索页的URL大致是:
https://search.jd.com/Search?keyword=手机&enc=utf-8但直接请求这个页面,返回的是经过服务端渲染的HTML,解析成本高,而且关键商品数据混在标签里,提取容易出错。更好的方式是找到页面加载时异步请求的JSON接口:
https://search.jd.com/s_new.php?keyword=手机&enc=utf-8&page=1&scrolling=y&show_items=商品ID列表实测中这个接口返回的是纯HTML片段,每个商品块里包含了:商品ID、标题、价格、评价数、店铺名称、图片链接等。配合正则或lxml解析即可。
如果只想抓手机类目,更稳定的入口是京东的"手机通讯"频道接口:
https://list.jd.com/list.html?cat=9987,653,655解析逻辑核心代码如下:
import requests import re import random import time def fetch_jd_phone_data(page): url = "https://search.jd.com/s_new.php" params = { "keyword": "手机", "enc": "utf-8", "page": page, "scrolling": "y" } headers = build_headers() # 详见下方 resp = requests.get(url, params=params, headers=headers, timeout=10) resp.encoding = "utf-8" # 提取商品ID pids = re.findall(r'data-sku="(\d+)"', resp.text) return pids3.2 四个必须处理的爬虫细节
3.2.1 User-Agent和Cookie策略
京东对单一UA的请求非常敏感。实测下来,同一UA连续请求超过20次,就大概率触发验证码。建议维护一个UA池,每次请求随机取用:
import random UA_POOL = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" ] def build_headers(): return { "User-Agent": random.choice(UA_POOL), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "keep-alive", "Referer": "https://www.jd.com/" }注意:不要直接加"Accept-Encoding: br",requests如果没有解压brotli的能力,反而会拿到乱码。安全做法是删掉这个头,让服务器返回gzip,requests会自动处理。
3.2.2 请求频率控制
我实测过,单IP每秒超过2次请求,几分钟内就会触发风控。因此每抓取一页,强制sleep随机1.5到3秒:
import time import random def safe_sleep(): time.sleep(random.uniform(1.5, 3.0))同时设置"从第1页抓到第20页后停15分钟再继续",保证整个采集过程在1小时内完成,单IP风险最小化。
3.2.3 代理池(可选但推荐)
如果追求稳妥,可以准备5~10个HTTP代理,在函数内部轮换。注意这里不需要代理池框架,一个列表加random.choice就够。但务必对代理做可用性校验,否则一个坏代理会拖垮整个抓取流程。
3.2.4 异常处理与断点续传
爬虫最容易在半夜出海量异常。建议用装饰器统一捕获超时、连接错误、JSON解析失败等异常,并将已抓取的页数记录到本地文件,实现断点续传:
import json import logging from functools import wraps logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") def retry_on_exception(retries=3, delay=2): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(retries): try: return func(*args, **kwargs) except Exception as e: logging.warning(f"Attempt {attempt+1} failed: {e}") time.sleep(delay) raise RuntimeError("超过最大重试次数") return wrapper return decorator @retry_on_exception(retries=3, delay=3) def fetch_page_data(page): # 抓取逻辑 pass3.3 解析商品数据
京东商品块HTML里可以提取到的字段非常有分析价值,我建议按下面的字段清单入库:
| 字段名 | 含义 | 示例值 |
|---|---|---|
| sku_id | 商品唯一ID | 100012043978 |
| title | 商品标题 | Apple iPhone 15 (A3092) 128GB 蓝色 |
| price | 当前价格 | 5999.00 |
| comment_count | 评价数 | 200000+ |
| shop_name | 店铺名 | Apple产品京东自营旗舰店 |
| brand | 品牌 | Apple/苹果 |
| category | 类目 | 手机通讯-手机 |
| detail_url | 商品链接 | https://item.jd.com/100012043978.html |
价格字段通常在独立的<span class="p-price">块里,随滚动加载出现。抓取时先拿商品列表页的所有,再针对价格区间用户最感兴趣的20个商品调一次sku.3.jd.com的接口获取最新价格即可。
4. 数据治理与入库:Pandas清洗的逻辑和坑
爬虫拿到的是"脏数据",直接用来分析一定会出问题。清洗这一步,表面上看是体力活,其实暗藏几个决定后续分析质量的细节。
4.1 清洗流程设计
import pandas as pd import re def clean_jd_data(raw_df): df = raw_df.copy() # 1. 去除标题中的无效字符 df["title"] = df["title"].str.replace("\n", "").str.strip() # 2. 价格字段清洗:去掉"¥"和逗号 df["price"] = df["price"].astype(str).str.replace("¥", "").str.replace(",", "").astype(float) # 3. 评价数字段清洗:"200000+"转为整数 df["comment_count"] = df["comment_count"].astype(str).str.replace("+", "").str.replace("万", "0000") df["comment_count"] = pd.to_numeric(df["comment_count"], errors="coerce").fillna(0).astype(int) # 4. 品牌字段从标题中提取 df["brand"] = df["brand"].fillna("未知") # 5. 去重:同一sku_id只保留一条 df = df.drop_duplicates(subset="sku_id", keep="first") # 6. 价格合理区间过滤:排除异常低价或高价 df = df[(df["price"] >= 100) & (df["price"] <= 20000)] return df4.2 三个容易翻车的清洗点
4.2.1 价格字段的类型转换
京东页面上的价格有时会显示成"5999.00",有时是"¥5,999.00",甚至促销时会变成"抢购价"。我在清洗时直接先转字符串再统一替换,稳妥。
4.2.2 评论数"万"单位的处理
评价数超过1万会显示"1.2万+",这需要特殊转换逻辑。上面代码里用.replace("万", "0000")有个漏洞——"1.2万"替换后会变成"1.20000",然后转float会得到12000,恰好是对的:
"1.2万" -> "1.20000" -> 12000.0但是"10万"会被替换成"100000",也正确。这个技巧虽然看着脏,但实测有效。更严谨的做法是:
def parse_comment_count(value: str) -> int: value = value.replace("+", "").strip() if "万" in value: return int(float(value.replace("万", "")) * 10000) return int(value) if value.isdigit() else 04.2.3 缺失值处理策略
品牌、店铺名可能为空的,推荐直接用"未知"填充,不要dropna,否则数据量会缩减20%以上。后续分析按"未知"单独分组即可。
4.3 入库操作
库选SQLite,ORM用SQLAlchemy。在Flask项目中,建一个models.py:
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() class PhoneProduct(db.Model): __tablename__ = "phone_products" sku_id = db.Column(db.String(50), primary_key=True) title = db.Column(db.String(500)) price = db.Column(db.Float) comment_count = db.Column(db.Integer) shop_name = db.Column(db.String(200)) brand = db.Column(db.String(100)) category = db.Column(db.String(100)) detail_url = db.Column(db.String(500))然后写一个store_data.py脚本,把清洗后的DataFrame批量写入:
import pandas as pd from models import db, PhoneProduct def batch_save(df): # 用SQLite的insert替代逐条commit,效率高很多 db.session.bulk_insert_mappings(PhoneProduct, df.to_dict(orient="records")) db.session.commit()5. 数据分析模块:从统计指标到业务洞察
数据清洗完之后,就可以开始分析"讲故事"了。分析不是堆砌图表,而是用数据回答业务问题。我总结了三个这个题目最值得做的分析方向。
5.1 价格分布与销量热度关联分析
手机产品的价格分布呈明显长尾状——5000元以上的机型数量不算最多,但评论热度往往集中在3000~6000元区间。用pyecharts做箱线图和散点图可以很直观地展示。
举个例子,分析"价格和评价数的关系",用Pandas分组聚合后绘制散点图:
import pandas as pd from pyecharts.charts import Scatter from pyecharts import options as opts def price_comment_scatter(df): data = df[["price", "comment_count"]].dropna() scatter = ( Scatter() .add_xaxis(data["price"].tolist()) .add_yaxis("评价数", data["comment_count"].tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="手机价格与评价数分布"), xaxis_opts=opts.AxisOpts(name="价格(元)", type_="value"), yaxis_opts=opts.AxisOpts(name="评价数", type_="log"), ) ) return scatter这里y轴用log刻度是为了避免少数爆款机型把其他散点压扁,细节决定图表质量。
5.2 品牌市占率与评论热度对比
用pyecharts的饼图或横向柱状图展示品牌数量占比,再用折线图叠加评论数均值,能回答"哪个品牌最受市场欢迎"。
from pyecharts.charts import Bar, Pie def brand_analysis(df): # 品牌机型数量TOP10 brand_count = df["brand"].value_counts().head(10) pie = ( Pie() .add("", [list(z) for z in zip(brand_count.index.tolist(), brand_count.values.tolist())]) .set_global_opts(title_opts=opts.TitleOpts(title="品牌机型占比")) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)")) ) return pie5.3 热门机型榜单与"性价比指数"
结合价格和评价数做一个"性价比指数":
性价比指数 = 评价数 / 价格这个指数反映"每一块钱能买到多少用户认可",数值越高说明越接近高性价比机型。计算后排名Top20,用柱状图展示。这个指标是答辩时的加分项,因为它体现了数据分析不只是统计,还有业务洞察。
df["cost_ratio"] = df["comment_count"] / df["price"] top_20 = df.nlargest(20, "cost_ratio")生成图表后,所有pyecharts实例通过render()输出HTML片段,交给Flask模板渲染。推荐使用Flask的render_template传入图表生成的HTML字符串,而不是走前后端分离的Ajax——后者会大幅增加前端工作量和复杂度。
6. 可视化大屏模块:Flask + pyecharts的整合实战
整个项目最终呈现在浏览器上的,是一个多图表联动的数据大屏。Flask整合pyecharts的两种方式,分为"后端生成HTML"和"前端异步加载"。毕业设计我推荐后端生成方式,逻辑直接,效果稳定。
6.1 后端生成图表并渲染
# app.py from flask import Flask, render_template from pyecharts import options as opts from pyecharts.charts import Bar, Pie, Scatter from models import PhoneProduct app = Flask(__name__) @app.route("/") def index(): # 从数据库读取数据 products = PhoneProduct.query.all() df = pd.DataFrame([{ "title": p.title, "price": p.price, "comment_count": p.comment_count, "brand": p.brand } for p in products]) # 生成各个图表 price_scatter = create_price_scatter(df) brand_pie = create_brand_pie(df) hot_bar = create_hot_bar(df) return render_template( "index.html", price_scatter=price_scatter.render_embed(), brand_pie=brand_pie.render_embed(), hot_bar=hot_bar.render_embed() ) if __name__ == "__main__": app.run(debug=True, port=5000)6.2 模板页面集成
在templates/index.html中,使用|safe过滤器渲染pyecharts生成的HTML片段:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>京东手机数据分析与推荐系统</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> </head> <body> <div class="container"> <h2>京东手机数据分析与推荐系统</h2> <div class="chart-item">{{ price_scatter | safe }}</div> <div class="chart-item">{{ brand_pie | safe }}</div> <div class="chart-item">{{ hot_bar | safe }}</div> </div> </body> </html>注意:必须引入ECharts的CDN,pyecharts生成的HTML默认依赖ECharts库。如果没引,图表会空白。这个坑很多新手踩过,写代码时记得。
6.3 Flask模板语法和pyecharts的相互作用
pyecharts渲染出来的render_embed()返回的是一段包含<div>和<script>的完整HTML,用Jinja2的safe过滤器才能让浏览器解析为DOM元素而不是文本。另外要注意:图表ID是pyecharts内部自动生成的,不需要手动处理。
6.4 连续图表间数据联动
如果想让图表之间有联动效果,比如点击品牌饼图,柱状图自动筛选该品牌机型,这就涉及前后端交互了。毕业设计如果时间不够,不建议做太复杂的联动,用三个独立图表展示即可。若想加点分,可以用Flask的/api/brand/<brand>接口配Ajax实现,但会引入明显的前端工作量,权衡取舍都行。
7. 推荐系统模块:相似度算法如何走出"花架子"
推荐系统是全项目最有区分度的模块。很多毕设项目打着"推荐"旗号,实则只是一个静态排行。要拿这个模块当论文亮点,至少要让推荐结果有两个特性:个性化和可解释性。
7.1 基于内容的相似度匹配
核心思想:根据用户输入"预算"和"偏好品牌",筛选候选商品,再用TF-IDF向量化 + 余弦相似度对商品标题计算推荐分。
步骤如下:
- 构建候选集:根据用户预算区间(如3000~4000元)过滤商品,并保留品牌偏好匹配的商品。
- 文本特征提取:对商品标题进行分词,构建TF-IDF向量。可以用
jieba分词。 - 向量相似度计算:把用户描述(如"拍照好 电池大 轻薄")转为同一维度向量,计算与每个候选商品标题的余弦相似度。
- 加权排序:在相似度的基础上叠加"性价比指数"作为加权因子,得到最终推荐分。
关键代码:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import jieba def compute_recommendations(user_desc, candidate_df, top_k=5): # 拼接用户描述和所有候选标题 texts = [user_desc] + candidate_df["title"].tolist() # jieba分词 + 空格连接 texts = [" ".join(jieba.cut(t)) for t in texts] vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform(texts) # 计算第一行(用户描述)与其余行的相似度 sims = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:]).flatten() # 结合性价比指数加权 candidate_df["sim_score"] = sims candidate_df["final_score"] = candidate_df["sim_score"] * 0.7 + candidate_df["cost_ratio"] * 0.3 result = candidate_df.nlargest(top_k, "final_score") return result7.2 引入DeepSeek生成推荐理由
纯相似度推荐的问题在于缺乏可解释性——用户只知道"推荐了这三个",不知道为什么。这里引入DeepSeek API:将推荐结果和用户需求作为提示词传给大模型,生成一段自然语言推荐理由。
调用逻辑:
import requests def generate_recommend_reason(user_desc, product_list): prompt = f"""用户需求:{user_desc} 候选手机: {chr(10).join([f"- {p['title']} (价格{p['price']}元,评价数{p['comment_count']})" for p in product_list])} 请用150字以内,从符合用户需求的角度,推荐其中1-2款手机,并说明理由。""" response = requests.post( url="https://api.deepseek.com/v1/chat/completions", headers={ "Authorization": f"Bearer {DEEPSEEK_API_KEY}", "Content-Type": "application/json" }, json={ "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.7, "max_tokens": 500 }, timeout=30 ) if response.status_code == 200: return response.json()["choices"][0]["message"]["content"] else: return "抱歉,推荐理由生成失败,以下是基于数据的客观推荐结果。"这里有几个实测要点:
7.2.1 API 429错误的处理
最近热词里反复出现exceeded retry limit, last status: 429 too many requests,说明很多同学在调用大模型API时踩了限流的坑。429代表请求频率超过接口限速,常见原因是循环调用或并发请求过猛。解决方案很简单:
import time import random def call_deepseek_with_retry(prompt, max_retries=3): for attempt in range(max_retries): try: return generate_recommend_reason(prompt) except Exception as e: if "429" in str(e) or attempt < max_retries - 1: wait_time = random.uniform(10, 20) * (attempt + 1) print(f"请求超频,{wait_time:.1f}秒后重试...") time.sleep(wait_time) else: raise raise RuntimeError("DeepSeek API 多次调用失败")另外一个更优雅的做法是在调用前先做本地缓存——用同样的用户需求+候选集,重复的推荐请求直接走缓存,不重复调API。这在答辩演示时也很有用,无论点多少次按钮都不会触发限流。
7.2.2 JSON返回结构的稳定性
DeepSeek的/v1/chat/completions接口返回结构稳定,取choices[0].message.content即可。但要注意设置max_tokens,否则模型有可能在生成中途截断,导致推荐理由不完整。
7.2.3 联网接口与离线兜底
答辩现场不能保证网络稳定。建议先跑通API逻辑,然后把生成结果缓存到本地JSON文件,演示时优先读缓存,接口不可用时自动切换兜底数据。这个设计细节既能体现工程素养,又能防翻车。
7.3 让推荐系统"不空转"的界面设计
推荐模块的入口是一个简单的表单:用户输入预算区间、选择偏好品牌(多选)、填写关键词(如"拍照 续航 轻薄"),点击提交后,后端完成候选集筛选、相似度计算、DeepSeek理由生成,前端展示推荐结果卡片。
这个交互闭环就是答辩讲故事的完整链路:用户输入 → 数据匹配 → 智能推荐 → 理由生成。每一步都有逻辑支撑,不会出现"怎么推荐的就是什么"的尴尬。
8. 完整项目实战:从零跑通全流程
这一节给一份可以直接照着操作的全流程清单。假设你在一台全新的Windows 10/11机器上,从安装Python到项目跑通,全流程约30分钟。
8.1 环境准备
1. 安装Python 3.9+(建议3.10或3.11,社区包兼容性最好) 2. 新建虚拟环境:python -m venv venv 3. 激活环境:venv\Scripts\activate 4. 安装依赖: pip install flask flask_sqlalchemy pandas requests pyecharts jieba scikit-learn注意:scikit-learn是TfidfVectorizer的依赖,如果只想跑基础版可以单独pip install scikit-learn。pyecharts版本建议1.x以上。
8.2 项目目录结构
phone-analysis-recommend/ ├── app.py # Flask主入口 ├── models.py # SQLAlchemy模型 ├── spider/ │ ├── jd_spider.py # 爬虫代码 │ └── clean_data.py # 数据清洗入库脚本 ├── analysis/ │ ├── statistics.py # 统计分析计算 │ └── charts.py # pyecharts图表生成 ├── recommend/ │ ├── content_based.py # 相似度推荐算法 │ └── deepseek_api.py # DeepSeek接口封装 ├── templates/ │ ├── index.html # 数据大屏页面 │ └── recommend.html # 推荐模块页面 ├── data/ │ └── phone.db # SQLite数据库 └── requirements.txt8.3 按顺序执行
# 第一步:运行爬虫,抓取数据存入SQLite python spider/jd_spider.py # 第二步:清洗入库 python spider/clean_data.py # 第三步:启动Flask python app.py然后打开浏览器访问http://127.0.0.1:5000,先看到数据大屏,再从导航进入推荐页面测试推荐功能。
8.4 如何扩数据量
如果一次抓取的商品数太少(比如只有几十条),分析图表会显得空。建议至少抓取6~10个品牌、每个品牌20~50款机型,总数据量控制在800~2000条。这个量级SQLite秒查,图表渲染也流畅。
如果不想等爬虫慢慢跑,也可以用requests请求京东的手机类目列表页,一次性批量抓取。但务必注意频率控制,宁慢勿快。
9. 论文与答辩:如何把项目"讲出彩"
代码写完只是第一步,论文和答辩是另一场硬仗。基于我带毕业设计的经验,这个题目的论文写作有几条关键主线:
9.1 论文结构建议
- 选题背景:从"电商数据分析的价值"切入,说明京东手机数据分析的意义,并点出传统静态分析缺乏个性化推荐的痛点。
- 关键技术综述:requests爬虫机制、Flask框架MVC模式、pyecharts可视化、TF-IDF余弦相似度、大模型API调用。
- 系统需求分析:功能需求(数据采集、数据清洗、数据分析、数据可视化、智能推荐)和非功能需求(响应时间、稳定性、易用性)。
- 系统详细设计:架构图 + 数据库设计 + 各模块接口设计。
- 系统实现与测试:贴关键代码 + 图表截图 + 推荐效果截图。
- 总结与展望:写"如何优化反爬策略、如何把推荐模型换成协同过滤或深度模型"。
9.2 答辩常见问题与应答思路
| 常见问题 | 应答思路 |
|---|---|
| "你的爬虫抓取的数据如何保证实时性?" | 答:当前是定时增量抓取,可以加APScheduler实现每日定时任务,保证数据新鲜度。 |
| "为什么用TF-IDF而非Word2Vec?" | 答:TF-IDF易于理解和复现,对短文本标题效果足够;Word2Vec需要大规模语料预训练,在小数据集上不稳定。 |
| "DeepSeek推荐和传统推荐有什么本质区别?" | 答:传统推荐输出商品ID列表,DeepSeek能生成结构化推荐理由,提升可解释性和用户体验。 |
| "你的系统在大数据量下的性能如何?" | 答:当前SQLite支撑万级数据查询毫秒级;如果数据量增长,可替换MySQL,并在查询上加上索引。 |
9.3 答辩演示避坑
现场演示最怕三件事:网络断了、环境缺包、数据没了。我的建议是准备一个"演示专用环境":
- 把Python虚拟环境装好并测试通过,答辩时不重新装包。
- 把数据库做一份快照,Switching到演示模式时根本不需要跑爬虫。
- DeepSeek接口用缓存结果兜底:如果现场API不通,直接读本地JSON展示推荐理由。
这些都是血泪教训,答辩前务必预演两遍。
10. 我踩过的那些坑,每个都值得你避开
最后集中复盘一下我自己完整跑通这个项目时踩过的坑,按严重程度排序。这些坑光看文档大概率遇不到,不写出来真的可惜。
10.1 pdfkit/environment相关的坑:千万别在Windows上折腾非主流库
有同学在爬虫清洗阶段需要把报告转PDF,引入了pdfkit或weasyprint,结果Windows上装wkhtmltopdf一路报错,白白浪费三个小时。毕业设计别碰这个方向,直接输出HTML或直接浏览器打印PDF,省心得多。
10.2 pyecharts版本兼容的坑
pyecharts 2.x和1.x的API有破坏性差异。如果照着网上老教程写,导入pyecharts.charts可能直接报错。统一使用pyecharts >= 2.0,并用官方最新API写图表代码。
10.3 Flask调试模式下重复执行代码的坑
app.run(debug=True)会启动werkzeug的重载器,意味着修改代码后服务会自动重启。如果爬虫或API调用写在全局层,可能会导致一次请求触发两次推荐。建议把所有耗时操作放在路由函数内部,不要放在模块顶层。
10.4 SQLite与Flask-SQLAlchemy的配置
数据库URI写成:
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///phone.db" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False这两个配置缺一不可。第二个设置不写会出现一堆警告,虽然不影响运行,答辩时被问到会减分。
10.5 时间字段的最佳实践
如果后续你想加"数据更新时间"、"爬取时间"等字段,建议直接用DateTime类型并设置默认值:
from datetime import datetime update_time = db.Column(db.DateTime, default=datetime.now)这里千万不要用datetime.now()做默认函数外的括号,否则是调用时固定时间,而不是每次插入时动态取当前时间。一个小括号的差别,坑得人怀疑人生。
这个题目从爬虫到可视化再到推荐系统,恰好是Python在"数据采集-数据治理-数据分析-智能应用"全链路能力的一次集中展示。如果你正为毕业设计选题发愁,这个方向性价比确实高。按上面的路线一步步走,快的话两周就能跑出完整效果,答辩时也完全不虚。