1. 项目背景与需求分析
在旅游行业数字化转型的浪潮中,酒店价格数据已成为行业竞争的核心资源。作为国内领先的OTA平台,携程网每天产生海量的酒店价格变动信息,这些实时数据对于旅游行业从业者、数据分析师以及普通消费者都具有重要价值。
我最近接到了一个酒店比价系统的开发需求,需要获取携程网上多个城市、不同日期段的酒店价格数据。经过技术调研,发现市面上现有的解决方案要么功能单一,要么无法稳定应对携程的反爬机制。于是决定自己开发一个健壮的爬虫系统,以下是整个项目的技术实现细节。
2. 技术选型与架构设计
2.1 核心工具链选择
经过对比测试,最终确定的技术栈组合如下:
- Requests:用于基础HTTP请求,相比urllib3有更简洁的API
- BeautifulSoup4:HTML解析首选库,比lxml更容错
- Selenium:处理动态加载内容,特别是价格日历等JS渲染部分
- PyMySQL:数据存储到MySQL数据库
- Fake-useragent:随机生成请求头
- Redis:代理IP池和任务队列管理
提示:选择Selenium而非纯Requests方案,是因为携程的价格日历等关键数据是通过AJAX动态加载的,单纯分析API接口难度较大且易失效。
2.2 系统架构设计
整个系统采用分层设计,主要模块包括:
- 调度中心:负责任务分发和状态监控
- 爬虫核心:实现具体页面抓取逻辑
- 代理中间件:管理IP代理池
- 存储模块:数据清洗和持久化
- 反反爬模块:处理验证码、请求频率控制等
python复制# 基础爬虫类结构示例
class BaseSpider:
def __init__(self):
self.session = requests.Session()
self.proxy = None
self.headers = {
'User-Agent': fake_useragent.UserAgent().random
}
def get_proxy(self):
# 从Redis获取可用代理
pass
def parse_hotel_list(self, html):
# 解析酒店列表页
pass
def parse_hotel_detail(self, html):
# 解析酒店详情页
pass
3. 核心实现细节
3.1 页面请求策略
携程的反爬机制相当完善,需要多维度应对:
- 请求头伪装:每次请求随机生成User-Agent,并携带合理的Referer
- 请求频率控制:设置随机间隔(1-3秒),高峰期适当延长
- IP代理池:使用付费代理服务,实现自动切换
- Cookie管理:定期更新会话Cookie,模拟真实用户
python复制def make_request(url, max_retry=3):
for _ in range(max_retry):
try:
proxy = self.get_proxy()
response = self.session.get(
url,
headers=self.headers,
proxies={"http": proxy, "https": proxy},
timeout=10
)
if self.check_anti_spider(response.text):
self.ban_proxy(proxy)
continue
return response
except Exception as e:
logger.error(f"请求失败: {str(e)}")
continue
raise Exception("超过最大重试次数")
3.2 数据解析关键点
酒店列表页和详情页的解析需要特别注意以下字段:
- 基础信息:酒店名称、星级、地址、经纬度
- 价格数据:不同房型价格、取消政策、早餐信息
- 评价数据:评分、评论数、标签(如"位置优越")
- 附属信息:设施服务、周边交通、政策说明
python复制def parse_hotel_list(self, html):
soup = BeautifulSoup(html, 'html.parser')
items = []
for item in soup.select('.hotel_item'):
try:
hotel = {
'name': item.select_one('.hotel_name a').text.strip(),
'price': item.select_one('.price').text.strip()[1:], # 去掉¥符号
'score': item.select_one('.hotel_value').text,
'location': item.select_one('.hotel_address').text,
'url': item.select_one('.hotel_name a')['href']
}
items.append(hotel)
except Exception as e:
logger.warning(f"解析酒店条目失败: {str(e)}")
return items
4. 反反爬实战经验
4.1 常见反爬现象识别
- 请求被重定向:返回302状态码跳转到验证页面
- 返回假数据:HTML结构正常但内容明显不合理
- 验证码挑战:出现滑动验证码或点选验证码
- 请求频率限制:短时间内返回大量429状态码
4.2 应对策略实测有效方案
-
动态等待策略:
- 基础等待:随机1-3秒
- 遇到验证码:等待30秒后重试
- 连续失败:指数退避,最长等待5分钟
-
浏览器指纹模拟:
- 使用selenium-wire捕获真实浏览器请求
- 复制关键请求头和Cookie
- 动态生成设备指纹参数
-
验证码处理方案:
- 对接第三方打码平台(如超级鹰)
- 人工打码备用通道
- 重要任务时启用人工干预模式
python复制# 验证码处理示例
def handle_captcha(self, image_url):
if self.use_captcha_service:
captcha_text = self.captcha_service.solve(image_url)
return captcha_text
else:
# 人工干预模式
self.driver.get(image_url)
captcha = input("请输入验证码:")
return captcha
5. 数据存储与优化
5.1 数据库设计
设计合理的数据库结构对后续分析至关重要:
sql复制CREATE TABLE hotels (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
city VARCHAR(100) NOT NULL,
district VARCHAR(100),
address TEXT,
star_level DECIMAL(2,1),
score DECIMAL(3,1),
comment_count INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE prices (
id INT AUTO_INCREMENT PRIMARY KEY,
hotel_id INT,
room_type VARCHAR(255),
date DATE,
price DECIMAL(10,2),
breakfast_included BOOLEAN,
cancel_policy VARCHAR(100),
FOREIGN KEY (hotel_id) REFERENCES hotels(id),
INDEX idx_hotel_date (hotel_id, date)
);
5.2 性能优化技巧
- 批量插入:使用executemany减少数据库IO
- 内存缓存:对重复出现的酒店信息进行缓存
- 异步存储:将存储操作放入独立线程/进程
- 数据去重:使用唯一索引避免重复数据
python复制# 批量插入示例
def batch_insert_hotels(self, hotels):
sql = """
INSERT INTO hotels
(name, city, district, address, star_level, score, comment_count)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
score = VALUES(score),
comment_count = VALUES(comment_count)
"""
self.cursor.executemany(sql, [
(h['name'], h['city'], h['district'],
h['address'], h['star_level'], h['score'], h['comment_count'])
for h in hotels
])
self.conn.commit()
6. 项目部署与监控
6.1 生产环境部署方案
推荐使用Docker容器化部署:
dockerfile复制FROM python:3.8-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py", "--mode=production"]
启动命令:
bash复制docker build -t ctrip-spider .
docker run -d --name spider \
-e REDIS_HOST=redis-server \
-e MYSQL_HOST=mysql-server \
ctrip-spider
6.2 监控指标设计
-
基础指标:
- 请求成功率
- 平均响应时间
- 代理IP可用率
-
业务指标:
- 每小时抓取酒店数
- 数据完整率
- 价格更新时效性
-
告警规则:
- 连续10次请求失败
- 1小时内验证码出现率>30%
- 代理IP可用率<60%
7. 法律合规与道德考量
在开发此类爬虫时,必须注意:
- 遵守robots.txt:检查目标网站的爬虫协议
- 控制请求频率:避免对目标服务器造成负担
- 数据使用限制:仅用于个人分析,不进行商业转售
- 用户隐私保护:不采集任何用户个人信息
我在实际开发中发现,携程对于合理的低频爬取相对宽容,但一旦检测到异常流量会立即封禁。建议:
- 尽量在非高峰时段运行(如凌晨1-5点)
- 控制并发数量(建议不超过3个并发)
- 准备多个备用账号轮换使用
这个项目从开发到稳定运行花了约两周时间,最大的收获是:反爬措施不是越复杂越好,而是要在模拟真实用户行为和系统复杂度之间找到平衡点。目前系统每天能稳定采集约2万条酒店价格数据,为后续的价格分析提供了可靠的数据基础。