尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Scrapy+BERT实现城市评论爬取与细粒度情感分析
简介本资源是一套面向Python爬虫与文本情感分析初学者的实战项目包聚焦旅游评论数据采集与情绪倾向挖掘适用于高校数据分析课程实践、个人技能进阶及文旅行业舆情研究参考。项目完整实现从潍坊、淄博两地5万条游客评论的定向爬取、清洗、存储到情感打分与可视化分析的全流程配套代码、原始数据与分析报告一应俱全。压缩包含1988个文件29.59MB以781个JavaScript脚本支撑爬虫逻辑656个txt文本存原始评论片段199个csv文件含结构化情感分析结果如wfcomments.csv、zbcomments.csv等另有68个TypeScript文件、63个JSON配置及25个Python工具脚本辅助处理README.md.bak等文档提供环境配置与运行说明。已有587人学习下载读者可直接复用爬虫框架、调用预训练情感模型、对照多维度分析结果理解城市口碑差异并基于真实数据开展二次建模或可视化拓展。1. 为什么爬5万条城市评论做情感分析比你想象中更难也更有价值你手头有个旅游App的运营需求想搞清楚用户对“成都”“西安”“长沙”这三座网红城市的实际评价倾向不是看官方宣传稿而是真实游客在小红书、马蜂窝、大众点评等平台留下的带图带情绪的短评。但人工翻1000条就眼花5万条光下载就得卡死浏览器。这时候“利用爬虫爬取5万条城市评论并对其进行情感分析”就不是一句技术口号——它是一条从数据荒漠通向决策依据的实操路径。关键在于爬得稳、存得准、判得细。很多团队卡在第一步以为写个requests正则就能跑通结果被反爬封IP、字段错位、HTML结构突变搞崩更多人倒在第三步用现成的SnowNLP或TextBlob一跑发现“这家店服务好但上菜慢”被整体判为正面漏掉了“但”之后的负面信号。本文不讲理论定义只拆解我去年落地某文旅局舆情监测项目的真实链路如何用ScrapyRedis去重BERT微调在3天内稳定抓取52,847条有效城市评论含发布时间、平台来源、用户等级、文本正文再完成细粒度情感极性方面级打标。适合有Python基础、能搭Linux环境、需要交差又不想被线上翻车拖垮的工程师。2. 用Scrapy构建可抗反爬的城市评论爬虫从目标定位到分布式调度2.1 锁定目标平台与反爬特征为什么放弃Requests而选Scrapy爬城市评论本质是和平台的前端渲染策略、接口加密逻辑、流量监控规则打时间差。我们实测过主流平台的反爬强度大众点评首页搜索页用Ajax加载但详情页评论区是SSR直出XPath稳定但每页请求需带__ts时间戳签名且IP频次超30次/分钟触发滑块验证小红书Web端评论需登录态但App端APIhttps://www.xiaohongshu.com/explore返回JSON需破解x-s和x-t两个header参数马蜂窝评论页URL带_r随机参数且页面内评论DOM结构嵌套深div div ul li:nth-child(2) div div p正则极易断裂。Requests虽轻量但面对动态参数生成、Cookie池管理、请求队列控制时代码会迅速变成“补丁堆”。Scrapy天然支持中间件Downloader Middleware统一注入User-Agent、Referer、动态HeaderCrawlSpider规则自动提取下一页链接避免手动拼接URLRedis-based DupeFilter实现跨爬虫去重防止同一评论被重复抓取CONCURRENT_REQUESTS 8DOWNLOAD_DELAY 1.5可精细控速绕过基础频控。提示不要迷信“全自动反爬破解”。我们最终方案是——对大众点评用ScrapySplash渲染JS对小红书用逆向App API固定设备指纹对马蜂窝用PC端静态HTMLXPath容错匹配。混合策略比单工具更稳。2.2 Scrapy项目结构与核心Spiders编写以大众点评为例创建项目后关键文件结构如下city_comment_spider/ ├── scrapy.cfg ├── city_comment/ │ ├── __init__.py │ ├── items.py # 定义数据字段 │ ├── middlewares.py # 反爬中间件 │ ├── pipelines.py # 数据清洗与存储 │ ├── spiders/ │ │ ├── __init__.py │ │ └── dianping_spider.py # 主爬虫 │ └── settings.pyitems.py定义结构化字段必须严格对应后续分析需求import scrapy class CityCommentItem(scrapy.Item): platform scrapy.Field() # 平台名dianping/xiaohongshu/mafengwo city_name scrapy.Field() # 城市名成都/西安/长沙 comment_id scrapy.Field() # 评论唯一ID用于去重 user_level scrapy.Field() # 用户等级LV3/LV6/新用户 publish_time scrapy.Field() # 发布时间2023-08-12 14:22:05 content scrapy.Field() # 原始评论文本保留换行、emoji star_rating scrapy.Field() # 星级评分4.5/5.0如有 image_count scrapy.Field() # 配图数量0/3/5dianping_spider.py核心逻辑关键点已注释import scrapy from scrapy import Selector from city_comment.items import CityCommentItem import re from urllib.parse import urljoin class DianpingSpider(scrapy.Spider): name dianping allowed_domains [www.dianping.com] # 起始URL成都美食搜索页按“好评优先”排序 start_urls [https://www.dianping.com/chengdu/ch10/g110] def parse(self, response): # 提取当前页所有商户链接每个商户页含用户评论 shop_urls response.css(div.list a.item-click::attr(href)).getall() for url in shop_urls[:10]: # 每页只取前10家店防过载 yield scrapy.Request( urlurljoin(response.url, url), callbackself.parse_shop_page, meta{city: 成都} # 传递城市名 ) # 翻页提取下一页链接注意大众点评下一页是JS事件此处用模拟点击 next_page response.css(a.next::attr(href)).get() if next_page and page in next_page: yield scrapy.Request( urlurljoin(response.url, next_page), callbackself.parse, dont_filterTrue ) def parse_shop_page(self, response): # 商户页中提取评论列表 comments response.css(div.comment-list .comment-item) for comment in comments: item CityCommentItem() item[platform] dianping item[city_name] response.meta[city] # 评论ID取data-id属性比URL更稳定 item[comment_id] comment.css(::attr(data-id)).get() or # 用户等级匹配LV\d文本 level_text comment.css(.user-info .user-level::text).get() item[user_level] re.search(rLV\d, level_text).group() if level_text else 新用户 # 发布时间处理“1天前”“2023-05-20”等多格式 time_text comment.css(.time::text).get() item[publish_time] self._parse_time(time_text) # 评论正文用xpath避免CSS选择器因换行失效 content_sel comment.xpath(.//div[classcontent]/p//text()).getall() item[content] .join([t.strip() for t in content_sel]).replace(\u200b, ) # 星级评分取class中的数字如star-45→4.5 star_class comment.css(.user-info .star::attr(class)).get() item[star_rating] float(re.search(rstar-(\d), star_class).group(1)) / 10 if star_class else None # 配图数量统计img标签 item[image_count] len(comment.css(.review-pictures img).getall()) yield item def _parse_time(self, time_str): 统一解析时间字符串返回标准datetime格式 if not time_str: return None time_str time_str.strip() # 处理相对时间 if 天前 in time_str: days int(re.search(r(\d)天前, time_str).group(1)) from datetime import datetime, timedelta return (datetime.now() - timedelta(daysdays)).strftime(%Y-%m-%d %H:%M:%S) # 处理绝对时间 elif - in time_str and len(time_str) 10: try: from datetime import datetime return datetime.strptime(time_str[:10], %Y-%m-%d).strftime(%Y-%m-%d 00:00:00) except: return None return None这段代码的关键设计点dont_filterTrue允许重复访问分页URLScrapy默认去重但分页参数变化时需关闭_parse_time()方法把“3小时前”“昨天”“2023-07-15”全转成YYYY-MM-DD HH:MM:SS为后续按时间切片分析打基础content字段用XPath而非CSS因为评论正文常含br、span嵌套CSS选择器易漏内容XPath的//text()更鲁棒comment_id取>import random from scrapy.downloadermiddlewares.retry import RetryMiddleware from scrapy.utils.python import to_bytes class ProxyMiddleware: def __init__(self): # 本地代理池实际项目应接付费代理API self.proxies [ http://user:passproxy1:port, http://user:passproxy2:port, http://user:passproxy3:port ] def process_request(self, request, spider): # 对大众点评请求启用代理其他平台暂不用 if dianping.com in request.url: request.meta[proxy] random.choice(self.proxies) # 添加认证头 request.headers[Proxy-Authorization] bBasic to_bytes( base64_encoded_auth )动态Header中间件应对User-Agent检测import random class RandomUserAgentMiddleware: def __init__(self): self.user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.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/16.6 Safari/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 ] def process_request(self, request, spider): # 小红书必须带特定UA否则返回空JSON if xiaohongshu.com in request.url: request.headers[User-Agent] Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 xhs/3.32.0 else: request.headers[User-Agent] random.choice(self.user_agents) # 所有请求加Referer防防盗链 request.headers[Referer] https://www.google.com/启用中间件settings.py# 启用自定义中间件数值越小越先执行 DOWNLOADER_MIDDLEWARES { city_comment.middlewares.RandomUserAgentMiddleware: 543, city_comment.middlewares.ProxyMiddleware: 544, scrapy.downloadermiddlewares.retry.RetryMiddleware: None, # 关闭默认重试 } # 自定义重试逻辑在spider中控制 RETRY_TIMES 3 RETRY_HTTP_CODES [500, 502, 503, 504, 408, 429]注意代理池不是万能解药。我们实测发现大众点评对代理IP的响应延迟2s时会直接返回验证码页。因此在settings.py中必须设置DOWNLOAD_TIMEOUT 15超时设长防误判RANDOMIZE_DOWNLOAD_DELAY True开启随机延时避免规律性请求3. 用Redis去重与MySQL存储确保5万条评论不重复、可追溯、易分析3.1 Redis去重机制为什么不用Scrapy默认的内存去重Scrapy默认使用RFPDupeFilter基于请求URL的MD5去重。但城市评论场景下问题突出同一评论可能出现在不同商户页如用户在“钟水饺”和“龙抄手”都发了同一条“成都小吃太辣”URL带分页参数?page1,?page2但内容重复动态渲染页URL相同但实际内容不同需比对comment_id。因此必须切换为基于评论ID的去重且需跨爬虫共享。Redis是唯一选择SET结构支持O(1)查重EXPIRE可设7天过期避免历史ID占满内存支持Scrapy-Redis扩展无缝集成。安装与配置pip install scrapy-redissettings.py关键配置# 启用Scrapy-Redis调度器 SCHEDULER scrapy_redis.scheduler.Scheduler DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # Redis连接 REDIS_URL redis://:password127.0.0.1:6379/0 # 持久化爬虫中断后继续 SCHEDULER_PERSIST True # 去重键名自定义 REDIS_DUPEFILTER_KEY city_comment:dupefilter但原生RFPDupeFilter仍基于URL需重写去重逻辑。在pipelines.py中添加import redis import json class RedisDupeFilterPipeline: def __init__(self): self.redis_client redis.from_url(redis://:password127.0.0.1:6379/0) self.dupe_key city_comment:comment_ids def process_item(self, item, spider): comment_id item.get(comment_id) if not comment_id: return item # 检查ID是否存在 if self.redis_client.sismember(self.dupe_key, comment_id): spider.logger.info(fDuplicate comment ID: {comment_id}) raise DropItem(fDuplicate comment ID: {comment_id}) # 存入Redis并设7天过期 self.redis_client.sadd(self.dupe_key, comment_id) self.redis_client.expire(self.dupe_key, 7 * 24 * 3600) return item然后在settings.py中启用该PipelineITEM_PIPELINES { city_comment.pipelines.RedisDupeFilterPipeline: 200, city_comment.pipelines.MySQLPipeline: 300, }3.2 MySQL建表与高效写入字段设计决定分析上限5万条评论若存CSV后续JOIN、时间范围查询、全文检索全瘫痪。MySQL是底线选择。建表语句必须包含复合索引(platform, city_name, publish_time)支撑按平台城市时间范围快速筛选全文索引FULLTEXT(content)支持模糊搜索“火锅”“交通不便”等关键词JSON字段预留extra_info JSON存平台特有字段如小红书的note_id、马蜂窝的trip_type。CREATE TABLE city_comments ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, platform VARCHAR(20) NOT NULL COMMENT 平台名, city_name VARCHAR(20) NOT NULL COMMENT 城市名, comment_id VARCHAR(64) NOT NULL COMMENT 评论唯一ID, user_level VARCHAR(20) DEFAULT NULL COMMENT 用户等级, publish_time DATETIME NOT NULL COMMENT 发布时间, content TEXT NOT NULL COMMENT 评论正文, star_rating DECIMAL(2,1) DEFAULT NULL COMMENT 星级评分, image_count TINYINT UNSIGNED DEFAULT 0 COMMENT 配图数量, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_comment_id (comment_id), KEY idx_platform_city_time (platform,city_name,publish_time), FULLTEXT KEY ft_content (content) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT城市评论主表;pipelines.py中的MySQL写入用pymysql批量插入非逐条import pymysql from twisted.enterprise import adbapi class MySQLPipeline: def __init__(self, db_params): self.db_params db_params self.dbpool adbapi.ConnectionPool(pymysql, **db_params) classmethod def from_crawler(cls, crawler): db_params { host: crawler.settings.get(MYSQL_HOST, 127.0.0.1), port: crawler.settings.get(MYSQL_PORT, 3306), user: crawler.settings.get(MYSQL_USER, root), passwd: crawler.settings.get(MYSQL_PASSWD, ), db: crawler.settings.get(MYSQL_DBNAME, city_comment), charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor, } return cls(db_params) def process_item(self, item, spider): # 构造插入SQL忽略已存在ID sql INSERT IGNORE INTO city_comments (platform, city_name, comment_id, user_level, publish_time, content, star_rating, image_count) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) values ( item[platform], item[city_name], item[comment_id], item[user_level], item[publish_time], item[content], item[star_rating], item[image_count] ) return self.dbpool.runOperation(sql, values)提示INSERT IGNORE比REPLACE INTO更安全——后者会触发DELETEINSERT导致自增ID跳跃影响后续分页。我们实测5万条数据批量插入耗时8秒SSD8G内存。3.3 数据质量校验脚本上线前必跑的3个检查爬完数据不能直接扔给算法同学。我们写了校验脚本validate_data.py每次全量爬取后必运行import pymysql import pandas as pd def check_data_quality(): conn pymysql.connect( host127.0.0.1, userroot, password, databasecity_comment ) # 检查1空评论占比content为空或仅空白符 df pd.read_sql(SELECT COUNT(*) as total, SUM(CASE WHEN TRIM(content) THEN 1 ELSE 0 END) as empty_cnt FROM city_comments, conn) empty_ratio df.iloc[0][empty_cnt] / df.iloc[0][total] print(f空评论占比: {empty_ratio:.2%}) assert empty_ratio 0.01, 空评论过多请检查XPath提取逻辑 # 检查2时间分布合理性近30天评论应占70%以上 df pd.read_sql(SELECT COUNT(*) as cnt, DATE(publish_time) as dt FROM city_comments WHERE publish_time DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY dt ORDER BY dt DESC LIMIT 5, conn) recent_days df[cnt].sum() total pd.read_sql(SELECT COUNT(*) as total FROM city_comments, conn).iloc[0][total] recent_ratio recent_days / total print(f近30天评论占比: {recent_ratio:.2%}) assert recent_ratio 0.6, 近期数据不足可能爬虫被限速或目标页失效 # 检查3平台分布均衡性三平台数据量差异不应超3倍 df pd.read_sql(SELECT platform, COUNT(*) as cnt FROM city_comments GROUP BY platform, conn) ratio_max_min df[cnt].max() / df[cnt].min() print(f平台数据量最大/最小比: {ratio_max_min:.1f}) assert ratio_max_min 3, 某平台数据严重缺失请检查对应Spider状态 if __name__ __main__: check_data_quality()这个脚本跑完才敢把数据交给下游。它堵住了90%的线上翻车——比如某次小红书API升级导致其爬虫返回空JSON校验脚本直接报错避免了错误数据流入分析环节。4. 情感分析落地从预训练模型微调到方面级打标拒绝“玄学”输出4.1 为什么不用SnowNLP/TextBlob细粒度分析的硬伤刚接手项目时算法同事用SnowNLP跑了一遍结果令人窒息“交通方便但酒店卫生差” → 整体得分0.72正面“景点很美就是排队3小时” → 得分0.65正面“服务态度一般但饭菜味道惊艳” → 得分0.51中性。问题根源在于SnowNLP是词典规则法无法理解转折词“但”“不过”“虽然…但是…”的语义权重。而城市评论的核心价值恰恰在于识别这种矛盾表达——游客既夸“美食”又骂“打车难”这才是治理痛点。我们切换为BERT微调方案理由明确中文BERTbert-base-chinese在微博、电商评论等短文本任务上F1达89.2%哈工大论文可通过添加[CLS]层全连接输出3分类正面/中性/负面更重要的是能用方面级Aspect-Based微调让模型同时输出整体情感极性各方面交通、住宿、美食、服务、景点的独立情感分。4.2 数据标注与微调流程用500条人工标注撬动5万条分析微调BERT不需要5万条标注数据。我们采用主动学习Active Learning策略先用SnowNLP对5万条评论打初筛标签粗略分3类挑出置信度最低的1000条评论如得分0.45~0.55人工标注用这1000条训初版模型再用初版模型预测剩余数据挑出预测概率0.6的样本循环标注。最终只标注527条覆盖所有典型句式转折句“虽然价格贵但体验值回票价” → 整体正面价格方面负面体验方面正面并列句“地铁直达酒店干净就是WiFi太慢” → 交通正面、住宿正面、网络负面模糊句“还行吧没什么特别的” → 整体中性。标注格式JSONL{ text: 地铁站就在门口房间很干净但WiFi连不上, aspect_polarity: [ {aspect: 交通, polarity: 正面}, {aspect: 住宿, polarity: 正面}, {aspect: 网络, polarity: 负面} ], overall_polarity: 中性 }微调代码基于transformers库from transformers import BertTokenizer, BertModel, Trainer, TrainingArguments import torch from torch.utils.data import Dataset class CommentDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_length128): self.texts texts self.labels labels self.tokenizer tokenizer self.max_length max_length def __len__(self): return len(self.texts) def __getitem__(self, idx): text str(self.texts[idx]) label self.labels[idx] encoding self.tokenizer( text, truncationTrue, paddingmax_length, max_lengthself.max_length, return_tensorspt ) return { input_ids: encoding[input_ids].flatten(), attention_mask: encoding[attention_mask].flatten(), labels: torch.tensor(label, dtypetorch.long) } # 加载预训练模型与分词器 model_name bert-base-chinese tokenizer BertTokenizer.from_pretrained(model_name) model BertModel.from_pretrained(model_name) # 构建分类头3分类 class AspectClassifier(torch.nn.Module): def __init__(self, num_labels3): super().__init__() self.bert BertModel.from_pretrained(model_name) self.dropout torch.nn.Dropout(0.3) self.classifier torch.nn.Linear(768, num_labels) # BERT hidden_size768 def forward(self, input_ids, attention_mask): outputs self.bert(input_idsinput_ids, attention_maskattention_mask) pooled_output outputs.pooler_output pooled_output self.dropout(pooled_output) return self.classifier(pooled_output) # 训练参数 training_args TrainingArguments( output_dir./results, num_train_epochs4, per_device_train_batch_size16, per_device_eval_batch_size16, warmup_steps500, weight_decay0.01, logging_dir./logs, logging_steps10, evaluation_strategysteps, eval_steps500, save_steps1000, load_best_model_at_endTrue, ) # 初始化Trainer trainer Trainer( modelAspectClassifier(), argstraining_args, train_datasettrain_dataset, eval_dataseteval_dataset, tokenizertokenizer, ) trainer.train()注意per_device_train_batch_size16是关键。BERT-base显存占用大V100 16G卡上若设32会OOM。我们实测16是最优平衡点——batch太小收敛慢太大显存崩。4.3 方面级情感打标用规则模型双保险提升准确率纯模型打标仍有缺陷对“地铁站就在门口”这种隐含方面BERT可能漏掉“交通”对“WiFi连不上”可能判为“网络”或“设施”。我们加入规则后处理层import re def extract_aspects_and_polarity(text, model_pred): 融合模型预测与规则抽取 aspects [] # 规则抽取高频方面词覆盖95%评论 aspect_keywords { 交通: [地铁, 公交, 打车, 出租, 步行, 机场, 高铁, 车站], 住宿: [酒店, 民宿, 房间, 床, 空调, 热水, 卫生], 美食: [餐厅, 小吃, 火锅, 川菜, 口味, 辣, 甜], 服务: [前台, 服务员, 态度, 响应, 投诉, 反馈], 景点: [景区, 博物馆, 古镇, 门票, 排队, 人多] } # 先用规则找出现的方面 for aspect, keywords in aspect_keywords.items(): for kw in keywords: if kw in text: # 查模型对该方面的预测需模型支持多输出 polarity model_pred.get(aspect, 中性) aspects.append({aspect: aspect, polarity: polarity}) break # 若规则未覆盖用模型主输出整体情感 if not aspects: aspects.append({ aspect: 整体, polarity: model_pred[overall] }) return aspects # 示例调用 text 地铁站就在门口房间很干净但WiFi连不上 pred {交通: 正面, 住宿: 正面, 网络: 负面, overall: 中性} result extract_aspects_and_polarity(text, pred) # 输出: [{aspect: 交通, polarity: 正面}, {aspect: 住宿, polarity: 正面}, {aspect: 网络, polarity: 负面}]这个规则层不是替代模型而是兜底增强当模型不确定时用关键词强匹配保召回当模型错判时用业务知识修正。最终方面级F1提升12.3%对比纯模型。5. 避坑指南5万条评论爬取与分析中踩过的7个真实血泪坑5.1 现象爬虫跑着跑着突然全量返回403日志显示IP被封原因大众点评的反爬策略是“动态阈值”——不是固定100次/IP而是根据请求头、User-Agent指纹、请求间隔方差综合判定。我们初期用固定DOWNLOAD_DELAY1.5但所有请求间隔完全一致被识别为机器人。解决改用RANDOMIZE_DOWNLOAD_DELAYTrue并在settings.py中设DOWNLOAD_DELAY1.0让实际间隔在0.8~1.2秒间随机波动。同时增加COOKIES_ENABLEDTrue让Scrapy自动管理Cookie模拟真实浏览器会话。5.2 现象小红书爬到第3页开始返回JSON里data字段为空原因小红书App API的x-sheader是基于设备ID、时间戳、请求体MD5三者加密生成。我们只更新了时间戳但设备IDdevice_id在headers里硬编码导致签名失效。解决将device_id改为动态生成用uuid.uuid4().hex并在每次请求前重新计算x-s。关键代码import hashlib import time import uuid def gen_x_s(device_id, ts, data): 生成x-s header s fdevice_id{device_id}ts{ts}data{data} return hashlib.md5(s.encode()).hexdigest() # 在Request中动态注入 ts int(time.time() * 1000) device_id str(uuid.uuid4()).replace(-, ) x_s gen_x_s(device_id, ts, json.dumps(payload)) request.headers[x-s] x_s5.3 现象MySQL插入时报错Incorrect string value: \xF0\x9F\x98\x8A for column content原因Emoji表情如是4字节UTF-8而MySQL默认utf8字符集只支持3字节实际是utf8mb3。解决修改数据库、表、字段字符集为utf8mb4ALTER DATABASE city_comment CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE city_comments CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;在pymysql连接参数中强制指定charset: utf8mb4, use_unicode: True,5.4 现象BERT微调时Loss不下降始终在0.9左右震荡原因数据标注不一致。标注员A把“还行”标为中性标注员B标为正面导致标签噪声过大。解决制定《标注规范手册》明确定义正面含“赞”“推荐”“超值”“惊艳”等词且无转折负面含“失望”“差”“垃圾”“后悔”等词且无弱化词如“有点差”算负面“稍微差”算中性每100条标注后由2人交叉校验Kappa系数0.8则返工。5.5 现象方面级打标结果中“WiFi”被归为“网络”方面但业务方要求归为“住宿”原因业务语义与通用NLP术语不一致。“WiFi”在酒店场景属住宿体验在咖啡馆场景属设施体验。解决在规则层加入场景判断def get_aspect_by_context(text): if any(kw in text for kw in [酒店, 民宿, 房间, 前台]): return 住宿 elif any(kw in text for kw in [咖啡, 餐厅, 商场]): return 设施 else: return 网络5.6 现象爬取的“发布时间”字段大量为None导致时间分析失效原因XPath提取time节点时部分商户页用span classtime2023-07-15/span部分用span classtime1天前/span而我们的_parse_time()方法未覆盖“刚刚”“半小时前”等格式本文还有配套的精品资源点击获取
RELATED

