外卖平台已经成为现代城市生活的基础设施,每天产生海量的商家信息和用户评价数据。这些数据背后隐藏着巨大的商业价值,但获取过程也面临诸多技术挑战。
从商业角度看,完整的外卖数据可以帮助我们:
但实际操作中会遇到几个典型技术难题:
重要提示:任何数据采集行为都必须遵守《网络安全法》和平台用户协议,仅限采集完全公开的非隐私数据。
美团/饿了么开放平台提供标准API接口,这是最合规稳定的选择。以美团商家开放平台为例:
申请流程:
典型接口示例:
python复制# 获取店铺基础信息
GET https://openapi.meituan.com/poi/query
params = {
'appkey': 'your_appkey',
'sign': 'generated_signature',
'poiIds': '123456,789012'
}
优势:
局限:
对于需要采集竞品数据的场景,技术爬虫是更灵活的选择。现代外卖平台普遍采用以下防护措施:
反爬特征:
__mta字段)sign字段)推荐技术栈组合:
python复制# 基础请求库
pip install requests playwright
# 数据处理
pip install pandas jmespath
# 代理管理
pip install redis hiredis
核心代码结构示例:
python复制import asyncio
from playwright.async_api import async_playwright
async def fetch_shop_data(shop_id):
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 设置真实浏览器指纹
await page.set_extra_http_headers({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36...'
})
# 控制加载节奏
await page.goto(f'https://meituan.com/shop/{shop_id}',
timeout=15000,
wait_until='networkidle')
# 等待关键元素出现
await page.wait_for_selector('.review-list')
# 提取结构化数据
data = await page.evaluate('''() => {
return {
name: document.querySelector('.shop-name').innerText,
rating: parseFloat(document.querySelector('.score').innerText),
monthlySales: parseInt(document.querySelector('.sale').innerText.match(/\d+/)[0])
}
}''')
await browser.close()
return data
对于非技术团队,可以考虑这些合规工具:
| 工具名称 | 特点 | 适用场景 | 价格区间 |
|---|---|---|---|
| 八爪鱼 | 可视化配置 | 固定格式数据采集 | ¥299-999/月 |
| 火车采集器 | 规则丰富 | 复杂页面抓取 | ¥599-1999/年 |
| 简数采集 | API支持 | 与内部系统对接 | 按量计费 |
使用建议:
当数据量较小时(<100家店铺),可以结合浏览器插件提高效率:
推荐组合:
操作流程:
典型采集字段清单:
商家基础信息
运营数据
用户评价
字段设计原则:
extend_info)分级防护应对方案:
| 防护级别 | 特征 | 解决方案 | 成本 |
|---|---|---|---|
| 初级 | User-Agent检测 | 轮换UA池 | 低 |
| 中级 | IP频率限制 | 代理IP池(建议5-10IP/分钟) | 中 |
| 高级 | 行为验证码 | 打码平台接入(约¥0.01/次) | 高 |
| 特级 | 参数签名 | 逆向JS分析 | 极高 |
推荐代理IP服务商:
实测数据:相同请求频率下,使用优质代理IP可以将成功率从32%提升至89%
常见数据问题及处理方法:
乱码问题
åè´§é度等乱码python复制text.encode('raw_unicode_escape').decode('utf-8')
单位标准化
python复制def normalize_sales(text):
if '万' in text:
return int(float(text.replace('万','')) * 10000)
return int(text)
评价情感分析
python复制from textblob import TextBlob
def get_sentiment(text):
analysis = TextBlob(text)
return analysis.sentiment.polarity # -1到1之间
根据数据规模选择存储方式:
| 数据量 | 推荐方案 | 优点 | 缺点 |
|---|---|---|---|
| <1万条 | CSV文件 | 无需环境依赖 | 查询效率低 |
| 1-50万 | SQLite | 单文件管理 | 并发性能弱 |
| >50万 | MySQL集群 | 支持复杂查询 | 维护成本高 |
| 非结构化 | MongoDB | 灵活schema | 占用空间大 |
创建MySQL表的示例:
sql复制CREATE TABLE `shop_info` (
`id` BIGINT PRIMARY KEY,
`name` VARCHAR(100) NOT NULL,
`avg_price` DECIMAL(10,2),
`monthly_sales` INT,
`rating` DECIMAL(3,1),
`address` VARCHAR(200),
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FULLTEXT INDEX `ft_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
现象:返回403状态码或验证码页面
排查步骤:
python复制import requests
resp = requests.get('http://httpbin.org/ip', proxies=proxy)
print(resp.json())
常见原因:
python复制# 不推荐
selector = 'div.content > ul > li:nth-child(3)'
# 推荐
selector = '[class*="review-item"]'
python复制await page.wait_for_function('''() => {
return document.querySelectorAll('.review-list li').length > 5
}''')
当触发验证码时,可以考虑:
行为模拟方案
python复制await page.mouse.move(x1, y1)
await page.mouse.down()
await page.mouse.move(x2, y2, steps=50)
await page.mouse.up()
第三方打码平台
python复制import chaojiying
cjy = chaojiying.ChaojiyingClient('user', 'pass', 'softid')
im = open('captcha.jpg', 'rb').read()
result = cjy.post_pic(im, 9004) # 9004为验证码类型
大规模采集时的优化建议:
异步并发控制
python复制import asyncio
from aiohttp import ClientSession
async def fetch(url, session):
async with session.get(url) as resp:
return await resp.text()
async def run(urls):
tasks = []
async with ClientSession() as session:
for url in urls:
task = asyncio.create_task(fetch(url, session))
tasks.append(task)
return await asyncio.gather(*tasks)
缓存已采集数据
python复制import sqlite3
def init_cache():
conn = sqlite3.connect('cache.db')
conn.execute('''CREATE TABLE IF NOT EXISTS shops
(id TEXT PRIMARY KEY, data TEXT)''')
return conn
断点续采机制
python复制def get_checkpoint():
try:
with open('checkpoint.txt') as f:
return int(f.read())
except:
return 0
def save_checkpoint(page):
with open('checkpoint.txt', 'w') as f:
f.write(str(page))
绝对禁止行为:
建议做法:
合法应用场景:
需避免行为:
建议增加:
python复制def anonymize(text):
return re.sub(r'\d{3}\*\*\d{4}', '[PHONE]', text)
在实际项目中,我们团队采用分级采集策略:核心数据通过官方API获取,补充数据用自研爬虫采集,最终通过数据清洗确保质量。一个典型的地域竞品分析项目(覆盖500家店铺)大约需要3天时间完成全量数据采集,成本控制在2000元以内。