1. 项目概述:爬取携程酒店评论的技术价值
酒店评论数据是消费者决策的重要参考依据,也是商家优化服务的关键指标。作为国内领先的OTA平台,携程积累了海量真实用户评价,这些数据对市场分析、竞品研究、舆情监控都具有极高价值。传统人工收集方式效率低下,而通过Python爬虫技术自动化采集并存储到MySQL数据库,可以实现高效、结构化的数据管理。
这个项目涉及的核心技术栈包括:
- Python网络请求库(如requests)
- 网页解析工具(如BeautifulSoup)
- 反爬绕过策略
- MySQL数据库操作
- 数据清洗与持久化
提示:实际操作中需严格遵守robots协议,控制请求频率,避免对目标服务器造成负担。建议在非高峰时段采集数据,单次采集量不超过100页。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案设计
2.1 整体架构设计
项目采用典型的三层爬虫架构:
- 数据采集层:模拟浏览器请求获取HTML源码
- 数据处理层:解析DOM提取关键字段
- 数据存储层:清洗后存入关系型数据库
mermaid复制graph TD
A[启动爬虫] --> B[发送HTTP请求]
B --> C{响应状态?}
C -->|200| D[解析HTML]
C -->|其他| E[异常处理]
D --> F[提取评论数据]
F --> G[数据清洗]
G --> H[存入MySQL]
H --> I[下一页?]
I -->|是| B
I -->|否| J[结束]
2.2 技术选型对比
| 组件类型 | 可选方案 | 本项目选择 | 理由 |
|---|---|---|---|
| 请求库 | requests/scrapy/httpx | requests | 学习曲线平缓,适合中小规模采集 |
| 解析库 | BeautifulSoup/lxml/pyquery | BeautifulSoup | 容错性好,文档丰富 |
| 数据库 | MySQL/PostgreSQL/SQLite | MySQL | 企业级应用广泛,支持事务 |
| 连接器 | pymysql/mysql-connector | pymysql | 纯Python实现,兼容性好 |
3. 核心实现步骤
3.1 环境准备
安装必要依赖库:
bash复制pip install requests beautifulsoup4 pymysql cryptography
MySQL数据库配置建议:
- 字符集:utf8mb4(支持emoji存储)
- 事务隔离级别:READ COMMITTED
- 为评论表添加全文索引(方便后续文本分析)
3.2 爬虫核心代码实现
请求头伪装技巧
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': 'https://hotels.ctrip.com/'
}
分页爬取逻辑
python复制def crawl_comments(hotel_id, max_pages=10):
base_url = f"https://hotels.ctrip.com/hotel/{hotel_id}.html"
comments = []
for page in range(1, max_pages+1):
try:
params = {'page': page, 'condition': 'latest'}
resp = requests.get(base_url, headers=headers, params=params, timeout=10)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, 'html.parser')
comments.extend(parse_comments(soup))
time.sleep(random.uniform(1, 3)) # 随机延迟
except Exception as e:
print(f"第{page}页抓取失败: {str(e)}")
break
return comments
数据解析示例
python复制def parse_comments(soup):
items = []
for div in soup.select('.comment_block'):
try:
item = {
'user': div.select_one('.user_name').text.strip(),
'score': float(div.select_one('.score span').text),
'date': div.select_one('.time').text.strip(),
'content': div.select_one('.comment_txt').text.strip(),
'room_type': div.select_one('.room_type').text.strip(),
'useful': int(div.select_one('.useful').text) if div.select_one('.useful') else 0
}
items.append(item)
except Exception as e:
print(f"解析异常: {str(e)}")
continue
return items
3.3 数据库存储设计
表结构设计
sql复制CREATE TABLE hotel_comments (
id INT AUTO_INCREMENT PRIMARY KEY,
hotel_id INT NOT NULL,
user_name VARCHAR(50),
score FLOAT,
comment_date DATE,
content TEXT,
room_type VARCHAR(100),
useful_count INT,
crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_hotel (hotel_id),
FULLTEXT INDEX ft_content (content)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据入库实现
python复制def save_to_mysql(comments, conn):
sql = """INSERT INTO hotel_comments
(hotel_id, user_name, score, comment_date, content, room_type, useful_count)
VALUES (%s, %s, %s, %s, %s, %s, %s)"""
with conn.cursor() as cursor:
for comment in comments:
try:
cursor.execute(sql, (
comment['hotel_id'],
comment['user'],
comment['score'],
datetime.strptime(comment['date'], '%Y-%m-%d').date(),
comment['content'],
comment['room_type'],
comment['useful']
))
except Exception as e:
print(f"入库失败: {str(e)}")
conn.rollback()
continue
conn.commit()
4. 反爬策略应对方案
4.1 常见反爬手段及破解
| 反爬类型 | 检测特征 | 应对方案 |
|---|---|---|
| User-Agent校验 | 非常规UA | 轮换主流浏览器UA |
| IP限制 | 单个IP高频访问 | 使用代理IP池(建议付费API) |
| 行为分析 | 固定间隔请求 | 随机延迟(1-5秒) |
| 验证码 | 弹出图形验证 | 第三方打码平台或暂停采集 |
4.2 请求优化技巧
python复制# 使用会话保持
session = requests.Session()
session.headers.update(headers)
# 自动重试装饰器
def retry(max_retries=3, delay=1):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_retries - 1:
raise
time.sleep(delay * (i + 1))
return wrapper
return decorator
5. 数据清洗与质量保障
5.1 常见数据问题处理
python复制def clean_comment(comment):
# 处理空值
comment['content'] = comment.get('content', '').strip() or '无内容'
# 分数归一化(携程是5分制)
score = comment.get('score', 0)
comment['score'] = max(0, min(5, float(score)))
# 日期格式标准化
if '今天' in comment['date']:
comment['date'] = datetime.now().strftime('%Y-%m-%d')
elif '昨天' in comment['date']:
comment['date'] = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
return comment
5.2 数据去重方案
sql复制-- 创建唯一索引防止重复存储
ALTER TABLE hotel_comments
ADD UNIQUE INDEX idx_unique (hotel_id, user_name, comment_date(10), content(20));
6. 性能优化建议
6.1 数据库批量插入
python复制def batch_insert(comments, conn, batch_size=100):
sql = """INSERT IGNORE INTO hotel_comments (...) VALUES (...)"""
with conn.cursor() as cursor:
for i in range(0, len(comments), batch_size):
batch = comments[i:i + batch_size]
try:
cursor.executemany(sql, [
(c['hotel_id'], c['user'], ..., c['useful'])
for c in batch
])
conn.commit()
except Exception as e:
print(f"批量插入失败: {str(e)}")
conn.rollback()
6.2 异步爬取实现(示例)
python复制import aiohttp
import asyncio
async def fetch_page(session, url):
async with session.get(url) as response:
return await response.text()
async def crawl_async(hotel_ids):
async with aiohttp.ClientSession(headers=headers) as session:
tasks = []
for hid in hotel_ids:
url = f"https://hotels.ctrip.com/hotel/{hid}.html"
tasks.append(fetch_page(session, url))
return await asyncio.gather(*tasks)
7. 项目扩展方向
- 情感分析:使用SnowNLP或BERT模型分析评论情感倾向
- 关键词提取:通过TF-IDF算法提取高频特征词
- 可视化报表:用Pyecharts生成评分分布、词云等图表
- 实时监控:设置定时任务持续跟踪新评论
注意:商业用途需获得平台授权,个人学习研究建议控制数据采集规模。建议存储原始HTML快照以备后续复查,重要字段建议增加版本控制。