相关推荐

5万条城市评论爬取与情感分析实战:从反爬到可归因热力图

5万条城市评论爬取与情感分析实战:从反爬到可归因热力图

简介:本资源是一套完整的城市旅游评论情感分析实战项目,面向Python爬虫与NLP初学者及数据分析实践者,聚焦潍坊、淄博两地游客真实评价的采集与情感倾向挖掘。项目通过结构化爬虫获取5万条原始评论,结合文本预处理、情感词典与模型…

📅 2026/10/11 13:36:31
SpringBoot2+Vue3电影订票系统:全栈源码解析与部署实战

SpringBoot2+Vue3电影订票系统:全栈源码解析与部署实战

拿到一套 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 的电影订票系统源码时,我最关心的不是代码能不能跑起来,而是这套项目能不能真正帮我把全栈开发的各个环节串起来。电影订票系统这个选题在毕设和练手项目里出现频率很高,核心原因是它的业…

📅 2026/10/11 13:36:31
手机号批量滚动抽奖:高性能前端实现与避坑指南

手机号批量滚动抽奖:高性能前端实现与避坑指南

简介:这份资源提供了一套基于JavaScript的手机号批量滚动抽奖实现代码,面向需要为年会、晚会或选秀类活动制作现场抽奖环节的前端开发者与活动策划人员。核心解决随机抽取且不重复的中奖号码问题,通过Math.random生成随机索引、维护已选集合避…

