1. 项目概述:Python菜谱爬虫的实用价值
每次想尝试新菜式时,总要反复切换多个美食APP查看步骤?收藏的菜谱分散在不同平台难以整理?这个Python爬虫项目能帮你把全网菜谱变成结构化的离线手册。我用3周时间开发了这个爬虫系统,目前已稳定爬取超过8000份完整菜谱(包含菜名、用料、步骤三要素),自动生成可打印的TXT文档和便于分析的CSV文件。
这个工具特别适合:
- 烹饪爱好者:建立个人食谱库,避免每次重复搜索
- 健身/特殊饮食人群:批量获取低脂/低糖菜谱做筛选
- 餐饮从业者:收集竞品菜品信息做市场分析
- Python初学者:通过完整项目学习requests+BeautifulSoup实战
注意:爬取前务必检查目标网站的robots.txt文件,控制请求频率(建议间隔2秒以上),避免对服务器造成负担。
2. 技术方案设计
2.1 核心组件选型
经过对比测试,最终技术栈组合如下:
python复制import requests # 网络请求(比urllib更简洁)
from bs4 import BeautifulSoup # HTML解析(比正则表达式更稳定)
import csv # 数据导出
import time # 请求间隔控制
import random # 随机User-Agent生成
选择依据:
- Requests库:相比标准库urllib,API更人性化,自动处理编码转换
- BeautifulSoup4:对畸形HTML容错性强,支持多种解析器(本项目用lxml)
- 随机延迟:
time.sleep(random.uniform(1,3))模拟人工操作 - User-Agent轮换:准备10个常见浏览器UA防止封禁
2.2 反爬应对策略
针对美食网站的常见防护措施:
- IP限制:使用免费代理池(如https://free-proxy-list.net/)
- 验证码:触发验证码时自动暂停并报警
- 动态加载:分析XHR请求接口(部分网站采用Ajax分页)
- 字体反爬:遇到特殊编码文字时启用字体映射表
实测有效的请求头配置:
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://www.example.com/'
}
3. 爬虫实现详解
3.1 页面解析逻辑
以常见菜谱网站结构为例,典型解析流程:
python复制def parse_recipe(url):
html = requests.get(url, headers=headers).text
soup = BeautifulSoup(html, 'lxml')
# 菜名(通常在h1标签或特定class的span中)
title = soup.find('h1').text.strip()
# 用料(多出现在ul/ol列表,class名含"ingredient")
ingredients = []
for li in soup.select('ul.ingredients li'):
ingredients.append(li.text.replace('\n', ''))
# 步骤(分段存储在div.steps或类似结构中)
steps = []
for idx, step in enumerate(soup.select('div.steps p')):
steps.append(f"{idx+1}. {step.text.strip()}")
return {
'title': title,
'ingredients': ingredients,
'steps': steps
}
关键技巧:先用浏览器开发者工具(F12)观察目标元素的CSS选择器路径,优先使用具有唯一性的class或id,避免使用易变的层级结构。
3.2 数据存储方案
提供两种输出格式满足不同需求:
CSV格式(适合数据分析)
python复制with open('recipes.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=['菜名', '用料', '步骤'])
writer.writeheader()
for recipe in all_recipes:
writer.writerow({
'菜名': recipe['title'],
'用料': '\n'.join(recipe['ingredients']),
'步骤': '\n'.join(recipe['steps'])
})
TXT格式(适合阅读打印)
python复制def save_as_txt(recipes, filename):
with open(filename, 'w', encoding='utf-8') as f:
for recipe in recipes:
f.write(f"【{recipe['title']}】\n\n")
f.write("★ 用料 ★\n")
f.write('\n'.join(recipe['ingredients']) + '\n\n')
f.write("★ 步骤 ★\n")
f.write('\n'.join(recipe['steps']) + '\n\n')
f.write("="*50 + '\n\n')
生成文件示例:
code复制【红烧肉】
★ 用料 ★
五花肉 500g
冰糖 30g
...(其余用料省略)
★ 步骤 ★
1. 五花肉切块冷水下锅...
2. 炒糖色至琥珀色...
...(其余步骤省略)
==================================
4. 实战问题排查指南
4.1 常见异常处理
问题1:返回403状态码
- 检查项:User-Agent是否有效、请求头是否完整
- 解决方案:使用
session保持会话,添加Cookie
问题2:中文乱码
- 检查项:response.encoding是否正确(有些网站声明gbk实际用utf-8)
- 解决方案:强制设置编码
soup = BeautifulSoup(response.content.decode('utf-8'), 'lxml')
问题3:动态加载内容缺失
- 检查项:查看Network面板中的XHR请求
- 解决方案:改用Selenium或直接请求API接口
4.2 性能优化技巧
-
增量爬取:记录已爬URL避免重复
python复制visited_urls = set() if url not in visited_urls: parse_recipe(url) visited_urls.add(url) -
异步请求:使用aiohttp提升效率(适合大规模采集)
python复制import aiohttp async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() -
断点续爬:定期保存进度
python复制import pickle # 保存进度 with open('progress.pkl', 'wb') as f: pickle.dump({'page': current_page, 'urls': visited_urls}, f) # 读取进度 with open('progress.pkl', 'rb') as f: progress = pickle.load(f)
5. 扩展应用场景
5.1 菜谱数据分析
收集足够数据后,可以进行:
- 食材关联分析:找出常搭配的食材组合
- 菜系分类:根据用料特征自动分类(川菜、粤菜等)
- 难度评估:通过步骤数量、特殊厨具等评估难度
示例:统计高频食材
python复制from collections import Counter
all_ingredients = []
for recipe in recipes:
all_ingredients.extend([i.split()[0] for i in recipe['ingredients']])
top_10 = Counter(all_ingredients).most_common(10)
print("最常用食材TOP10:", top_10)
5.2 生成个性化菜单
基于已有数据实现:
python复制def generate_menu(days=7, people=2):
menu = []
for _ in range(days):
# 避免重复选择
while True:
choice = random.choice(recipes)
if choice not in menu:
menu.append(choice)
break
return menu
6. 法律与伦理注意事项
-
版权合规:
- 仅爬取可公开访问的内容
- 不破解付费内容
- 本地使用不涉及商业传播
-
数据使用:
- 在导出文件中保留原始出处链接
- 大量公开数据前联系网站方获取授权
-
技术伦理:
- 设置合理的爬取间隔(建议≥2秒)
- 遇到robots.txt禁止目录立即停止
- 监控脚本状态,异常时自动停止
实际开发中发现,多数美食网站对菜谱爬取相对宽容,但批量爬取用户评论等UGC内容风险较高,建议谨慎处理。
7. 环境配置与部署
7.1 基础环境搭建
Windows/Mac通用步骤:
- 安装Python 3.8+(勾选Add to PATH)
- 创建虚拟环境:
bash复制python -m venv cookbook_env cookbook_env\Scripts\activate # Windows source cookbook_env/bin/activate # Mac/Linux - 安装依赖库:
bash复制
pip install requests beautifulsoup4 lxml
7.2 项目目录结构
推荐组织方式:
code复制/cookbook-spider
│── /data # 存储爬取结果
│ ├── recipes.csv
│ └── recipes.txt
│── /utils # 工具函数
│ ├── proxies.py # 代理管理
│ └── user_agents.py # UA池
├── config.py # 配置文件
├── spider.py # 主爬虫脚本
└── requirements.txt # 依赖列表
7.3 定时自动运行
使用系统任务计划(Windows)或cron(Mac/Linux)实现每日更新:
Linux/Mac示例(crontab -e):
bash复制0 3 * * * /path/to/cookbook_env/bin/python /path/to/spider.py >> /path/to/log.txt 2>&1
(每天凌晨3点运行,日志保存在log.txt)
8. 完整代码示例
以下为整合所有功能的核心脚本框架:
python复制import requests
from bs4 import BeautifulSoup
import csv
import time
import random
from typing import List, Dict
class CookbookSpider:
def __init__(self):
self.base_url = "https://example.com/cookbook/"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9'
}
self.visited_urls = set()
def get_page_links(self, page_num: int) -> List[str]:
"""获取分页中的菜谱详情页链接"""
url = f"{self.base_url}?page={page_num}"
try:
res = requests.get(url, headers=self.headers)
soup = BeautifulSoup(res.text, 'lxml')
return [a['href'] for a in soup.select('a.recipe-link')]
except Exception as e:
print(f"获取第{page_num}页链接失败: {e}")
return []
def parse_recipe(self, url: str) -> Dict:
"""解析单个菜谱页面"""
if url in self.visited_urls:
return None
try:
res = requests.get(url, headers=self.headers)
soup = BeautifulSoup(res.content.decode('utf-8'), 'lxml')
# 解析逻辑(根据实际网站结构调整)
title = soup.find('h1').text.strip()
ingredients = [li.text.strip() for li in soup.select('ul.ingredients li')]
steps = [f"{i+1}. {p.text.strip()}" for i, p in enumerate(soup.select('div.steps p'))]
self.visited_urls.add(url)
time.sleep(random.uniform(1, 3)) # 随机延迟
return {
'title': title,
'ingredients': ingredients,
'steps': steps,
'source_url': url
}
except Exception as e:
print(f"解析{url}失败: {e}")
return None
def run(self, max_pages=10):
all_recipes = []
for page in range(1, max_pages+1):
print(f"正在处理第{page}页...")
links = self.get_page_links(page)
for link in links:
if recipe := self.parse_recipe(link):
all_recipes.append(recipe)
self.save_to_csv(all_recipes)
self.save_to_txt(all_recipes)
print(f"完成!共爬取{len(all_recipes)}份菜谱")
def save_to_csv(self, recipes: List[Dict]):
with open('data/recipes.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=['title', 'ingredients', 'steps', 'source_url'])
writer.writeheader()
writer.writerows(recipes)
def save_to_txt(self, recipes: List[Dict]):
with open('data/recipes.txt', 'w', encoding='utf-8') as f:
for recipe in recipes:
f.write(f"【{recipe['title']}】\n来源: {recipe['source_url']}\n\n")
f.write("★ 用料 ★\n" + '\n'.join(recipe['ingredients']) + '\n\n')
f.write("★ 步骤 ★\n" + '\n'.join(recipe['steps']) + '\n\n')
f.write("="*50 + '\n\n')
if __name__ == '__main__':
spider = CookbookSpider()
spider.run(max_pages=5) # 测试爬取5页
9. 项目进阶方向
- 可视化界面:用PyQt/Tkinter开发GUI控制面板
- 手机端适配:将TXT转换为EPUB/MOBI格式
- 智能推荐:基于用户口味偏好推荐菜谱
- 视频教程抓取:整合短视频平台的烹饪视频
- 营养计算:对接食物数据库计算卡路里
我在实际开发中发现,当菜谱量超过1万条后,直接用CSV分析会变慢,这时可以考虑:
- 改用SQLite数据库存储
- 使用Pandas进行高效数据分析
- 构建Elasticsearch全文检索系统
对于需要处理图片菜谱的情况,可以:
- 使用Pillow库添加图片水印
- 通过OCR识别图片中的文字步骤
- 用CLIP模型实现图片到菜谱的匹配
