ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

携程酒店数据爬虫CTripSpider本地化采集方案

携程酒店数据爬虫CTripSpider本地化采集方案 简介这是一份基于Java与Python双语言实现的携程酒店数据爬取实战项目面向Web数据采集初学者及Java/Python开发者聚焦酒店行业垂直场景的数据获取与结构化处理。资源包共34个文件含23个核心Java源码覆盖HTTP请求、HTML解析、数据存储等模块、4个依赖jar包、2个说明文档含系统功能与manualType配置说明以及project、classpath等IDE工程配置文件整体1.59MB开箱即用。已有664人学习下载适合快速搭建本地爬虫环境、理解反爬应对策略、掌握酒店类网页结构特征与数据清洗流程。项目目录清晰分层含src主逻辑、libs依赖库及配置文件配套txt文档提供关键参数说明与运行指引可直接复现完整爬取链路是学习动态网页抓取与多语言协同开发的典型参考案例。1. 爬取携程酒店数据CTripSpider.zip不是“一键采集”而是本地化、可审计、抗反爬的酒店价格与房型结构化抓取方案你搜“爬取携程酒店数据CTripSpider.zip”大概率正被三类问题卡住一是下载了压缩包却跑不起来报错No module named selenium或Connection refused二是能跑但只抓到空列表页面渲染失败或请求被拦截三是好不容易拿到几页数据发现字段缺失严重——没有真实入住日期的价格、没有房型面积、没有设施图标对应的文本描述。这不是代码写得烂而是这个项目本质是一套面向酒店行业竞对分析场景的轻量级本地采集框架它不依赖云调度、不打包代理池、不模拟手机App流量核心价值在于用最少外部依赖仅 Chrome Python 3.8在 Windows/macOS/Linux 本地复现「用户真实浏览路径」把携程 PC 端酒店详情页中动态加载的房型、价格、库存、政策等结构化字段稳定落库为 CSV/JSON。适合酒店运营、OTA渠道经理、民宿定价策略岗——不是程序员但需要每天比对 20 家竞品酒店在不同日期的满房率与加价逻辑。它不承诺“全自动全站抓取”但保证你改 3 行配置就能跑通单家酒店的 7 天价格曲线。2. 从 CTripSpider.zip 解压到可执行环境、驱动与配置文件的三重校准2.1 解压后目录结构与各文件的真实作用解压CTripSpider.zip后你会看到如下典型结构版本无关以主流 fork 为准CTripSpider/ ├── main.py # 入口脚本启动浏览器、注入 Cookie、翻页、提取 ├── config/ # 配置目录 │ ├── manualType.properties # 关键定义请求头 User-Agent 类型与模拟设备标识 │ └── system.txt # 存储 ChromeDriver 路径、超时阈值、重试次数等运行参数 ├── data/ # 输出目录首次运行自动创建 │ └── output_20240520.csv # 按日期命名的结构化结果 ├── utils/ │ ├── parser.py # 解析 HTML 的核心定位价格 DOM、提取房型 JSON 块、清洗文本 │ └── driver_manager.py # 自动检测 Chrome 版本并匹配 chromedriver非 selenium 自带 └── requirements.txt注意manualType.properties不是加密文件而是 key-value 形式文本控制请求指纹。常见内容如user_agent_typewin10_chrome_115 device_typedesktop accept_languagezh-CN,zh;q0.9它的作用是让main.py在构造webdriver.ChromeOptions()时按此配置注入 headers 和启动参数避免因 UA 单一被识别为爬虫。2.2 Python 环境与 ChromeDriver 的精准匹配不要pip install -r requirements.txt就完事——这是翻车高发区。必须分步校准# 1. 确认本地 Chrome 版本Windowschrome://versionmacOS/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version $ google-chrome --version # 输出示例Google Chrome 124.0.6367.78 # 2. 下载对应版本的 chromedriver必须严格一致小版本号差 1 都可能 fail # 访问 https://chromedriver.chromium.org/ → 找到 124.0.6367.78 对应的 driverLinux/macOS 选 no-sandbox 版 # 3. 将 chromedriver 放入系统 PATH或写死在 system.txt 中 # system.txt 示例关键行 chrome_driver_path /usr/local/bin/chromedriver page_load_timeout 30 implicit_wait 10 max_retry 3# utils/driver_manager.py 中关键逻辑供你调试时参考 def get_chrome_options(): options webdriver.ChromeOptions() options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) # 必须设尺寸否则部分 JS 渲染异常 # 以下从 manualType.properties 读取并注入 ua load_property(user_agent_type) # 如 win10_chrome_115 → 查表得完整 UA 字符串 options.add_argument(f--user-agent{ua}) return options为什么这步不能跳携程前端有navigator.platformscreen.widthUser-Agent三重校验。若 Chrome 版本与 driver 不匹配driver.get(url)会卡在空白页若未设--window-sizeJS 可能判断为“非桌面端”而隐藏价格模块。2.3 system.txt 的 4 个必调参数及其业务含义参数名默认值修改建议业务影响chrome_driver_path./chromedriver绝对路径优先如/opt/chromedriver避免WebDriverException: unknown error: cannot find Chrome binarypage_load_timeout20提高至45尤其抓取高星酒店页页面含大量图片/地图/评论加载慢易超时中断implicit_wait5设为8等待价格 DOM 出现find_element(By.XPATH, //div[classprice])易因 JS 加载延迟找不到max_retry2设为3应对携程临时验证码验证码弹窗时脚本会刷新页面重试3 次内大概率通过提示system.txt中所有路径请用正斜杠/Windows 也适用Pythonpathlib会自动兼容。不要用反斜杠\否则open()报错。3. 抓取逻辑拆解如何从携程酒店页 DOM 中稳提价格与房型结构3.1 携程酒店页的三层数据加载机制决定你必须用 Selenium携程酒店详情页如https://hotels.ctrip.com/hotel/123456.html的数据并非静态 HTML而是分三层加载首屏骨架 HTML含酒店名称、地址、星级但无价格AJAX 房型列表点击“选择日期”后向https://m.ctrip.com/restapi/soa2/13444/json/getHotelRoomRatePOST 请求返回 JSON 格式房型与价格动态渲染价格块前端 JS 将 JSON 解析后插入div classJ_PriceList容器并绑定 hover 事件显示“含早/取消政策”。CTripSpider的设计聪明点在于不自己拼接 AJAX 请求易被 signature 拦截而是用 Selenium 真实触发用户操作——模拟点击日历、选择入住离店日期、等待.J_PriceList出现后再解析 DOM。# main.py 中关键流程简化版 def crawl_hotel(hotel_id: str, check_in: str, check_out: str): driver init_driver() # 加载 system.txt manualType.properties url fhttps://hotels.ctrip.com/hotel/{hotel_id}.html driver.get(url) # 步骤1等待酒店基础信息加载规避首屏广告遮挡 WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CLASS_NAME, hotel-name)) ) # 步骤2点击日历图标 → 弹出日期选择器 → 输入日期非直接改 input value calendar_btn driver.find_element(By.CLASS_NAME, calendar-trigger) calendar_btn.click() time.sleep(1) # 等待日历 DOM 渲染 # 步骤3用 JS 执行日期输入绕过 disabled 属性 driver.execute_script( arguments[0].value arguments[1];, driver.find_element(By.ID, checkIn), check_in ) driver.execute_script( arguments[0].value arguments[1];, driver.find_element(By.ID, checkOut), check_out ) # 步骤4点击“搜索”按钮触发房型加载 search_btn driver.find_element(By.CLASS_NAME, search-btn) search_btn.click() # 步骤5等待价格区块出现关键 WebDriverWait(driver, 45).until( EC.presence_of_element_located((By.CLASS_NAME, J_PriceList)) ) # 步骤6交给 parser.py 提取 html driver.page_source result parse_hotel_page(html, hotel_id, check_in, check_out) save_to_csv(result)3.2 parser.py 如何从混乱 DOM 中提取结构化字段携程前端喜欢用># utils/parser.py 核心提取逻辑 def parse_hotel_page(html: str, hotel_id: str, check_in: str, check_out: str) - List[Dict]: soup BeautifulSoup(html, lxml) results [] # 1. 提取酒店基础信息静态部分 hotel_name soup.select_one(h1.hotel-name).get_text(stripTrue) address soup.select_one(.hotel-address .content).get_text(stripTrue) # 2. 定位所有房型区块每个 .J_RoomItem 是一个房型 room_items soup.select(.J_RoomItem) for room in room_items: # 关键从>ICONS_TO_TEXT { icon-breakfast: 含双早, icon-free-cancellation: 免费取消, icon-wifi: 免费WiFi, icon-parking: 免费停车, icon-spa: SPA, icon-pool: 游泳池 }为什么不用 XPath 直接取文本因为携程会动态插入span classprice-highlight¥/spanspan classnum388/spanXPath//div[classJ_Price]/text()会返回[¥, 388]需拼接。而>WebDriverWait(driver, 30).until( EC.element_to_be_clickable((By.CLASS_NAME, search-btn)) )检查parser.py中 selector 是否过时访问目标酒店页 → F12 → 搜索J_PriceList确认当前 class添加广告关闭逻辑在init_driver()后插入try: close_ad driver.find_element(By.CLASS_NAME, close-ad) close_ad.click() except: pass # 无广告则跳过4.2 现象抓取数据中 price 全为 0 或乱码如¥undefined原因>price_container driver.find_element(By.CLASS_NAME, J_PriceList) driver.execute_script(arguments[0].scrollIntoView(true);, price_container) time.sleep(1) # 确保 JS 触发渲染parser.py中BeautifulSoup初始化指定编码soup BeautifulSoup(html.encode(utf-8), lxml) # 强制 UTF-8若需登录态如抓取会员价在init_driver()后添加 Cookie 注入# system.txt 中增加 cookie_file ./cookies.json # cookies.json 格式[{name:cticket,value:xxx,domain:.ctrip.com}] cookies json.load(open(cookies.json)) for cookie in cookies: driver.add_cookie(cookie)4.3 现象TimeoutException频发尤其在WebDriverWait(..., 45)阶段原因Chrome 启动时未禁用图片加载--blink-settingsimagesEnabledfalse缺失网络 DNS 解析慢携程域名hotels.ctrip.com被本地 ISP 缓存异常page_load_timeout与WebDriverWait冲突前者超时抛异常后者未捕获。解决在get_chrome_options()中添加图片禁用options.add_argument(--blink-settingsimagesEnabledfalse)在system.txt中增加 DNS 配置Linux/macOSdns_server 114.114.114.114Windows 需手动改 hosts此处不展开包裹WebDriverWait在 try-except 中并记录失败 URLtry: WebDriverWait(driver, 45).until(...) except TimeoutException: logging.error(fTimeout on {url}, saving screenshot) driver.save_screenshot(ftimeout_{hotel_id}.png) raise4.4 现象data/目录下 CSV 文件为空或只有表头无数据原因parser.py中room_items soup.select(.J_RoomItem)返回空列表selector 错误或页面无房型hotel_id输入错误如https://hotels.ctrip.com/hotel/abc.html中abc非数字 ID携程返回“暂无房型”提示但 DOM 中无.J_RoomItem脚本未处理该分支。解决在parse_hotel_page()开头加兜底检查if not room_items: # 检查是否显示“暂无房型” no_room soup.select_one(.no-room-tip) if no_room: logging.warning(fNo rooms available for {hotel_id} on {check_in}-{check_out}) return [] # 返回空列表不中断流程 else: raise ValueError(fNo .J_RoomItem found for {hotel_id})hotel_id校验逻辑加入main.pyif not re.match(r^\d$, hotel_id): raise ValueError(fInvalid hotel_id: {hotel_id}. Must be digits only.)手动访问https://hotels.ctrip.com/hotel/{hotel_id}.html确认页面真实存在且有房型。5. 进阶技巧用 system.txt 控制抓取粒度实现多酒店多日期批量作业5.1 从单酒店单日期到批量任务的配置改造CTripSpider默认只支持main.py -i 123456 -s 2024-06-01 -e 2024-06-05单酒店 5 天。要批量抓 50 家酒店未来 30 天价格需改造system.txt和main.py的入口逻辑第一步扩展 system.txt 支持批量模式# system.txt 新增 section [batches] hotel_list_file ./config/hotels.txt date_range_days 30 concurrent_workers 3 # 同时开 3 个 Chrome 实例需内存 ≥16GB # hotels.txt 格式每行一个 hotel_id 123456 789012 345678 ...第二步修改 main.py 的主函数def main(): parser argparse.ArgumentParser() parser.add_argument(-i, --hotel_id, typestr, helpSingle hotel ID) parser.add_argument(-s, --start_date, typestr, helpStart date (YYYY-MM-DD)) parser.add_argument(-e, --end_date, typestr, helpEnd date (YYYY-MM-DD)) args parser.parse_args() # 读取 system.txt 配置 config load_system_config() # 返回 dict if args.hotel_id: # 原逻辑单任务 crawl_hotel(args.hotel_id, args.start_date, args.end_date) else: # 新逻辑批量任务 with open(config[batches][hotel_list_file]) as f: hotel_ids [line.strip() for line in f if line.strip()] # 生成日期范围未来 30 天 start datetime.now().date() end start timedelta(daysint(config[batches][date_range_days])) dates [(start timedelta(daysi)).strftime(%Y-%m-%d) for i in range((end-start).days)] # 多进程执行注意Chrome 实例间不共享 session from concurrent.futures import ProcessPoolExecutor with ProcessPoolExecutor(max_workersint(config[batches][concurrent_workers])) as executor: futures [] for hotel_id in hotel_ids: for i in range(0, len(dates), 7): # 每次抓 7 天防请求过密 batch_dates dates[i:i7] for check_in in batch_dates: check_out (datetime.strptime(check_in, %Y-%m-%d) timedelta(days1)).strftime(%Y-%m-%d) futures.append( executor.submit(crawl_hotel, hotel_id, check_in, check_out) ) # 等待全部完成 for future in futures: future.result()5.2 用 manualType.properties 实现 UA 轮换降低封禁风险单纯固定 UA 极易被识别。CTripSpider支持在manualType.properties中定义多组 UA运行时随机选用# manualType.properties 改造版 # 格式type_name User-Agent字符串 win10_chrome_124 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 macos_safari_17 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15 android_chrome_123 Mozilla/5.0 (Linux; Android 13; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.86 Mobile Safari/537.36 # 新增启用轮换 enable_ua_rotation true# utils/driver_manager.py 中 load_property 改造 def load_property(key: str): if key user_agent_type and config.get(enable_ua_rotation, false) true: # 随机选一个 type_name types [k for k in properties.keys() if k ! enable_ua_rotation] selected random.choice(types) return properties[selected] return properties.get(key, )血泪经验UA 轮换 --window-size随机化如1920x1080,1366x768,1440x900 每次请求间隔random.uniform(2.5, 5.0)秒可将单 IP 日请求上限从 200 提升至 1200且不触发图形验证码。但注意concurrent_workers 3时需配独立 IP否则仍会被限流。5.3 数据质量自检用 pandas 快速验证 CSV 字段完整性抓完data/output_*.csv后别急着导入 BI 工具。先用 10 行代码做基础校验import pandas as pd df pd.read_csv(data/output_20240520.csv) # 检查空值率 print(空值统计) print(df.isnull().sum() / len(df)) # 检查 price 异常值负数、超 10 万 print(f\nprice 异常值0 或 100000{len(df[(df[price] 0) | (df[price] 100000)])}) # 检查重复房型同一 hotel_id room_id check_in dups df.duplicated(subset[hotel_id, room_id, check_in], keepFalse) if dups.any(): print(f\n重复记录{dups.sum()} 条示例) print(df[dups].head(3)) # 检查日期格式 df[check_in] pd.to_datetime(df[check_in], errorscoerce) print(f\ncheck_in 无效日期{df[check_in].isna().sum()})输出示例及应对若price空值率 5%说明style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表