📅 2026/10/11 13:31:31
MORE NEWS

更多资讯

📰

MATLAB与HFSS联合仿真偶极子天线:自动化优化全流程解析

简介:面向天线设计与射频学习者的MATLAB与HFSS联合仿真偶极子天线资料包,解决增益与回波损耗参数获取、自动化仿真流程搭建等问题。压缩包共2个文件,均为.m脚本,体积仅2KB,包含主控脚本与增益导出脚本,可控…

📰

LaMa + OpenVINO 图像修复实践:从掩码处理到 CPU 部署

简介:面向图像处理开发者的 LaMa 图像修复 OpenVINO 演示工程,提供基于 LaMa 模型的图像修补解决方案,可用于移除图片中不需要的物体、水印或修复缺损区域,适合研究 OpenVINO 部署与图像修复技术的初中级开发者。包内共 383 个文件…

📰

旅行社财务管理系统开发实战:按团核算、Python实现与账龄分析

简介:《旅行社财务管理系统》是一套面向旅行社财务岗位与信息化开发者的内部财务管理软件,融合人工智能与信息管理系统思路,用于优化账目记录、费用报销、预算管理与利润统计等流程,降低人工差错、提升核算效率。资源包共12个文件…

📰

PHP调用Codex处理PHP特定语法【操作】:把endpoint改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

汉江平原矢量范围界线数据:Shapefile三件套解析与Python实战

简介:这份汉江平原矢量范围界线数据集面向地理信息、区域规划与土地利用等方向的研究人员和学生,用于解决区域空间边界获取与配准问题。压缩包共11个文件,约29KB,以Shapefile体系为主:.shp记录地理实体位置与形状&…

📰

免费本地AI绘图:MeiGen AI Design MCP接入ComfyUI完整教程,GPU零成本出图

【免费下载链接】MeiGen-AI-Design-MCP Supports GPT Image 2, Seedance & ComfyUI, with a 1,400 prompt library, carefully crafted hooks and a multi-task orchestration system 项目地址: https://gitcode.com/gh_mirrors/me/MeiGen-AI-Design-MCP 点击查…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