资讯动态

Python爬虫实战:携程酒店数据采集与分析

发布时间:2026/9/17 12:58:09 来源:尧图企业网站定制
1. 项目背景与需求解析最近帮朋友公司做酒店行业数据分析时发现市面上公开的酒店数据要么太贵要么字段不全。于是花了两个周末时间用Python写了个爬虫工具专门抓取携程酒店列表页的详细信息。这个脚本不仅能获取基础房型价格还能提取用户评分分项卫生、位置、服务等最终自动整理成结构化的Excel表格。对于酒店行业从业者、OTA竞品分析人员或者旅游行业研究者来说这类数据至少有三大实用场景竞品酒店价格监控动态调整自家定价策略用户评价维度分析找出自家酒店短板区域市场饱和度调研新开店选址参考2. 技术方案设计2.1 反爬策略应对携程的反爬机制在2023年升级后变得相当严格经过实测需要组合以下方案请求头伪装必须携带完整的headers特别注意headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://hotels.ctrip.com/, X-Requested-With: XMLHttpRequest }IP轮换池建议使用付费代理服务如Luminati实测单个IP存活时间不超过30分钟请求频率控制随机延迟设置在3-8秒之间过快必定触发验证码2.2 数据提取方案通过Chrome开发者工具分析发现携程新版页面采用混合渲染方式基础信息直接存在于HTML中可用BeautifulSoup解析动态加载的价格数据通过XHR接口返回需模拟Ajax请求用户评分使用特殊的加密字体需要字体文件解码关键接口示例https://m.ctrip.com/restapi/soa2/xxx/getHotelPrice?_fxpcqlniredt090310...3. 核心代码实现3.1 页面解析模块def parse_hotel_list(html): soup BeautifulSoup(html, lxml) items [] for item in soup.select(.hotel_item): # 基础信息提取 name item.select_one(.hotel_name a).text.strip() address item.select_one(.area_contair).text.strip() # 评分处理特殊字体解密 score decrypt_font(item.select_one(.hotel_value).text) items.append({ name: name, address: address, score: score }) return items3.2 动态价格获取async def fetch_prices(hotel_id): url fhttps://m.ctrip.com/restapi/soa2/xxx/getHotelPrice?hotelId{hotel_id} async with session.get(url, headersheaders) as resp: data await resp.json() return { lowest_price: data[price][lowest], room_types: [r[name] for r in data[rooms]] }3.3 字体解密算法携程使用自定义字体渲染评分数字需要先下载字体文件def decrypt_font(encoded_text): # 1. 下载当前页面使用的字体文件动态变化 # 2. 解析字体文件的cmap表 # 3. 建立编码到实际数字的映射关系 # ... return decoded_text4. 完整爬虫架构graph TD A[启动爬虫] -- B[获取城市列表] B -- C[遍历酒店列表页] C -- D{是否末页?} D --|否| E[解析当前页酒店] E -- F[异步获取价格数据] F -- G[存储到临时列表] D --|是| H[导出Excel]5. 数据存储优化5.1 异常处理机制try: await fetch_pages() except Exception as e: logger.error(f抓取失败: {str(e)}) if 验证码 in str(e): await solve_captcha() elif IP限制 in str(e): rotate_proxy()5.2 数据去重方案使用Bloom Filter算法高效去重from pybloom_live import ScalableBloomFilter bf ScalableBloomFilter(initial_capacity100000) if hotel_id not in bf: process_hotel(hotel_id) bf.add(hotel_id)6. Excel导出实现使用openpyxl库实现带格式的Excel导出def export_to_excel(data): wb Workbook() ws wb.active # 设置标题行样式 header_font Font(boldTrue, colorFFFFFF) header_fill PatternFill(fill_typesolid, fgColor4F81BD) # 写入数据 for row in data: ws.append([ row[name], row[address], row[score], # ... ]) # 自动调整列宽 for col in ws.columns: max_length 0 for cell in col: try: if len(str(cell.value)) max_length: max_length len(cell.value) except: pass adjusted_width (max_length 2) * 1.2 ws.column_dimensions[col[0].column_letter].width adjusted_width wb.save(hotels.xlsx)7. 部署与调度7.1 定时任务配置使用APScheduler实现每日自动运行scheduler BackgroundScheduler() scheduler.add_job( main, cron, day_of_week0-6, hour3, minute30 ) scheduler.start()7.2 日志监控系统配置多级别日志记录logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(spider.log), logging.StreamHandler() ] )8. 反反爬进阶技巧8.1 浏览器指纹模拟使用selenium-wire捕获完整请求流options { proxy: { http: fhttp://{proxy}, https: fhttps://{proxy}, }, ignore_http_errors: True, disable_capture: True } driver webdriver.Chrome( seleniumwire_optionsoptions, optionschrome_options )8.2 验证码破解方案对于出现的滑块验证码使用opencv识别缺口位置计算滑块移动轨迹模拟人类拖动行为def slide_verification(driver): # 获取背景图和滑块图 bg_img get_bg_image(driver) slide_img get_slide_image(driver) # 计算滑动距离 distance calculate_gap(bg_img, slide_img) # 生成移动轨迹 track generate_track(distance) # 执行滑动 drag_slider(driver, track)9. 数据字段说明最终导出的Excel包含以下核心字段字段名类型说明hotel_id字符串酒店唯一标识name字符串酒店名称address字符串详细地址overall_score数值综合评分(5分制)hygiene_score数值卫生分项得分location_score数值位置分项得分service_score数值服务分项得分lowest_price数值最低房价(元)room_types字符串房型列表(JSON格式)comment_count整数点评总数last_update日期数据更新时间10. 常见问题排查10.1 数据缺失处理现象部分酒店缺少价格信息解决方案检查是否触发频率限制验证代理IP是否有效尝试切换User-Agent10.2 字体解析失败现象评分显示为乱码解决方法# 强制更新字体缓存 def refresh_font_cache(): if os.path.exists(font_cache.json): os.remove(font_cache.json)10.3 内存泄漏问题现象长时间运行后内存占用过高优化方案使用生成器替代列表存储中间结果定期调用gc.collect()限制并发请求数量11. 性能优化记录通过以下改进将采集效率提升6倍异步请求改造aiohttp替代requests使用uvloop加速事件循环实现零拷贝数据传递优化正则表达式模式最终性能指标单线程约120家酒店/分钟10并发约600家酒店/分钟内存占用稳定在350MB左右12. 法律风险提示重要注意事项严格遵守robots.txt规定设置合理的请求间隔禁止将数据用于商业交易建议在夜间低峰期运行数据使用需遵守《个人信息保护法》法律提示本代码示例仅用于技术研究请勿用于非法用途。实际使用前请咨询专业法律意见。13. 项目扩展方向后续可改进功能增加自动数据更新对比集成到BI工具实时展示添加价格波动预警功能结合地图API可视化分布# 示例价格波动监测 def price_change_alert(): old load_previous_data() new load_current_data() for hotel in new: if hotel[price] old[hotel[id]][price] * 0.9: send_alert(f{hotel[name]}价格下降超过10%)14. 完整项目结构建议的代码目录结构/ctrip-spider │── main.py # 主入口 │── config.py # 配置文件 │── requirements.txt # 依赖库 ├── /core │ ├── downloader.py # 下载器 │ ├── parser.py # 解析器 │ └── storage.py # 存储器 ├── /utils │ ├── logger.py # 日志工具 │ └── proxy.py # 代理管理 └── /data ├── fonts/ # 字体文件 └── output/ # 输出文件15. 环境配置指南推荐使用conda创建独立环境conda create -n ctrip python3.8 conda activate ctrip pip install -r requirements.txtrequirements.txt内容示例aiohttp3.8.1 beautifulsoup44.11.1 openpyxl3.0.10 pybloom-live3.0.0 uvloop0.16.016. 代理IP配置技巧优质代理的特征高匿名性Elite级别低延迟500ms高可用率95%支持HTTPS协议推荐测试方法def test_proxy(proxy): try: async with aiohttp.ClientSession() as session: async with session.get(http://httpbin.org/ip, proxyproxy, timeout10) as resp: return resp.status 200 except: return False17. 字体反爬深度解析携程使用的字体加密原理每次访问动态生成字体文件相同Unicode码点对应不同字形需要建立动态映射关系破解步骤下载当前页面字体文件.woff格式使用FontTools解析字体表提取数字字符的轮廓特征建立编码到实际值的映射from fontTools.ttLib import TTFont def parse_font(font_path): font TTFont(font_path) cmap font.getBestCmap() # 分析字形特征建立映射... return mapping18. 数据清洗策略原始数据常见问题处理价格显示起字去除price price_text.replace(起, ).strip()评分中的分字去除地址冗余信息清理房型名称标准化def clean_data(raw): cleaned raw.copy() # 价格处理 cleaned[price] float(cleaned[price].replace(¥, )) # 房型标准化 cleaned[room_type] STANDARD_ROOM_TYPES.get( raw[room_type], 其他 ) return cleaned19. 分布式扩展方案当需要大规模采集时可以考虑使用Redis作为任务队列redis.rpush(ctrip:start_urls, url)采用Scrapy-Redis架构部署到多台服务器使用Kubernetes管理集群性能预期10节点集群约5,000家酒店/分钟数据一致性保证通过唯一索引去重20. 自动化运维监控建议部署以下监控指标成功率监控成功请求/总请求代理IP健康度验证码触发频率数据存储延迟Prometheus配置示例scrape_configs: - job_name: ctrip_spider static_configs: - targets: [spider-server:9090]21. 数据可视化示例使用PandasMatplotlib快速分析import pandas as pd import matplotlib.pyplot as plt df pd.read_excel(hotels.xlsx) top10 df.sort_values(price, ascendingFalse).head(10) plt.figure(figsize(10,6)) plt.barh(top10[name], top10[price]) plt.title(Top10高价酒店) plt.tight_layout() plt.savefig(price_rank.png)22. 异常邮件报警集成SMTP报警功能def send_alert(subject, content): msg MIMEText(content) msg[Subject] f[爬虫报警] {subject} msg[From] spiderexample.com msg[To] adminexample.com with smtplib.SMTP(smtp.example.com) as server: server.login(user, pass) server.send_message(msg)触发条件示例连续5次请求失败验证码出现频率20%代理IP存活率80%23. 移动端API分析相比PC端移动端API特点接口参数更简洁返回数据为纯JSON反爬机制相对宽松需要模拟设备指纹关键接口示例GET /restapi/hotelapi/hotels/search/v2 参数 cityId: 123 checkIn: 2026-01-01 page: 124. 数据更新策略智能更新机制设计全量采集每周一次周末凌晨增量更新每天检查价格变动紧急更新当价格波动15%时触发def need_update(hotel): last get_last_record(hotel[id]) if not last: return True return abs(hotel[price]-last[price])/last[price] 0.1525. 最终效果展示导出Excel包含三个工作表酒店列表基础信息价格明细历史价格评分分析各维度雷达图![示例截图] 字段齐全开箱即用支持直接导入数据库或分析工具。实际测试采集上海地区2000家酒店完整信息含30天价格耗时约45分钟数据准确率98.7%。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价