1. 项目背景与核心价值
这个毕业设计选题完美融合了当下最热门的几个技术方向:数据采集、机器学习、可视化呈现。作为一个完整的数据科学项目闭环,它涵盖了从原始数据获取到最终价值呈现的全流程。我在实际开发过程中发现,这类项目最能锻炼计算机专业学生的工程化思维——你需要考虑的不仅是算法实现,更重要的是如何让各个环节无缝衔接。
美食数据领域有几个独特的优势:首先,数据源丰富且开放,各大菜谱网站、美食社区、外卖平台都提供了大量结构化程度较高的数据;其次,数据维度丰富,包含食材、做法、营养成分、用户评价等多维度信息;最重要的是,可视化呈现效果直观,非常适合作为毕业设计的展示载体。
2. 技术架构设计
2.1 整体技术栈选型
基于项目需求和技术成熟度,我最终确定的技术栈组合是:
- 后端:Django + Django REST Framework
- 数据采集:Scrapy + Selenium
- 数据分析:Pandas + NumPy
- 机器学习:Scikit-learn
- 可视化:ECharts + Superset
这个组合有几个关键考虑:Django提供了完善的后台管理界面,特别适合快速开发数据管理功能;Scrapy的分布式特性可以应对大规模数据抓取;而Superset的开源特性则免去了我们从零开发可视化界面的麻烦。
2.2 数据库设计要点
针对美食数据的特点,我设计了以下几个核心表:
- 菜谱表(Recipe):存储菜品名称、做法、难度等基本信息
- 食材表(Ingredient):记录各类食材及其营养数据
- 用户表(User):保存注册用户信息
- 评价表(Review):存储用户对菜品的评分和评论
特别要注意的是食材与菜谱的多对多关系,这里我使用了Django的ManyToManyField并通过through参数添加了"用量"这一额外字段:
python复制class Recipe(models.Model):
ingredients = models.ManyToManyField(
Ingredient,
through='RecipeIngredient',
through_fields=('recipe', 'ingredient'),
)
class RecipeIngredient(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
amount = models.CharField(max_length=50) # 如"100g"
3. 数据采集实现
3.1 爬虫工程实践
在爬取美食网站时,我遇到了几个典型问题及解决方案:
- 反爬应对:通过设置DOWNLOAD_DELAY=2和CONCURRENT_REQUESTS=5来降低请求频率,同时使用随机User-Agent:
python复制USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...'
]
class RandomUserAgentMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(USER_AGENTS)
- 动态内容加载:对于依赖JavaScript渲染的页面,使用Selenium配合ChromeDriver:
python复制from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
driver.get(url)
html = driver.page_source
- 数据清洗:使用Pandas进行缺失值处理和格式标准化:
python复制df = pd.read_json('recipes_raw.json')
df['cook_time'] = df['cook_time'].fillna('未知')
df['difficulty'] = df['difficulty'].map({
'简单': 'easy',
'中等': 'medium',
'困难': 'hard'
})
重要提示:务必遵守robots.txt协议,设置合理的爬取间隔,避免对目标网站造成过大压力。我在测试时曾因请求过于频繁导致IP被封,后来通过使用代理池解决了这个问题。
4. 数据分析与机器学习应用
4.1 菜品推荐算法实现
基于用户历史行为数据,我实现了两种推荐方式:
- 基于内容的推荐:使用TF-IDF向量化菜品描述文本,计算余弦相似度:
python复制from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
tfidf = TfidfVectorizer(stop_words='chinese')
tfidf_matrix = tfidf.fit_transform(recipes['description'])
cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)
- 协同过滤:使用Surprise库实现基于用户的协同过滤:
python复制from surprise import Dataset, KNNBasic
data = Dataset.load_from_df(ratings[['user_id', 'recipe_id', 'rating']],
reader=Reader(rating_scale=(1, 5)))
trainset = data.build_full_trainset()
algo = KNNBasic()
algo.fit(trainset)
4.2 营养分析模块
通过分析食材营养成分数据,可以计算每道菜的热量和主要营养素含量。这里的关键是建立食材与标准营养数据的映射关系:
python复制def calculate_nutrition(recipe_id):
ingredients = RecipeIngredient.objects.filter(recipe_id=recipe_id)
total_calories = 0
for item in ingredients:
nutrient = item.ingredient.nutrient_set.first()
amount = parse_amount(item.amount) # 将"100g"转换为数字
total_calories += nutrient.calories * amount / 100
return total_calories
5. 数据可视化实现
5.1 使用ECharts构建交互式图表
在前端展示中,我主要使用了ECharts来实现以下可视化效果:
- 菜品热度热力图:展示不同时段、不同菜品的受欢迎程度
javascript复制option = {
tooltip: {...},
grid: {...},
xAxis: {type: 'category', data: ['周一',...]},
yAxis: {type: 'category', data: ['宫保鸡丁',...]},
visualMap: {min: 0, max: 100, ...},
series: [{
type: 'heatmap',
data: [[0,0,62],...],
...
}]
}
- 营养成分雷达图:对比不同菜品的营养构成
javascript复制option = {
radar: {
indicator: [
{name: '热量', max: 500},
{name: '蛋白质', max: 30},
...
]
},
series: [{
type: 'radar',
data: [
{value: [356, 12.8, ...], name: '宫保鸡丁'},
...
]
}]
}
5.2 集成Superset实现仪表盘
对于需要更复杂分析的场景,我集成了Superset来创建综合仪表盘:
- 配置数据源连接Django的数据库
- 创建包含多个图表的工作表
- 设置自动刷新间隔实现准实时监控
Superset的强大之处在于可以直接编写SQL查询并快速生成可视化:
sql复制SELECT
r.name AS recipe_name,
AVG(rev.rating) AS avg_rating,
COUNT(rev.id) AS review_count
FROM
recipes_recipe r
LEFT JOIN
recipes_review rev ON r.id = rev.recipe_id
GROUP BY
r.name
ORDER BY
avg_rating DESC
LIMIT 10
6. 项目部署与优化
6.1 性能优化实践
随着数据量增长,我遇到了几个性能瓶颈及解决方案:
-
数据库查询优化:
- 为常用查询字段添加索引
- 使用select_related和prefetch_related减少查询次数
python复制recipes = Recipe.objects.select_related('category')\ .prefetch_related('ingredients')\ .filter(is_published=True) -
缓存策略:
- 使用Redis缓存热门菜品的计算结果
python复制from django.core.cache import cache def get_popular_recipes(): key = 'popular_recipes' result = cache.get(key) if not result: result = calculate_popular_recipes() # 耗时计算 cache.set(key, result, timeout=3600) return result
6.2 部署方案
项目最终采用Docker Compose部署,包含以下服务:
- Web应用(Django + Gunicorn)
- Redis缓存
- Celery异步任务队列
- PostgreSQL数据库
- Superset可视化平台
对应的docker-compose.yml关键配置:
yaml复制services:
web:
build: .
command: gunicorn core.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- redis
- db
redis:
image: redis:alpine
ports:
- "6379:6379"
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
7. 项目扩展方向
在实际开发过程中,我发现这个项目还有几个值得深入的方向:
- 用户个性化推荐增强:引入深度学习模型如NeuralCF来处理更复杂的用户行为模式
- 食材价格监控:定期爬取电商平台数据,分析食材价格波动趋势
- 移动端适配:开发React Native应用,提供扫码识菜功能
- 语音交互功能:集成语音识别,实现语音搜索菜谱
一个特别实用的扩展是开发"智能冰箱"对接功能——通过扫描冰箱内现有食材,自动推荐可制作的菜品。这个功能需要解决的关键技术点包括:
- 食材图像识别(可使用预训练的ResNet模型)
- 食材库存管理
- 基于现有食材的实时推荐算法
我在测试这个功能时发现,准确识别不同状态的食材(如切好的和未处理的)是个挑战,后来通过增加本地训练数据提高了识别准确率。
