从AJAX到CSV:携程评论爬虫的工程化实践与数据流转

在数据驱动的商业决策时代,用户评论数据已成为旅游行业的重要资产。本文将深入探讨如何构建一个高性能的携程景点评论爬虫系统,实现从动态数据获取到结构化存储的全流程优化。

1. 动态数据获取与接口逆向工程

现代网页普遍采用AJAX技术实现动态加载,携程的评论系统也不例外。通过分析网络请求,我们发现核心接口为getCommentCollapseList,采用POST方式传输JSON格式参数。

关键请求参数解析:

{
  "arg": {
    "channelType": 2,
    "pageIndex": 2,
    "pageSize": 10,
    "poiId": 10558619,
    "sortType": 3
  },
  "head": {
    "cid": "09031027214030973865",
    "lang": "01"
  }
}

参数逆向要点:

  • poiId是景点的唯一标识符,可通过页面源码或地图接口获取
  • pageIndex控制分页,需配合totalCount计算总页数
  • channelType和sortType影响评论排序方式

实际请求示例:

import requests

def fetch_comments(poi_id, page=1):
    url = "https://m.ctrip.com/restapi/soa2/13444/json/getCommentCollapseList"
    payload = {
        "arg": {
            "channelType": 2,
            "pageIndex": page,
            "pageSize": 10,
            "poiId": poi_id,
            "sortType": 3
        },
        "head": {
            "cid": "09031027214030973865",
            "lang": "01"
        }
    }
    response = requests.post(url, json=payload)
    return response.json()

2. 分页控制与异常处理机制

稳定的爬虫系统需要完善的异常处理机制。携程接口常见异常包括频率限制、参数变更和数据结构调整。

分页控制策略:

  1. 首次请求获取总评论数totalCount
  2. 计算总页数:total_pages = math.ceil(totalCount / pageSize)
  3. 实现指数退避的重试机制

异常处理方案对比:

异常类型检测方法处理方案
频率限制状态码429增加延迟或更换IP
参数失效返回空数据重新分析接口
数据变更字段缺失动态适配解析逻辑

优化后的分页实现:

from time import sleep
from math import ceil

def get_all_comments(poi_id, max_pages=200):
    try:
        first_page = fetch_comments(poi_id, 1)
        total = first_page['result']['totalCount']
        pages = min(ceil(total/10), max_pages)
        
        for page in range(1, pages+1):
            try:
                data = fetch_comments(poi_id, page)
                yield data['result']['items']
                sleep(1.5)  # 礼貌性延迟
            except Exception as e:
                print(f"Page {page} failed: {str(e)}")
                sleep(5)  # 异常后延长等待
    except Exception as e:
        print(f"Initial request failed: {str(e)}")

3. 内存优化与大数据量处理

当爬取大量景点评论时,内存管理成为关键问题。我们采用流式处理和分块写入策略。

内存优化技巧:

  • 使用生成器避免一次性加载所有数据
  • 分块写入文件而非内存缓存
  • 及时释放不再使用的对象

性能对比测试:

数据量传统方式内存占用优化后内存占用
1万条约120MB约20MB
10万条超过1GB约50MB

分块写入实现:

import csv
from itertools import islice

def save_to_csv(poi_id, filename, chunk_size=1000):
    with open(filename, 'w', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(['id', 'content', 'rating', 'date'])
        
        comments = get_all_comments(poi_id)
        while True:
            chunk = list(islice(comments, chunk_size))
            if not chunk:
                break
                
            for item in chunk:
                row = [
                    item['id'],
                    item['content'],
                    item['star'],
                    item['date']
                ]
                writer.writerow(row)
            
            f.flush()  # 确保数据写入磁盘

4. 数据清洗与字段映射

原始数据需要经过清洗和转换才能用于分析。常见问题包括HTML标签、特殊字符和异构数据格式。

数据清洗流程:

  1. 去除HTML标签和空白字符
  2. 统一日期时间格式
  3. 情感词提取
  4. 异常值处理

字段映射表示例:

原始字段标准化字段处理方式
contentcomment_text去除HTML标签
starrating转换为1-5分制
datecomment_date统一为YYYY-MM-DD
userIduser_id保留原始ID

高级清洗函数示例:

import re
from datetime import datetime

def clean_comment(raw):
    # 去除HTML标签
    text = re.sub(r'<[^>]+>', '', raw['content'])
    # 转换日期格式
    date = datetime.strptime(raw['date'], '%Y-%m-%dT%H:%M:%S')
    # 处理评分
    rating = min(5, max(1, int(raw['star'])))
    
    return {
        'id': raw['id'],
        'text': text.strip(),
        'rating': rating,
        'date': date.strftime('%Y-%m-%d'),
        'user': raw['userId']
    }

5. 反爬策略与合规注意事项

商业爬虫必须遵守网站规则和法律法规。携程常见的反爬机制包括:

  • 请求频率限制
  • 用户行为分析
  • 参数签名验证
  • IP地址封禁

合规爬取建议:

严格遵守robots.txt规定 设置合理的请求间隔(建议≥1.5秒) 使用真实User-Agent 避免影响网站正常服务

技术对抗措施示例:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Referer': 'https://m.ctrip.com/',
    'X-Requested-With': 'XMLHttpRequest'
}

proxies = {
    'http': 'http://user:pass@proxy:port',
    'https': 'http://user:pass@proxy:port'
}

response = requests.post(url, json=payload, headers=headers, proxies=proxies)

在实际项目中,我们更推荐使用Scrapy等成熟框架,配合中间件实现自动限速和代理轮换。以下是一个完整的工程化爬虫项目结构:

ctrip_comments/
├── spiders/
│   ├── comment_spider.py
├── middlewares.py
├── pipelines.py
├── items.py
└── settings.py

通过系统化的工程实践,我们能够构建稳定高效的爬虫系统,为数据分析提供高质量的基础数据。在最近的一个实际项目中,这套方案成功爬取了超过50万条评论数据,平均成功率保持在98%以上。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