Python 爬虫实战:精准抓取携程旅行酒店价格数据

大会官网:https://ais.cn/u/Y3aAzy

会议时间:2026年2月6-8日

会议地点:中国-广州

前言

携程旅行作为国内领先的在线旅游平台,其酒店价格数据包含实时房价、房型信息、优惠活动、用户评分等核心维度,是旅游数据分析、价格监控、竞品分析的重要数据源。相较于博客园的静态页面,携程酒店页面融合了动态加载、反爬验证、数据加密等机制,抓取难度更高。本文将从页面分析、反反爬策略、动态数据抓取等维度,系统讲解如何使用 Python 实现携程酒店价格数据的高效抓取,帮助开发者突破平台限制,获取结构化的酒店价格信息。

摘要

本文聚焦携程旅行酒店价格爬虫的全流程实现,核心涵盖动态页面数据抓取请求头加密参数处理分页与多城市数据抓取三大核心技术点,通过requests库发送 HTTP 请求、jsonpath解析 JSON 数据、fake-useragent伪装请求特征,结合实战案例完成酒店名称、价格、房型、评分、位置等核心字段的抓取。实战目标链接:携程酒店查询页,最终实现支持多城市、多日期的酒店价格爬虫脚本,并提供数据清洗与可视化基础方案。

一、技术原理与环境准备

1.1 核心技术原理

携程酒店价格抓取的核心难点与解决思路:

  • 酒店列表数据通过异步接口返回 JSON 格式,而非直接渲染在 HTML 中,需先抓包定位数据接口;
  • 请求头包含RefererOrigin等校验字段,且部分接口需携带Cookie才能返回完整数据;
  • 高频请求会触发 IP 封禁或验证码,需结合代理 IP、请求延迟、UA 随机化等反反爬策略;
  • 价格数据实时变动,接口参数包含时间戳、城市编码等动态信息,需动态构造请求参数。

1.2 环境配置

工具 / 库版本作用
Python3.8+核心开发语言
requests2.31.0发送 HTTP 请求
jsonpath0.82解析 JSON 数据(高效提取嵌套字段)
fake-useragent1.4.0随机生成 User-Agent
python-dotenv1.0.0管理环境变量(存储敏感参数)
pandas2.1.4数据清洗与结构化存储
环境安装命令

bash

运行

pip install requests jsonpath fake-useragent python-dotenv pandas 

二、实战开发:携程酒店价格爬虫

2.1 核心思路拆解

  1. 抓包定位携程酒店列表数据接口(通过 Chrome F12 Network 面板);
  2. 分析接口请求参数(城市编码、入住 / 离店日期、页码等);
  3. 构造合规的请求头与请求参数,模拟真实请求;
  4. 发送请求获取 JSON 数据,使用 jsonpath 提取核心字段;
  5. 实现多页数据自动抓取,处理接口返回的分页标识;
  6. 数据清洗与结构化存储,输出 Excel/JSON 格式结果。

2.2 完整代码实现

python

运行

