ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Scrapy爬虫实战:豆瓣电影评论数据采集、清洗与情感分析全流程解析

Scrapy爬虫实战:豆瓣电影评论数据采集、清洗与情感分析全流程解析 简介本资源是一套完整的豆瓣电影TOP250短评数据挖掘与中文情感分析实战项目面向Python爬虫初学者、NLP入门者及数据分析爱好者解决从网页抓取、多源数据清洗、统计可视化到多种主流模型构建的全流程实践问题。压缩包含214个文件73.43MB涵盖Scrapy爬虫脚本.py、结构化数据.json、分析结果图表.png、Jupyter实验笔记.ipynb、预训练词向量.vec及模型文件.bin/.pyc支持即开即用与模块化学习。已有3772人下载学习项目严格按「movie_item–movie_comment–movie_people」三维度组织不仅提供肖申克的救赎等典型影片的词云与多模型对比朴素贝叶斯/SVM/FastText/CNN/RNN/GRU还包含跨表协同分析、地域分布热力图、导演/演员/语言等多维统计及基于简介与短评的联合情感建模具备完整工程闭环与可复现性。1. 项目概述从数据采集到情感洞察的全链路实践最近在复盘几个数据项目时我又把经典的豆瓣电影TOP250短评爬取与分析案例拿出来跑了一遍。这个项目之所以常做常新是因为它几乎涵盖了数据工程与数据分析的一个标准微型闭环从目标网站豆瓣电影定向采集数据Scrapy爬虫到对非规整的文本数据进行清洗与规整数据清理再到基础的描述性统计数据分析最后尝试构建一个能理解中文文本情感的模型情感分析模型。整个过程就像把一堆原始的矿石网页数据开采出来经过多道工序的提炼和加工最终得到有商业或研究价值的金属情感倾向洞察。对于想入门数据科学或者希望系统性掌握从数据获取到价值提取全流程的朋友来说这是一个绝佳的练手项目。它不仅要求你懂点Python和爬虫还考验你对脏数据的处理耐心以及对自然语言处理NLP基础模型的理解与应用能力。这个项目的核心价值在于其完整性和实用性。你最终得到的不是一个孤立的脚本或模型而是一套可以复用于其他类似场景如电商评论、社交媒体舆情的方法论。通过它你可以清晰地看到数据是如何一步步从网页上的杂乱文字变成结构化的表格再转化为模型可以“理解”的特征最终输出一个判断正面、负面或中性。接下来我将拆解这个全链路过程中的每一个关键环节分享我实际操作中积累的配置细节、踩过的坑以及提升效率的小技巧。2. 项目整体设计与核心思路拆解2.1 目标定义与技术栈选型我们的终极目标是分析豆瓣TOP250电影短评的情感倾向。要实现它需要拆解为四个顺序执行的阶段每个阶段对应不同的技术任务数据采集从豆瓣电影TOP250榜单页面开始逐部电影地抓取其短评页面的数据包括评论内容、评分、评论时间、有用数等。数据清理与存储爬取到的原始数据包含HTML标签、不规则字符、缺失值等需要进行清洗。清洗后的规整数据需要持久化存储供后续分析使用。探索性数据分析对清洗后的数据进行基本的统计分析例如评分分布、评论长度分布、高频词汇等以直观了解数据全貌。情感分析模型构建利用清理后的评论文本训练或微调一个能够自动判断评论情感极性的分类模型。基于以上任务技术栈的选择就非常明确了爬虫框架Scrapy。相比requestsBeautifulSoup的手工组合Scrapy是一个为大规模、结构化爬取而生的异步框架。它内置的请求调度、去重、管道Pipeline和中间件Middleware机制能让我们更专注于数据提取规则XPath/CSS选择器的编写而不用操心并发、队列等底层问题。对于豆瓣这种有一定反爬策略的网站Scrapy的中间件可以方便地集成代理、自定义User-Agent等反反爬措施。数据存储SQLite / MySQL / CSV。对于这个量级的数据250部电影 * 每部约20-40条评论总计约数千到一万条SQLite这种轻量级数据库完全够用且便于移植。如果考虑后续扩展或团队协作MySQL是更正式的选择。在初期快速验证阶段直接导出为CSV文件进行分析也未尝不可。数据分析Pandas Matplotlib/Seaborn。Pandas是Python数据分析的事实标准其DataFrame结构非常适合进行数据清洗、转换和聚合。Matplotlib和Seaborn则用于将分析结果可视化生成直观的图表。情感分析模型Jieba Sklearn / Transformers。中文文本处理首先要分词Jieba是久经考验的分词库。对于模型部分有两种主流路径一是采用传统机器学习方法如使用sklearn的朴素贝叶斯、支持向量机SVM等结合TF-IDF特征二是使用预训练的深度学习模型如Hugging Face的Transformers库中的BERT、RoBERTa等进行微调。前者速度快、可解释性强适合入门后者效果好、更能理解上下文但需要更多计算资源。注意在爬虫环节务必严格遵守豆瓣的robots.txt协议并设置合理的请求间隔如DOWNLOAD_DELAY 2秒避免对目标服务器造成压力这也是一个负责任的爬虫工程师的基本素养。2.2 爬虫策略与反爬应对思路豆瓣网对爬虫有一定的防御措施。直接粗暴请求很容易被识别并限制访问。我们的爬虫策略需要更加“拟人化”和“友好化”。入口与分页入口是固定的豆瓣电影TOP250页面。爬虫需要先解析这个页面提取出250部电影的详情页链接。对于每部电影的短评通常需要处理分页。观察URL规律短评分页通常通过start参数控制如?start20。请求头Headers定制这是最基本也是最有效的一步。必须在Scrapy的Request中模拟真实浏览器的Headers特别是User-Agent。最好准备一个User-Agent列表在中间件中随机切换。请求延迟与并发控制在settings.py中设置DOWNLOAD_DELAY如2秒和CONCURRENT_REQUESTS_PER_DOMAIN如1降低请求频率模拟人类浏览速度。IP代理池的考量对于大规模爬取IP被封锁是常见问题。可以考虑使用IP代理池。但在本项目数据量下通过严格遵守延迟规则通常可以避免触发IP封锁。如果必须使用可以编写一个下载器中间件来集成代理。Cookie的处理部分内容如查看更多短评可能需要登录后的Cookie。我们可以先手动登录豆瓣在浏览器开发者工具中复制Cookie字符串在Scrapy Request中携带。但请注意Cookie会过期且本项目爬取公开短评通常无需登录。动态内容的应对豆瓣的短评页面是静态的但有些网站评论是通过JavaScript动态加载的。如果遇到这种情况传统的Scrapy无法直接获取。这时就需要用到像Splash或Scrapy-Playwright这样的工具来渲染页面。从相关热词scrapy playwright 动态 iframe可以看出这是当前处理动态网页的主流方案。虽然豆瓣短评不需要但掌握这一技能对爬虫工程师至关重要。3. Scrapy爬虫核心细节与实操要点3.1 创建Scrapy项目与爬虫结构首先通过命令行创建项目骨架这是保持代码组织清晰的基础。scrapy startproject douban_top250 cd douban_top250 scrapy genspider movie_spider movie.douban.com这会产生一个标准的项目结构。我们需要重点关注以下几个文件items.py: 定义我们要爬取的数据结构类似字典的模板。spiders/movie_spider.py: 爬虫的核心逻辑包括如何发起请求和解析响应。pipelines.py: 数据清洗和存储的逻辑。middlewares.py: 自定义中间件用于处理请求和响应如设置代理、更换User-Agent。settings.py: 项目全局设置如延迟、并发、启用的组件。3.2 定义数据模型与解析规则在items.py中我们定义一个DoubanCommentItem类明确要抓取的字段。import scrapy class DoubanCommentItem(scrapy.Item): # 定义字段像定义数据库表的列 movie_name scrapy.Field() # 电影名称 movie_rank scrapy.Field() # TOP250排名 comment_content scrapy.Field() # 评论内容 comment_rating scrapy.Field() # 用户评分五星制如“力荐”对应5 comment_time scrapy.Field() # 评论时间 comment_votes scrapy.Field() # “有用”数 # 还可以添加用户ID、城市等信息根据需求定接下来是重头戏在movie_spider.py中编写爬虫逻辑。核心是parse方法以及可能有的parse_comment方法。import scrapy from ..items import DoubanCommentItem from urllib.parse import urljoin class MovieSpiderSpider(scrapy.Spider): name movie_spider allowed_domains [movie.douban.com] start_urls [https://movie.douban.com/top250] def parse(self, response): # 1. 解析TOP250列表页获取每部电影的链接和排名 movie_elements response.css(.grid_view .item) for idx, movie in enumerate(movie_elements, start1): movie_detail_url movie.css(.hd a::attr(href)).get() movie_name movie.css(.title::text).get().strip() # 构建请求传入电影名和排名作为meta用于在回调函数中传递 yield scrapy.Request( urlmovie_detail_url, callbackself.parse_movie_detail, meta{movie_name: movie_name, movie_rank: idx} ) # 2. 处理TOP250列表的分页如果有 next_page response.css(.paginator .next a::attr(href)).get() if next_page: next_page_url urljoin(response.url, next_page) yield scrapy.Request(urlnext_page_url, callbackself.parse) def parse_movie_detail(self, response): # 进入电影详情页寻找短评链接 movie_name response.meta[movie_name] movie_rank response.meta[movie_rank] # 短评页面的URL模式 comments_url response.url comments?statusP # 请求短评页面并传递电影信息 yield scrapy.Request( urlcomments_url, callbackself.parse_comments, meta{movie_name: movie_name, movie_rank: movie_rank}, # 可以在这里添加headers或cookies ) def parse_comments(self, response): movie_name response.meta[movie_name] movie_rank response.meta[movie_rank] # 解析当前页的短评 comment_elements response.css(.comment-item) for comment in comment_elements: item DoubanCommentItem() item[movie_name] movie_name item[movie_rank] movie_rank # 使用XPath或CSS选择器精确提取内容 item[comment_content] comment.css(.short::text).get().strip() # 评分可能为空用户未评分需要处理 rating_class comment.css(.rating::attr(class)).get() if rating_class: # 从allstar50 rating这样的字符串中提取数字 item[comment_rating] int(rating_class.split(allstar)[1].split( )[0]) // 10 else: item[comment_rating] None item[comment_time] comment.css(.comment-time::text).get().strip() item[comment_votes] int(comment.css(.votes::text).get()) yield item # 将提取到的item抛给Pipeline处理 # 处理短评分页 next_page response.css(#paginator a.next::attr(href)).get() if next_page: next_page_url urljoin(response.url, next_page) yield scrapy.Request( urlnext_page_url, callbackself.parse_comments, meta{movie_name: movie_name, movie_rank: movie_rank} )实操要点选择器的稳定性豆瓣的网页结构可能会微调。上述CSS选择器是基于某一时期的页面结构编写的。在实际操作前务必使用浏览器的开发者工具F12重新检查元素确认选择器是否依然有效。XPath通常更精确但CSS选择器更易读。异常处理代码中对于comment_rating的处理就是一个例子。不是所有评论都有评分所以需要判断rating_class是否存在否则会抛出AttributeError。在实际项目中每个字段的提取都应该包裹在try...except中或者进行空值判断确保爬虫的健壮性。Meta数据的传递在多层回调如从列表页到详情页再到评论页时使用Request的meta参数传递数据如电影名是标准做法。3.3 配置优化与反爬策略实施在settings.py中进行关键配置这是爬虫能否稳定运行的核心。# 设置全局请求延迟单位秒。2-3秒是比较友好的间隔。 DOWNLOAD_DELAY 2.5 # 对同一域名的并发请求数设置为1可以配合延迟更好地模拟人工。 CONCURRENT_REQUESTS_PER_DOMAIN 1 # 禁用Cookie除非需要。豆瓣查看公开评论通常不需要Cookie禁用可以减少被追踪的风险。 COOKIES_ENABLED False # 设置一个看起来像真实浏览器的User-Agent。 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 # 为了更好地模拟可以启用一个随机User-Agent中间件。 # 首先安装pip install scrapy-fake-useragent # 然后在settings.py中配置 DOWNLOADER_MIDDLEWARES { scrapy.downloadermiddlewares.useragent.UserAgentMiddleware: None, # 禁用默认 scrapy_fake_useragent.middleware.RandomUserAgentMiddleware: 400, # 启用随机 } # 启用并配置Item Pipeline用于数据清洗和存储。 ITEM_PIPELINES { douban_top250.pipelines.DoubanCommentPipeline: 300, } # 遵守robots协议建议开启 ROBOTSTXT_OBEY True注意事项DOWNLOAD_DELAY的设置需要权衡。太短如0.1秒容易被封太长如10秒则效率极低。对于豆瓣2-5秒是一个安全范围。你可以先设置得长一些稳定运行后再尝试缩短。如果遇到403禁止访问除了检查User-Agent还可以尝试在请求头中添加Referer字段其值设置为上一个页面的URL。启用RandomUserAgentMiddleware后记得在middlewares.py中查看或配置其使用的UA列表确保其有效性。4. 数据清理、存储与分析实战4.1 构建数据清洗管道爬虫抓取到的原始数据是“脏”的需要在pipelines.py中进行清洗。清洗逻辑因人而异但通常包括以下步骤import pymysql from itemadapter import ItemAdapter import re class DoubanCommentPipeline: def process_item(self, item, spider): # 1. 去除评论内容中的多余空白字符包括换行、制表符、连续空格 if item.get(comment_content): # 替换所有空白字符包括全角空格为单个空格并去除首尾空格 content item[comment_content] content re.sub(r\s, , content) # \s匹配任何空白字符 item[comment_content] content.strip() # 2. 清洗评论时间提取标准日期部分 # 原始格式可能为“2023-12-01 20:15”或“2023-12-01” if item.get(comment_time): time_str item[comment_time] # 使用正则匹配日期部分 match re.search(r(\d{4}-\d{2}-\d{2}), time_str) if match: item[comment_time] match.group(1) else: item[comment_time] None # 无法解析则置空 # 3. 处理评分字段确保是整数或None # 在爬虫解析阶段已经做了处理这里可以再次校验或转换 if item.get(comment_rating) is not None: try: item[comment_rating] int(item[comment_rating]) except ValueError: item[comment_rating] None # 4. 处理“有用”数确保是整数 if item.get(comment_votes): try: item[comment_votes] int(item[comment_votes]) except ValueError: item[comment_votes] 0 # 5. 可选简单的文本过滤如去除纯符号或过短的无效评论 if item.get(comment_content) and len(item[comment_content].strip()) 5: # 如果清洗后内容过短可以选择丢弃该item # raise DropItem(f评论过短被丢弃: {item}) pass # 这里我们选择保留但可以标记 return item清洗之后我们需要将数据存储起来。这里以存储到MySQL数据库为例。class MysqlPipeline: def __init__(self, mysql_settings): self.mysql_settings mysql_settings classmethod def from_crawler(cls, crawler): # 从settings.py读取数据库配置 return cls( mysql_settings{ host: crawler.settings.get(MYSQL_HOST, localhost), port: crawler.settings.get(MYSQL_PORT, 3306), user: crawler.settings.get(MYSQL_USER, root), password: crawler.settings.get(MYSQL_PASSWORD, ), database: crawler.settings.get(MYSQL_DATABASE, douban), charset: utf8mb4, # 重要支持存储Emoji等四字节字符 } ) def open_spider(self, spider): # 爬虫启动时连接数据库 self.connection pymysql.connect(**self.mysql_settings) self.cursor self.connection.cursor() # 创建表如果不存在 create_table_sql CREATE TABLE IF NOT EXISTS movie_comments ( id INT AUTO_INCREMENT PRIMARY KEY, movie_name VARCHAR(255) NOT NULL, movie_rank INT, comment_content TEXT, comment_rating INT, comment_time DATE, comment_votes INT, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; self.cursor.execute(create_table_sql) self.connection.commit() def process_item(self, item, spider): # 将item插入数据库 insert_sql INSERT INTO movie_comments (movie_name, movie_rank, comment_content, comment_rating, comment_time, comment_votes) VALUES (%s, %s, %s, %s, %s, %s) self.cursor.execute(insert_sql, ( item[movie_name], item[movie_rank], item[comment_content], item[comment_rating], item[comment_time], item[comment_votes] )) self.connection.commit() return item def close_spider(self, spider): # 爬虫关闭时关闭数据库连接 self.cursor.close() self.connection.close()记得在settings.py中配置数据库连接信息并启用这个Pipeline。实操心得字符编码是坑中文网站和Emoji表情需要使用utf8mb4字符集否则存储时会出现乱码或错误。异常处理与日志数据库操作连接、插入必须进行异常处理try...except并记录日志便于排查是网络问题、SQL语法错误还是数据格式错误。增量爬取如果计划定期更新数据需要在数据库设计上考虑。可以为每条评论生成一个唯一标识如基于用户ID、电影ID、评论时间哈希并在插入前检查是否已存在实现去重和增量更新。这比清空重爬要友好得多。4.2 使用Pandas进行探索性数据分析数据存入数据库后我们可以用Python连接数据库用Pandas加载数据进行分析。import pandas as pd import pymysql import matplotlib.pyplot as plt import seaborn as sns from wordcloud import WordCloud import jieba # 1. 连接数据库读取数据 connection pymysql.connect(hostlocalhost, userroot, passwordyour_password, databasedouban, charsetutf8mb4) df pd.read_sql(SELECT * FROM movie_comments, conconnection) connection.close() print(f数据总览共 {df.shape[0]} 条评论{df.shape[1]} 个字段) print(df.info()) # 查看数据类型和缺失值 print(df.head()) # 查看前几行数据 # 2. 数据质量检查与处理 # 检查缺失值 print(df.isnull().sum()) # 处理缺失的评分可以用中位数填充或单独标记为“未评分” df[comment_rating].fillna(0, inplaceTrue) # 假设0代表未评分 # 3. 基础统计分析 # 评分分布 rating_dist df[comment_rating].value_counts().sort_index() plt.figure(figsize(10,6)) rating_dist.plot(kindbar) plt.title(豆瓣TOP250电影短评评分分布) plt.xlabel(评分 (星)) plt.ylabel(评论数量) plt.xticks(rotation0) plt.show() # 评论长度分布 df[content_length] df[comment_content].apply(len) plt.figure(figsize(10,6)) df[content_length].hist(bins50, edgecolorblack) plt.title(评论内容长度分布) plt.xlabel(评论长度 (字符数)) plt.ylabel(频次) plt.show() print(f平均评论长度{df[content_length].mean():.2f} 字符) # 电影维度分析哪部电影评论最多 movie_comment_count df[movie_name].value_counts().head(10) plt.figure(figsize(12,6)) movie_comment_count.plot(kindbarh) # 水平条形图更易读 plt.title(评论数量最多的前十部电影) plt.xlabel(评论数量) plt.show() # 4. 文本数据初步探索生成词云 # 将所有评论拼接成一个长文本 all_comments .join(df[comment_content].dropna().astype(str).tolist()) # 使用jieba分词 words .join(jieba.lcut(all_comments)) # 生成词云排除一些无意义的停用词需要准备停用词表 stopwords set([电影, 一部, 这个, 没有, 什么, 就是, 还是, 可以, 因为, 所以]) wordcloud WordCloud(font_pathsimhei.ttf, width800, height600, background_colorwhite, stopwordsstopwords).generate(words) plt.figure(figsize(12,8)) plt.imshow(wordcloud, interpolationbilinear) plt.axis(off) plt.title(豆瓣TOP250短评词云) plt.show()通过以上分析我们可以快速得到一些直观结论例如评分分布是否呈两极分化很多人打5星或1星评论平均长度是多少哪些电影的讨论热度最高词云中出现了哪些高频词汇如“经典”、“剧情”、“演技”、“感动”这些都为后续的情感分析提供了背景和假设。5. 构建中文文本情感分析模型情感分析是NLP的经典任务。对于中文我们有两种主流路径基于情感词典/规则的传统方法以及基于机器学习/深度学习的方法。前者速度快但精度有限后者精度高但需要训练数据。这里我们重点介绍更通用、效果更好的机器学习方法。5.1 数据准备与标注机器学习模型需要带标签的数据。豆瓣短评自带用户评分1-5星这天然构成了一个情感标签我们可以将评分映射为情感类别正面 (Positive): 4星或5星负面 (Negative): 1星或2星中性 (Neutral): 3星这是一个有监督学习任务。我们首先需要根据这个规则为每条评论打上标签。# 根据评分定义情感标签 def rating_to_sentiment(rating): if rating 4: return positive elif rating 2: return negative else: return neutral df[sentiment] df[comment_rating].apply(rating_to_sentiment) print(df[sentiment].value_counts())注意这种映射并非绝对准确。一个用户打了3星其评论内容可能是中性的也可能是褒贬参半的。但在缺乏人工标注的情况下利用评分作为代理标签是实践中非常常见的做法且通常能获得不错的效果。5.2 特征工程从文本到向量计算机无法直接理解文本我们需要将文本评论转换为数值向量特征。最经典的方法是TF-IDF。分词首先使用jieba对每条评论进行分词。去除停用词去除“的”、“了”、“在”等对情感判断无意义的常用词。TF-IDF向量化计算每个词在文档中的重要性并将其转换为一个高维稀疏向量。from sklearn.feature_extraction.text import TfidfVectorizer import jieba # 准备停用词列表可以从网上下载或自己维护一个 def load_stopwords(filepath): with open(filepath, r, encodingutf-8) as f: stopwords [line.strip() for line in f] return set(stopwords) stopwords load_stopwords(chinese_stopwords.txt) # 定义分词函数 def chinese_cut(text): # 对文本进行分词并过滤停用词 words jieba.lcut(str(text)) return .join([w for w in words if w not in stopwords and w.strip() ! ]) # 应用分词 df[comment_cut] df[comment_content].apply(chinese_cut) # 划分训练集和测试集 from sklearn.model_selection import train_test_split X df[comment_cut] y df[sentiment] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42, stratifyy) # 使用TF-IDF进行特征提取 vectorizer TfidfVectorizer(max_features5000) # 限制最大特征数防止维度爆炸 X_train_tfidf vectorizer.fit_transform(X_train) X_test_tfidf vectorizer.transform(X_test) print(f特征矩阵形状{X_train_tfidf.shape})5.3 模型训练、评估与优化有了特征向量和标签我们就可以训练分类模型了。这里以逻辑回归和朴素贝叶斯为例它们在小规模文本分类上往往有不错的表现。from sklearn.linear_model import LogisticRegression from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import classification_report, confusion_matrix, accuracy_score import numpy as np # 训练逻辑回归模型 lr_model LogisticRegression(max_iter1000, random_state42) lr_model.fit(X_train_tfidf, y_train) y_pred_lr lr_model.predict(X_test_tfidf) print( 逻辑回归模型性能 ) print(f准确率{accuracy_score(y_test, y_pred_lr):.4f}) print(classification_report(y_test, y_pred_lr)) # 训练朴素贝叶斯模型特别适合文本分类 nb_model MultinomialNB() nb_model.fit(X_train_tfidf, y_train) y_pred_nb nb_model.predict(X_test_tfidf) print(\n 朴素贝叶斯模型性能 ) print(f准确率{accuracy_score(y_test, y_pred_nb):.4f}) print(classification_report(y_test, y_pred_nb)) # 可以查看哪些词对判断正面/负面情感贡献最大模型可解释性 feature_names vectorizer.get_feature_names_out() for i, class_label in enumerate(lr_model.classes_): # 获取该类别的权重系数排序 top10 np.argsort(lr_model.coef_[i])[-10:] # 取权重最高的10个特征索引 print(f\n对于类别 {class_label}最重要的10个词是) print([feature_names[j] for j in top10])模型选择与调优传统模型逻辑回归、朴素贝叶斯、支持向量机SVM都是不错的选择。可以尝试用GridSearchCV搜索最佳的超参数如逻辑回归的C值SVM的核函数。深度学习模型如果追求更高的准确率可以考虑使用预训练的中文BERT模型如bert-base-chinese进行微调。这需要安装transformers库并且训练时间会显著增加但对上下文的理解能力更强。类别不平衡检查y_train的分布。如果正面、负面、中性评论数量差异巨大需要考虑在模型训练时使用class_weightbalanced参数或者对少数类进行上采样。5.4 模型部署与应用训练好模型后我们可以将其保存下来用于对新评论进行情感预测。import joblib import pickle # 保存模型和向量化器 joblib.dump(lr_model, sentiment_lr_model.pkl) # 向量化器也需要保存因为新文本需要用同样的方式转换 with open(tfidf_vectorizer.pkl, wb) as f: pickle.dump(vectorizer, f) # 加载模型进行预测 loaded_model joblib.load(sentiment_lr_model.pkl) loaded_vectorizer pickle.load(open(tfidf_vectorizer.pkl, rb)) def predict_sentiment(text): # 对新文本进行同样的预处理和向量化 processed_text chinese_cut(text) text_vector loaded_vectorizer.transform([processed_text]) prediction loaded_model.predict(text_vector)[0] # 可以同时获取预测概率 proba loaded_model.predict_proba(text_vector)[0] return prediction, dict(zip(loaded_model.classes_, proba)) # 测试 new_comment 这部电影的剧情太拖沓了看得我昏昏欲睡。 sentiment, prob_dict predict_sentiment(new_comment) print(f评论{new_comment}) print(f预测情感{sentiment}) print(f各类别概率{prob_dict})至此我们完成了一个从数据采集、清洗、分析到建模预测的完整闭环。你可以将这个流程封装成一个简单的Web服务或API实现实时对输入文本进行情感判断的功能。6. 常见问题与排查技巧实录在实际操作这个项目的过程中你几乎一定会遇到下面这些问题。这里记录了我踩过的坑和解决方法希望能帮你节省时间。6.1 爬虫环节常见问题问题1爬虫运行后很快被屏蔽返回403或验证码页面。排查首先检查User-Agent是否设置且模拟得像真实浏览器。其次检查请求频率是否过高。解决降低频率增加DOWNLOAD_DELAY到3-5秒并将CONCURRENT_REQUESTS_PER_DOMAIN设为1。丰富Headers除了User-Agent添加Referer通常设置为豆瓣域名首页、Accept-Language等。使用代理IP如果单个IP被封锁这是终极方案。可以购买付费代理服务或搭建自己的代理池。在Scrapy中通过下载器中间件设置request.meta[proxy]。模拟登录如果非登录状态只能看到少量评论可能需要处理登录。使用scrapy.FormRequest模拟POST登录并管理好Session Cookie。问题2解析不到数据XPath/CSS选择器返回空列表。排查网页结构可能已更新。打开浏览器开发者工具重新检查目标元素的选择器。确认爬虫请求的页面是否包含了预期的数据有时数据可能在后续的AJAX请求中。解决更新选择器使用浏览器开发者工具的“Copy selector”或“Copy XPath”功能获取最新路径但要注意其可能过于脆弱。尽量使用基于class或id的相对稳定的选择器。检查响应内容在爬虫的parse函数中打印response.text[:1000]看看是否真的获取到了包含目标数据的HTML。如果没有可能是触发了反爬返回了不同的页面。处理动态加载如果数据是JS动态加载的需要借助Splash或Scrapy-Playwright。安装scrapy-playwright后在Request中使用scrapy.Request(url, callback, meta{playwright: True})。问题3数据存入数据库时出现乱码或报错“Incorrect string value”。排查数据库、表和连接字符集设置不正确无法存储Emoji或某些特殊中文字符。解决确保数据库、表、字段的字符集均为utf8mb4。创建连接时指定charsetutf8mb4。在Python连接MySQL时设置pymysql.connect(charsetutf8mb4)。对于其他数据库如SQLite也需要确保使用正确的编码。6.2 数据分析与建模环节常见问题问题1情感分析模型准确率很低比如低于60%。排查数据质量问题标签评分映射的情感噪声太大。很多3星评论内容可能是负面的。特征工程问题TF-IDF特征维度不够或太多没有进行有效的特征选择。停用词表不准确过滤掉了有情感色彩的词。类别不平衡某个情感类别的样本数远少于其他类别。解决数据清洗考虑过滤掉过短如少于3个词或内容无意义的评论。可以尝试只使用极端评分1-2星和4-5星的数据做二分类准确率通常会提升。优化特征调整TfidfVectorizer的参数如max_df忽略出现频率太高的词、min_df忽略出现频率太低的词、ngram_range尝试加入二元词组(1,2)。使用sklearn.feature_selection.SelectKBest选择最重要的K个特征。处理不平衡使用from imblearn.over_sampling import SMOTE进行过采样或在模型中使用class_weightbalanced。尝试其他模型SVM、随机森林、或者简单的深度学习模型如TextCNN、LSTM。使用预训练模型直接微调bert-base-chinese这是提升精度最有效的方法但需要GPU资源。问题2分词效果不理想将专有名词如电影名、演员名切分。排查jieba的默认词典可能不包含这些新词或领域词。解决加载自定义词典将电影名、常见演员名等整理成一个文本文件每行一个词使用jieba.load_userdict(my_dict.txt)加载。调整分词模式对于短文本使用jieba.lcut_for_search可能比jieba.lcut更细粒度但也不一定。需要根据效果测试。问题3词云图显示的都是“电影”、“好看”这类通用词没有区分度。排查停用词表不完善没有过滤掉这些高频但无分析价值的词。解决维护一个更全面的中文停用词表。可以在开源的中文停用词表如hit_stopwords、baidu_stopwords基础上根据你的分析目标这里是电影评论手动添加一些领域内的高频无意义词如“电影”、“一部”、“这个”、“觉得”等。这个项目麻雀虽小五脏俱全。每一次复现你都可能因为网站改版、库版本更新或对问题有了新的理解而遇到不同的挑战。解决问题的过程正是能力提升最快的时候。建议你在完成基础流程后尝试一些扩展比如用Scrapy-Redis实现分布式爬取用PySpark处理更大的数据集或者尝试更复杂的深度学习模型让这个项目成为你数据技能树上的一块坚实基石。本文还有配套的精品资源点击获取
返回列表