Python数据分析实战:爬虫+情感分析处理电影评论数据

Python数据分析实战:爬虫+情感分析处理电影评论数据
最近在整理恐怖电影观影笔记时发现2021年上映的韩国电影《怪奇宅》凭借其独特的叙事结构和贴近生活的恐怖元素给观众留下了深刻印象。本文将从技术角度分享如何利用Python进行恐怖电影数据的处理与分析通过爬虫获取信息、数据清洗、情感分析等步骤构建一套完整的电影数据处理流程。无论你是数据分析新手还是希望结合兴趣项目练手的开发者都能从本文获得可复用的代码和实操思路。1. 背景与核心概念1.1 恐怖电影数据分析的价值恐怖电影作为一种特殊的文化产品其观众评价、票房数据、题材分类等信息的分析能帮助开发者练习数据爬取、文本处理、可视化等技能。以《怪奇宅》为例电影由五个独立又相互关联的怪谈故事组成分析其评分趋势、评论关键词分布可以直观感受数据挖掘在文化领域的应用场景。1.2 关键技术栈介绍本项目主要涉及以下技术点Python爬虫通过Requests库获取网页数据BeautifulSoup解析HTML结构。数据清洗使用Pandas处理缺失值、重复项规整数据格式。情感分析借助Jieba分词和SnowNLP库对影评进行情感倾向判断。可视化分析通过Matplotlib或Pyecharts生成评分分布、词云图等。1.3 项目目标通过完整流程实现以下目标从公开平台获取《怪奇宅》的基本信息与评论数据。清洗并结构化存储数据导出为CSV文件。分析评论情感倾向识别正面/负面评价关键词。生成可视化图表直观展示分析结果。2. 环境准备与版本说明2.1 基础环境配置本项目在以下环境中测试通过建议读者使用相似环境以避免依赖冲突操作系统Windows 10 / macOS Monterey / Ubuntu 20.04 LTSPython版本3.8本文示例使用Python 3.9.7包管理工具pip 21.2.42.2 依赖库安装通过以下命令安装所需Python库建议使用虚拟环境隔离项目依赖pip install requests beautifulsoup4 pandas jieba snownlp matplotlib pyecharts2.3 版本兼容性说明重点依赖库版本建议Requests 2.26.0用于HTTP请求注意2.x版本接口变化。BeautifulSoup4 4.10.0解析HTML兼容Python 3.6。Pandas 1.3.3数据处理核心库注意1.0以上API调整。Jieba 0.42.1中文分词库需额外下载词典文件。SnowNLP 0.12.3情感分析库基于贝叶斯算法。若遇到版本冲突可通过pip freeze检查已安装版本或使用requirements.txt统一管理requests2.26.0 beautifulsoup44.10.0 pandas1.3.3 jieba0.42.1 snownlp0.12.3 matplotlib3.4.3 pyecharts1.9.13. 核心语法、配置或原理拆解3.1 Requests库网络请求详解Requests库是Python中最常用的HTTP客户端库其核心方法为get()和post()。以获取电影详情页为例import requests # 设置请求头模拟浏览器访问 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } # 发送GET请求 url https://movie.douban.com/subject/35192021/ # 《怪奇宅》豆瓣页面 response requests.get(url, headersheaders) # 检查请求状态 if response.status_code 200: html_content response.text print(页面获取成功) else: print(f请求失败状态码{response.status_code})关键参数说明headers设置User-Agent避免被反爬虫机制拦截。timeout添加超时参数如timeout10防止长时间等待。proxies如需代理访问可配置代理服务器字典。3.2 BeautifulSoup解析策略BeautifulSoup将HTML文档转换为树形结构支持多种解析器。常用查找方法包括find()和find_all()from bs4 import BeautifulSoup # 创建BeautifulSoup对象指定HTML解析器 soup BeautifulSoup(html_content, html.parser) # 查找电影标题根据豆瓣页面结构调整选择器 title_tag soup.find(span, propertyv:itemreviewed) if title_tag: movie_title title_tag.text print(f电影标题{movie_title}) # 查找评分信息 rating_tag soup.find(strong, class_ll rating_num) if rating_tag: rating rating_tag.text print(f评分{rating})解析器选择建议html.parserPython内置速度适中兼容性好。lxml需要额外安装解析速度快推荐生产环境使用。html5lib容错性最强但速度较慢。3.3 Pandas数据处理核心操作Pandas的DataFrame是二维表格数据结构支持高效的数据清洗和分析import pandas as pd # 创建示例DataFrame存储电影信息 data { title: [怪奇宅, 其他电影1, 其他电影2], rating: [6.9, 7.5, 8.2], votes: [15207, 23456, 18923] } df pd.DataFrame(data) # 基本数据操作 print(df.shape) # 输出数据维度 print(df.head()) # 查看前5行 print(df.describe()) # 数值型字段统计描述 # 数据筛选评分高于7分的电影 high_rating_movies df[df[rating] 7] print(high_rating_movies)常用数据清洗函数drop_duplicates()去除重复行。fillna()填充缺失值。astype()转换数据类型。apply()应用自定义函数处理数据。3.4 Jieba分词原理与配置Jieba分词基于前缀词典和动态规划算法支持三种分词模式import jieba # 默认分词模式 text 《怪奇宅》的恐怖氛围营造得非常出色 seg_list jieba.cut(text, cut_allFalse) print(精确模式 /.join(seg_list)) # 全模式分词 seg_list_all jieba.cut(text, cut_allTrue) print(全模式 /.join(seg_list_all)) # 搜索引擎模式 seg_list_search jieba.cut_for_search(text) print(搜索引擎模式 /.join(seg_list_search))分词优化策略加载自定义词典jieba.load_userdict(user_dict.txt)添加领域专有词汇。调整词频jieba.suggest_freq((恐怖, 氛围), True)提高特定词语识别精度。并行分词jieba.enable_parallel(4)启用多进程提升大数据集处理速度。3.5 SnowNLP情感分析基础SnowNLP的情感分析基于朴素贝叶斯分类器训练数据来自商品评论库from snownlp import SnowNLP # 简单情感分析示例 text1 这部电影太精彩了强烈推荐 text2 剧情拖沓恐怖元素不足 s1 SnowNLP(text1) s2 SnowNLP(text2) print(f正面评论情感值{s1.sentiments}) # 接近1表示正面 print(f负面评论情感值{s2.sentiments}) # 接近0表示负面情感值解读0.5为中性阈值大于0.6可视为正面评价小于0.4视为负面评价。由于训练数据来源对影视评论的准确率约70-80%可针对领域微调模型。4. 完整实战案例4.1 项目结构设计创建清晰的项目目录结构便于模块化管理horror_movie_analysis/ ├── src/ │ ├── crawler.py # 爬虫模块 │ ├── data_processor.py # 数据处理模块 │ ├── analyzer.py # 分析模块 │ └── visualizer.py # 可视化模块 ├── data/ │ ├── raw/ # 原始数据 │ ├── processed/ # 处理后的数据 │ └── results/ # 分析结果 ├── config/ │ └── settings.py # 配置文件 └── main.py # 主程序入口4.2 爬虫模块实现创建crawler.py实现数据获取功能import requests from bs4 import BeautifulSoup import time import random class MovieCrawler: def __init__(self): self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept-Language: zh-CN,zh;q0.9,en;q0.8 } self.base_url https://movie.douban.com/subject/35192021/ self.comments_url https://movie.douban.com/subject/35192021/comments def get_movie_basic_info(self): 获取电影基本信息 try: response requests.get(self.base_url, headersself.headers, timeout10) if response.status_code ! 200: return None soup BeautifulSoup(response.text, html.parser) # 解析电影基本信息 info {} title_tag soup.find(span, propertyv:itemreviewed) info[title] title_tag.text if title_tag else 未知标题 rating_tag soup.find(strong, class_ll rating_num) info[rating] float(rating_tag.text) if rating_tag else 0.0 # 更多信息解析... return info except Exception as e: print(f获取电影信息失败{e}) return None def get_comments(self, pages5): 获取评论数据 comments [] for page in range(pages): try: params {start: page * 20, limit: 20, status: P} response requests.get(self.comments_url, headersself.headers, paramsparams, timeout10) if response.status_code 200: soup BeautifulSoup(response.text, html.parser) comment_items soup.find_all(div, class_comment-item) for item in comment_items: comment {} # 解析单条评论 content_tag item.find(span, class_short) if content_tag: comment[content] content_tag.text.strip() comment[page] page 1 comments.append(comment) # 随机延时避免请求过快 time.sleep(random.uniform(1, 3)) except Exception as e: print(f第{page1}页评论获取失败{e}) continue return comments # 使用示例 if __name__ __main__: crawler MovieCrawler() basic_info crawler.get_movie_basic_info() comments crawler.get_comments(pages3) print(f电影信息{basic_info}) print(f获取到{len(comments)}条评论)4.3 数据处理模块创建data_processor.py实现数据清洗和存储import pandas as pd import jieba import re from datetime import datetime class DataProcessor: def __init__(self): # 加载停用词表 with open(data/stopwords.txt, r, encodingutf-8) as f: self.stopwords set([line.strip() for line in f]) def clean_text(self, text): 文本清洗 if not text: return # 去除特殊字符和标点 text re.sub(r[^\w\s], , text) # 去除数字 text re.sub(r\d, , text) # 去除多余空格 text re.sub(r\s, , text).strip() return text def tokenize_text(self, text): 中文分词 words jieba.cut(text, cut_allFalse) # 去除停用词和单字 filtered_words [word for word in words if word not in self.stopwords and len(word) 1] return .join(filtered_words) def process_comments(self, comments): 处理评论数据 df pd.DataFrame(comments) # 数据清洗 df df.dropna(subset[content]) # 删除空评论 df df.drop_duplicates(subset[content]) # 去重 # 文本预处理 df[cleaned_content] df[content].apply(self.clean_text) df[tokenized_content] df[cleaned_content].apply(self.tokenize_text) df[content_length] df[cleaned_content].apply(len) # 过滤过短评论 df df[df[content_length] 5] return df def save_to_csv(self, df, filename): 保存为CSV文件 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) full_filename fdata/processed/{filename}_{timestamp}.csv df.to_csv(full_filename, indexFalse, encodingutf-8-sig) print(f数据已保存至{full_filename}) # 使用示例 if __name__ __main__: # 模拟数据测试 sample_comments [ {content: 真的很恐怖推荐, page: 1}, {content: 剧情一般不算太吓人, page: 1} ] processor DataProcessor() processed_df processor.process_comments(sample_comments) print(processed_df.head())4.4 情感分析模块创建analyzer.py实现情感分析和关键词提取import pandas as pd from snownlp import SnowNLP from collections import Counter import jieba.analyse class SentimentAnalyzer: def __init__(self): self.positive_threshold 0.6 self.negative_threshold 0.4 def analyze_sentiment(self, text): 分析单条文本情感 s SnowNLP(text) sentiment s.sentiments if sentiment self.positive_threshold: return positive, sentiment elif sentiment self.negative_threshold: return negative, sentiment else: return neutral, sentiment def batch_analyze(self, df, text_columncleaned_content): 批量情感分析 results [] for text in df[text_column]: if pd.isna(text) or len(text.strip()) 0: results.append((neutral, 0.5)) else: results.append(self.analyze_sentiment(text)) df[[sentiment, sentiment_score]] pd.DataFrame( results, indexdf.index ) return df def extract_keywords(self, texts, top_k10): 提取关键词 combined_text .join([str(text) for text in texts if pd.notna(text)]) keywords jieba.analyse.extract_tags( combined_text, topKtop_k, withWeightTrue ) return keywords def generate_sentiment_report(self, df): 生成情感分析报告 sentiment_counts df[sentiment].value_counts() total_comments len(df) report { total_comments: total_comments, positive_count: sentiment_counts.get(positive, 0), negative_count: sentiment_counts.get(negative, 0), neutral_count: sentiment_counts.get(neutral, 0), positive_ratio: sentiment_counts.get(positive, 0) / total_comments, negative_ratio: sentiment_counts.get(negative, 0) / total_comments } # 提取各情感类型的关键词 positive_texts df[df[sentiment] positive][cleaned_content] negative_texts df[df[sentiment] negative][cleaned_content] report[positive_keywords] self.extract_keywords(positive_texts) report[negative_keywords] self.extract_keywords(negative_texts) return report # 使用示例 if __name__ __main__: analyzer SentimentAnalyzer() # 测试情感分析 test_text 《怪奇宅》的恐怖氛围很棒但剧情有些拖沓 sentiment, score analyzer.analyze_sentiment(test_text) print(f文本{test_text}) print(f情感{sentiment}得分{score:.3f})4.5 可视化模块创建visualizer.py生成分析图表import matplotlib.pyplot as plt from pyecharts.charts import Bar, Pie, WordCloud from pyecharts import options as opts import pandas as pd plt.rcParams[font.sans-serif] [SimHei] # 解决中文显示问题 plt.rcParams[axes.unicode_minus] False class DataVisualizer: def __init__(self): self.theme white # pyecharts主题 def create_sentiment_pie(self, report, save_path): 创建情感分布饼图 labels [正面, 负面, 中性] sizes [ report[positive_count], report[negative_count], report[neutral_count] ] pie ( Pie(init_optsopts.InitOpts(themeself.theme)) .add(, [list(z) for z in zip(labels, sizes)]) .set_global_opts( title_optsopts.TitleOpts(title评论情感分布), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%) ) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {c} ({d}%))) ) pie.render(save_path) return pie def create_word_cloud(self, keywords, save_path, title关键词词云): 创建词云图 wc ( WordCloud(init_optsopts.InitOpts(themeself.theme)) .add(, keywords, word_size_range[20, 100]) .set_global_opts(title_optsopts.TitleOpts(titletitle)) ) wc.render(save_path) return wc def create_sentiment_trend(self, df, save_path): 创建情感趋势图按页码 sentiment_by_page df.groupby(page)[sentiment].value_counts().unstack() bar ( Bar(init_optsopts.InitOpts(themeself.theme)) .add_xaxis([f第{i}页 for i in sentiment_by_page.index]) .add_yaxis(正面, sentiment_by_page[positive].tolist()) .add_yaxis(负面, sentiment_by_page[negative].tolist()) .add_yaxis(中性, sentiment_by_page[neutral].tolist()) .set_global_opts( title_optsopts.TitleOpts(title各页面情感分布), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate-45)) ) ) bar.render(save_path) return bar # 使用示例 if __name__ __main__: visualizer DataVisualizer() # 测试数据 sample_report { positive_count: 150, negative_count: 80, neutral_count: 70 } sample_keywords [(恐怖, 100), (氛围, 85), (剧情, 75), (推荐, 60)] # 生成图表 visualizer.create_sentiment_pie(sample_report, sentiment_pie.html) visualizer.create_word_cloud(sample_keywords, wordcloud.html)4.6 主程序整合创建main.py整合所有模块from src.crawler import MovieCrawler from src.data_processor import DataProcessor from src.analyzer import SentimentAnalyzer from src.visualizer import DataVisualizer import os def ensure_directories(): 确保所需目录存在 directories [data/raw, data/processed, data/results] for directory in directories: os.makedirs(directory, exist_okTrue) def main(): # 初始化目录 ensure_directories() # 初始化各模块 crawler MovieCrawler() processor DataProcessor() analyzer SentimentAnalyzer() visualizer DataVisualizer() print(开始获取电影数据...) # 1. 获取数据 basic_info crawler.get_movie_basic_info() comments crawler.get_comments(pages5) print(f获取到{len(comments)}条评论) # 2. 数据处理 processed_df processor.process_comments(comments) processor.save_to_csv(processed_df, processed_comments) # 3. 情感分析 analyzed_df analyzer.batch_analyze(processed_df) report analyzer.generate_sentiment_report(analyzed_df) # 4. 生成可视化结果 visualizer.create_sentiment_pie(report, data/results/sentiment_pie.html) positive_keywords [(word, int(weight * 100)) for word, weight in report[positive_keywords]] visualizer.create_word_cloud(positive_keywords, data/results/positive_wordcloud.html, 正面评论关键词) print(分析完成) print(f总评论数{report[total_comments]}) print(f正面比例{report[positive_ratio]:.2%}) print(f负面比例{report[negative_ratio]:.2%}) if __name__ __main__: main()4.7 运行结果说明执行主程序后预期得到以下输出文件和分析结果数据文件data/processed/processed_comments_20231201_143022.csv清洗后的评论数据包含原始评论、清洗后文本、分词结果、情感标签等字段可视化结果data/results/sentiment_pie.html情感分布饼图data/results/positive_wordcloud.html正面评论词云图控制台输出示例开始获取电影数据... 获取到89条评论 数据已保存至data/processed/processed_comments_20231201_143022.csv 分析完成 总评论数85 正面比例58.82% 负面比例25.88%典型分析发现正面评论关键词可能包括恐怖、氛围、推荐、精彩负面评论关键词可能包括拖沓、失望、一般、无聊情感分布反映电影的整体接受程度5. 常见问题与排查思路5.1 网络请求相关问题问题现象常见原因解决思路请求返回403错误网站反爬虫机制触发1. 检查User-Agent设置2. 添加请求延时3. 使用代理IP连接超时网络不稳定或目标服务器限制1. 增加timeout参数2. 添加重试机制3. 检查网络连接数据获取不完整页面结构变化或解析规则错误1. 更新CSS选择器2. 检查页面JavaScript加载3. 验证解析逻辑代码级解决方案# 添加重试机制的请求函数 def robust_request(url, max_retries3): for attempt in range(max_retries): try: response requests.get(url, headersheaders, timeout10) if response.status_code 200: return response except requests.exceptions.RequestException as e: print(f第{attempt1}次请求失败{e}) time.sleep(2 ** attempt) # 指数退避 return None5.2 数据处理常见错误问题现象常见原因解决思路中文乱码编码格式不匹配1. 统一使用utf-8编码2. 文件保存时指定encodingutf-8-sig3. 检查源网页编码分词效果差未加载专业词典1. 添加领域专有词典2. 调整停用词表3. 使用自定义分词规则情感分析不准训练数据不匹配1. 使用领域数据微调模型2. 调整情感阈值3. 结合规则方法补充编码问题解决示例# 正确处理中文编码 with open(file.csv, r, encodingutf-8-sig) as f: content f.read() # 或者使用chardet检测编码 import chardet with open(file.csv, rb) as f: encoding chardet.detect(f.read())[encoding]5.3 性能优化问题问题现象常见原因解决思路程序运行缓慢大量网络请求或复杂计算1. 使用多线程/异步请求2. 添加缓存机制3. 优化数据处理算法内存占用过高大数据集一次性加载1. 分块读取数据2. 使用生成器替代列表3. 及时释放不需要的变量性能优化示例# 使用生成器分批处理大数据 def batch_process(data, batch_size1000): for i in range(0, len(data), batch_size): batch data[i:i batch_size] yield process_batch(batch) # 异步请求优化 import aiohttp import asyncio async def fetch_page(session, url): async with session.get(url) as response: return await response.text()6. 最佳实践与工程建议6.1 代码规范与可维护性模块化设计按照功能拆分独立模块提高代码复用性。配置文件管理将URL、请求头、文件路径等配置信息集中管理。日志记录使用logging模块替代print语句便于调试和监控。配置管理示例# config/settings.py class Settings: BASE_URL https://movie.douban.com HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } REQUEST_DELAY 1 # 请求间隔秒数 MAX_PAGES 10 # 最大爬取页数 # 使用配置 from config.settings import Settings headers Settings.HEADERS6.2 错误处理与容错机制异常捕获针对网络请求、文件操作等可能失败的操作添加异常处理。数据验证在处理前验证数据完整性和格式正确性。重试机制对临时性错误实现自动重试逻辑。健壮的数据处理def safe_data_processing(df): 安全的数据处理函数 try: # 数据验证 if df.empty: raise ValueError(数据框为空) # 必要的列检查 required_columns [content, page] missing_columns [col for col in required_columns if col not in df.columns] if missing_columns: raise ValueError(f缺失必要列{missing_columns}) # 数据处理逻辑 processed_df processing_pipeline(df) return processed_df except Exception as e: logging.error(f数据处理失败{e}) # 返回空数据框或采取其他恢复措施 return pd.DataFrame()6.3 安全与合规考虑遵守robots.txt爬取前检查目标网站的爬虫协议。请求频率控制添加适当的延时避免对目标服务器造成压力。数据使用边界仅用于学习研究不进行商业用途或大量公开传播。合规爬虫实践import time from urllib.robotparser import RobotFileParser def check_robots_permission(base_url, path): 检查robots.txt权限 parser RobotFileParser() parser.set_url(f{base_url}/robots.txt) parser.read() return parser.can_fetch(*, f{base_url}{path}) # 在爬取前检查 if check_robots_permission(Settings.BASE_URL, /subject/35192021/): # 执行爬取 pass else: print(根据robots.txt协议不允许爬取该页面)6.4 性能监控与优化进度显示对于长时间运行的任务添加进度条显示。内存监控定期检查内存使用情况避免内存泄漏。结果缓存对耗时操作的结果进行缓存避免重复计算。带进度显示的处理from tqdm import tqdm def process_with_progress(data_list): 带进度条的数据处理 results [] for item in tqdm(data_list, desc处理进度): result process_item(item) results.append(result) return results通过本文的完整实战案例你不仅学会了如何对《怪奇宅》这类恐怖电影数据进行爬取和分析更重要的是掌握了一套可复用的数据处理流程。在实际项目中可以根据具体需求调整分析维度和技术方案比如增加更复杂的情感分析模型、结合票房数据进行预测分析等。