import requests import json import time import os from fake_useragent import UserAgent from jsonpath import jsonpath import pandas as pd from dotenv import load_dotenv # 加载环境变量(避免硬编码敏感参数) load_dotenv() class CtripHotelSpider: def __init__(self): """初始化爬虫配置""" # 基础配置 self.ua = UserAgent() self.headers = { "User-Agent": self.ua.random, "Referer": "https://hotels.ctrip.com/", "Origin": "https://hotels.ctrip.com", "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "keep-alive", # Cookie可从浏览器抓包获取,建议存入.env文件 "Cookie": os.getenv("CTRIP_COOKIE", ""), "Content-Type": "application/json;charset=UTF-8" } self.timeout = 15 # 请求超时时间 self.delay = 3 # 请求延迟(携程反爬严格,建议3-5秒) self.all_hotel_data = [] # 存储所有酒店数据 # 接口配置(携程酒店列表接口,已简化) self.api_url = "https://hotels.ctrip.com/hotel/api/search/poiList" def build_request_params(self, city_code="北京", check_in="2026-01-20", check_out="2026-01-22", page=1): """ 构造接口请求参数 :param city_code: 城市名称/编码(支持中文或城市编码,如北京=1) :param check_in: 入住日期(YYYY-MM-DD) :param check_out: 离店日期(YYYY-MM-DD) :param page: 页码 :return: 请求参数字典 """ params = { "cityId": self.get_city_code(city_code), # 转换城市名称为编码 "checkIn": check_in, "checkOut": check_out, "pageNum": page, "pageSize": 20, # 每页20条数据 "sortType": "9", # 9=价格从低到高,1=推荐排序 "allianceid": "4902", "sid": "29155245", "callback": "jQuery1830" + str(int(time.time() * 1000)), # 动态回调参数 "_": int(time.time() * 1000) # 时间戳参数 } return params def get_city_code(self, city_name): """ 简易城市名称转编码(可扩展完整城市编码表) :param city_name: 城市名称 :return: 城市编码 """ city_code_map = { "北京": 1, "上海": 2, "广州": 3, "深圳": 4, "杭州": 5, "成都": 6 } return city_code_map.get(city_name, 1) # 默认返回北京编码 def get_hotel_data(self, params): """ 发送请求获取酒店数据 :param params: 请求参数 :return: 解析后的JSON数据/None """ try: # 延迟请求,规避反爬 time.sleep(self.delay) # 发送GET请求 response = requests.get( url=self.api_url, headers=self.headers, params=params, timeout=self.timeout ) # 检查请求状态 response.raise_for_status() # 处理携程接口的JSONP格式返回(去除callback包裹) response_text = response.text if "jQuery" in response_text: # 提取JSON核心部分 json_start = response_text.find("(") + 1 json_end = response_text.rfind(")") json_data = json.loads(response_text[json_start:json_end]) else: json_data = response.json() return json_data except requests.exceptions.RequestException as e: print(f"请求接口失败:{str(e)}") return None except json.JSONDecodeError as e: print(f"JSON解析失败:{str(e)}") return None def parse_hotel_data(self, json_data): """ 解析酒店数据,提取核心字段 :param json_data: 接口返回的JSON数据 :return: 结构化酒店数据列表 """ if not json_data or json_data.get("code") != 200: print("接口返回异常或无数据") return [] hotel_list = [] # 使用jsonpath提取酒店列表(适配嵌套JSON结构) hotels = jsonpath(json_data, "$..hotelList")[0] if jsonpath(json_data, "$..hotelList") else [] for hotel in hotels: try: # 提取核心字段(使用jsonpath避免KeyError) hotel_name = jsonpath(hotel, "$.hotelName")[0] if jsonpath(hotel, "$.hotelName") else "未知酒店" hotel_id = jsonpath(hotel, "$.hotelId")[0] if jsonpath(hotel, "$.hotelId") else "" price = jsonpath(hotel, "$.lowPrice")[0] if jsonpath(hotel, "$.lowPrice") else 0 score = jsonpath(hotel, "$.hotelScore")[0] if jsonpath(hotel, "$.hotelScore") else 0.0 address = jsonpath(hotel, "$.address")[0] if jsonpath(hotel, "$.address") else "未知地址" star_rating = jsonpath(hotel, "$.starRating")[0] if jsonpath(hotel, "$.starRating") else "无星级" room_type = jsonpath(hotel, "$.roomTypeName")[0] if jsonpath(hotel, "$.roomTypeName") else "未知房型" distance = jsonpath(hotel, "$.distance")[0] if jsonpath(hotel, "$.distance") else "未知距离" # 封装酒店数据 hotel_info = { "酒店名称": hotel_name, "酒店ID": hotel_id, "最低价格(元)": price, "酒店评分": score, "酒店星级": star_rating, "房型": room_type, "地址": address, "距离商圈(km)": distance } hotel_list.append(hotel_info) except Exception as e: print(f"解析单条酒店数据失败:{str(e)}") continue return hotel_list def crawl_hotels(self, city="北京", check_in="2026-01-20", check_out="2026-01-22", max_page=3): """ 批量抓取指定城市的酒店数据 :param city: 城市名称 :param check_in: 入住日期 :param check_out: 离店日期 :param max_page: 最大抓取页数 :return: 所有酒店数据 """ print(f"开始抓取{city}市{check_in}至{check_out}的酒店数据,共抓取{max_page}页...") for page in range(1, max_page + 1): print(f"正在抓取第{page}页...") # 构造请求参数 params = self.build_request_params(city, check_in, check_out, page) # 获取接口数据 json_data = self.get_hotel_data(params) # 解析酒店数据 page_hotels = self.parse_hotel_data(json_data) if not page_hotels: print(f"第{page}页无数据,停止抓取") break # 添加到总列表 self.all_hotel_data.extend(page_hotels) print(f"第{page}页抓取完成,共{len(page_hotels)}家酒店") print(f"抓取完成!总计获取{len(self.all_hotel_data)}家酒店数据") return self.all_hotel_data def save_to_excel(self, file_path="ctrip_hotel_prices.xlsx"): """ 将酒店数据保存为Excel文件(便于数据分析) :param file_path: 保存路径 """ if not self.all_hotel_data: print("无数据可保存") return try: # 转换为DataFrame df = pd.DataFrame(self.all_hotel_data) # 按价格升序排序 df = df.sort_values(by="最低价格(元)", ascending=True) # 保存为Excel df.to_excel(file_path, index=False, engine="openpyxl") print(f"数据已成功保存到:{file_path}") except Exception as e: print(f"保存Excel失败:{str(e)}") def print_sample_results(self, sample_num=5): """ 打印示例结果(前N条) :param sample_num: 示例数量 """ if not self.all_hotel_data: print("无抓取结果") return print(f"\n===== 携程酒店价格抓取结果(前{sample_num}条) =====") # 转换为DataFrame便于格式化输出 df = pd.DataFrame(self.all_hotel_data[:sample_num]) print(df.to_string(index=False)) if __name__ == "__main__": # 实例化爬虫 spider = CtripHotelSpider() # 抓取北京2026-01-20至2026-01-22的酒店数据(前3页) spider.crawl_hotels( city="北京", check_in="2026-01-20", check_out="2026-01-22", max_page=3 ) # 打印示例结果 spider.print_sample_results(sample_num=5) # 保存数据到Excel spider.save_to_excel() 

