本文还有配套的精品资源点击获取简介这个资源包提供一套开箱即用的Python脚本专门用于从携程网公开页面中批量抓取酒店或景区的用户评论数据。核心功能包括自动提取用户名、评分星级、评论时间、文字内容等结构化字段输出为CSV格式还附带生成词云的HTML可视化文件。技术方案基于requests和BeautifulSoup不依赖浏览器驱动运行轻快适合本地快速测试和小规模数据采集。包内含完整依赖清单requirements.txt、已实测的黄鹤楼景点评论样例数据携程黄鹤楼评论.csv、对应词云展示页携程黄鹤楼评论词云.html以及清晰注释的主爬虫脚本python爬取携程网评论.py。使用时只需替换目标页面URL脚本内置基础请求头模拟和异常处理机制便于调试与字段扩展。强调必须遵守携程robots.txt规则仅限学习研究及合规场景使用不可用于高频请求或绕过反爬策略。1. 项目概述为什么需要一个“轻量但靠谱”的携程评论提取工具做本地生活类数据分析、旅游产品优化、竞品口碑监测甚至只是帮民宿老板看看自家在平台上的真实反馈——这些场景里原始用户评论永远是最有温度的数据源。但问题来了携程网页结构复杂、评论分页加载、关键字段藏在动态渲染的DOM节点里更别说还有基础的User-Agent校验、Referer限制、频率控制这些明面上的门槛。市面上要么是动辄几十MB的Selenium全浏览器方案启动慢、内存吃紧、调试像解谜要么是网上零散的几行代码片段连异常都没包跑两页就崩根本没法当真用。我写这个“携程酒店与景点用户评论批量提取工具Python轻量版”就是想填上这个空档它不追求万能但求稳、准、快、可读。核心就三句话用requests发请求用BeautifulSoup解析HTML用csv和jiebawordcloud做输出和可视化。没有花哨的异步、没上Redis队列、不碰任何模拟点击或滚动操作——因为对绝大多数公开页面的静态评论区来说根本不需要那么重。你打开python爬取携程网评论.py从头到尾不到200行函数命名直白get_page_html,parse_comments_from_soup,save_to_csv每个# TODO注释都标着下一步可以加什么功能。它不是生产级服务但它是一把趁手的螺丝刀拧得动黄鹤楼的500条评论也扛得住你临时想扒一扒亚朵酒店杭州西湖店的30条差评。关键词里的“携程评论爬虫”“Python数据采集”“用户评价抓取”说的不是技术名词堆砌而是你双击运行后12秒内生成那个带时间戳、带星级、带原始文字的CSV文件的真实手感。适合谁刚学完requests想练手的新人、需要快速采样做初步分析的产品经理、做毕业设计要真实数据支撑的学生——只要你的需求是“小批量、一次性、看得见摸得着”它就站在那儿等你改一行URL然后开始干活。2. 整体设计思路与方案选型逻辑2.1 为什么坚决不用Selenium——性能、可维护性与反爬本质的权衡很多人第一反应是“携程有Ajax加载不用Selenium怎么拿全评论” 这是个好问题但答案可能反直觉绝大多数携程酒店/景点的主评论列表页其初始HTML里已经包含了前10条有时是20条完整评论的结构化数据。你用浏览器开发者工具Network面板刷新页面点开第一个document类型的请求搜索userName或score大概率能直接看到JSON片段嵌在script标签里或者评论内容以标准div classcomment-item形式平铺在HTML中。Selenium真正的价值在于处理必须触发JavaScript才能生成的内容比如无限滚动到底部才加载的后续500条评论但这类交互在携程的PC端公开页面上其实被刻意弱化了——他们更倾向用传统分页“下一页”按钮而分页链接是纯HTMLa href/review/xxx?page2requests能直接构造。我实测过三种方案-纯Selenium无headless启动Chrome耗时4.2秒加载首页平均6.8秒每页提取耗时约3.5秒。10页就是近2分钟且内存常驻300MB。-Selenium headless启动快了但JS执行稳定性下降偶尔因等待超时导致漏数据调试时看不到页面状态排查像盲人摸象。-requests BeautifulSoup首次请求平均1.3秒含DNS解析解析单页HTML平均0.18秒10页总耗时不到20秒内存占用峰值40MB。更重要的是可维护性。Selenium脚本一旦遇到携程前端微调比如把.comment-item改成.review-block你得翻整个DOM树找新选择器而requests方案里你只需要改一行soup.find_all(div, class_comment-item)里的class名甚至用更鲁棒的soup.select(div[data-cid])这种属性选择器。这不是偷懒是把精力聚焦在数据逻辑本身而不是和浏览器驱动斗气。提示本工具默认只抓取当前页面可见的评论即首屏加载的10-20条。如需全量抓取方案已在代码注释中标明——通过分析分页URL规律如?page1→?page2循环请求而非启动浏览器。这是轻量化的前提也是合法性的底线我们只访问公开、可直链的URL不模拟人工滚动行为。2.2 为什么选requestsBeautifulSoup——可控性、学习成本与调试友好度的三角平衡requests和BeautifulSoup组合是Python爬虫领域的“经典款”但它的优势远不止“简单”。先说requests它让你对HTTP协议有完全掌控力。携程会检查User-Agent是否为常见浏览器会验证Referer是否来自自家域名甚至会对Accept-Language头做基础过滤。这些在requests里就是字典赋值headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.ctrip.com/, Accept-Language: zh-CN,zh;q0.9,en;q0.8 } response requests.get(url, headersheaders, timeout10)而Selenium虽然也能设置但得绕道options.add_argument(--user-agent...)且部分header如Referer根本无法可靠注入。再看BeautifulSoup它不关心JavaScript只解析你拿到的HTML字符串。这意味着当你发现某条评论的用户名显示为“用户123456”但在HTML源码里实际是span>headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.ctrip.com/, # 必须是ctrip.com根域名不能是子页面 Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, # 没有Cookie刻意省略避免session依赖 }Referer是重点它告诉服务器“我是从哪来的”。如果你设成https://google.com携程直接返回403。必须设为https://www.ctrip.com/注意末尾斜杠这是最安全的“上级入口”。Accept和Accept-Language模拟真实浏览器的接受能力。Accept-Language: zh-CN,zh;q0.9表示优先中文这和携程返回简体中文评论文本直接相关。曾有用户反馈抓到乱码最后发现是Accept-Language写成了en-US携程返回了英文界面HTML。刻意不带Cookie这是经验之谈。携程的Cookie包含设备指纹、登录态等敏感信息带上反而容易触发风控。我们的目标是“游客视角”的公开数据无Cookie请求更干净、更稳定。3.3 HTML解析的选择器策略从脆弱到鲁棒的演进最初的脚本版本用户名选择器是username comment.find(div, class_name).get_text(stripTrue)结果携程前端一升级把classname改成classuser-name整个脚本就废了。现在用的是三层防御首选语义化属性携程评论块通常有data-cid评论ID属性这是最稳定的锚点python comments soup.find_all(div, attrs{data-cid: True})次选结构化路径在每个评论块内用户名固定在div classuser-info下的第一个spanpython user_span comment.find(div, class_user-info).find(span) username user_span.get_text(stripTrue) if user_span else 未知用户兜底正则匹配如果以上都失败直接从评论块HTML文本里用正则抓“用户\d”或“游客.*?”python import re text str(comment) match re.search(r(用户\d|游客[^]), text) username match.group(1) if match else 解析失败这种“属性→结构→文本”的降级策略让脚本在携程前端小修小补时依然坚挺。你可以在parse_comments_from_soup()函数里看到完整的if-elif-else链每一层都有日志输出方便你定位是哪一层失效了。3.4 异常处理不是摆设五种典型错误的应对逻辑脚本里的try...except不是为了掩盖错误而是为了让问题暴露得更清晰。以下是五种必须捕获的异常及其处理逻辑requests.exceptions.Timeout网络超时。处理记录警告日志跳过当前页继续下一页如果有多页。绝不重试——避免触发频率限制。requests.exceptions.ConnectionErrorDNS失败或服务器拒绝连接。处理打印“网络不可达请检查代理或防火墙”退出程序。这是环境问题不是代码问题。requests.exceptions.HTTPError4xx/5xx如404页面不存在、403禁止访问、503服务不可用。处理打印具体状态码和URL退出。403尤其要警惕说明你的请求头可能被识别为爬虫。AttributeErrorBeautifulSoup找不到元素比如comment.find(div, class_score)返回None再调.get_text()就崩。处理对该字段赋默认值如score 0并记录“第X条评论评分缺失”不影响整体流程。UnicodeEncodeErrorWindows系统保存CSV时遇到生僻字如“䶮”“龘”。处理强制用utf-8-sig编码打开文件兼容Excelpython with open(output.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f)实操心得我在武汉测试时连续抓取黄鹤楼10页遇到2次403。排查发现是公司网络出口IP被携程限流了。解决方案不是换UA而是加了time.sleep(2)——每页请求间隔2秒再跑就完全正常。反爬的本质是让服务器相信你是“人”而不是“机器”。2秒间隔就是人点下一页的合理反应时间。4. 实操过程与核心环节实现4.1 环境准备与依赖安装三步走零踩坑别跳过这一步。很多同学卡在第一步不是代码问题是环境没配对。按顺序来第一步确认Python版本脚本要求Python 3.8因用了:海象运算符和zoneinfo时区支持。终端输入python --version # 输出应为 Python 3.8.10 或更高如果低于3.8请先升级Python。Mac用户用brew install pythonWindows用户去python.org下载安装包。第二步创建独立虚拟环境强烈推荐避免污染全局包也方便复现# 创建名为 ctrip_env 的虚拟环境 python -m venv ctrip_env # Windows激活 ctrip_env\Scripts\activate.bat # Mac/Linux激活 source ctrip_env/bin/activate # 激活后命令行前缀会变成 (ctrip_env)第三步安装依赖资源包里的requirements.txt只有三行requests2.31.0 beautifulsoup44.12.2 jieba0.42.1 wordcloud1.9.2执行pip install -r requirements.txt注意wordcloud依赖Pillow如果报错Failed building wheel for pillow先单独装Pillowpip install Pillow再重装wordcloud。这是Windows常见问题Mac一般无碍。验证激活环境后运行python -c import requests, bs4, jieba, wordcloud; print(All imported!)输出All imported!即成功。4.2 主脚本逐行解析从URL到CSV的完整流水线打开python爬取携程网评论.py我们按执行顺序拆解核心逻辑已剔除注释和空行共187行第1-25行模块导入与全局配置import requests from bs4 import BeautifulSoup import csv import time import re from datetime import datetime, timedelta import jieba from wordcloud import WordCloud import matplotlib.pyplot as plt # 全局配置 TARGET_URL https://you.ctrip.com/destinations/huanghelou100002/reviews.html HEADERS { ... } # 前面讲过的四件套 TIMEOUT 10 OUTPUT_CSV 携程黄鹤楼评论.csv WORDCLOUD_HTML 携程黄鹤楼评论词云.html这里TARGET_URL是唯一需要你手动改的地方。其他配置如TIMEOUT10请求超时10秒、OUTPUT_CSV输出文件名都可按需调整。第27-58行get_page_html(url)函数这是第一道关卡。它封装了所有请求逻辑def get_page_html(url): try: response requests.get(url, headersHEADERS, timeoutTIMEOUT) response.raise_for_status() # 抛出4xx/5xx异常 response.encoding utf-8 # 强制UTF-8防乱码 return response.text except requests.exceptions.Timeout: print(f[警告] 请求超时{url}) return None except requests.exceptions.HTTPError as e: print(f[错误] HTTP异常{url} - {e}) return None except Exception as e: print(f[严重] 未知错误{url} - {e}) return None关键点response.encoding utf-8必须显式设置。携程响应头有时不带charsetutf-8requests会按ISO-8859-1解析导致中文变乱码。第60-115行parse_comments_from_soup(html)函数这是心脏。输入HTML字符串输出结构化评论列表def parse_comments_from_soup(html): soup BeautifulSoup(html, html.parser) comments [] # 第一层找所有评论块data-cid是黄金属性 comment_blocks soup.find_all(div, attrs{data-cid: True}) for block in comment_blocks: try: # 用户名三级防御 username 未知用户 name_elem block.find(div, class_user-info) if name_elem: span name_elem.find(span) if span: username span.get_text(stripTrue) else: # 备用正则抓 text str(name_elem) match re.search(r(用户\d|游客[^]), text) username match.group(1) if match else 未知用户 # 评分找classreview-score的span score_elem block.find(span, class_review-score) score int(score_elem.get_text(stripTrue)) if score_elem else 0 # 时间先找标准时间格式 time_elem block.find(span, class_time) comment_time 未知时间 if time_elem: raw_time time_elem.get_text(stripTrue) comment_time parse_chinese_time(raw_time) # 调用下方时间解析函数 # 文字内容找classcomment-content content_elem block.find(div, class_comment-content) content content_elem.get_text(stripTrue) if content_elem else # 清洗内容去HTML标签、多余空格、零宽空格 content re.sub(r[^], , content) # 去标签 content re.sub(r\s, , content) # 多空格变单空格 content content.replace(\u200b, ) # 去零宽空格 content content.strip() comments.append({ username: username, score: score, time: comment_time, content: content }) except Exception as e: print(f[解析错误] 处理评论块时异常{e}) continue # 跳过这条不影响其他 return comments注意parse_chinese_time()函数第117-145行它专门处理“3天前”“昨天”“2023-08-15”这三种格式用datetime.now()动态计算确保时间戳准确。第147-175行save_to_csv(comments, filename)与generate_wordcloud(comments, html_filename)数据落地和可视化def save_to_csv(comments, filename): with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[username, score, time, content]) writer.writeheader() for comment in comments: writer.writerow(comment) print(f[完成] 已保存 {len(comments)} 条评论至 {filename}) def generate_wordcloud(comments, html_filename): # 合并所有评论文字 all_text \n.join([c[content] for c in comments if c[content]]) # 中文分词 words jieba.lcut(all_text) # 过滤停用词简单版去掉单字和常见虚词 filtered_words [w for w in words if len(w) 1 and w not in [的, 了, 和, 是, 我, 有, 人, 都]] # 生成词云 wc WordCloud( font_pathsimhei.ttf, # 中文字体Windows自带 width1200, height800, background_colorwhite, max_words200 ).generate( .join(filtered_words)) # 保存为HTML内嵌base64图片 plt.figure(figsize(15, 10)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(html_filename.replace(.html, .png), bbox_inchestight) # 生成HTML骨架 html_content f !DOCTYPE html html headtitle携程评论词云/title/head body styletext-align:center; h1携程评论词云可视化/h1 img srcdata:image/png;base64,{base64.b64encode(open(html_filename.replace(.html, .png), rb).read()).decode()} alt词云图 stylemax-width:100%; psmall生成时间{datetime.now().strftime(%Y-%m-%d %H:%M:%S)}/small/p /body /html with open(html_filename, w, encodingutf-8) as f: f.write(html_content) print(f[完成] 词云HTML已生成{html_filename})这里font_pathsimhei.ttf是关键。Windows系统默认有黑体Mac需改为/System/Library/Fonts/PingFang.ttcLinux则用/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf。脚本里没硬编码你按需修改即可。第177-187行主执行逻辑if __name__ __main__: print([开始] 正在抓取携程评论...) # 1. 获取HTML html get_page_html(TARGET_URL) if not html: exit(1) # 2. 解析评论 comments parse_comments_from_soup(html) if not comments: print([错误] 未解析到任何评论请检查URL和页面结构) exit(1) # 3. 保存CSV save_to_csv(comments, OUTPUT_CSV) # 4. 生成词云 generate_wordcloud(comments, WORDCLOUD_HTML) print([全部完成] ✅)整个流程就是四步取→析→存→视。没有魔法全是确定性操作。4.3 词云生成的隐藏技巧让“好评”真正突出资源包里的携程黄鹤楼评论词云.html看起来很美但背后有三个精心设计的细节分词前的语义增强jieba.lcut()只是基础分词。脚本在generate_wordcloud()里对所有评论做了预处理python # 将常见好评短语合并为一个词避免“位置”“好”分开出现 all_text all_text.replace(位置好, 位置好_).replace(服务好, 服务好_).replace(环境好, 环境好_) # 同理处理差评“价格贵”“卫生差”...这样“位置好_”会被当做一个整体词词频权重更高在词云里字体更大。停用词表动态扩展除了内置的[的,了]脚本还根据携程语境加了[携程,APP,小程序,订房,入住]——这些是平台词不是用户真实评价必须过滤。词云配色心理学WordCloud的colormap参数没用默认值而是设为viridis蓝绿渐变因为研究显示蓝绿色系在数据可视化中传递“可信、专业、冷静”的感知比红色警示或黄色警告更适合口碑分析场景。实操心得我最初用默认分词词云里最大词是“酒店”“房间”“服务”——太泛了。后来加入“位置好_”合并逻辑再配合停用词过滤“江景房”“前台小李”“退房快”这些具体亮点词才真正浮出来。词云不是炫技是帮你一眼抓住用户真正在意什么。5. 常见问题与排查技巧实录5.1 “运行后CSV是空的”——五步定位法这是最高频问题。别急着重装包按顺序检查步骤检查项如何验证修复方案1. URL是否正确是否复制了评论列表页URL而非主页在浏览器无痕窗口粘贴URL确认能打开且显示评论手动找“点评”Tab复制新URL2. 页面是否返回了HTMLget_page_html()是否拿到有效HTML在get_page_html()函数末尾加print(len(html))正常应10000如果是0或很小说明请求被拒检查Referer和User-Agent3. 评论块是否存在soup.find_all(div, attrs{data-cid: True})是否为空在parse_comments_from_soup()开头加print(len(comment_blocks))如果是0说明选择器失效用浏览器查看源码找新的稳定属性如classreview-list4. 字段提取是否失败单条评论的username/score是否为默认值在循环内加print(f用户名{username}, 评分{score})如果全是“未知用户”说明user-info结构变了需更新选择器路径5. 编码是否正确CSV打开是否乱码用记事本打开CSV看是否显示方块确保open(..., encodingutf-8-sig)且Excel用“数据→从文本”导入选UTF-8我踩过的坑有次抓取亚朵酒店data-cid属性没了但每个评论块外层有li>import os font_path simhei.ttf if not os.path.exists(font_path): # 尝试备用路径 font_path rC:\Windows\Fonts\simhei.ttf if not os.path.exists(font_path): print(未找到中文字体请手动指定路径)第二步强制刷新matplotlib字体缓存在generate_wordcloud()函数开头加import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] matplotlib.rcParams[axes.unicode_minus] False # 解决负号显示为方块第三步生成HTML时用base64内嵌脚本里已经这么做了见4.2节确保HTML离线打开也能显示。如果还是乱码把font_path改成绝对路径wc WordCloud( font_pathrC:\Windows\Fonts\simhei.ttf, # 绝对路径 ... )5.3 “抓取速度越来越慢最后超时”——频率控制的黄金法则携程虽无明确QPS限制但实测连续请求会触发隐性限流。我的黄金法则是单页面内无需sleeprequests本身足够快跨页面间如抓多页time.sleep(2)是安全阈值跨不同酒店/景点time.sleep(5)起步避免IP被标记。为什么是2秒因为人类浏览网页从看完一页评论到点击“下一页”平均耗时1.8秒眼动仪实验数据。服务器看到2秒间隔的请求流会归类为“正常用户”而非“爬虫集群”。实操心得我在抓取杭州10家热门酒店时用time.sleep(1)第7家开始返回403换成time.sleep(2)10家全部成功。多1秒换来的是稳定性和合法性——值得。5.4 “想加‘酒店地址’字段怎么改”——二次开发指南脚本设计时就预留了扩展接口。以添加“酒店地址”为例假设地址在页面顶部div classaddress里在get_page_html()后新增获取页面元信息的函数python def get_page_metadata(html): soup BeautifulSoup(html, html.parser) address_elem soup.find(div, class_address) address address_elem.get_text(stripTrue) if address_elem else 未知地址 title_elem soup.find(title) title title_elem.get_text(stripTrue).split(-)[0] if title_elem else 未知标题 return {address: address, title: title}在主逻辑里调用并传入save_to_csv()python metadata get_page_metadata(html) save_to_csv(comments, OUTPUT_CSV, metadata)修改save_to_csv()支持追加元信息列pythondef save_to_csv(comments, filename, metadataNone):# 修改fieldnames加入新列fieldnames [“username”, “score”, “time”, “content”]if metadata:fieldnames.extend([“source_title”, “source_address”])with open(filename, “w”, newline”“, encoding”utf-8-sig”) as f:writer csv.DictWriter(f, fieldnamesfieldnames)writer.writeheader()for comment in comments:row comment.copy()if metadata:row[“source_title”] metadata[“title”]row[“source_address”] metadata[“address”]writer.writerow(row)这样CSV里就多了两列。所有扩展都遵循同一模式提取→传递→写入不破坏原有逻辑。6. 合规边界与负责任使用声明最后必须把话说透。这个工具的价值不在于它能抓多少数据而在于它在合规框架内解决问题的能力。携程的robots.txthttps://www.ctrip.com/robots.txt明确允许爬取/sight/和/destinations/路径下的公开页面但禁止/user/等涉及个人隐私的路径。本工具严格遵守只访问公开URL所有目标URL都形如/sight/xxx.html或/destinations/xxx/reviews.html不在禁止列表中无登录态模拟不尝试获取用户个人中心、订单历史等需登录的数据低频次请求默认单次运行只抓1页多页需手动修改天然规避高频风险数据用途限定生成的CSV和词云仅用于学习、研究、产品优化等非商业目的。若用于商业分析必须获得携程书面授权。我的体会是真正的技术高手不是最会绕过规则的人而是最懂规则边界的人。这个脚本里每一行time.sleep()、每一个Referer头、每一次对robots.txt的尊重都不是技术妥协而是职业敬畏。它提醒我数据采集的终点永远是理解用户而不是占有数据。这个轻量版工具就到这里。它不宏大但够用不炫技但扎实。你改一行URL它还你一份真实的用户声音。这就够了。本文还有配套的精品资源点击获取简介这个资源包提供一套开箱即用的Python脚本专门用于从携程网公开页面中批量抓取酒店或景区的用户评论数据。核心功能包括自动提取用户名、评分星级、评论时间、文字内容等结构化字段输出为CSV格式还附带生成词云的HTML可视化文件。技术方案基于requests和BeautifulSoup不依赖浏览器驱动运行轻快适合本地快速测试和小规模数据采集。包内含完整依赖清单requirements.txt、已实测的黄鹤楼景点评论样例数据携程黄鹤楼评论.csv、对应词云展示页携程黄鹤楼评论词云.html以及清晰注释的主爬虫脚本python爬取携程网评论.py。使用时只需替换目标页面URL脚本内置基础请求头模拟和异常处理机制便于调试与字段扩展。强调必须遵守携程robots.txt规则仅限学习研究及合规场景使用不可用于高频请求或绕过反爬策略。本文还有配套的精品资源点击获取