1. 项目概述
最近在做一个食品营养分析的小工具,需要收集大量食品营养成分数据。市面上虽然有现成的数据库,但要么收费昂贵,要么数据不全。于是决定自己动手写个爬虫,从公开的食品营养网站上抓取数据。这个过程中积累了不少实战经验,今天就来分享下如何用Python构建一个稳定高效的食品营养成分爬虫系统。
2. 技术选型与准备
2.1 核心工具选择
经过对比测试,最终选择了以下工具链:
- Python 3.8+:语法简洁,社区支持完善
- Requests:处理HTTP请求
- BeautifulSoup4:HTML解析
- Pandas:数据清洗和存储
- Selenium:应对动态加载内容
提示:建议使用虚拟环境管理依赖,避免版本冲突
2.2 目标网站分析
以某食品营养数据库网站为例,分析其页面结构:
- 首页按食品类别分类
- 每类下有详细食品列表
- 每种食品有独立详情页,包含完整的营养成分表
关键发现:
- 数据通过后端API返回,前端渲染
- 分页采用异步加载
- 反爬措施包括:请求频率限制、User-Agent检测
3. 爬虫实现详解
3.1 基础爬取流程
python复制import requests
from bs4 import BeautifulSoup
def get_nutrition_data(food_id):
url = f"https://example.com/food/{food_id}"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
try:
response = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
# 解析营养成分表
nutrition_table = soup.find("table", class_="nutrition-facts")
data = {}
for row in nutrition_table.find_all("tr"):
cols = row.find_all("td")
if len(cols) == 2:
nutrient = cols[0].text.strip()
value = cols[1].text.strip()
data[nutrient] = value
return data
except Exception as e:
print(f"Error fetching {food_id}: {str(e)}")
return None
3.2 处理动态内容
对于需要JavaScript渲染的页面,使用Selenium:
python复制from selenium import webdriver
from selenium.webdriver.chrome.options import Options
def get_dynamic_page(url):
options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
try:
driver.get(url)
# 等待元素加载
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "nutrition-facts"))
)
html = driver.page_source
return html
finally:
driver.quit()
4. 数据清洗与存储
4.1 数据标准化
常见问题处理:
- 单位不一致(如mg/g)
- 特殊字符(如"<0.1")
- 缺失值处理
python复制def clean_nutrition_data(raw_data):
cleaned = {}
for nutrient, value in raw_data.items():
# 去除非数字字符
num_value = re.sub(r"[^\d.]", "", value)
if num_value:
cleaned[nutrient] = float(num_value)
else:
cleaned[nutrient] = 0.0
return cleaned
4.2 存储方案
根据数据量选择存储方式:
- 小规模:CSV文件
- 中规模:SQLite
- 大规模:MySQL/MongoDB
python复制import pandas as pd
def save_to_csv(data_list, filename):
df = pd.DataFrame(data_list)
df.to_csv(filename, index=False)
5. 反爬策略应对
5.1 常见反爬措施
- IP限制:使用代理池
- User-Agent检测:轮换UA
- 请求频率限制:添加随机延迟
- 验证码:使用打码平台或OCR
5.2 实战配置示例
python复制import random
import time
# 用户代理列表
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X)"
]
def get_with_retry(url, max_retries=3):
for i in range(max_retries):
try:
headers = {"User-Agent": random.choice(USER_AGENTS)}
response = requests.get(url, headers=headers,
timeout=10,
proxies={"http": "http://proxy.example.com:8080"})
time.sleep(random.uniform(1, 3)) # 随机延迟
return response
except Exception as e:
print(f"Attempt {i+1} failed: {str(e)}")
time.sleep(2 ** i) # 指数退避
return None
6. 项目优化建议
6.1 性能优化
- 使用aiohttp实现异步请求
- 建立本地缓存避免重复请求
- 分布式爬虫架构
6.2 数据质量保障
- 设置数据校验规则
- 定期更新数据
- 建立异常值检测机制
7. 常见问题解决
7.1 图片文字处理
当营养成分数据以图片形式呈现时:
- 使用OCR技术识别(如Tesseract)
- 寻找替代数据源
- 联系网站获取API权限
python复制import pytesseract
from PIL import Image
def ocr_image(img_path):
img = Image.open(img_path)
text = pytesseract.image_to_string(img)
return text
7.2 登录会话维持
对于需要登录的网站:
python复制session = requests.Session()
login_data = {
"username": "your_username",
"password": "your_password"
}
session.post("https://example.com/login", data=login_data)
# 后续使用session保持登录状态
response = session.get("https://example.com/protected_page")
8. 完整项目架构
建议的项目目录结构:
code复制food-nutrition-crawler/
├── crawler/ # 爬虫核心代码
│ ├── __init__.py
│ ├── base.py # 基础爬虫类
│ ├── sites/ # 各网站具体实现
│ └── utils.py # 工具函数
├── config/ # 配置文件
│ ├── proxies.yaml
│ └── user_agents.yaml
├── data/ # 数据存储
│ ├── raw/ # 原始数据
│ └── processed/ # 清洗后数据
├── requirements.txt # 依赖文件
└── main.py # 入口文件
9. 法律与道德考量
- 遵守robots.txt协议
- 控制请求频率
- 不抓取敏感数据
- 注明数据来源
重要:商业使用前务必咨询法律意见
10. 扩展应用方向
收集到的数据可用于:
- 营养分析应用开发
- 饮食建议系统
- 食品成分研究
- 健康管理平台
在实际项目中,我发现设置合理的请求间隔和使用可靠的代理服务是保证爬虫稳定运行的关键。另外,定期备份数据可以避免意外丢失。对于需要长期运行的项目,建议添加监控和报警机制,及时发现并处理异常情况。