2.3 环境变量配置(.env 文件)

在项目根目录创建.env文件,填入携程 Cookie(从浏览器抓包获取):

env

CTRIP_COOKIE="你的携程Cookie" 

2.4 代码输出结果示例

plaintext

开始抓取北京市2026-01-20至2026-01-22的酒店数据,共抓取3页... 正在抓取第1页... 第1页抓取完成,共20家酒店 正在抓取第2页... 第2页抓取完成,共20家酒店 正在抓取第3页... 第3页抓取完成,共20家酒店 抓取完成!总计获取60家酒店数据 ===== 携程酒店价格抓取结果(前5条) ===== 酒店名称 酒店ID 最低价格(元) 酒店评分 酒店星级 房型 地址 距离商圈(km) 0 北京如家快捷酒店(天安门店) 12345678 199 4.5 二星级 标准双人间 北京市东城区东单大街1号 0.5 1 北京7天连锁酒店(王府井店) 12345679 219 4.3 二星级 大床房A 北京市东城区王府井大街88号 0.8 2 北京汉庭酒店(西单店) 12345680 249 4.6 三星级 高级大床房 北京市西城区西单北大街12号 1.2 3 北京锦江之星(北京站店) 12345681 269 4.4 三星级 商务标准间 北京市东城区北京站东街5号 1.5 4 北京全季酒店(崇文门店) 12345682 299 4.8 四星级 豪华大床房 北京市东城区崇文门西大街9号 1.0 数据已成功保存到:ctrip_hotel_prices.xlsx 

2.5 核心代码原理说明

代码模块核心原理关键作用
__init__ 方法初始化请求头(含随机 UA、Cookie)、接口地址等模拟真实用户请求,通过 Cookie 绕过基础鉴权
build_request_params 方法动态构造接口参数(含时间戳、城市编码)适配携程接口的动态参数校验,支持多城市 / 多日期
get_hotel_data 方法发送 GET 请求,处理 JSONP 格式返回数据解决携程接口 JSONP 包裹问题,正确解析数据
parse_hotel_data 方法使用 jsonpath 提取嵌套 JSON 字段避免 KeyError,高效解析多层嵌套的酒店数据
crawl_hotels 方法循环构造分页参数,批量抓取多页数据实现自动化分页抓取,支持终止无数据页面
save_to_excel 方法转换为 DataFrame 并排序后保存为 Excel结构化存储数据,便于后续价格分析 / 可视化

三、反反爬优化策略

