1. 项目概述
今天要分享的是一个实用又有趣的Python爬虫项目——从美食网站抓取完整菜谱数据并生成离线手册。这个项目特别适合Python初学者练手,也适合美食爱好者搭建自己的私人食谱库。我们将使用Python 3.8+环境,通过requests和BeautifulSoup这两个经典库来实现网页抓取和解析。
这个爬虫会完整抓取每道菜的三大要素:菜名、用料清单和详细步骤说明。最终数据会保存为CSV和TXT两种格式,CSV适合用Excel打开查看和筛选,TXT则方便打印或导入到其他APP。我曾用这个脚本为自己整理了2000+道家常菜谱,现在出门旅游都会带着这份离线手册。
2. 核心需求解析
2.1 目标网站分析
选择目标网站时需要考虑几个关键因素:
- 网站结构是否规整,便于批量抓取
- 是否有反爬机制
- 菜谱信息是否完整规范
经过对比测试,我发现"下厨房"和"美食天下"这类专业美食社区最合适。它们的页面结构清晰,菜谱都包含标准化的用料表和步骤说明。以"下厨房"为例,每个菜谱页面的HTML结构中:
- 菜名位于
<h1 class="page-title">标签 - 用料表在
<div class="ings">下的<tr>列表 - 步骤说明在
<div class="steps">的<li>项中
2.2 技术选型理由
选择requests+BeautifulSoup组合是因为:
- requests比urllib更简洁易用,自动处理编码转换
- BeautifulSoup的HTML解析API直观,学习曲线平缓
- 两者组合足以应对静态页面的抓取需求
- 社区资源丰富,遇到问题容易找到解决方案
对于动态加载内容的网站,可以考虑加入selenium,但会增加复杂度。作为入门项目,我们优先选择静态页面抓取方案。
3. 开发环境准备
3.1 Python环境配置
推荐使用Python 3.8或更高版本。安装时务必勾选"Add Python to PATH"选项,这样可以直接在命令行使用python命令。验证安装:
bash复制python --version
pip --version
3.2 必要库安装
通过pip安装项目依赖:
bash复制pip install requests beautifulsoup4 pandas
- requests:网络请求库
- beautifulsoup4:HTML解析库
- pandas:数据处理和CSV导出
3.3 开发工具建议
VS Code是不错的选择,安装Python扩展后:
- 创建项目文件夹
- 新建
crawler.py文件 - 配置Python解释器路径(Ctrl+Shift+P → Python: Select Interpreter)
4. 爬虫核心实现
4.1 网页请求与响应处理
基础请求代码框架:
python复制import requests
from bs4 import BeautifulSoup
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
def get_recipe(url):
try:
response = requests.get(url, headers=headers)
response.raise_for_status() # 检查请求是否成功
response.encoding = response.apparent_encoding # 自动识别编码
return response.text
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None
关键点说明:
- 必须设置User-Agent模拟浏览器访问
raise_for_status()会在状态码非200时抛出异常apparent_encoding比直接指定编码更可靠
4.2 数据解析实现
解析函数示例:
python复制def parse_recipe(html):
soup = BeautifulSoup(html, 'html.parser')
# 提取菜名
name = soup.find('h1', class_='page-title').get_text().strip()
# 提取用料
ingredients = []
for item in soup.select('div.ings tr'):
name = item.select_one('td.name').get_text().strip()
amount = item.select_one('td.amount').get_text().strip()
ingredients.append(f"{name} {amount}")
# 提取步骤
steps = []
for i, step in enumerate(soup.select('div.steps li'), 1):
desc = step.get_text().strip()
steps.append(f"步骤{i}: {desc}")
return {
'name': name,
'ingredients': '\n'.join(ingredients),
'steps': '\n'.join(steps)
}
解析技巧:
- 使用CSS选择器比遍历DOM更简洁
get_text()会自动合并多个空格strip()去除首尾空白字符- 给步骤添加序号提高可读性
4.3 数据存储实现
存储为CSV和TXT:
python复制import pandas as pd
import os
def save_to_csv(recipes, filename):
df = pd.DataFrame(recipes)
df.to_csv(filename, index=False, encoding='utf-8-sig')
def save_to_txt(recipes, folder):
if not os.path.exists(folder):
os.makedirs(folder)
for recipe in recipes:
filename = f"{folder}/{recipe['name']}.txt"
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"菜名: {recipe['name']}\n\n")
f.write("用料:\n")
f.write(recipe['ingredients'] + "\n\n")
f.write("步骤:\n")
f.write(recipe['steps'] + "\n")
存储注意事项:
- CSV使用
utf-8-sig编码避免Excel乱码 - TXT文件按菜名单独保存,方便查找
- 创建目录前检查是否存在
5. 完整爬虫流程
5.1 主程序逻辑
python复制def main():
base_url = "https://www.xiachufang.com"
start_url = f"{base_url}/category/40076/" # 家常菜分类
# 获取分类页列表
html = get_recipe(start_url)
soup = BeautifulSoup(html, 'html.parser')
recipe_links = [base_url + a['href'] for a in soup.select('div.recipe a')]
# 遍历每个菜谱页
recipes = []
for link in recipe_links[:10]: # 测试先抓10个
print(f"正在处理: {link}")
recipe_html = get_recipe(link)
if recipe_html:
recipe = parse_recipe(recipe_html)
recipes.append(recipe)
# 保存结果
save_to_csv(recipes, "recipes.csv")
save_to_txt(recipes, "recipes_txt")
print("抓取完成!")
if __name__ == '__main__':
main()
5.2 分页处理技巧
要抓取更多菜谱,需要处理分页:
python复制def get_all_recipe_links(base_url, category_url):
links = []
page = 1
while True:
url = f"{category_url}?page={page}"
html = get_recipe(url)
if not html:
break
soup = BeautifulSoup(html, 'html.parser')
new_links = [base_url + a['href'] for a in soup.select('div.recipe a')]
if not new_links:
break
links.extend(new_links)
page += 1
time.sleep(1) # 礼貌性延迟
return links
分页注意事项:
- 添加适当延迟避免被封
- 检查是否还有下一页
- 记录已抓取的URL避免重复
6. 高级优化技巧
6.1 反爬应对策略
- 随机User-Agent:
python复制from fake_useragent import UserAgent
ua = UserAgent()
headers = {'User-Agent': ua.random}
- 代理IP池:
python复制proxies = {
'http': 'http://proxy_ip:port',
'https': 'https://proxy_ip:port'
}
response = requests.get(url, headers=headers, proxies=proxies)
- 请求间隔随机化:
python复制import random
time.sleep(random.uniform(0.5, 2))
6.2 异常处理增强
健壮性改进:
python复制def safe_get_text(element, default=""):
return element.get_text().strip() if element else default
def parse_recipe(html):
try:
soup = BeautifulSoup(html, 'html.parser')
# 使用safe_get_text替代直接get_text
name = safe_get_text(soup.find('h1', class_='page-title'))
# ...其余解析逻辑
except Exception as e:
print(f"解析失败: {e}")
return None
6.3 增量抓取实现
记录已抓取的URL:
python复制import json
def load_crawled_urls(filename="crawled.json"):
try:
with open(filename, 'r') as f:
return set(json.load(f))
except:
return set()
def save_crawled_urls(urls, filename="crawled.json"):
with open(filename, 'w') as f:
json.dump(list(urls), f)
使用方式:
python复制crawled = load_crawled_urls()
new_links = [url for url in all_links if url not in crawled]
# ...抓取new_links...
crawled.update(new_links)
save_crawled_urls(crawled)
7. 常见问题与解决方案
7.1 编码问题
症状:抓取的中文显示为乱码
解决方法:
- 确保response.encoding正确设置
- 存储时使用utf-8-sig编码
- 检查编辑器/查看器的编码设置
7.2 元素定位失败
症状:soup.select()返回空列表
排查步骤:
- 检查网页结构是否已更新
- 确认CSS选择器是否正确
- 查看网页是否有动态加载内容
7.3 请求被拒绝
症状:返回403状态码
应对措施:
- 更换User-Agent
- 添加Referer头
- 使用代理IP
- 降低请求频率
7.4 数据清洗问题
常见脏数据:
- 用量单位不统一(克/g/gram)
- 步骤中的特殊符号
- 多余的空格和换行
清洗函数示例:
python复制def clean_text(text):
text = re.sub(r'\s+', ' ', text) # 合并空白字符
text = re.sub(r'[^\w\s\u4e00-\u9fff]', '', text) # 去除非中英文符号
return text.strip()
8. 项目扩展方向
8.1 数据可视化分析
使用matplotlib分析:
python复制import matplotlib.pyplot as plt
# 统计常用食材
ingredient_counts = Counter()
for recipe in recipes:
for ing in recipe['ingredients'].split('\n'):
name = ing.split()[0]
ingredient_counts[name] += 1
top10 = ingredient_counts.most_common(10)
plt.barh([x[0] for x in top10], [x[1] for x in top10])
plt.title("最常用食材Top10")
plt.show()
8.2 转为Web应用
使用Flask搭建简单网站:
python复制from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
df = pd.read_csv('recipes.csv')
return render_template('index.html', recipes=df.to_dict('records'))
8.3 移动端适配
生成电子书格式:
- 使用pandoc将TXT转为EPUB
- 用Kindle Comic Converter制作MOBI格式
- 通过Calibre管理电子书库
9. 最终成果展示
运行脚本后,你将得到:
-
recipes.csv- 所有菜谱的结构化数据- 列名:name,ingredients,steps
- 可直接用Excel打开筛选
-
recipes_txt/目录 - 每个菜谱单独的TXT文件- 命名格式:菜名.txt
- 内容包含完整用料和步骤
示例TXT文件内容:
code复制菜名: 红烧肉
用料:
五花肉 500g
冰糖 20g
生抽 2勺
老抽 1勺
...
步骤:
步骤1: 五花肉切块焯水
步骤2: 炒糖色至琥珀色
...
10. 实际应用建议
- 定期运行脚本更新菜谱库
- 将TXT文件同步到手机或电纸书
- 使用Everything等工具快速搜索
- 按食材筛选CSV找到可用菜谱
- 打印常用菜谱贴厨房
我在实际使用中发现,把每周要做的菜谱单独保存为一个"本周菜单.txt",然后投影到厨房墙面上特别实用。另外,将菜谱按类型(荤菜/素菜/汤羹)分类存储,方便快速查找搭配。
