简介面向旅游评论数据分析场景压缩包提供了一套完整的“爬虫采集情感分析”项目实现方案适合想学习Python爬虫、文本情感分析或城市旅游舆情分析的开发者参考。资源共收录1988个文件压缩包约29.59MB主要包含781个JavaScript脚本、656个文本说明、199个CSV评论数据、68个TypeScript文件、63个JSON配置、57个Markdown文档以及25个Python脚本等其中CSV保存了潍坊、淄博两地的评论原始数据脚本与文档覆盖数据采集、清洗、情感打分和结果可视化等环节。目前已有587人学习下载对于入门旅游评论挖掘或城市形象分析有直接借鉴价值。通过学习这套资源可以获得可运行的爬虫代码、情感分析模型调用思路、评论数据字段说明及项目目录结构梳理尤其适合希望快速搭建同类分析流程的初中级爬虫与数据分析爱好者。1. 爬5万条城市评论做情感分析这不是一场数据狂欢而是一场清洗苦旅当你想知道一座城市在游客和居民心中到底是“值得去”还是“下次不来了”最可靠的办法不是看官方宣传片而是去爬几万条真实评论做情感分析。标题里的“5万条”并不是一个拍脑袋的数字——它刚好跨过了统计上能压住方差的门槛也让分词和情感打分的结果在可视化时能看出清晰的分布曲线。爬虫负责把散落在点评平台上的文本搬到本地情感分析则负责把这些“好”“坏”“一般”的人话翻译成可量化的分值落地之后你甚至能按区域、按商户类型拆出城市口碑的短板。这条链路听起来简单但真正动过手的人都知道坑全在细节上城市评论的反爬策略一天一个样清洗文本时表情符号和繁体字能把分词器打懵你以为的正面词在“交通”这个维度下可能是负面信号。这篇文章的目标不是给你一份说明书而是把我自己踩过的、以及同行常用的解决方案拆开讲。如果你想做的是城市舆情监测、旅游口碑分析或者本地生活服务商的选址参考这套方案可以直接复刻如果你只是想交一份课程作业它能让你省下至少两周调试代码的时间。2. 数据源选型与合规边界第一步不是写爬虫而是确认能爬什么2.1 城市评论的几类来源与选型标准城市评论的数据源大体分三类综合生活服务平台的POI点评、旅游垂直网站的景点/酒店评价、以及社交媒体上的短文本打卡内容。它们各有各的脾气——综合平台的评论结构最规整评分、标签、时间、用户等级都分好了字段但反爬强度最高旅游垂直站的文本质量最好用户会把“交通便利”“海鲜新鲜”这种维度词写得明明白白不过覆盖面窄社交媒体的评论量最大、最实时但垃圾文本和营销水军也多你需要额外花一道清洗工序。选型标准我一般看三个维度字段结构化程度、反爬对抗成本、文本对情感词的覆盖率。如果你要做的是城市口碑大盘综合平台加旅游垂直站两头抓各爬一部分再做去重补充如果目标聚焦在“某商圈餐饮口碑”这种垂直课题旅游网站的数据质量远高于综合平台。切记不要只盯着一家平台爬满5万条——同一平台的行为模式太单一情感阈值会偏置得非常厉害。2.2 爬虫的合规红线 robots、用户协议与个人信息保护在我动手之前先把合规问题讲透因为这不是“能不能绕过封禁”的问题而是“你愿不愿意把前途搭进去”的问题。国内各大平台对爬虫的态度早已从“默许”转向“签订协议才放行”你在注册开发者账号时会看到“不得使用自动化脚本采集”的条款——直白说签了协议再爬就是合同违约不签协议硬爬就是绕开技术保护措施。我的落地建议是三步走。第一步优先寻找开放接口或数据集很多高校和科研机构都有公开的城市评论语料质量不输爬取结果还能省去对抗成本第二步如果必须自采控制采集频率在1-2次请求/秒只爬公开页面内容不碰用户头像、手机号等个人字段第三步判研阶段绝不把数据商用哪怕你做的是毕业设计也要在文档里写明“仅用于学术研究数据版权归原平台所有”。这一步值得你多花半天做好记录后面做评审提交时能免掉很多麻烦。2.3 用通用爬虫框架以单城市为目标的最小可运行方案选型确认后落地就进入代码环节。这里我以通用爬虫框架配合某点评类网站为例演示一个最小可运行方案目标是单城市的餐饮类评论。框架的选择上常见做法是使用带异步支持的通用网络请求库配合HTML解析器而不是直接上重型爬虫框架——原因是城市评论的分页规则常常藏在URL参数或接口返回值里轻量组合更容易调试。下面是一个只依赖requests和parsel的版本import requests from parsel import Selector import time import random def fetch_city_comments(city_id, page_start0, max_pages50): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: fhttps://example-review-site.com/city/{city_id}/food } for page in range(max_pages): url fhttps://api.example-review-site.com/v1/reviews?city_id{city_id}offset{page_start page * 20} resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: print(f请求失败: HTTP {resp.status_code}, 跳过第{page}页) continue items resp.json().get(data, {}).get(items, []) if not items: break # 没有数据就终止循环 for item in items: yield { shop_name: item.get(shop_name, ), score: item.get(rating, 0), comment: item.get(text, ), category: item.get(category, ), publish_time: item.get(time, ) } time.sleep(random.uniform(1.0, 2.5)) # 限速 # 单城市示例city_id1010 for i, comment in enumerate(fetch_city_comments(1010, max_pages250)): if i 50000: break print(i, comment[shop_name], comment[score], comment[comment][:30])这段代码的核心逻辑是分页请求接口每页拿20条用生成器逐条吐出结构化字段到达5万条即停。参数说明city_id是城市的内部编码需要你先打开网页版找到网址里的数字offset是分页偏移量每页递增20max_pages250意味着最多请求250页也就是5000条如果你要攒到5万需要把max_pages放大到2500并配合延时扩大headers里的Referer必须保留部分接口会校验来源页面。跑通之后不要急着扩大规模先让它连续运行10分钟观察是否有报错或数据重复。2.4 为什么5万条是最低门槛方差、置信度与维度切分你可能会问为什么非要是5万这个量级我的习惯是拿它当“最低可分析门槛”。情感分析在单条文本上是很不稳定的一条“这也太绝了”既可能是夸环境也可能是骂排队只有聚合成大样本后评论在“环境”“服务”“口味”等维度上的情感分布才会收敛。做过AB测试的人都知道样本量低于8000时正面情感占比浮动3%以上都算正常而当你按城市的行政区、商户品类做交叉切片时每个切片可能只有200-1000条文本5万的基数能保证每个切片仍然有足够的统计意义不会出现某一个区因为只有30条样本就被判定为“口碑极差”的乌龙。这个数字也基本对应了覆盖一个中等城市的全部核心商圈POI评论所需的总量再多就要面临重复内容和边际递减。3. 反爬对抗与断点续爬从几十条到5万条的工程化台阶3.1 城市评论站点的反爬手段与应对策略盘点从几百条爬到5万条你的爬虫一定会撞上三堵墙。第一堵是频率限制同一IP在短时间内请求超过阈值会触发验证码第二堵是参数签名许多评论接口的URL里带有由时间戳和密钥计算出的签名你直接改offset就会发现返回401第三堵是数据混淆部分平台返回的不是标准JSON而是把评论嵌在HTML的script变量里你需要从中正则抽取。应对策略上我建议按“躲避而非对抗”的思路来。频率限制用多套UA轮换和随机延时解决延时区间不要固定1.5-3.5秒随机分布比固定2秒更难触发阈值参数签名优先寻找移动端的接口不少平台的App接口签名校验比Web端宽松得多用Charles抓一次包就能拿到字段数据混淆则走渲染日志或者直接用解析库从script标签里抽文本不追求实时性的话这类页面往往有静态化的镜像用镜像地址反而省事。记住一个准则别去碰验证码识别那是刑法里非法获取计算机信息系统数据的重灾区而且成功率低、封号率极高。3.2 随机延时、UA池与代理池的工程实现避不开的代理池还是要有备选方案的。但先说明我的默认顺序是纯随机延时加UA池跑通小批量只有当前两者失效时才上代理池。这里给出一份可以直接落地的UA池与延时配置import random import time import requests UA_POOL [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Version/17.0 Safari/605.1.15, Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 Mobile/15E148 ] def randomized_session(): sess requests.Session() sess.headers.update({User-Agent: random.choice(UA_POOL)}) return sess def safe_get(sess, url, retries3): for attempt in range(retries): try: resp sess.get(url, timeout10) if resp.status_code in (403, 429): # 退避策略拉长等待时间不更换会话避免异常行为模式 time.sleep(10 attempt * 15) continue return resp except requests.RequestException as e: print(f第{attempt 1}次请求异常: {e}) time.sleep(5) return None参数说明UA_POOL里同时放入Windows和iPhone的标识能覆盖大部分服务端设备指纹判断safe_get的退避策略里我故意没有立即更换Session而是用递增的等待时间让服务端认为你是人为操作卡顿更换Session反而容易暴露频繁新建连接的行为模式retries设为3是因为城市评论接口的临时性故障概率不低超过3次基本就是IP被标记再试下去没有意义。这里补充一点如果你在代理池方案里使用了动态住宅代理请一定确认供应商的合规经营资质避免用了来路不明的代理导致数据链路被篡改。3.3 断点续爬为什么5万条任务必须断点续爬而不是重头再来爬多了的人都有过这种体验任务跑到第3万条时网络波动代码崩溃重新跑了一遍发现平台把你标记了前3万条白爬。断点续爬的意义在这里就体现出来了。我习惯的做法是把“已爬取的数量”和“当前分页偏移量”以CSV和JSON的方式写进本地存档每成功解析一页就覆盖保存一次进程重启时读取存档决定从哪个offset继续。断点续爬还顺带解决了数据校验问题——你能直接从存档里看到已拿到的数据量和最后一条评论的时间戳和平台的评论总数做比对就能估算出是否漏页。工程上注意一点写入存档要和数据写入解耦先存持久化文件再在内存里做后续处理防止崩溃时两边损坏。3.4 数据落地CSV、Parquet与字段冗余设计爬下来的5万条评论以什么格式落盘我的选择是Parquet而不是CSV。原因很简单5万条文本加字段的CSV大概有30-50MBExcle打开就卡死而Parquet用列式存储加压缩之后可能只有10MB后面做pandas聚合统计时性能差距是三倍以上。如果你还是习惯CSV那务必在write时加上encodingutf-8-sig否则Windows平台的Excel打开中文会乱码。字段设计上保留shop_name、comment、score、category、publish_time这五个核心字段就够了额外的用户ID和地理位置字段属于个人信息范畴非必要不采集。每个字段都保留原始值不做清洗清洗放在读入阶段——比如score字段可能是字符串4.5分而不是浮点数4.5你清洗时再转换避免污染原始数据。4. 清洗破壁与情感分析从“人话”到“分值”的转化链路4.1 清洗规则表情、繁体、口语化短句与重复评论的预处理拿到5万条评论后第一件事不是写情感模型而是做数据清洗。城市评论里的噪音比一般语料严重得多“味道好[色][色]”里的emoji、“这家店超好吃”里的连续感叹号、“這家店服務態度很好”的繁体字都会被常规分词器切成碎块进而干扰情感打分。我的清洗流程是固定五步去除HTML标签和URL统一全角半角繁体转简体去除连续重复字符“好吃好吃好吃”压缩成“好吃”过滤掉纯数字、纯符号和低于5个字符的超短评论。每一步都可能伤到情感信号比如“一般一般”被压缩成“一般”就丢了程度含义但对于5万条的体量大数定理会把这些噪声淹没掉这个损失可以接受。4.2 情感分析选型SnowNLP、预训练模型与领域阈值的再训练当前做中文情感分析有三条成熟路线我按推荐指数排个序领域微调的预训练模型 基于词典的情感计算 开箱即用的情感分析库如SnowNLP。SnowNLP本身是基于电商购物评论训练的拿到城市评论上会有明显的领域偏差——“这家酒店房间很大”里的“大”在电商语境是好评但放在酒店里也确实是好评不过“位置偏远”这种在电商语料里没有体现的维度它就看不出情感倾向了。所以我的方案是用预训练模型做底座拿人工标注的2000条城市评论做二次微调把准确率从不足70%拉高到85%以上。有人会问用大模型直接零样本分析不行吗可以但成本是硬伤。5万条评论走一次大模型接口的调用费用不低而且速度慢、依赖外部服务不适合批量离线分析。更稳妥的路线是用本地跑的预训练模型做批量推理在意外的长尾样本上再用大模型抽检复核。下面给出基于某开源中文评论情感模型的微调代码骨架from datasets import Dataset from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments model_name some-chinese-sentiment-model tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForSequenceClassification.from_pretrained(model_name, num_labels2) # 假设你已经有2000条人工标注数据label 1为正0为负 train_data Dataset.from_json(manual_labeled_reviews.json) def tokenize_batch(batch): return tokenizer(batch[text], paddingmax_length, truncationTrue, max_length128) train_data train_data.map(tokenize_batch, batchedTrue) training_args TrainingArguments( output_dir./sentiment_finetuned, learning_rate2e-5, per_device_train_batch_size16, num_train_epochs3, evaluation_strategyepoch, save_total_limit1, ) trainer Trainer(modelmodel, argstraining_args, train_datasettrain_data, eval_datasettrain_data) trainer.train() trainer.save_model(./sentiment_finetuned)这段代码的核心参数说明max_length128意味着单条评论只要前128个token对于城市评论足够了再长就要截断抓主干强制截断因为关键情感词往往在句首和句尾learning_rate2e-5是微调预训练模型的标准起始值调高到1e-4容易灾难性遗忘调低到1e-6则训练缓慢num_labels2是因为我把情感简化为正负两类来处理如果你需要中性类就把num_labels改成3并准备对应数据。注意这个方案的前提是你有一批人工标注的种子数据如果没有先跳到下一节用规则法做粗标。4.3 规则词典与模型打分的互补低成本把准确率拉到80%人工标注的种子数据不是每个人都有这时候可以用“规则词典模型打分”的混合方案过渡。规则词典指的是情感词典把“赞”“惊艳”“踩雷”“难吃”这类词配上正负极性再结合否定词和程度副词如“非常”“有点”“不”做加权计算。每一条评论先跑词典打分把绝对值大于阈值的评论当成高置信样本自动标记再加入模型训练集。我实际跑过的经验是词典法单独用准确率在65%左右但能筛出大约40%的高置信样本把这两类样本和模型预测的结果取交集准确率能到80%以上。这个方法的价值在于你不用一次标完2000条——先把100条标出来做种子让词典帮你扩到800条再人工纠正其中的错误循环两轮就够训练了。4.4 情感分析结果的输出维度正面占比、负面标签与分区域聚合模型跑完5万条之后输出的不是0和1而是一个介于0到1之间的情感分。我的可视化和统计习惯是做三个维度的聚合总体情感分布直方图、按行政区的正面评论占比排序、以及按商户品类餐饮/住宿/景点的情感均值对比。这里有一个容易被忽略的细节很多评论的情感是复杂的“环境好但服务差”在序列模型里可能被综合成中性格这时候你需要回看原文前面提到的规则词典可以帮上忙——把“环境好”和“服务差”各算一个子情感分存储成两个字段而不是合并成一个分值。分区域聚合时我习惯用商圈词匹配而不是行政区编码因为城市评论里很少写行政区但常常出现“近地铁站”“在XX广场旁边”用关键词匹配商圈比强行标准化成行政区准确得多。5. 从爬取到分析踩过的坑高频翻车点与定位排障手册5.1 评论字段全是空值接口字段名对不上现象请求成功返回200但解析出来的comment字段全是空字符串其他字段正常。原因评论平台在接口版本升级后把“text”字段改名成了“content”或“reviewBody”你的代码还在用旧字段名。解决先把单页响应原文打印出来看JSON结构确认字段名后再改解析逻辑。这个坑尤其容易出现在跨城市采集时——不同城市的API版本未必一致我的习惯是写一个自适应字段名映射函数从候选字段名中挑第一个非空的值返回。5.2 评分全是5分情感分析被好评刷偏现象爬下来的5万条里打分5星的占90%但评论文本里能看到不少吐槽内容——用户习惯先给低分再在文本里骂或者平台只展示默认好评。原因多数点评平台的排序算法默认把“好评优先”爬虫顺着默认排序抓到的大部分是高分评论低分评论被压到后面几页。解决在请求参数里显式加入sorttime或sortscore的排序条件按时间正序爬取这样能同时抓到新旧评论而不是只看到好评。如果你想分析的是真实口碑一定要做这个操作否则后面的情感分析模型会因为训练分布偏差失去参考价值。5.3 爬取到一半被封IP前3万条全部丢失现象代码在凌晨3点自动停止登录平台后台发现账号被暂时限制之前存下的CSV文件只有前几百条。原因你没做断点续爬也没有实时落盘数据全部存在内存列表里崩溃即失。解决放弃一次性收集再写的做法每解析完一页就增量追加写入文件然后让主循环读取光标位置。需要强调光标位置不能只存offset因为有些平台的分页偏移量会随着新评论产生而变动要同时存最后一条评论的时间戳重启时用“时间戳小于最后一条”作为过滤条件去重。5.4 情感模型把“排队久”判断成中性甚至正面现象模型输出的“排队一小时味道也就那样”情感分56属于中性偏正人工看明显是负面。原因预训练模型没有见过“排队”这个场景词与负面情感的共现模式而“味道也就那样”里“也就那样”是典型的贬义表达但在电商语料里少。解决给模型补充领域语料从清洗后的5万条数据里抽出包含“排队”“等位”“上菜慢”“服务差”的句子人工标注200条负面样本加入训练集。另一个快速缓解手段是设置否定词词典当评论包含“没”“不”“没有”且紧跟正面词时把模型分值往负面方向修正0.15虽然粗暴但有效。5.5 文本编码直接报错UnicodeDecodeError现象用pandas读取评论CSV时报编码错误报错信息提示utf-8无法解码某个字节。原因部分评论文本包含特殊的Unicode字符比如颜文字和生僻字写入CSV时默认编码可能没有完整覆盖或者你用的是utf-8读但文件实际是gbk。解决写入时用utf-8-sig读取时先尝试utf-8再fallback到gbk或者在爬虫阶段就把文本做一次encode(utf-8, errorsignore)丢弃掉非法字符。这一步放在源头解决最省事不要在分析阶段再补。6. 情感分析结果的“可信度”验证交叉标注与置信度过滤技巧模型输出分值不等于分析结论你需要在发布结果前做一次可信度验证。我的习惯是每个批次的5万条数据里抽300条做人工盲评和模型预测比对出一致率如果一致率低于85%说明清洗或微调环节还有问题不要急着出图写结论如果一致率在85%-90%模型可以直接用超过90%的情况在我的经验里几乎没有——除非数据分布太均衡那反而是值得警惕的信号可能你的爬虫排序规则漏掉了极端负面的评论。一个很实用的置信度过滤技巧在情感分析模型的输出层后面加一个温度缩放把0.4以下和0.6以上的样本视为高置信正负明确0.4-0.6之间视为模糊样本。统计时分别输出“全部样本的情感均值”和“高置信样本的情感均值”对比两者差异。如果差异超过0.1说明模糊样本在系统性地拉偏均值这时你要人工查看模糊样本集中在哪些商户类型——往往就是“口味一般但环境很好”的连锁餐饮店这类评论按维度拆分而不是按整体均值呈现更合理。另一个验证方法是把分析结果按月份切分时间线如果负面占比在某个城市事件之后有明显突变说明管道是灵敏的如果时间线是一条直线那你的情感模型大概率把很多负面文本平滑掉了需要回炉加训。这套验证流程做下来你的分析报告拿出来才有底气——爬虫能证明数据是真实的置信度能证明分析是可信的剩下的价值交付就是你自己的业务解读了。最后说个我的教训我曾为了凑5万条把去重阈值开得很宽结果同一条评论在不同商店的页面重复采集了三千多次情感分析结果整体偏乐观。后来我强行按“评论文本哈希店铺名”做唯一约束把重复数据踢出去最终报告才经得起推敲。做这类分析数据干净比模型炫酷重要得多。希望这篇笔记能帮你少走一段弯路把精力留给真正困难的部分——从数据里读出城市的问题。本文还有配套的精品资源点击获取