3.1 IP 代理池集成

携程对单 IP 高频请求敏感,可集成代理 IP 池轮换 IP:

python

运行

def get_proxy(self): """从代理池获取代理IP(示例,需替换为实际代理池接口)""" try: proxy_response = requests.get("http://你的代理池接口/get_proxy") proxy = proxy_response.json().get("proxy") return {"http": f"http://{proxy}", "https": f"https://{proxy}"} except Exception as e: print(f"获取代理失败:{str(e)}") return None # 在get_hotel_data方法中添加代理参数 # proxies = self.get_proxy() # response = requests.get(url=self.api_url, headers=self.headers, params=params, proxies=proxies, timeout=self.timeout) 

Cookie 过期后可通过 Selenium 模拟登录获取新 Cookie:

python

运行

from selenium import webdriver from selenium.webdriver.common.by import By import time def refresh_cookie(self): """模拟登录携程获取新Cookie""" driver = webdriver.Chrome() driver.get("https://www.ctrip.com/") print("请手动完成登录,30秒后继续...") time.sleep(30) # 获取登录后的Cookie cookies = driver.get_cookies() cookie_str = "; ".join([f"{c['name']}={c['value']}" for c in cookies]) self.headers["Cookie"] = cookie_str # 保存Cookie到.env文件 with open(".env", "w", encoding="utf-8") as f: f.write(f'CTRIP_COOKIE="{cookie_str}"') driver.quit() print("Cookie已更新并保存") 

3.3 请求频率动态调整

根据接口返回状态动态调整请求延迟:

python

运行

def adjust_delay(self, response_status): """ 根据响应状态调整延迟 :param response_status: 响应状态码 """ if response_status == 429: # 请求过于频繁 self.delay += 2 print(f"请求被限制,延迟调整为{self.delay}秒") elif response_status == 200 and self.delay > 3: self.delay -= 0.5 print(f"请求正常,延迟调整为{self.delay}秒") 

四、数据扩展与分析

4.1 价格趋势监控

扩展爬虫实现定时抓取,监控价格变动:

python

运行

import schedule def scheduled_crawl(): """定时抓取任务""" spider = CtripHotelSpider() spider.crawl_hotels(city="北京", check_in="2026-01-20", check_out="2026-01-22", max_page=1) spider.save_to_excel(f"ctrip_hotel_prices_{time.strftime('%Y%m%d_%H%M%S')}.xlsx") # 每天9点和18点抓取一次 schedule.every().day.at("09:00").do(scheduled_crawl) schedule.every().day.at("18:00").do(scheduled_crawl) # 启动定时任务 while True: schedule.run_pending() time.sleep(60) 

4.2 价格可视化分析

使用matplotlib绘制酒店价格分布直方图:

bash

运行

pip install matplotlib 

python

运行

import matplotlib.pyplot as plt def visualize_price_distribution(self): """可视化酒店价格分布""" if not self.all_hotel_data: print("无数据可可视化") return # 设置中文字体 plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False # 提取价格数据 prices = [hotel["最低价格(元)"] for hotel in self.all_hotel_data] # 绘制直方图 plt.figure(figsize=(10, 6)) plt.hist(prices, bins=10, color="skyblue", edgecolor="black") plt.title("携程北京酒店价格分布") plt.xlabel("价格(元)") plt.ylabel("酒店数量") plt.grid(axis="y", alpha=0.75) plt.savefig("hotel_price_distribution.png") plt.show() 

五、注意事项与合规声明

  1. 合规性:抓取携程酒店数据仅限个人学习、研究使用,不得用于商业竞争、价格篡改等违规场景,需遵守《网络安全法》及携程平台用户协议;
  2. 反爬尊重:严格控制请求频率(建议 3-5 秒 / 次),避免给携程服务器造成压力,不得使用爬虫进行恶意攻击;
  3. 数据时效性:酒店价格、库存等数据实时变动,抓取结果仅代表抓取时刻的状态;
  4. Cookie 安全:Cookie 包含个人登录信息,切勿泄露给他人,定期更换以降低账号风险。

总结

  1. 携程酒店价格爬虫的核心是定位异步数据接口+构造合规请求参数,通过 jsonpath 解析嵌套 JSON 数据,实现精准字段提取;
  2. 反反爬的关键在于请求特征随机化(UA/IP 轮换)、频率动态调整Cookie 有效期管理
  3. 抓取的数据可扩展实现价格监控、可视化分析等功能,满足旅游数据分析的核心需求,同时需严格遵守平台规则与法律法规。

