简介:这是一套面向计算机、数学及电子信息类专业学生的综合性Python数据项目实践资源,聚焦疫情数据采集、社交媒体舆情挖掘与多维度分析全流程。资源完整覆盖疫情实时爬虫、微博关键词定向抓取(含MySQL数据库存储)、结构化数据清洗与特征工程、Matplotlib/Seaborn可视化图表生成,以及基于词典与简单NLP模型的情感倾向判别,适合作为课程设计、期末大作业或毕业设计的参考实现。压缩包共15个文件,含7个核心Python脚本(涵盖爬虫、预处理、情感分析等模块)、3个CSV数据集(如nCoV_100k训练样本、微博情感标注语料)、2个情感词典文本、1个JSON微博原始数据样例、1张可视化结果PNG图及1份Markdown项目说明文档,整体大小23.87MB。已有728人学习下载,提供可直接运行的源码框架、清晰的模块划分与典型数据样例,便于理解数据流闭环逻辑、调试关键环节并拓展自定义分析需求。
1. 项目缘起与核心价值:从数据孤岛到决策洞察
最近在整理硬盘,翻出来一个前两年做的老项目,一个集成了疫情数据抓取、微博舆情监控、数据清洗和情感分析的综合数据管道。当时做这个的初衷很简单,就是想看看公开的疫情数据和社交媒体上的公众情绪之间,到底有没有什么肉眼可见的关联。比如,当某地新增病例数出现波动时,微博上相关话题的讨论热度、情感倾向会不会同步变化?这背后反映的是公众的恐慌、关切还是其他情绪?
这个项目虽然不算复杂,但它完整地串联了一个数据从业者(或者说一个用Python解决实际问题的开发者)从数据获取、存储、处理到分析、可视化的全链路。它不像一个单纯的“爬虫教程”只教你怎么把数据抓下来,也不像单纯的“数据分析案例”只给你一个清洗好的CSV文件让你画图。它的价值在于**“端到端”**:你从零开始,亲手搭建一个能持续运行、自动更新、并产出有业务意义结论的小系统。在这个过程中,你会遇到并解决一系列非常典型的问题:反爬策略应对、非结构化文本处理、数据库设计、时序数据可视化、情感分析模型的适用性等等。
对于想从“写脚本”进阶到“做项目”的Python学习者,或者想了解如何将多个数据源整合分析的初学者来说,这个项目的代码和思路有很强的参考价值。它用到的技术栈非常“接地气”:requests/Scrapy爬虫、MySQL/SQLite数据库、pandas进行数据预处理、matplotlib/pyecharts做可视化、再用snownlp或jieba+情感词典做简单的情感分析。没有用到特别高深莫测的框架,但组合起来就能解决一个真实的、有趣的问题。
接下来,我会把这个项目的核心模块拆开,详细讲讲每个部分的设计思路、实现时踩过的坑,以及如何让这些模块像齿轮一样咬合在一起,稳定运转。你会发现,很多设计决策,比如为什么选这个库、数据表为什么这样设计,都是基于实际运行中遇到的“坑”而调整的。
2. 数据采集层:双线并行的爬虫架构设计与实战坑点
整个项目的基石是数据。我们需要两类数据:一是结构化的、权威的疫情统计数据(如每日新增、累计确诊等),二是非结构化的、来自社交媒体的公众舆论数据(微博博文及评论)。这两类数据来源不同、结构不同、反爬策略也不同,因此需要设计两套独立的爬虫,但又要在数据层面为后续的关联分析做好准备。
2.1 疫情数据爬虫:应对API与页面结构的变迁
疫情数据通常来自各级卫健委的官方页面或一些聚合数据平台。几年前,很多网站还提供清晰的JSON格式API,爬取起来非常方便。但后来,越来越多的网站加强了反爬,或改用了动态渲染。
核心策略:优先寻找官方或可靠的聚合数据接口。在项目初期,我首先尝试了某数据平台的公开API。它的好处是数据结构化程度高,包含省份、城市、新增确诊、累计确诊、治愈、死亡等关键字段,且更新及时。使用Python的requests库,配合设置合理的请求头(User-Agent,Referer等),就能稳定获取。
import requests import pandas as pd import time def fetch_epidemic_data_api(date): """ 模拟从某个数据接口获取疫情数据 """ url = "https://api.example.com/epidemic/data" # 示例URL,实际需替换 headers = { 'User-Agent': 'Mozilla/5.0...', 'Accept': 'application/json', } params = {'date': date} try: resp = requests.get(url, headers=headers, params=params, timeout=10) resp.raise_for_status() # 检查HTTP错误 data = resp.json() # 假设返回数据格式为 {'data': [list of records]} df = pd.DataFrame(data['data']) return df except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return pd.DataFrame() # 示例:获取多日数据 all_data = [] for single_date in pd.date_range(start='2023-01-01', end='2023-01-07'): df_day = fetch_epidemic_data_api(single_date.strftime('%Y-%m-%d')) if not df_day.empty: all_data.append(df_day) time.sleep(1) # 礼貌性延迟,避免请求过快注意:这里是一个示例框架。实际项目中,你需要根据目标网站的具体情况调整URL、参数和解析逻辑。务必遵守网站的
robots.txt协议,并控制请求频率,避免对对方服务器造成压力。
当API不可用时的备选方案:解析HTML页面。如果找不到稳定的API,就不得不转向解析HTML。这时BeautifulSoup或lxml是得力工具。关键点在于定位数据所在的HTML元素。你需要仔细分析网页结构,通常数据会放在<table>标签内,或者由特定的<div>和<span>组合而成。
from bs4 import BeautifulSoup def parse_epidemic_data_html(html_content): """ 从HTML页面中解析疫情数据表格 """ soup = BeautifulSoup(html_content, 'html.parser') # 假设数据在一个id为'data-table'的表格中 table = soup.find('table', {'id': 'data-table'}) if not table: return pd.DataFrame() # 提取表头和数据行 headers = [th.get_text(strip=True) for th in table.find('thead').find_all('th')] rows = [] for tr in table.find('tbody').find_all('tr'): cells = [td.get_text(strip=True) for td in tr.find_all('td')] if cells: # 避免空行 rows.append(cells) df = pd.DataFrame(rows, columns=headers) # 后续需要进行数据清洗,如转换数字类型、处理空值等 return df踩坑实录:网页结构变动与数据字段不一致。这是做爬虫最头疼的问题之一。项目运行了几个月后,源网站进行了一次改版,原先的CSS选择器全部失效,导致爬虫中断。解决方案是:第一,在代码中增加更健壮的查找逻辑(例如,同时尝试多个可能的选择器);第二,将关键的解析规则(如选择器路径)写入配置文件,而不是硬编码在代码里,这样修改起来更方便;第三,建立监控告警,一旦连续几次爬取不到数据或数据格式异常,就立即通知维护者。
另一个坑是数据字段的语义变化。比如,早期数据中“新增”可能指“新增确诊”,后期可能包含了“新增无症状”。如果不加区分地入库,会导致时间序列分析出现偏差。解决办法是在数据清洗阶段增加严格的校验规则,对字段名和值进行逻辑检查,发现异常时记录日志并人工介入确认。
2.2 微博关键词爬虫:动态内容、反爬与数据关联
微博数据爬取的复杂度远高于疫情数据。微博页面是典型的动态加载(Ajax),且反爬机制非常严格。
技术选型:Selenium 与 逆向工程结合。对于需要登录、有复杂JavaScript交互的页面,Selenium模拟浏览器操作是一个可行的方案。它可以等待页面完全加载,执行点击、滚动等操作,获取渲染后的完整HTML。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def fetch_weibo_by_keyword_selenium(keyword, pages=3): """ 使用Selenium模拟搜索微博关键词并抓取 """ driver = webdriver.Chrome() # 需安装对应浏览器的driver driver.get(f"https://s.weibo.com/weibo?q={keyword}") weibo_data = [] for page in range(pages): # 等待博文列表加载 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, "card-wrap")) ) soup = BeautifulSoup(driver.page_source, 'html.parser') cards = soup.find_all('div', class_='card-wrap') for card in cards: # 解析博文内容、发布时间、发布人、转发/评论/点赞数 # 这里的选择器是示例,实际微博页面结构非常复杂且经常变动 content_elem = card.find('p', class_='txt') if content_elem: content = content_elem.get_text(strip=True) # ... 解析其他字段 weibo_data.append({ 'keyword': keyword, 'content': content, # ... 其他字段 }) # 尝试点击“下一页” try: next_button = driver.find_element(By.CSS_SELECTOR, 'a.next') next_button.click() time.sleep(3) # 等待页面跳转 except: print(f"已抓取 {page+1} 页,或无下一页。") break driver.quit() return pd.DataFrame(weibo_data)重要提示:使用Selenium效率较低,且容易被识别。仅适用于数据量小、频率低的情况。对于大规模爬取,更优解是分析微博的移动端或API接口。通过浏览器开发者工具的“网络(Network)”选项卡,监控搜索或刷新时的XHR请求,找到返回JSON数据的真实接口。然后,用
requests库模拟这些请求,这需要你仔细分析请求头(特别是Cookie和Authorization)和参数。这种方式速度快、资源消耗小,但需要一定的逆向工程能力,且接口也可能变更。
反爬应对策略:微博的反爬非常成熟,包括验证码、请求频率限制、账号封锁等。实践中,我采用了组合策略:
- 使用代理IP池:避免单个IP请求过快被封。
- 设置随机延迟:在请求间插入
time.sleep(random.uniform(2, 5)),模拟人类操作。 - 维护多个Cookie池:如果接口需要登录态,准备多个账号的Cookie轮换使用。
- 优雅降级:当主要接口(如API)失效时,自动切换到备用方案(如Selenium),并发送警报。
数据关联设计:微博数据表的设计必须包含能与疫情数据关联的字段。除了博文内容、时间、用户等基本信息外,我额外增加了:
location:从用户简介或博文内容中提取的地理位置(如“北京”、“上海”),便于与疫情数据按地域关联。related_region:通过文本匹配,识别博文中提及的疫情相关地区(如“西安疫情”)。crawl_date:爬取日期,用于按时间与疫情数据对齐。
这样,在后续分析时,就可以轻松地查询“2023年1月5日,关于‘西安’的微博情感倾向,与当日西安市疫情数据的关系”。
3. 数据存储与预处理:构建分析就绪的数据仓库
原始爬取的数据是“脏”的,无法直接用于分析。这一步的目标是将来自两个源头的数据,清洗、转换并存储到一个结构清晰、查询高效的“数据仓库”中。
3.1 数据库设计:平衡灵活性与查询效率
我选择了MySQL作为关系型数据库,因为它足够成熟,社区支持好,且适合处理结构化的表关联查询。
疫情数据表 (epidemic_data) 设计:
CREATE TABLE epidemic_data ( id INT AUTO_INCREMENT PRIMARY KEY, date DATE NOT NULL COMMENT '数据日期', province VARCHAR(50) COMMENT '省份', city VARCHAR(50) COMMENT '城市', new_confirmed INT DEFAULT 0 COMMENT '新增确诊', cumulative_confirmed INT DEFAULT 0 COMMENT '累计确诊', new_asymptomatic INT DEFAULT 0 COMMENT '新增无症状', cured INT DEFAULT 0 COMMENT '治愈', dead INT DEFAULT 0 COMMENT '死亡', data_source VARCHAR(100) COMMENT '数据来源', crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '爬取时间', UNIQUE KEY uk_date_province_city (date, province, city) -- 防止重复数据 );设计考量:设立(date, province, city)联合唯一键,可以确保每天每个地区只有一条记录,方便使用INSERT ... ON DUPLICATE KEY UPDATE语句进行增量更新,避免重复。
微博数据表 (weibo_posts) 设计:
CREATE TABLE weibo_posts ( id INT AUTO_INCREMENT PRIMARY KEY, weibo_id VARCHAR(50) NOT NULL COMMENT '微博唯一ID', keyword VARCHAR(100) NOT NULL COMMENT '搜索关键词', content TEXT COMMENT '博文内容', publish_time DATETIME COMMENT '发布时间', user_id VARCHAR(50) COMMENT '用户ID', user_name VARCHAR(100) COMMENT '用户名', user_location VARCHAR(100) COMMENT '用户所在地', reposts_count INT DEFAULT 0 COMMENT '转发数', comments_count INT DEFAULT 0 COMMENT '评论数', attitudes_count INT DEFAULT 0 COMMENT '点赞数', extracted_region VARCHAR(100) COMMENT '从内容提取的相关地区', sentiment_score FLOAT COMMENT '情感分析得分(预处理后存入)', crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '爬取时间', INDEX idx_keyword_time (keyword, publish_time), -- 按关键词和时间查询 INDEX idx_region_time (extracted_region, publish_time) -- 按地区和时间查询 );设计考量:
weibo_id是微博平台自身的ID,作为业务主键,防止同一博文被重复入库。- 对
content字段使用TEXT类型,因为博文可能较长。 - 添加了
extracted_region和sentiment_score字段。前者是在入库前的预处理阶段通过文本匹配(如地名库)提取的,后者是情感分析的结果。将预处理结果直接存入数据库,可以极大提高后续分析的查询速度,这是一种“空间换时间”和“计算前置”的常用策略。 - 索引
idx_keyword_time和idx_region_time是针对核心查询场景(“某关键词在某时间段内的博文”、“某地区在某时间段内的博文”)优化的,能显著提升关联查询性能。
3.2 数据清洗与预处理流水线
爬虫获取的原始数据需要经过一系列清洗才能入库。我使用pandas在内存中构建了一条处理流水线。
疫情数据清洗要点:
- 缺失值处理:对于数字字段,如果某天某地区数据缺失,是填0、填前一日值、还是标记为NULL?这需要根据业务逻辑判断。我通常对“新增”类字段填0,对“累计”类字段向前填充(ffill)。
- 格式统一:确保日期字段统一为
YYYY-MM-DD格式,省份城市名称统一(如“北京市”和“北京”统一为“北京”)。 - 异常值检测:比如“新增确诊”为负数,或某日数据较前一日激增百倍,这可能是数据源错误或解析错误,需要记录日志并人工核查。
import pandas as pd import numpy as np def clean_epidemic_data(raw_df): """ 清洗疫情数据 """ df = raw_df.copy() # 1. 重命名列,统一格式 df.columns = ['date', 'province', 'city', 'new_confirmed', 'cumulative_confirmed', ...] # 2. 日期格式化 df['date'] = pd.to_datetime(df['date'], errors='coerce').dt.date # 3. 处理缺失值 # 假设新增数据缺失填0,累计数据向前填充 df['new_confirmed'] = df['new_confirmed'].fillna(0) df['cumulative_confirmed'] = df.groupby(['province', 'city'])['cumulative_confirmed'].fillna(method='ffill') # 4. 去除完全空白的行 df.dropna(subset=['province', 'city'], inplace=True) # 5. 异常值过滤(示例:新增确诊不应超过10000) df = df[(df['new_confirmed'] >= 0) & (df['new_confirmed'] <= 10000)] return df微博数据清洗与预处理要点:
- 文本清洗:去除博文中的URL、@用户、话题标签
#...#、表情符号[表情]等噪声。可以使用正则表达式。 - 提取关键信息:使用地名库(可以从公开资源获取)匹配博文内容,提取提到的地区,存入
extracted_region字段。 - 情感分析预处理:调用情感分析模块(见下一章)计算每条博文的情感得分,并存入
sentiment_score字段。这里将计算密集型任务前置,避免在分析时重复计算。 - 去重:根据
weibo_id去除重复爬取的博文。
import re import jieba # 假设有一个地名列表 REGION_LIST = ['北京', '上海', '广州', '深圳', '西安', ...] def clean_weibo_content(text): """清洗单条微博文本""" if not isinstance(text, str): return '' # 去除URL text = re.sub(r'http[s]?://\S+', '', text) # 去除@用户 text = re.sub(r'@[\w\u4e00-\u9fa5]+', '', text) # 去除话题标签 text = re.sub(r'#.+?#', '', text) # 去除表情符号 text = re.sub(r'\[.*?\]', '', text) # 去除多余空白字符 text = ' '.join(text.split()) return text def extract_region_from_text(text, region_list): """从文本中提取地区""" for region in region_list: if region in text: return region return None def preprocess_weibo_data(raw_df): """ 预处理微博数据 """ df = raw_df.copy() # 1. 文本清洗 df['cleaned_content'] = df['content'].apply(clean_weibo_content) # 2. 提取地区 df['extracted_region'] = df['cleaned_content'].apply(lambda x: extract_region_from_text(x, REGION_LIST)) # 3. 情感分析(假设已有函数get_sentiment_score) # df['sentiment_score'] = df['cleaned_content'].apply(get_sentiment_score) # 注意:情感分析可能较慢,可以考虑批量处理或异步处理 # 4. 时间格式化 df['publish_time'] = pd.to_datetime(df['publish_time'], errors='coerce') # 5. 去重 df.drop_duplicates(subset=['weibo_id'], inplace=True) return df清洗和预处理后的数据,通过pandas的to_sql方法或SQLAlchemy库,批量写入MySQL数据库。务必使用事务(transaction)确保数据一致性,并处理好可能出现的重复键问题。
4. 情感分析模块:从词典匹配到模型应用的权衡
情感分析是连接客观数据(疫情数字)与主观舆论(微博情绪)的桥梁。在这个项目中,我尝试并对比了两种主流方法:基于情感词典的方法和基于预训练模型的方法。
4.1 基于情感词典(SnowNLP)的快速实现
对于中文文本,SnowNLP是一个简单易用的库,它内置了情感分析功能。其原理大致是基于一个情感词典,计算文本中积极和消极词汇的权重。
from snownlp import SnowNLP def sentiment_analysis_with_snownlp(text): """ 使用SnowNLP进行情感分析 返回得分(0-1之间,越接近1表示越积极) """ if not text or not isinstance(text, str) or len(text.strip()) == 0: return 0.5 # 中性默认值 try: s = SnowNLP(text) return s.sentiments except Exception as e: print(f"SnowNLP分析出错: {e}, 文本: {text[:50]}...") return 0.5 # 批量处理 df['sentiment_score_snownlp'] = df['cleaned_content'].apply(sentiment_analysis_with_snownlp)优点:速度快,无需训练,开箱即用,对于通用领域的情感倾向(积极/消极)有不错的基线效果。缺点与坑点:
- 领域不匹配:SnowNLP的词典是针对通用语料训练的。在疫情这个特定领域,“新增下降”、“清零”等词在通用语境下可能是中性或消极,但在疫情语境下是积极的。这会导致分析偏差。
- 无法识别反讽和复杂句式:比如“这防疫政策真是太好了,好到让人无话可说”,SnowNLP很可能判断为积极。
- 结果只有积极概率:缺乏更细粒度的情感分类(如愤怒、恐惧、悲伤、乐观等)。
4.2 基于预训练模型(如BERT)的进阶方案
为了获得更准确、更细粒度的分析,我探索了使用预训练语言模型,如BERT,进行微调(Fine-tuning)。Hugging Face的transformers库让这个过程变得相对简单。
思路:收集一批疫情相关的微博文本,人工标注情感类别(例如:-1消极,0中性,1积极,或者更细的类别)。然后用这批数据在预训练的BERT模型上进行微调,得到一个针对疫情领域的情感分类模型。
# 这是一个高度简化的示例框架,实际需要数据准备、训练循环、评估等完整流程 from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments import torch from datasets import Dataset # 1. 准备数据 # 假设我们有标注好的数据,格式为 [{'text': '...', 'label': 0/1}, ...] train_texts = [...] # 训练文本列表 train_labels = [...] # 对应标签列表 # 2. 加载分词器和模型 model_name = 'bert-base-chinese' tokenizer = BertTokenizer.from_pretrained(model_name) model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2) # 二分类 # 3. 数据编码 def encode(examples): return tokenizer(examples['text'], truncation=True, padding='max_length', max_length=128) train_dataset = Dataset.from_dict({'text': train_texts, 'label': train_labels}) train_dataset = train_dataset.map(encode, batched=True) train_dataset.set_format(type='torch', columns=['input_ids', 'attention_mask', 'label']) # 4. 定义训练参数并训练 training_args = TrainingArguments( output_dir='./results', num_train_epochs=3, per_device_train_batch_size=16, logging_dir='./logs', ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, ) trainer.train()优点:准确率高,能理解上下文和语义,可以定制化分类体系。缺点与挑战:
- 需要标注数据:人工标注成本高,是最大的门槛。
- 计算资源要求高:训练和推理都需要GPU支持,尤其是对于海量微博数据。
- 部署复杂:需要将训练好的模型部署为API服务,供爬虫或预处理程序调用,增加了系统复杂度。
项目中的折中方案:在实际项目中,我采用了混合策略。对于初步分析和趋势观察,使用速度快的SnowNLP进行全量分析。同时,人工筛选出一部分关键时间段(如疫情爆发期、政策调整期)的博文,进行精细标注,训练一个轻量级的领域模型,用于对SnowNLP结果进行校验和关键洞察的深度分析。这样既保证了效率,又在关键点上提升了准确性。
5. 数据可视化与关联分析:让数据讲故事
当数据清洗完毕并存储了情感得分后,就到了最激动人心的环节——让数据可视化,并尝试发现疫情数据与舆论情感之间的关联。
5.1 疫情数据可视化:时序趋势与地理分布
使用matplotlib和pyecharts可以创建丰富的图表。matplotlib更灵活,适合定制化强的科研图表;pyecharts生成的交互式图表更适合在网页上展示。
示例1:全国每日新增确诊趋势图(折线图)
import matplotlib.pyplot as plt import pandas as pd from sqlalchemy import create_engine # 连接数据库 engine = create_engine('mysql+pymysql://user:password@localhost/db_name') # 查询数据 sql = """ SELECT date, SUM(new_confirmed) as national_new_confirmed FROM epidemic_data WHERE province != '中国' -- 排除全国总计行,如果存在 GROUP BY date ORDER BY date """ df_national = pd.read_sql(sql, engine) plt.figure(figsize=(14, 7)) plt.plot(df_national['date'], df_national['national_new_confirmed'], marker='o', linewidth=2) plt.title('全国每日新增确诊病例趋势', fontsize=16) plt.xlabel('日期', fontsize=12) plt.ylabel('新增确诊数', fontsize=12) plt.grid(True, linestyle='--', alpha=0.5) plt.xticks(rotation=45) plt.tight_layout() plt.savefig('national_trend.png', dpi=300) plt.show()示例2:各省份累计确诊地图(使用pyecharts)
from pyecharts import options as opts from pyecharts.charts import Map import pandas as pd # 查询各省份最新累计数据 sql_province = """ SELECT province, SUM(cumulative_confirmed) as total_confirmed FROM epidemic_data WHERE date = (SELECT MAX(date) FROM epidemic_data) AND city IS NULL -- 取省级数据 GROUP BY province """ df_province = pd.read_sql(sql_province, engine) # 准备地图数据,省份名称需要与pyecharts内置地图匹配(如‘北京’,‘上海’) data_pairs = [(row['province'], row['total_confirmed']) for _, row in df_province.iterrows()] map_chart = ( Map() .add("累计确诊", data_pairs, "china") .set_global_opts( title_opts=opts.TitleOpts(title="全国各省份累计确诊病例分布"), visualmap_opts=opts.VisualMapOpts(max_=max(df_province['total_confirmed']), is_piecewise=True), ) ) map_chart.render("china_epidemic_map.html") # 生成可交互的HTML文件5.2 微博情感趋势可视化
情感得分是连续值,可以按时间聚合(如每日平均情感得分),绘制时序折线图,观察公众情绪的波动。
# 查询每日微博平均情感得分 sql_sentiment = """ SELECT DATE(publish_time) as date, AVG(sentiment_score) as avg_sentiment, COUNT(*) as post_count FROM weibo_posts WHERE keyword IN ('疫情', '新冠') AND publish_time IS NOT NULL GROUP BY DATE(publish_time) ORDER BY date """ df_sentiment = pd.read_sql(sql_sentiment, engine) fig, ax1 = plt.subplots(figsize=(14, 7)) color = 'tab:red' ax1.set_xlabel('日期') ax1.set_ylabel('平均情感得分', color=color) ax1.plot(df_sentiment['date'], df_sentiment['avg_sentiment'], color=color, label='情感得分', marker='s') ax1.tick_params(axis='y', labelcolor=color) ax1.legend(loc='upper left') # 可以添加第二Y轴显示微博数量 ax2 = ax1.twinx() color = 'tab:blue' ax2.set_ylabel('微博数量', color=color) ax2.bar(df_sentiment['date'], df_sentiment['post_count'], alpha=0.3, color=color, label='博文量') ax2.tick_params(axis='y', labelcolor=color) ax2.legend(loc='upper right') plt.title('疫情相关微博情感趋势与讨论热度', fontsize=16) fig.tight_layout() plt.savefig('sentiment_trend.png', dpi=300) plt.show()5.3 关联分析尝试:数据同步性探索
这是项目的核心探索部分。我们可以将疫情数据(如每日新增)与微博情感数据(如每日平均情感得分)放在同一时间轴上对比。
# 将疫情数据和情感数据按日期合并 df_merge = pd.merge(df_national[['date', 'national_new_confirmed']], df_sentiment[['date', 'avg_sentiment']], on='date', how='inner') # 内连接,只取两者都有的日期 # 计算相关性(皮尔逊相关系数) correlation = df_merge['national_new_confirmed'].corr(df_merge['avg_sentiment']) print(f"新增确诊与微博情感得分的相关系数: {correlation:.3f}") # 绘制双轴图 fig, ax1 = plt.subplots(figsize=(15, 8)) ax1.plot(df_merge['date'], df_merge['national_new_confirmed'], 'g-', label='全国新增确诊', linewidth=2) ax1.set_xlabel('日期') ax1.set_ylabel('新增确诊数', color='g') ax1.tick_params(axis='y', labelcolor='g') ax1.legend(loc='upper left') ax2 = ax1.twinx() ax2.plot(df_merge['date'], df_merge['avg_sentiment'], 'b-', label='微博平均情感', linewidth=2) ax2.set_ylabel('情感得分', color='b') ax2.tick_params(axis='y', labelcolor='b') ax2.legend(loc='upper right') plt.title(f'疫情数据与微博情感同步性分析 (相关系数: {correlation:.3f})', fontsize=16) fig.tight_layout() plt.savefig('correlation_analysis.png', dpi=300) plt.show()解读与局限性:相关系数只是一个初步的线性关系度量。即使存在相关性,也不等于因果关系。情绪变化可能由疫情数字引发,也可能由其他因素(如政策发布、媒体报道)引发,或者两者共同受第三个因素影响。更深入的分析可能需要引入时间滞后性分析(Cross-Correlation)、格兰杰因果检验等更复杂的统计方法。这个项目更多的是提供一个数据获取、处理和可视化的完整管道,并为后续的深入分析打下坚实的数据基础。通过这个可视化对比,你可以直观地提出假设,例如“新增病例数激增后的一两天,微博情感得分是否会显著下降?”,然后用更严谨的统计方法去验证。
本文还有配套的精品资源,点击获取