1. 项目背景与核心价值
商业地理大数据正在成为企业选址、竞品分析和市场预测的关键依据。美团和大众点评作为本地生活服务的头部平台,积累了海量商户信息、用户评价和消费数据,这些数据对于零售、餐饮、地产等行业具有极高的商业价值。
传统爬虫方案在面对这类现代Web应用时往往力不从心。反爬机制日益复杂,特别是美团和大众点评这类平台普遍采用动态渲染、行为验证和IP封锁等防护手段。单机爬虫不仅效率低下,而且容易被识别封锁。
我们设计的这套系统结合了Playwright的浏览器自动化能力和Asyncio的异步并发特性,实现了分布式架构下的高效数据采集。与常见的Scrapy+Requests方案相比,本系统具有三个显著优势:
- 真实浏览器环境:Playwright可以模拟人类操作行为,有效绕过瑞数等动态验证码系统
- 资源高效利用:异步IO模型让单个节点可以同时管理多个浏览器实例
- 弹性扩展能力:分布式架构轻松应对千万级数据采集需求
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 整体架构图
code复制[分布式调度节点] ←→ [消息队列] ←→ [多个爬虫节点]
↑ ↑
[任务管理界面] [数据存储集群]
2.2 核心组件选型
浏览器自动化层:
- Playwright(优于Selenium的选择):
- 支持Chromium/Firefox/WebKit三大引擎
- 内置自动等待机制,减少时序问题
- 可以拦截和修改网络请求
- 提供移动设备模拟功能
异步编程层:
- Python Asyncio:
- 原生协程支持(async/await语法)
- 高性能事件循环机制
- 与Playwright API完美集成
分布式协调层:
- Redis:
- 分布式锁实现(RedLock算法)
- 消息队列功能
- 任务状态缓存
存储层:
- MongoDB:
- 灵活的模式设计适应多变页面结构
- 内置地理空间索引
- 高吞吐写入能力
3. 关键实现细节
3.1 Playwright高级对抗策略
瑞数验证码破解方案:
python复制async def bypass_anti_spider(page):
# 设置真实的浏览器指纹
await page.emulate_media(color_scheme="dark")
await page.set_viewport_size({"width": 1920, "height": 1080})
# 注入随机鼠标移动轨迹
await page.mouse.move(random.randint(100,500), random.randint(100,500))
# 模拟人类输入速度
await page.type("#search-input", "火锅", delay=random.uniform(50, 150))
# 随机滚动页面
await page.evaluate(f"window.scrollBy(0, {random.randint(200,800)})")
动态iframe处理技巧:
python复制# 等待iframe加载并切换上下文
frame = await page.wait_for_selector("iframe[name='dynamic-content']")
frame = await frame.content_frame()
# 在iframe内执行操作
await frame.click(".rating-filter")
3.2 分布式任务调度
任务分片算法:
python复制def generate_geo_grid(start_lat, start_lng, end_lat, end_lng, precision):
"""将地理区域划分为网格单元"""
grids = []
lat_step = (end_lat - start_lat)/precision
lng_step = (end_lng - start_lng)/precision
for i in range(precision):
for j in range(precision):
grids.append({
'min_lat': start_lat + i*lat_step,
'max_lat': start_lat + (i+1)*lat_step,
'min_lng': start_lng + j*lng_step,
'max_lng': start_lng + (j+1)*lng_step
})
return grids
分布式锁实现:
python复制class RedisLock:
def __init__(self, redis_conn, lock_name, timeout=30):
self.redis = redis_conn
self.lock_name = lock_name
self.timeout = timeout
self.identifier = str(uuid.uuid4())
async def acquire(self):
end = time.time() + 10
while time.time() < end:
if await self.redis.set(
self.lock_name,
self.identifier,
ex=self.timeout,
nx=True
):
return True
await asyncio.sleep(0.1)
return False
async def release(self):
script = """
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
"""
await self.redis.eval(script, 1, self.lock_name, self.identifier)
4. 性能优化实战
4.1 浏览器实例管理池
python复制class BrowserPool:
def __init__(self, max_browsers=5):
self.semaphore = asyncio.Semaphore(max_browsers)
self.browsers = []
async def get_browser(self):
await self.semaphore.acquire()
if self.browsers:
return self.browsers.pop()
browser = await playwright.chromium.launch(
headless=True,
args=[
'--disable-blink-features=AutomationControlled',
'--no-sandbox'
]
)
return browser
async def release_browser(self, browser):
self.browsers.append(browser)
self.semaphore.release()
4.2 智能请求调度算法
python复制def calculate_delay(last_response_time):
"""根据服务器响应时间动态调整请求间隔"""
base_delay = 2.0 # 基础延迟
sensitivity = 0.5 # 响应时间敏感系数
if last_response_time < 1.0:
return base_delay * 0.8
elif 1.0 <= last_response_time < 3.0:
return base_delay
else:
return base_delay * (1 + sensitivity * (last_response_time - 3.0))
5. 数据分析应用
5.1 商户热度指数计算
python复制def calculate_hot_index(shop_data):
"""
综合评分 =
(评价数量标准化值 × 0.4) +
(平均评分标准化值 × 0.3) +
(人均消费标准化值 × 0.2) +
(收藏量标准化值 × 0.1)
"""
scaler = StandardScaler()
features = scaler.fit_transform([
[d['review_count'], d['avg_rating'], d['per_capita'], d['favorite_count']]
for d in shop_data
])
return [0.4*f[0] + 0.3*f[1] + 0.2*f[2] + 0.1*f[3] for f in features]
5.2 地理热力图生成
python复制def generate_heatmap(geo_data):
m = folium.Map(location=[geo_data[0]['lat'], geo_data[0]['lng']], zoom_start=12)
heat_data = [[d['lat'], d['lng'], d['weight']] for d in geo_data]
HeatMap(
heat_data,
radius=15,
blur=20,
max_zoom=13,
gradient={0.4: 'blue', 0.6: 'lime', 0.8: 'orange', 1.0: 'red'}
).add_to(m)
return m
6. 运维监控体系
6.1 健康检查看板指标
| 指标名称 | 计算方式 | 告警阈值 |
|---|---|---|
| 节点存活率 | 在线节点数/总节点数 | <90% |
| 任务成功率 | 成功任务数/总任务数 | <95% |
| 平均响应时间 | 所有请求耗时平均值 | >3s |
| 反爬触发率 | 验证码出现次数/总请求数 | >5% |
| 数据重复率 | 重复数据量/总数据量 | >2% |
6.2 自动恢复机制
python复制async def monitor_task():
while True:
dead_nodes = await check_node_health()
for node in dead_nodes:
await restart_node(node)
stuck_tasks = await detect_stuck_tasks()
for task in stuck_tasks:
await reschedule_task(task)
await asyncio.sleep(60)
7. 实战经验总结
IP代理管理要点:
- 使用混合代理策略(数据中心IP+住宅IP)
- 每个浏览器实例绑定固定代理IP
- 实现IP自动熔断机制(连续失败3次即暂停使用)
数据清洗经验:
python复制def clean_review_text(text):
# 去除水军评论特征
patterns = [
r'美团.*券',
r'下次还会来',
r'^\d+字评价',
r'默认好评'
]
for pattern in patterns:
if re.search(pattern, text):
return None
# 标准化处理
text = re.sub(r'\s+', ' ', text).strip()
return text if len(text) >= 15 else None
性能瓶颈突破:
- 将Playwright启动参数调整为:
python复制browser = await chromium.launch( headless=True, args=['--single-process', '--no-zygote'] ) - 启用HTTP缓存减少重复下载:
python复制context = await browser.new_context( ignore_https_errors=True, storage_state="auth.json" ) - 实现增量采集模式:
python复制async def get_updates(since): return await db.query( "SELECT * FROM shops WHERE update_time > %s", (since,) )