Read more

极致性能的服务器Redis之Hash类型及相关指令介绍

极致性能的服务器Redis之Hash类型及相关指令介绍

目录 1. Hash介绍 2. hset 3. hget 3. hdel 5. hkeys 6. hvals 编辑 7. hgetall  8. hexists 9. hmget 10. hlen 11. hsetnx 12. hincrby 13. hincrbyfloat 1. Hash介绍 Redis 哈希类型是键值对的集合,字段与值均支持字符串、数字等类型,适合建模用户信息、配置项等对象类数据。其支持单字段 / 多字段的增删改查、字段存在性判断、值自增自减等原子操作,且底层通过压缩列表或哈希表优化存储,空间利用率高、查询效率快,是 Redis 中存储结构化数据的核心类型之一。 在Redis中因为本身就是按照哈希的KV结构来进行存储的,所以当我们想要使用Redis里面的哈希的时候,实际上是哈希的哈希,在后者中,

By Ne0inhk

Engram 中的多头哈希理解举例

我们以处理句子“DeepSeek improves memory retrieval with Multi-Head Hashing”为例,完整演示多头哈希(Multi-Head Hashing)的具体执行流程。为简化理解,我们设定关键参数:N-gram阶数=2(即提取连续2个Token组成的语义单元)、哈希头数量K=2(并行使用2个独立哈希函数)、嵌入表大小=101(选择质数以优化哈希分布)。 步骤1:上下文压缩(Tokenizer Compression) 首先通过词表规范化合并语义等价Token。 常见误解澄清:词表规范化压缩的是词表的ID空间大小,而非输入Token序列的长度。输入序列长度在此步骤中保持不变。 原始词表中,同一语义的不同形态(大小写、形态变体)被分配了不同的ID,造成嵌入表冗余。规范化的目标是将这些语义等价的Token归并到统一ID: 原始词表条目原始ID压缩后ID处理说明DeepSeek102102保留标准形式improves345345保留标准形式memory789789保留标准形式retrieval210210保留标准形式with

By Ne0inhk
数据结构:手撕堆和哈希表,字符串哈希详解----小白也能懂

数据结构:手撕堆和哈希表,字符串哈希详解----小白也能懂

🎬 博主名称:个人主页 🔥 个人专栏: 《算法通关》,《Java讲解》 ⛺️心简单,世界就简单 序言 其实是想把这篇写到上一篇里面的,但是中途困了,趴桌子上睡着了,真是没招 这篇文章,来手撕 堆和哈希表,这一般面试可能会问到,我们来了解他的思想和思路也是比较舒服的 目录 序言 堆 堆的存储 堆有两个基本操作 1,down( x ) 2 , up( x ) 操作一:插入一个数 操作二:求集合中的最小值 操作三:删除最小值 操作四:删除任意一个元素 操作五:修改任意一个元素 题目模板练习1 题目模板练习二 总结: 哈希表 存储结构:拉链法 存储结构:开放寻址法 处理冲突思路: 查找 删除 总结

By Ne0inhk
小红书笔记详情API接口基础解析:数据结构与调用方式

小红书笔记详情API接口基础解析:数据结构与调用方式

小红书笔记详情 API 接口基础解析:数据结构与调用方式 小红书笔记详情 API 是面向官方合作方 / 授权开发者提供笔记核心数据的标准化接口,主要用于合规场景下的内容展示、数据分析(需授权)等需求。该接口的设计围绕数据标准化、权限管控、内容合规三大核心,其数据结构与调用方式具有鲜明的内容社区属性。 一、接口基础信息 1. 接口访问前提 * 权限限制:小红书笔记详情 API不对外开放,仅对通过企业资质审核的合作方(如品牌服务商、合规内容平台)开放,个人开发者无法申请。 * 调用协议:支持 HTTPS 协议,保障数据传输安全;高并发场景可申请 gRPC 协议支持。 * 请求方式:以 GET 为主(只读查询),部分带用户个性化参数的请求支持 POST。 * 数据格式:默认返回 JSON 格式,支持 Protobuf 二进制格式(

By Ne0inhk