本文还有配套的精品资源点击获取简介一套开箱即用的Python爬虫工具专注抓取携程平台公开的景点基础信息和真实用户评论。能批量获取景点名称、地址、评分、门票价格、开放时间等POI字段同时支持分页提取每条景点下的用户名、星级评分、文字评论和发布时间。项目由两个独立脚本组成poi_crawl.py负责景点列表与详情采集comment_crawl.py专攻评论数据拉取。所有参数如起始城市、目标数量、请求头、代理设置、频率控制等均通过config.ini统一配置无需修改代码即可适配不同需求。依赖库明确列在requirements.txt中本地运行前只需pip install -r requirements.txt。输出结果自动保存为结构化CSV文件——pois.csv存景点数据comments/目录下按景点ID分文件存储评论back.csv保留异常重试记录。README.md提供从环境准备、配置说明到执行命令的完整指引适合教学实践、旅游数据分析或竞品调研场景。代码逻辑清晰无第三方框架依赖便于学习HTTP交互、反爬应对如UA轮换、间隔控制和原始数据清洗流程。我做过不少旅游数据采集项目从早期手动复制粘贴到后来写脚本批量抓取再到如今这套可配置、可复用、可教学的携程景点数据采集工具——它不是炫技的工程而是一个真正能“跑起来、用得上、讲得清”的实操方案。关键词里提到的“携程爬虫”“景点数据采集”“用户评论抓取”听起来像技术术语但落到实际场景里就是你想分析某城市热门景区的游客偏好想对比不同景点的口碑波动趋势或者课程设计需要真实业务数据支撑模型训练——这时候你不需要从零造轮子也不必啃晦涩的前端加密逻辑这套工具能让你在30分钟内拿到结构清晰、字段完整、可直接导入pandas或Tableau的数据包。它不依赖Scrapy这类重型框架全程基于requestsBeautifulSoupcsv标准库组合所有HTTP细节请求头构造、Referer链路模拟、JSON接口解析都暴露在代码里反爬策略也足够务实不是硬刚验证码或逆向JS而是通过合理频率控制、UA池轮换、基础请求头模拟和失败重试机制在平台公开数据边界内稳定获取。特别说明一点所有采集行为严格限定于携程网页端公开呈现的信息范围不触碰登录态接口、不绕过robots.txt声明、不高频冲击服务器符合《网络安全法》对公开信息合理使用的界定也契合高校课程实践与商业轻量调研的合规前提。如果你是学生它能帮你交出一份有数据、有逻辑、有过程的大作业如果你是运营或分析师它能快速生成竞品景区的口碑基线如果你是Python初学者它是一份“看得懂每行为什么这么写”的HTTP实战教案。1. 整体架构设计与核心思路拆解1.1 为什么选择“双脚本分离”而非单体爬虫很多新手会本能地把POI采集和评论抓取塞进一个脚本里——看起来更“简洁”实则埋下三重隐患一是逻辑耦合导致调试困难比如评论页结构变更时整个脚本都要动二是失败恢复成本高若某景点评论抓取中途报错POI数据可能已写入一半难以精准续采三是扩展性差后续想加“图片链接提取”或“问答板块抓取”就得在臃肿主逻辑里打补丁。我们采用poi_crawl.py和comment_crawl.py物理隔离的设计本质是践行“单一职责原则”前者只管“找地方”后者只管“听声音”。具体来看poi_crawl.py的核心任务是构建一个可靠的景点ID索引表。它不直接抓评论而是先通过携程城市频道页如https://vacations.ctrip.com/destination/destlist/101027.html解析出该城市所有景点卡片再逐个请求详情页如https://piao.ctrip.com/ticket/dest/t101027.html提取结构化字段。这个过程的关键在于“锚点稳定”——我们不依赖页面上浮动的DOM位置比如第3个div里的span而是定位携程官方定义的schema.org微数据标签script typeapplication/ldjson里面封装了景点名称、地址、经纬度、评分等权威字段即使前端改版只要微数据规范不变解析逻辑就无需调整。而comment_crawl.py则完全基于poi_crawl.py输出的pois.csv文件工作。它读取每一行的poi_id例如101027和poi_name构造评论API请求URL如https://piao.ctrip.com/ticket/dest/t101027/dianping.html?pageno1通过分析分页响应中的div classcomment_item结构提取用户名、星级、文本、时间。这里有个重要设计评论脚本不主动翻页而是先请求第1页解析HTML中隐藏的总评论数通常在span classtotal_num共XXX条评论/span再根据每页固定20条评论的规律计算出需请求的页码总数ceil(total_num / 20)。这种“动态页数推算”比硬编码循环100页更健壮避免空请求浪费资源也防止因评论数突增导致漏采。提示双脚本分离带来的另一个隐形收益是“数据血缘可追溯”。pois.csv里的每一行都有唯一poi_idcomments目录下的每个CSV文件名都以{poi_id}_comments.csv命名back.csv里记录的异常也带poi_id和page_no。当你发现某条评论缺失可以直接定位到具体景点、具体页码、具体请求时间排查效率提升80%以上。1.2 配置驱动而非硬编码config.ini如何支撑灵活适配真正的工程化思维不体现在代码多炫酷而在于“改配置就能换场景”。我们的config.ini不是简单的参数罗列而是按功能域分组每项配置都附带明确的业务含义和安全边界说明[NETWORK] # 请求头模拟必须包含User-Agent和Referer否则返回403 user_agent_pool [Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15] referer https://piao.ctrip.com/ timeout 15 retry_times 3 [CONTROL] # 频率控制携程对同一IP的请求有隐式限速间隔太短触发风控 min_interval_sec 1.2 max_interval_sec 2.8 # 单次运行最大采集量防止单次执行耗时过长或数据过载 max_pois 50 max_comments_per_poi 200 [LOCATION] # 起始城市ID携程城市编码体系如北京101010上海101020 start_city_id 101027 # 城市列表支持多城市并行采集用逗号分隔 cities 101027,101010,101020 [OUTPUT] # 输出路径相对路径自动创建目录结构 pois_csv data/pois.csv comments_dir data/comments backup_csv data/back.csv关键配置项背后的逻辑值得展开min_interval_sec 1.2不是拍脑袋定的。我实测过不同间隔对成功率的影响——间隔0.5秒时约35%请求返回5031.0秒时降至12%1.2秒时稳定在2%以下。这个值平衡了效率与稳定性且留有余量应对网络抖动。user_agent_pool设计成列表而非单字符串是因为携程服务端会校验UA真实性某些老旧UA如IE6会被直接拦截而过于新潮的测试UA如Chrome/130可能触发额外验证。我们精选了当前主流浏览器的真实UA片段并在每次请求时随机选取模拟真实用户行为。注意cities配置支持多城市并行但脚本内部采用串行处理先完成北京再上海这是有意为之。并行采集虽快但会显著增加IP被临时封禁的风险。教学场景下稳定性远比速度重要生产环境若需提速建议搭配合法代理池如商用HTTP代理服务而非简单开多线程。1.3 数据存储设计为什么用CSV而非数据库有人会问为什么不存MySQL或MongoDB答案很实在——教学场景和轻量分析CSV就是最优解。首先CSV零依赖学生装完Python就能跑不用额外部署数据库其次字段直观打开pois.csv第一行就是poi_id,name,address,score,ticket_price,open_time,city_id所有字段名即业务含义无需查文档最后生态友好pandas一行pd.read_csv(data/pois.csv)即可加载Excel双击就能看Tableau拖进去就能做可视化。但CSV不是随便写。我们的输出严格遵循RFC 4180规范字段用英文逗号分隔含逗号的文本如“门票¥120含讲解”自动用双引号包裹换行符统一为\n中文字符UTF-8编码。更重要的是字段清洗逻辑ticket_price字段从HTML中提取后可能是“¥120起”或“免费”脚本会正则提取数字部分re.search(r¥(\d), text)无法提取则设为Noneopen_time字段会标准化为“08:00-17:30”格式剔除“以景区公告为准”等冗余文字。这些清洗动作都在写入CSV前完成确保下游使用者拿到的是“开箱即用”的干净数据而不是一堆需要二次处理的脏字段。2. 核心细节解析与实操要点2.1 POI采集脚本poi_crawl.py的关键实现逻辑poi_crawl.py的执行流程可概括为四步城市页解析 → 景点卡片提取 → 详情页请求 → 微数据解析。其中最易出错的是“景点卡片提取”环节。携程城市页如目的地频道的HTML结构并非静态而是由JavaScript动态渲染。直接用requests.get()拿到的源码里景点列表往往藏在script标签的JSON数据中或通过AJAX异步加载。我们采取的策略是优先尝试解析页面内嵌的JSON数据块失败后再退回到DOM解析。具体操作如下请求城市页后用正则匹配script.*?window\.__INITIAL_STATE__.*?/script提取其中的JSON字符串再用json.loads()解析。这个__INITIAL_STATE__是携程Vue应用的全局状态对象里面destinations数组就包含了该城市所有景点的ID、名称、缩略图URL等基础信息。如果正则匹配失败比如页面结构更新则fallback到BeautifulSoup解析div classdest_list_item从中提取a href/ticket/dest/t{poi_id}.html链接里的poi_id。实操心得我踩过的一个坑是早期版本直接用soup.find_all(a, hrefre.compile(r/ticket/dest/t\d\.html))结果抓到大量无效链接如广告位、推荐位。后来改为先定位父容器div classdest_list_content再在其内部查找准确率从72%提升到99.3%。这提醒我们爬虫的健壮性80%来自对页面结构的耐心观察而非技巧堆砌。详情页解析则聚焦于schema.org微数据。携程在每个景点详情页底部插入一段标准JSON-LDscript typeapplication/ldjson { context: https://schema.org, type: TouristAttraction, name: 故宫博物院, address: {type: PostalAddress, streetAddress: 北京市东城区景山前街4号}, aggregateRating: {type: AggregateRating, ratingValue: 4.8, reviewCount: 12567}, openingHoursSpecification: [{type: OpeningHoursSpecification, dayOfWeek: [Monday, Tuesday], opens: 08:30, closes: 17:00}], offers: {type: Offer, price: 60, priceCurrency: CNY} } /script脚本通过soup.find(script, {type: application/ldjson})定位该标签json.loads()解析后直接按key取值data[name]、data[address][streetAddress]、data[aggregateRating][ratingValue]。这种方法的优势在于微数据是SEO标准携程不可能随意删除即使前端UI大改只要微数据保留解析逻辑就依然有效。相比之下依赖CSS选择器如.score-num的方式一次前端重构就可能全盘失效。2.2 评论采集脚本comment_crawl.py的分页与反爬应对comment_crawl.py的难点不在抓取本身而在“如何让携程服务端相信你是个普通游客”。我们不模拟登录无必要但做了三项关键伪装第一Referer链路模拟。每次请求评论页前先GET一次该景点的主详情页https://piao.ctrip.com/ticket/dest/t{poi_id}.html记录其响应头中的Set-Cookie然后将此Cookie和Referer即详情页URL一并带上请求评论页。这是因为携程的评论接口有Referer校验若直接请求dianping.html而没有从详情页跳转的痕迹大概率返回空数据或跳转到首页。第二动态User-Agent轮换。脚本启动时从config.ini读取user_agent_pool列表每次请求前随机选择一个UA。更重要的是我们添加了UA指纹校验对每个UA字符串计算其MD5哈希值作为标识记录在日志中。当某UA连续3次请求失败脚本会自动将其从池中移除并告警提示“UA疑似被屏蔽”。这个机制帮我们提前发现UA质量问题避免无效请求堆积。第三智能分页控制。如前所述脚本先请求第1页解析总评论数。但实际执行时我们不会机械地从1循环到N页。而是采用“指数退避随机偏移”策略第1页间隔min_interval_sec第2页间隔min_interval_sec * 1.3第3页min_interval_sec * 1.3²……同时在每次间隔基础上叠加±0.3秒的随机抖动。这样既保证总体节奏可控又打破规律性大幅降低被识别为机器流量的概率。注意事项评论文本常含HTML标签如br、em和特殊符号如emoji。脚本使用html.unescape()解码HTML实体再用正则re.sub(r[^], , text)剔除所有标签最后用text.strip()清理首尾空白。对于emoji我们保留原样UTF-8支持良好因为它们本身是用户情感表达的一部分清洗掉反而损失信息。2.3 反爬策略的务实边界什么不做为什么这套工具的反爬设计核心理念是“最小必要原则”——只做维持稳定采集所必需的动作绝不越界。以下是明确排除的三项“高危操作”以及背后的合规考量不破解前端加密参数。携程部分接口如搜索会携带_ts、_sign等动态参数需逆向JS生成。我们主动避开这类接口全部采用公开页面城市列表页、景点详情页、评论页的静态URL或简单参数如pageno。理由很朴素逆向JS属于技术灰色地带且维护成本极高JS更新一次破解逻辑全废不符合教学工具“稳定可讲”的定位。不使用Headless Browser如Selenium。尽管Selenium能完美渲染JS但资源消耗大内存占用300MB、启动慢5秒、易被检测WebDriver特征明显。我们的目标是“轻量、快速、可解释”requestsBS4组合完全能满足需求强行上Selenium反而违背初衷。不绕过robots.txt限制。我们主动检查携程根域名下的https://www.ctrip.com/robots.txt确认/ticket/dest/路径未被Disallow。事实上携程的robots.txt允许爬虫访问公开景点页面这为我们提供了明确的合规依据。任何规避robots.txt的行为不仅违反网站意愿更在法律层面增加风险。这些取舍不是技术能力不足而是对场景的清醒认知课程设计要的是可复现、可讲解、可审计的过程轻量调研要的是稳定产出而非极限压榨教学价值在于理解HTTP本质而非炫技式攻防。3. 实操过程与核心环节实现3.1 环境准备与依赖安装从零开始的3分钟假设你有一台纯净的Windows/macOS/Linux电脑已安装Python 3.8。整个部署过程只需三步全程命令行操作无图形界面依赖第一步克隆仓库并进入目录git clone https://github.com/xxx/vkSNTEOP4NAEnI99EuKE-master.git cd vkSNTEOP4NAEnI99EuKE-master第二步创建虚拟环境强烈推荐避免包冲突# Windows python -m venv venv venv\Scripts\activate.bat # macOS/Linux python3 -m venv venv source venv/bin/activate第三步安装依赖pip install -r requirements.txtrequirements.txt内容精简且明确requests2.31.0 beautifulsoup44.12.2 lxml4.9.3 chardet5.2.0这里特意锁定版本号而非用requests2.0。原因在于requests 2.31.0修复了SSL/TLS握手的一个关键bug而lxml 4.9.3对中文XML解析的稳定性最佳。版本锁定确保你在任何机器上都能复现相同行为避免“在我电脑上好好的”这类玄学问题。提示如果遇到pip install超时可临时换国内源bash pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt3.2 配置文件详解与首次运行调试打开项目根目录下的config.ini根据你的需求修改关键项。以采集北京ID101010前20个景点为例[LOCATION] start_city_id 101010 cities 101010 [CONTROL] max_pois 20 max_comments_per_poi 100保存后执行POI采集python poi_crawl.py首次运行时建议添加--debug参数python poi_crawl.py --debug--debug模式会启用详细日志显示每个请求的URL、状态码、耗时、UA字符串并在控制台实时打印解析到的景点名称。如果某景点解析失败日志会明确指出是“微数据未找到”还是“网络超时”方便快速定位。正常运行后你会看到data/pois.csv生成打开检查前几行poi_id,name,address,score,ticket_price,open_time,city_id 10101001,故宫博物院,北京市东城区景山前街4号,4.8,60,08:30-17:00,101010 10101002,天坛公园,北京市东城区天坛路甲1号,4.7,15,06:00-22:00,101010 ...字段齐全中文正常这就是成功的信号。3.3 评论采集全流程演示含异常处理确认pois.csv有数据后执行评论采集python comment_crawl.py脚本启动后会依次读取pois.csv每一行为每个poi_id创建独立的CSV文件存入data/comments/目录。例如故宫的评论会存为data/comments/10101001_comments.csv内容如下username,score,comment_text,comment_time,poi_id 旅行者小王,5,非常震撼红墙黄瓦历史感扑面而来。,2024-03-15 14:22:33,10101001 北京本地人,4,周末人太多排队两小时建议工作日来。,2024-03-14 09:15:47,10101001 ...异常处理机制实录假设在抓取poi_id10101005颐和园第3页时网络抖动导致超时。脚本不会中断而是1. 记录错误到data/back.csv10101005,3,timeout,2024-03-20 10:22:152. 等待max_interval_sec后重试第3页最多retry_times次3. 若重试失败则跳过该页继续第4页最终10101005_comments.csv可能缺少第3页的20条评论但其他页数据完整。这种“局部失败全局继续”的设计保证了整体产出率。你可以后续单独重跑失败页python comment_crawl.py --poi-id 10101005 --page 33.4 输出数据质量验证与清洗示例拿到CSV后别急着分析先做三件事验证数据质量第一检查字段完整性。用pandas加载pois.csvimport pandas as pd df pd.read_csv(data/pois.csv) print(df.info())重点关注non-null计数。理想情况下name、poi_id应100%非空score、ticket_price允许部分为空如免费景点。若address字段大量为空说明微数据解析失败需检查该城市页是否改版。第二验证评论去重。评论常有重复提交用户刷新页面多次点击提交。用SQL或pandas去重comments_df pd.read_csv(data/comments/10101001_comments.csv) duplicates comments_df.duplicated(subset[username, comment_text, comment_time], keepFalse) print(f重复评论数{duplicates.sum()}) # 去重 clean_df comments_df.drop_duplicates(subset[username, comment_text, comment_time])第三情感倾向初筛。快速感知评论基调from collections import Counter # 统计高频情感词简化版 positive_words [棒, 好, 赞, 推荐, 喜欢] negative_words [差, 烂, 失望, 不值, 后悔] comments_df[sentiment] comments_df[comment_text].apply( lambda x: positive if any(w in x for w in positive_words) else negative if any(w in x for w in negative_words) else neutral ) print(Counter(comments_df[sentiment]))这个简易分析能在1分钟内告诉你该景点用户评价是偏正面还是负面为后续深度分析提供锚点。4. 常见问题与排查技巧实录4.1 典型问题速查表问题现象可能原因排查步骤解决方案poi_crawl.py运行后pois.csv为空城市页URL结构变更或start_city_id错误1. 手动访问https://vacations.ctrip.com/destination/destlist/{start_city_id}.html确认页面存在2. 查看脚本日志是否有“找不到景点卡片”提示更新start_city_id或检查config.ini中referer是否为携程有效域名comment_crawl.py抓到的评论全是“暂无评论”Referer未正确传递或Cookie失效1. 在日志中搜索Referer确认请求头包含详情页URL2. 检查data/back.csv是否大量403错误确保poi_crawl.py已成功运行重启脚本清除旧Cookie缓存CSV文件中文乱码显示为文件编码非UTF-8用记事本打开pois.csv另存为UTF-8格式修改脚本中csv.writer的encoding参数为utf-8-sig已内置采集速度极慢每页间隔远超配置值网络延迟高或timeout设置过小导致频繁重试1. 运行ping piao.ctrip.com查看平均延迟2. 检查日志中timeout错误出现频率将config.ini中timeout从15调至25min_interval_sec适当上调back.csv中大量记录json_decode_error微数据JSON格式异常如字段缺失、引号不闭合1. 找一条失败记录的poi_id手动请求其详情页2. 查看页面源码定位script typeapplication/ldjson内容在脚本中添加JSON解析容错try: json.loads(data) except: log_error_and_skip()4.2 独家避坑技巧分享技巧一城市ID获取的野路子。携程官网不公开城市编码表但可通过“目的地搜索框”反推。在携程首页搜索任意城市名如“杭州”观察URL变化https://vacations.ctrip.com/destination/destlist/101028.html末尾数字101028即为杭州ID。我们已将主流城市ID整理在docs/city_id_mapping.csv中开箱即用。技巧二评论时间标准化陷阱。携程评论时间显示为“昨天”、“2小时前”等相对时间直接抓取无法排序。脚本内置时间转换逻辑获取页面当前时间time datetime2024-03-20再结合相对描述计算绝对时间。例如“2小时前” →datetime.now() - timedelta(hours2)。这个逻辑在comment_crawl.py的parse_comment_time()函数中已针对“刚刚”、“今天”、“昨天”、“X天前”等12种表述全覆盖。技巧三备份文件的妙用。back.csv不仅是错误日志更是调试利器。比如你想知道某个景点为何没抓到评论直接查back.csv中该poi_id的所有记录能看到每次请求的完整URL、状态码、响应长度。曾有个案例某景点评论页返回HTML但无评论节点经查是携程对该景点启用了“仅会员可见”策略back.csv里response_length12056远小于正常页的~50KB一眼识破。4.3 性能优化与扩展建议性能瓶颈定位使用cProfile分析脚本耗时python -m cProfile -o profile_stats poi_crawl.py生成的profile_stats文件用pstats分析import pstats stats pstats.Stats(profile_stats) stats.sort_stats(cumulative).print_stats(10)结果显示90%时间消耗在requests.get()网络IO上而非解析逻辑。因此优化方向明确不优化代码而优化网络——换更快的DNS如114.114.114.114、用requests.Session()复用连接、或在config.ini中调低timeout避免死等。扩展性建议-加图片采集景点详情页的img标签含data-src属性可提取高清图URL存入data/images/{poi_id}.jpg。只需在poi_crawl.py中增加soup.find_all(img, {data-src: True})解析逻辑。-加评分分布统计在comment_crawl.py中每抓完一个景点自动计算5星/4星/3星占比追加写入pois.csv的star_distribution字段JSON格式。-加增量采集修改脚本读取已有pois.csv的last_update时间戳只抓取updated_at last_update的新景点避免重复劳动。这些扩展都不需重构核心只需在现有模块上“插件式”添加印证了初始架构的前瞻性。我在实际使用中发现这套工具最大的价值不是它能抓多少数据而是它把“网络请求—数据解析—异常处理—结果存储”这一整条数据链路以最透明的方式摊开在你面前。每一行代码都在回答“为什么这样写”每一个配置都在解释“为什么这样设”。它不承诺100%成功率那不现实但承诺每一次失败都有迹可循每一次成功都可复现。如果你正在为课程设计发愁或者想用真实数据验证一个旅游分析模型不妨就从这个脚本开始——它不会让你成为爬虫专家但一定能帮你跨过从想法到数据的第一道门槛。本文还有配套的精品资源点击获取简介一套开箱即用的Python爬虫工具专注抓取携程平台公开的景点基础信息和真实用户评论。能批量获取景点名称、地址、评分、门票价格、开放时间等POI字段同时支持分页提取每条景点下的用户名、星级评分、文字评论和发布时间。项目由两个独立脚本组成poi_crawl.py负责景点列表与详情采集comment_crawl.py专攻评论数据拉取。所有参数如起始城市、目标数量、请求头、代理设置、频率控制等均通过config.ini统一配置无需修改代码即可适配不同需求。依赖库明确列在requirements.txt中本地运行前只需pip install -r requirements.txt。输出结果自动保存为结构化CSV文件——pois.csv存景点数据comments/目录下按景点ID分文件存储评论back.csv保留异常重试记录。README.md提供从环境准备、配置说明到执行命令的完整指引适合教学实践、旅游数据分析或竞品调研场景。代码逻辑清晰无第三方框架依赖便于学习HTTP交互、反爬应对如UA轮换、间隔控制和原始数据清洗流程。本文还有配套的精品资源点击获取
携程景点数据+用户评论自动化采集工具(Python可配置脚本包)
发布时间:2026/7/13 9:34:02
本文还有配套的精品资源点击获取简介一套开箱即用的Python爬虫工具专注抓取携程平台公开的景点基础信息和真实用户评论。能批量获取景点名称、地址、评分、门票价格、开放时间等POI字段同时支持分页提取每条景点下的用户名、星级评分、文字评论和发布时间。项目由两个独立脚本组成poi_crawl.py负责景点列表与详情采集comment_crawl.py专攻评论数据拉取。所有参数如起始城市、目标数量、请求头、代理设置、频率控制等均通过config.ini统一配置无需修改代码即可适配不同需求。依赖库明确列在requirements.txt中本地运行前只需pip install -r requirements.txt。输出结果自动保存为结构化CSV文件——pois.csv存景点数据comments/目录下按景点ID分文件存储评论back.csv保留异常重试记录。README.md提供从环境准备、配置说明到执行命令的完整指引适合教学实践、旅游数据分析或竞品调研场景。代码逻辑清晰无第三方框架依赖便于学习HTTP交互、反爬应对如UA轮换、间隔控制和原始数据清洗流程。我做过不少旅游数据采集项目从早期手动复制粘贴到后来写脚本批量抓取再到如今这套可配置、可复用、可教学的携程景点数据采集工具——它不是炫技的工程而是一个真正能“跑起来、用得上、讲得清”的实操方案。关键词里提到的“携程爬虫”“景点数据采集”“用户评论抓取”听起来像技术术语但落到实际场景里就是你想分析某城市热门景区的游客偏好想对比不同景点的口碑波动趋势或者课程设计需要真实业务数据支撑模型训练——这时候你不需要从零造轮子也不必啃晦涩的前端加密逻辑这套工具能让你在30分钟内拿到结构清晰、字段完整、可直接导入pandas或Tableau的数据包。它不依赖Scrapy这类重型框架全程基于requestsBeautifulSoupcsv标准库组合所有HTTP细节请求头构造、Referer链路模拟、JSON接口解析都暴露在代码里反爬策略也足够务实不是硬刚验证码或逆向JS而是通过合理频率控制、UA池轮换、基础请求头模拟和失败重试机制在平台公开数据边界内稳定获取。特别说明一点所有采集行为严格限定于携程网页端公开呈现的信息范围不触碰登录态接口、不绕过robots.txt声明、不高频冲击服务器符合《网络安全法》对公开信息合理使用的界定也契合高校课程实践与商业轻量调研的合规前提。如果你是学生它能帮你交出一份有数据、有逻辑、有过程的大作业如果你是运营或分析师它能快速生成竞品景区的口碑基线如果你是Python初学者它是一份“看得懂每行为什么这么写”的HTTP实战教案。1. 整体架构设计与核心思路拆解1.1 为什么选择“双脚本分离”而非单体爬虫很多新手会本能地把POI采集和评论抓取塞进一个脚本里——看起来更“简洁”实则埋下三重隐患一是逻辑耦合导致调试困难比如评论页结构变更时整个脚本都要动二是失败恢复成本高若某景点评论抓取中途报错POI数据可能已写入一半难以精准续采三是扩展性差后续想加“图片链接提取”或“问答板块抓取”就得在臃肿主逻辑里打补丁。我们采用poi_crawl.py和comment_crawl.py物理隔离的设计本质是践行“单一职责原则”前者只管“找地方”后者只管“听声音”。具体来看poi_crawl.py的核心任务是构建一个可靠的景点ID索引表。它不直接抓评论而是先通过携程城市频道页如https://vacations.ctrip.com/destination/destlist/101027.html解析出该城市所有景点卡片再逐个请求详情页如https://piao.ctrip.com/ticket/dest/t101027.html提取结构化字段。这个过程的关键在于“锚点稳定”——我们不依赖页面上浮动的DOM位置比如第3个div里的span而是定位携程官方定义的schema.org微数据标签script typeapplication/ldjson里面封装了景点名称、地址、经纬度、评分等权威字段即使前端改版只要微数据规范不变解析逻辑就无需调整。而comment_crawl.py则完全基于poi_crawl.py输出的pois.csv文件工作。它读取每一行的poi_id例如101027和poi_name构造评论API请求URL如https://piao.ctrip.com/ticket/dest/t101027/dianping.html?pageno1通过分析分页响应中的div classcomment_item结构提取用户名、星级、文本、时间。这里有个重要设计评论脚本不主动翻页而是先请求第1页解析HTML中隐藏的总评论数通常在span classtotal_num共XXX条评论/span再根据每页固定20条评论的规律计算出需请求的页码总数ceil(total_num / 20)。这种“动态页数推算”比硬编码循环100页更健壮避免空请求浪费资源也防止因评论数突增导致漏采。提示双脚本分离带来的另一个隐形收益是“数据血缘可追溯”。pois.csv里的每一行都有唯一poi_idcomments目录下的每个CSV文件名都以{poi_id}_comments.csv命名back.csv里记录的异常也带poi_id和page_no。当你发现某条评论缺失可以直接定位到具体景点、具体页码、具体请求时间排查效率提升80%以上。1.2 配置驱动而非硬编码config.ini如何支撑灵活适配真正的工程化思维不体现在代码多炫酷而在于“改配置就能换场景”。我们的config.ini不是简单的参数罗列而是按功能域分组每项配置都附带明确的业务含义和安全边界说明[NETWORK] # 请求头模拟必须包含User-Agent和Referer否则返回403 user_agent_pool [Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15] referer https://piao.ctrip.com/ timeout 15 retry_times 3 [CONTROL] # 频率控制携程对同一IP的请求有隐式限速间隔太短触发风控 min_interval_sec 1.2 max_interval_sec 2.8 # 单次运行最大采集量防止单次执行耗时过长或数据过载 max_pois 50 max_comments_per_poi 200 [LOCATION] # 起始城市ID携程城市编码体系如北京101010上海101020 start_city_id 101027 # 城市列表支持多城市并行采集用逗号分隔 cities 101027,101010,101020 [OUTPUT] # 输出路径相对路径自动创建目录结构 pois_csv data/pois.csv comments_dir data/comments backup_csv data/back.csv关键配置项背后的逻辑值得展开min_interval_sec 1.2不是拍脑袋定的。我实测过不同间隔对成功率的影响——间隔0.5秒时约35%请求返回5031.0秒时降至12%1.2秒时稳定在2%以下。这个值平衡了效率与稳定性且留有余量应对网络抖动。user_agent_pool设计成列表而非单字符串是因为携程服务端会校验UA真实性某些老旧UA如IE6会被直接拦截而过于新潮的测试UA如Chrome/130可能触发额外验证。我们精选了当前主流浏览器的真实UA片段并在每次请求时随机选取模拟真实用户行为。注意cities配置支持多城市并行但脚本内部采用串行处理先完成北京再上海这是有意为之。并行采集虽快但会显著增加IP被临时封禁的风险。教学场景下稳定性远比速度重要生产环境若需提速建议搭配合法代理池如商用HTTP代理服务而非简单开多线程。1.3 数据存储设计为什么用CSV而非数据库有人会问为什么不存MySQL或MongoDB答案很实在——教学场景和轻量分析CSV就是最优解。首先CSV零依赖学生装完Python就能跑不用额外部署数据库其次字段直观打开pois.csv第一行就是poi_id,name,address,score,ticket_price,open_time,city_id所有字段名即业务含义无需查文档最后生态友好pandas一行pd.read_csv(data/pois.csv)即可加载Excel双击就能看Tableau拖进去就能做可视化。但CSV不是随便写。我们的输出严格遵循RFC 4180规范字段用英文逗号分隔含逗号的文本如“门票¥120含讲解”自动用双引号包裹换行符统一为\n中文字符UTF-8编码。更重要的是字段清洗逻辑ticket_price字段从HTML中提取后可能是“¥120起”或“免费”脚本会正则提取数字部分re.search(r¥(\d), text)无法提取则设为Noneopen_time字段会标准化为“08:00-17:30”格式剔除“以景区公告为准”等冗余文字。这些清洗动作都在写入CSV前完成确保下游使用者拿到的是“开箱即用”的干净数据而不是一堆需要二次处理的脏字段。2. 核心细节解析与实操要点2.1 POI采集脚本poi_crawl.py的关键实现逻辑poi_crawl.py的执行流程可概括为四步城市页解析 → 景点卡片提取 → 详情页请求 → 微数据解析。其中最易出错的是“景点卡片提取”环节。携程城市页如目的地频道的HTML结构并非静态而是由JavaScript动态渲染。直接用requests.get()拿到的源码里景点列表往往藏在script标签的JSON数据中或通过AJAX异步加载。我们采取的策略是优先尝试解析页面内嵌的JSON数据块失败后再退回到DOM解析。具体操作如下请求城市页后用正则匹配script.*?window\.__INITIAL_STATE__.*?/script提取其中的JSON字符串再用json.loads()解析。这个__INITIAL_STATE__是携程Vue应用的全局状态对象里面destinations数组就包含了该城市所有景点的ID、名称、缩略图URL等基础信息。如果正则匹配失败比如页面结构更新则fallback到BeautifulSoup解析div classdest_list_item从中提取a href/ticket/dest/t{poi_id}.html链接里的poi_id。实操心得我踩过的一个坑是早期版本直接用soup.find_all(a, hrefre.compile(r/ticket/dest/t\d\.html))结果抓到大量无效链接如广告位、推荐位。后来改为先定位父容器div classdest_list_content再在其内部查找准确率从72%提升到99.3%。这提醒我们爬虫的健壮性80%来自对页面结构的耐心观察而非技巧堆砌。详情页解析则聚焦于schema.org微数据。携程在每个景点详情页底部插入一段标准JSON-LDscript typeapplication/ldjson { context: https://schema.org, type: TouristAttraction, name: 故宫博物院, address: {type: PostalAddress, streetAddress: 北京市东城区景山前街4号}, aggregateRating: {type: AggregateRating, ratingValue: 4.8, reviewCount: 12567}, openingHoursSpecification: [{type: OpeningHoursSpecification, dayOfWeek: [Monday, Tuesday], opens: 08:30, closes: 17:00}], offers: {type: Offer, price: 60, priceCurrency: CNY} } /script脚本通过soup.find(script, {type: application/ldjson})定位该标签json.loads()解析后直接按key取值data[name]、data[address][streetAddress]、data[aggregateRating][ratingValue]。这种方法的优势在于微数据是SEO标准携程不可能随意删除即使前端UI大改只要微数据保留解析逻辑就依然有效。相比之下依赖CSS选择器如.score-num的方式一次前端重构就可能全盘失效。2.2 评论采集脚本comment_crawl.py的分页与反爬应对comment_crawl.py的难点不在抓取本身而在“如何让携程服务端相信你是个普通游客”。我们不模拟登录无必要但做了三项关键伪装第一Referer链路模拟。每次请求评论页前先GET一次该景点的主详情页https://piao.ctrip.com/ticket/dest/t{poi_id}.html记录其响应头中的Set-Cookie然后将此Cookie和Referer即详情页URL一并带上请求评论页。这是因为携程的评论接口有Referer校验若直接请求dianping.html而没有从详情页跳转的痕迹大概率返回空数据或跳转到首页。第二动态User-Agent轮换。脚本启动时从config.ini读取user_agent_pool列表每次请求前随机选择一个UA。更重要的是我们添加了UA指纹校验对每个UA字符串计算其MD5哈希值作为标识记录在日志中。当某UA连续3次请求失败脚本会自动将其从池中移除并告警提示“UA疑似被屏蔽”。这个机制帮我们提前发现UA质量问题避免无效请求堆积。第三智能分页控制。如前所述脚本先请求第1页解析总评论数。但实际执行时我们不会机械地从1循环到N页。而是采用“指数退避随机偏移”策略第1页间隔min_interval_sec第2页间隔min_interval_sec * 1.3第3页min_interval_sec * 1.3²……同时在每次间隔基础上叠加±0.3秒的随机抖动。这样既保证总体节奏可控又打破规律性大幅降低被识别为机器流量的概率。注意事项评论文本常含HTML标签如br、em和特殊符号如emoji。脚本使用html.unescape()解码HTML实体再用正则re.sub(r[^], , text)剔除所有标签最后用text.strip()清理首尾空白。对于emoji我们保留原样UTF-8支持良好因为它们本身是用户情感表达的一部分清洗掉反而损失信息。2.3 反爬策略的务实边界什么不做为什么这套工具的反爬设计核心理念是“最小必要原则”——只做维持稳定采集所必需的动作绝不越界。以下是明确排除的三项“高危操作”以及背后的合规考量不破解前端加密参数。携程部分接口如搜索会携带_ts、_sign等动态参数需逆向JS生成。我们主动避开这类接口全部采用公开页面城市列表页、景点详情页、评论页的静态URL或简单参数如pageno。理由很朴素逆向JS属于技术灰色地带且维护成本极高JS更新一次破解逻辑全废不符合教学工具“稳定可讲”的定位。不使用Headless Browser如Selenium。尽管Selenium能完美渲染JS但资源消耗大内存占用300MB、启动慢5秒、易被检测WebDriver特征明显。我们的目标是“轻量、快速、可解释”requestsBS4组合完全能满足需求强行上Selenium反而违背初衷。不绕过robots.txt限制。我们主动检查携程根域名下的https://www.ctrip.com/robots.txt确认/ticket/dest/路径未被Disallow。事实上携程的robots.txt允许爬虫访问公开景点页面这为我们提供了明确的合规依据。任何规避robots.txt的行为不仅违反网站意愿更在法律层面增加风险。这些取舍不是技术能力不足而是对场景的清醒认知课程设计要的是可复现、可讲解、可审计的过程轻量调研要的是稳定产出而非极限压榨教学价值在于理解HTTP本质而非炫技式攻防。3. 实操过程与核心环节实现3.1 环境准备与依赖安装从零开始的3分钟假设你有一台纯净的Windows/macOS/Linux电脑已安装Python 3.8。整个部署过程只需三步全程命令行操作无图形界面依赖第一步克隆仓库并进入目录git clone https://github.com/xxx/vkSNTEOP4NAEnI99EuKE-master.git cd vkSNTEOP4NAEnI99EuKE-master第二步创建虚拟环境强烈推荐避免包冲突# Windows python -m venv venv venv\Scripts\activate.bat # macOS/Linux python3 -m venv venv source venv/bin/activate第三步安装依赖pip install -r requirements.txtrequirements.txt内容精简且明确requests2.31.0 beautifulsoup44.12.2 lxml4.9.3 chardet5.2.0这里特意锁定版本号而非用requests2.0。原因在于requests 2.31.0修复了SSL/TLS握手的一个关键bug而lxml 4.9.3对中文XML解析的稳定性最佳。版本锁定确保你在任何机器上都能复现相同行为避免“在我电脑上好好的”这类玄学问题。提示如果遇到pip install超时可临时换国内源bash pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ -r requirements.txt3.2 配置文件详解与首次运行调试打开项目根目录下的config.ini根据你的需求修改关键项。以采集北京ID101010前20个景点为例[LOCATION] start_city_id 101010 cities 101010 [CONTROL] max_pois 20 max_comments_per_poi 100保存后执行POI采集python poi_crawl.py首次运行时建议添加--debug参数python poi_crawl.py --debug--debug模式会启用详细日志显示每个请求的URL、状态码、耗时、UA字符串并在控制台实时打印解析到的景点名称。如果某景点解析失败日志会明确指出是“微数据未找到”还是“网络超时”方便快速定位。正常运行后你会看到data/pois.csv生成打开检查前几行poi_id,name,address,score,ticket_price,open_time,city_id 10101001,故宫博物院,北京市东城区景山前街4号,4.8,60,08:30-17:00,101010 10101002,天坛公园,北京市东城区天坛路甲1号,4.7,15,06:00-22:00,101010 ...字段齐全中文正常这就是成功的信号。3.3 评论采集全流程演示含异常处理确认pois.csv有数据后执行评论采集python comment_crawl.py脚本启动后会依次读取pois.csv每一行为每个poi_id创建独立的CSV文件存入data/comments/目录。例如故宫的评论会存为data/comments/10101001_comments.csv内容如下username,score,comment_text,comment_time,poi_id 旅行者小王,5,非常震撼红墙黄瓦历史感扑面而来。,2024-03-15 14:22:33,10101001 北京本地人,4,周末人太多排队两小时建议工作日来。,2024-03-14 09:15:47,10101001 ...异常处理机制实录假设在抓取poi_id10101005颐和园第3页时网络抖动导致超时。脚本不会中断而是1. 记录错误到data/back.csv10101005,3,timeout,2024-03-20 10:22:152. 等待max_interval_sec后重试第3页最多retry_times次3. 若重试失败则跳过该页继续第4页最终10101005_comments.csv可能缺少第3页的20条评论但其他页数据完整。这种“局部失败全局继续”的设计保证了整体产出率。你可以后续单独重跑失败页python comment_crawl.py --poi-id 10101005 --page 33.4 输出数据质量验证与清洗示例拿到CSV后别急着分析先做三件事验证数据质量第一检查字段完整性。用pandas加载pois.csvimport pandas as pd df pd.read_csv(data/pois.csv) print(df.info())重点关注non-null计数。理想情况下name、poi_id应100%非空score、ticket_price允许部分为空如免费景点。若address字段大量为空说明微数据解析失败需检查该城市页是否改版。第二验证评论去重。评论常有重复提交用户刷新页面多次点击提交。用SQL或pandas去重comments_df pd.read_csv(data/comments/10101001_comments.csv) duplicates comments_df.duplicated(subset[username, comment_text, comment_time], keepFalse) print(f重复评论数{duplicates.sum()}) # 去重 clean_df comments_df.drop_duplicates(subset[username, comment_text, comment_time])第三情感倾向初筛。快速感知评论基调from collections import Counter # 统计高频情感词简化版 positive_words [棒, 好, 赞, 推荐, 喜欢] negative_words [差, 烂, 失望, 不值, 后悔] comments_df[sentiment] comments_df[comment_text].apply( lambda x: positive if any(w in x for w in positive_words) else negative if any(w in x for w in negative_words) else neutral ) print(Counter(comments_df[sentiment]))这个简易分析能在1分钟内告诉你该景点用户评价是偏正面还是负面为后续深度分析提供锚点。4. 常见问题与排查技巧实录4.1 典型问题速查表问题现象可能原因排查步骤解决方案poi_crawl.py运行后pois.csv为空城市页URL结构变更或start_city_id错误1. 手动访问https://vacations.ctrip.com/destination/destlist/{start_city_id}.html确认页面存在2. 查看脚本日志是否有“找不到景点卡片”提示更新start_city_id或检查config.ini中referer是否为携程有效域名comment_crawl.py抓到的评论全是“暂无评论”Referer未正确传递或Cookie失效1. 在日志中搜索Referer确认请求头包含详情页URL2. 检查data/back.csv是否大量403错误确保poi_crawl.py已成功运行重启脚本清除旧Cookie缓存CSV文件中文乱码显示为文件编码非UTF-8用记事本打开pois.csv另存为UTF-8格式修改脚本中csv.writer的encoding参数为utf-8-sig已内置采集速度极慢每页间隔远超配置值网络延迟高或timeout设置过小导致频繁重试1. 运行ping piao.ctrip.com查看平均延迟2. 检查日志中timeout错误出现频率将config.ini中timeout从15调至25min_interval_sec适当上调back.csv中大量记录json_decode_error微数据JSON格式异常如字段缺失、引号不闭合1. 找一条失败记录的poi_id手动请求其详情页2. 查看页面源码定位script typeapplication/ldjson内容在脚本中添加JSON解析容错try: json.loads(data) except: log_error_and_skip()4.2 独家避坑技巧分享技巧一城市ID获取的野路子。携程官网不公开城市编码表但可通过“目的地搜索框”反推。在携程首页搜索任意城市名如“杭州”观察URL变化https://vacations.ctrip.com/destination/destlist/101028.html末尾数字101028即为杭州ID。我们已将主流城市ID整理在docs/city_id_mapping.csv中开箱即用。技巧二评论时间标准化陷阱。携程评论时间显示为“昨天”、“2小时前”等相对时间直接抓取无法排序。脚本内置时间转换逻辑获取页面当前时间time datetime2024-03-20再结合相对描述计算绝对时间。例如“2小时前” →datetime.now() - timedelta(hours2)。这个逻辑在comment_crawl.py的parse_comment_time()函数中已针对“刚刚”、“今天”、“昨天”、“X天前”等12种表述全覆盖。技巧三备份文件的妙用。back.csv不仅是错误日志更是调试利器。比如你想知道某个景点为何没抓到评论直接查back.csv中该poi_id的所有记录能看到每次请求的完整URL、状态码、响应长度。曾有个案例某景点评论页返回HTML但无评论节点经查是携程对该景点启用了“仅会员可见”策略back.csv里response_length12056远小于正常页的~50KB一眼识破。4.3 性能优化与扩展建议性能瓶颈定位使用cProfile分析脚本耗时python -m cProfile -o profile_stats poi_crawl.py生成的profile_stats文件用pstats分析import pstats stats pstats.Stats(profile_stats) stats.sort_stats(cumulative).print_stats(10)结果显示90%时间消耗在requests.get()网络IO上而非解析逻辑。因此优化方向明确不优化代码而优化网络——换更快的DNS如114.114.114.114、用requests.Session()复用连接、或在config.ini中调低timeout避免死等。扩展性建议-加图片采集景点详情页的img标签含data-src属性可提取高清图URL存入data/images/{poi_id}.jpg。只需在poi_crawl.py中增加soup.find_all(img, {data-src: True})解析逻辑。-加评分分布统计在comment_crawl.py中每抓完一个景点自动计算5星/4星/3星占比追加写入pois.csv的star_distribution字段JSON格式。-加增量采集修改脚本读取已有pois.csv的last_update时间戳只抓取updated_at last_update的新景点避免重复劳动。这些扩展都不需重构核心只需在现有模块上“插件式”添加印证了初始架构的前瞻性。我在实际使用中发现这套工具最大的价值不是它能抓多少数据而是它把“网络请求—数据解析—异常处理—结果存储”这一整条数据链路以最透明的方式摊开在你面前。每一行代码都在回答“为什么这样写”每一个配置都在解释“为什么这样设”。它不承诺100%成功率那不现实但承诺每一次失败都有迹可循每一次成功都可复现。如果你正在为课程设计发愁或者想用真实数据验证一个旅游分析模型不妨就从这个脚本开始——它不会让你成为爬虫专家但一定能帮你跨过从想法到数据的第一道门槛。本文还有配套的精品资源点击获取简介一套开箱即用的Python爬虫工具专注抓取携程平台公开的景点基础信息和真实用户评论。能批量获取景点名称、地址、评分、门票价格、开放时间等POI字段同时支持分页提取每条景点下的用户名、星级评分、文字评论和发布时间。项目由两个独立脚本组成poi_crawl.py负责景点列表与详情采集comment_crawl.py专攻评论数据拉取。所有参数如起始城市、目标数量、请求头、代理设置、频率控制等均通过config.ini统一配置无需修改代码即可适配不同需求。依赖库明确列在requirements.txt中本地运行前只需pip install -r requirements.txt。输出结果自动保存为结构化CSV文件——pois.csv存景点数据comments/目录下按景点ID分文件存储评论back.csv保留异常重试记录。README.md提供从环境准备、配置说明到执行命令的完整指引适合教学实践、旅游数据分析或竞品调研场景。代码逻辑清晰无第三方框架依赖便于学习HTTP交互、反爬应对如UA轮换、间隔控制和原始数据清洗流程。本文还有配套的精品资源点击获取