據(jù)驅(qū)動(dòng)項(xiàng)目實(shí)戰(zhàn):疫情數(shù)據(jù)與微博情感關(guān)聯(lián)分析系統(tǒng))
簡(jiǎn)介本資源是一套完整的Python數(shù)據(jù)工程實(shí)踐項(xiàng)目面向計(jì)算機(jī)、數(shù)學(xué)、電子信息等專業(yè)的本科生及初學(xué)者聚焦疫情數(shù)據(jù)與社交媒體輿情的采集、治理與分析全流程。項(xiàng)目涵蓋疫情實(shí)時(shí)數(shù)據(jù)爬取、微博關(guān)鍵詞定向抓取含MySQL數(shù)據(jù)庫(kù)存儲(chǔ)、結(jié)構(gòu)化數(shù)據(jù)清洗與特征構(gòu)建、多維度可視化呈現(xiàn)含疫情趨勢(shì)圖與詞云以及基于情感詞典的微博文本細(xì)粒度情感分析適合作為課程設(shè)計(jì)、期末大作業(yè)或畢業(yè)設(shè)計(jì)參考。壓縮包共15個(gè)文件含7個(gè)核心Python腳本爬蟲、預(yù)處理、可視化、情感分析模塊、3個(gè)CSV數(shù)據(jù)集含標(biāo)注樣本與情感關(guān)鍵詞庫(kù)、2個(gè)文本詞典正/負(fù)向詞表、1個(gè)JSON微博原始數(shù)據(jù)、1張結(jié)果示意圖及1份Markdown項(xiàng)目說(shuō)明文檔整體大小23.87MB。已有728人學(xué)習(xí)下載提供開箱即用的完整代碼鏈路、清晰的模塊劃分與可復(fù)現(xiàn)的數(shù)據(jù)分析流程便于理解數(shù)據(jù)從采集到價(jià)值輸出的閉環(huán)邏輯。1. 項(xiàng)目概述一個(gè)數(shù)據(jù)驅(qū)動(dòng)的疫情社會(huì)觀察系統(tǒng)最近在整理硬盤翻出來(lái)一個(gè)前兩年做的老項(xiàng)目當(dāng)時(shí)主要是想看看社交媒體上的公眾情緒和官方發(fā)布的疫情數(shù)據(jù)之間有沒(méi)有什么有趣的關(guān)聯(lián)。這個(gè)項(xiàng)目麻雀雖小五臟俱全從數(shù)據(jù)抓取、清洗、存儲(chǔ)到分析和可視化走完了一個(gè)完整的數(shù)據(jù)分析閉環(huán)。它不是簡(jiǎn)單的“爬蟲可視化”的堆砌核心在于將結(jié)構(gòu)化的疫情統(tǒng)計(jì)數(shù)據(jù)與非結(jié)構(gòu)化的社交媒體文本進(jìn)行關(guān)聯(lián)分析試圖從數(shù)據(jù)層面理解特定時(shí)期的社會(huì)心態(tài)。簡(jiǎn)單來(lái)說(shuō)這個(gè)系統(tǒng)干了三件事第一從權(quán)威數(shù)據(jù)源定時(shí)抓取結(jié)構(gòu)化的疫情數(shù)據(jù)如新增確診、累計(jì)治愈等第二同步從微博抓取與疫情相關(guān)的關(guān)鍵詞下的實(shí)時(shí)討論內(nèi)容第三將這兩類數(shù)據(jù)清洗后存入數(shù)據(jù)庫(kù)并進(jìn)行情感傾向分析最后通過(guò)可視化圖表呈現(xiàn)數(shù)據(jù)趨勢(shì)與公眾情緒的對(duì)比。這非常適合想通過(guò)一個(gè)實(shí)戰(zhàn)項(xiàng)目系統(tǒng)學(xué)習(xí)Python數(shù)據(jù)獲取、處理、分析和呈現(xiàn)全流程的朋友尤其是對(duì)社會(huì)計(jì)算或輿情分析感興趣的同學(xué)。2. 項(xiàng)目整體架構(gòu)與技術(shù)選型解析2.1 核心模塊拆解與設(shè)計(jì)思路整個(gè)項(xiàng)目可以清晰地劃分為四個(gè)核心模塊它們之間通過(guò)數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)流轉(zhuǎn)形成一個(gè)松耦合但邏輯緊密的管道。數(shù)據(jù)采集層這是項(xiàng)目的“觸手”。我將其設(shè)計(jì)為兩個(gè)獨(dú)立的爬蟲但共享同一套請(qǐng)求管理和異常處理機(jī)制。疫情數(shù)據(jù)爬蟲目標(biāo)是各級(jí)衛(wèi)健委官網(wǎng)或聚合數(shù)據(jù)平臺(tái)。這類數(shù)據(jù)通常以JSON接口或規(guī)整的HTML表格形式存在變化頻率低每日更新但要求數(shù)據(jù)準(zhǔn)確、穩(wěn)定。因此這個(gè)爬蟲的核心是穩(wěn)健性需要處理好反爬策略如請(qǐng)求頭、頻率限制并設(shè)計(jì)重試和驗(yàn)證邏輯確保抓取到的每個(gè)數(shù)字都是可靠的。微博關(guān)鍵詞爬蟲目標(biāo)是微博的搜索頁(yè)面或移動(dòng)端接口。微博數(shù)據(jù)是非結(jié)構(gòu)化的文本、時(shí)間、用戶信息混合體且反爬嚴(yán)密動(dòng)態(tài)參數(shù)、登錄態(tài)、滑塊驗(yàn)證。這個(gè)爬蟲的核心是突破性與效率。我選擇模擬移動(dòng)端請(qǐng)求并利用關(guān)鍵詞輪詢持續(xù)抓取最新發(fā)布的微博。這里的關(guān)鍵在于如何穩(wěn)定地獲取數(shù)據(jù)而不是一味求快。注意微博爬蟲的合規(guī)性是紅線。本項(xiàng)目代碼僅用于學(xué)習(xí)網(wǎng)絡(luò)請(qǐng)求解析、動(dòng)態(tài)頁(yè)面處理和數(shù)據(jù)抽取技術(shù)。實(shí)際應(yīng)用中必須嚴(yán)格遵守robots.txt協(xié)議控制請(qǐng)求頻率避免對(duì)目標(biāo)服務(wù)器造成壓力且所有數(shù)據(jù)僅應(yīng)用于個(gè)人學(xué)習(xí)與研究分析。數(shù)據(jù)存儲(chǔ)層選用關(guān)系型數(shù)據(jù)庫(kù)MySQL作為數(shù)據(jù)倉(cāng)庫(kù)。為什么不用更簡(jiǎn)單的CSV或NoSQL因?yàn)槲覀冃枰幚韮深惒町惡艽蟮臄?shù)據(jù)并且后續(xù)需要頻繁地進(jìn)行關(guān)聯(lián)查詢和聚合分析。表結(jié)構(gòu)設(shè)計(jì)epidemic_data表存儲(chǔ)日期、地區(qū)、新增確診、新增疑似、累計(jì)確診、治愈、死亡等字段。以(date, region)作為復(fù)合主鍵。weibo_data表存儲(chǔ)微博ID、發(fā)布時(shí)間、用戶昵稱、微博正文、關(guān)鍵詞、抓取時(shí)間等。最重要的是我增加了一個(gè)cleaned_text字段用于存放預(yù)處理后的純文本為情感分析做準(zhǔn)備。這種設(shè)計(jì)便于我們執(zhí)行諸如“查詢某日全國(guó)新增確診數(shù)并關(guān)聯(lián)分析當(dāng)天‘隔離’關(guān)鍵詞下的微博情感傾向”這樣的操作。數(shù)據(jù)處理與分析層這是項(xiàng)目的“大腦”。數(shù)據(jù)預(yù)處理這是最繁瑣但決定分析質(zhì)量的一步。對(duì)于疫情數(shù)據(jù)主要是處理缺失值、異常值如某日數(shù)據(jù)為負(fù)。對(duì)于微博文本則復(fù)雜得多去除廣告、表情符號(hào)、URL鏈接進(jìn)行中文分詞去除停用詞如“的”、“了”文本規(guī)范化。情感分析我采用了基于預(yù)訓(xùn)練模型如SnowNLP或百度AI開放平臺(tái)的情感傾向分析API的方法。SnowNLP適合離線、大批量處理雖然精度不是最高但一致性較好。將微博cleaned_text字段的內(nèi)容送入模型得到情感極性分值如0-1越接近1越積極存入數(shù)據(jù)庫(kù)的新字段sentiment_score中。數(shù)據(jù)可視化層使用MatplotlibSeaborn庫(kù)目標(biāo)是生成能清晰對(duì)比趨勢(shì)的圖表。疫情數(shù)據(jù)通常用折線圖展示新增/累計(jì)趨勢(shì)用堆疊面積圖展示確診、治愈、死亡病例的構(gòu)成變化。微博情感數(shù)據(jù)可以用每日情感平均分折線圖來(lái)觀察情緒波動(dòng)用熱力圖展示不同關(guān)鍵詞在不同時(shí)間段的情感強(qiáng)度。最關(guān)鍵的是將兩條折線例如“當(dāng)日新增確診數(shù)”和“當(dāng)日‘疫情’關(guān)鍵詞微博平均情感分”放在同一個(gè)坐標(biāo)系中觀察它們的走勢(shì)關(guān)聯(lián)這是整個(gè)項(xiàng)目視覺(jué)上的亮點(diǎn)。2.2 關(guān)鍵技術(shù)棧與工具選型理由Python 3.8: 生態(tài)豐富是數(shù)據(jù)科學(xué)和爬蟲領(lǐng)域的事實(shí)標(biāo)準(zhǔn)。Requests BeautifulSoup4: 用于疫情數(shù)據(jù)爬取和靜態(tài)頁(yè)面解析。Requests簡(jiǎn)潔高效BeautifulSoup對(duì)新手友好足以應(yīng)對(duì)大多數(shù)政府公開數(shù)據(jù)頁(yè)面。Selenium (可選): 作為微博爬蟲的備選方案。當(dāng)接口逆向工程困難時(shí)可以用Selenium模擬瀏覽器行為直接獲取渲染后的數(shù)據(jù)缺點(diǎn)是速度慢、資源消耗大。本項(xiàng)目核心是學(xué)習(xí)因此我同時(shí)提供了Requests逆向和Selenium兩種思路的代碼注釋。PyMySQL / SQLAlchemy: 用于連接和操作MySQL數(shù)據(jù)庫(kù)。PyMySQL更直接SQLAlchemy的ORM模式能讓代碼更優(yōu)雅適合中型項(xiàng)目。Pandas NumPy: 數(shù)據(jù)預(yù)處理的絕對(duì)核心。Pandas的DataFrame是處理表格數(shù)據(jù)的利器清洗、轉(zhuǎn)換、聚合操作都非常方便。Jieba: 最好的中文分詞Python庫(kù)之一用于對(duì)微博正文進(jìn)行分詞。SnowNLP / TextBlob: 輕量級(jí)的情感分析庫(kù)。SnowNLP針對(duì)中文優(yōu)化開箱即用適合學(xué)習(xí)入門。Matplotlib Seaborn:Matplotlib是基礎(chǔ)繪圖庫(kù)功能強(qiáng)大但配置繁瑣Seaborn基于前者提供了更美觀的統(tǒng)計(jì)圖表樣式和高級(jí)接口兩者結(jié)合使用效率最高。Schedule / APScheduler: 用于實(shí)現(xiàn)爬蟲的定時(shí)任務(wù)讓數(shù)據(jù)采集自動(dòng)化運(yùn)行。3. 核心模塊實(shí)現(xiàn)細(xì)節(jié)與實(shí)操要點(diǎn)3.1 雙爬蟲的穩(wěn)健實(shí)現(xiàn)與防封禁策略疫情數(shù)據(jù)爬蟲的實(shí)現(xiàn) 關(guān)鍵在于找到穩(wěn)定、結(jié)構(gòu)化的數(shù)據(jù)源。我以某數(shù)據(jù)平臺(tái)的公開API為例。import requests import pandas as pd import time def fetch_epidemic_data(date_str): 獲取指定日期的疫情數(shù)據(jù) url fhttps://api.example.com/epidemic/data # 示例URL實(shí)際需替換 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36..., Accept: application/json } params {date: date_str} try: # 添加延遲避免請(qǐng)求過(guò)快 time.sleep(1) resp requests.get(url, headersheaders, paramsparams, timeout10) resp.raise_for_status() # 檢查HTTP錯(cuò)誤 data resp.json() # 解析JSON轉(zhuǎn)換為規(guī)整的列表 records [] for item in data[results]: record { date: item[date], region: item[region][name], confirmed_new: item[confirmed][new], confirmed_total: item[confirmed][total], cured_new: item[cured][new], # ... 其他字段 } records.append(record) return pd.DataFrame(records) except requests.exceptions.RequestException as e: print(f請(qǐng)求失敗: {e}) return pd.DataFrame() # 返回空DataFrame后續(xù)統(tǒng)一處理實(shí)操心得對(duì)于公開API務(wù)必仔細(xì)閱讀其文檔了解調(diào)用頻率限制。將請(qǐng)求頭User-Agent設(shè)置為常見(jiàn)的瀏覽器標(biāo)識(shí)并合理使用time.sleep()是基本的禮貌。所有網(wǎng)絡(luò)請(qǐng)求都必須包裹在try-except塊中并記錄日志這是保證爬蟲長(zhǎng)期穩(wěn)定運(yùn)行的基礎(chǔ)。微博關(guān)鍵詞爬蟲的實(shí)現(xiàn) 這里以通過(guò)解析移動(dòng)端接口為例難度較高。import requests import re import json def fetch_weibo_by_keyword(keyword, max_page5): 通過(guò)關(guān)鍵詞搜索微博模擬移動(dòng)端接口 base_url https://m.weibo.cn/api/container/getIndex headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15..., Referer: fhttps://m.weibo.cn/search?keyword{keyword} } weibo_list [] for page in range(1, max_page1): params { containerid: f100103type1q{keyword}, page_type: searchall, page: page } try: resp requests.get(base_url, headersheaders, paramsparams, timeout15) data resp.json() # 解析cards中的微博信息 if data.get(ok) 1: cards data.get(data, {}).get(cards, []) for card in cards: mblog card.get(mblog) if mblog: weibo_info { id: mblog.get(id), text: mblog.get(text), created_at: mblog.get(created_at), user_name: mblog.get(user, {}).get(screen_name), keyword: keyword } # 簡(jiǎn)單清洗HTML標(biāo)簽 weibo_info[text] re.sub(r[^], , weibo_info[text]) weibo_list.append(weibo_info) time.sleep(2) # 非常重要頁(yè)間延遲 except Exception as e: print(f抓取關(guān)鍵詞{keyword}第{page}頁(yè)失敗: {e}) break return weibo_list核心技巧微博的反爬策略會(huì)經(jīng)常變動(dòng)。containerid等參數(shù)可能需要通過(guò)瀏覽器開發(fā)者工具抓包分析獲取。User-Agent必須設(shè)置為移動(dòng)端。頁(yè)間延遲(time.sleep)是避免IP被臨時(shí)封禁的最有效手段之一不要貪快。此外可以考慮使用IP代理池來(lái)應(yīng)對(duì)更嚴(yán)格的限制但這超出了基礎(chǔ)學(xué)習(xí)的范圍。3.2 數(shù)據(jù)庫(kù)設(shè)計(jì)與高效數(shù)據(jù)入庫(kù)我使用SQLAlchemy來(lái)定義數(shù)據(jù)模型這樣代碼更清晰也便于維護(hù)。from sqlalchemy import create_engine, Column, String, Integer, Float, Date, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker Base declarative_base() class EpidemicData(Base): __tablename__ epidemic_data id Column(Integer, primary_keyTrue, autoincrementTrue) date Column(Date, nullableFalse) region Column(String(50), nullableFalse) confirmed_new Column(Integer) confirmed_total Column(Integer) cured_new Column(Integer) # ... 其他字段 # 設(shè)置復(fù)合唯一約束防止重復(fù)插入同一天同一地區(qū)的數(shù)據(jù) __table_args__ (UniqueConstraint(date, region, nameuix_date_region),) class WeiboData(Base): __tablename__ weibo_data id Column(Integer, primary_keyTrue, autoincrementTrue) weibo_id Column(String(30), uniqueTrue, nullableFalse) # 微博唯一ID created_at Column(String(30)) user_name Column(String(100)) raw_text Column(Text) # 原始文本 cleaned_text Column(Text) # 清洗后文本 keyword Column(String(50)) sentiment_score Column(Float) # 情感分析得分 crawl_time Column(Date) # 抓取時(shí)間 # 數(shù)據(jù)庫(kù)連接與會(huì)話 engine create_engine(mysqlpymysql://user:passwordlocalhost:3306/epidemic_analysis?charsetutf8mb4) Base.metadata.create_all(engine) # 創(chuàng)建表 Session sessionmaker(bindengine)注意事項(xiàng)字符集微博文本包含大量Emoji和特殊符號(hào)MySQL的utf8mb4字符集是必須的標(biāo)準(zhǔn)的utf8無(wú)法存儲(chǔ)四字節(jié)的Emoji。唯一約束為微博ID設(shè)置UNIQUE約束可以避免在多次抓取中插入重復(fù)數(shù)據(jù)。疫情數(shù)據(jù)表則通過(guò)(date, region)的復(fù)合唯一約束來(lái)去重。批量插入無(wú)論是Pandas的to_sql方法設(shè)置if_existsappend還是使用SQLAlchemy的session.bulk_save_objects()都比逐條插入session.add()快一個(gè)數(shù)量級(jí)。處理爬蟲數(shù)據(jù)時(shí)務(wù)必采用批量操作。3.3 數(shù)據(jù)預(yù)處理的實(shí)戰(zhàn)流程數(shù)據(jù)預(yù)處理在單獨(dú)的腳本中完成它從數(shù)據(jù)庫(kù)讀取原始數(shù)據(jù)處理后再寫回或存入新表。疫情數(shù)據(jù)清洗import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://...) df_epidemic pd.read_sql(SELECT * FROM epidemic_data WHERE date 2022-01-01, engine) # 1. 處理缺失值對(duì)于數(shù)值字段用前向填充或插值法 df_epidemic[confirmed_new].fillna(methodffill, inplaceTrue) # 2. 處理異常值假設(shè)新增確診不應(yīng)為負(fù)將其置為0或標(biāo)記 df_epidemic.loc[df_epidemic[confirmed_new] 0, confirmed_new] 0 # 3. 確保日期格式 df_epidemic[date] pd.to_datetime(df_epidemic[date]) # 4. 去重基于數(shù)據(jù)庫(kù)約束這里作為二次保險(xiǎn) df_epidemic.drop_duplicates(subset[date, region], keepfirst, inplaceTrue)微博文本清洗與情感分析 這是更核心的部分。import re import jieba from snownlp import SnowNLP import numpy as np def clean_weibo_text(text): 清洗單條微博文本 if not isinstance(text, str): return # 去除網(wǎng)頁(yè)標(biāo)簽、URL、用戶、話題符號(hào) text re.sub(r[^], , text) text re.sub(rhttp[s]?://\S, , text) text re.sub(r[\w\u4e00-\u9fa5], , text) text re.sub(r#([^#])#, r\1, text) # 保留話題內(nèi)容去掉#號(hào) # 去除Emoji和特殊符號(hào)簡(jiǎn)單版 text re.sub(r[^\w\u4e00-\u9fa5。、“”‘’《》【】\s], , text) # 分詞并用空格連接SnowNLP可直接處理字符串但分詞后效果更好 words jieba.lcut(text) cleaned .join([w for w in words if w.strip()]) return cleaned def analyze_sentiment_batch(text_list): 批量情感分析返回分?jǐn)?shù)列表 scores [] for text in text_list: if text and len(text.strip()) 5: # 過(guò)濾過(guò)短文本 try: s SnowNLP(text) scores.append(s.sentiments) # 情感積極度0-1 except: scores.append(0.5) # 分析失敗取中性值 else: scores.append(0.5) # 無(wú)意義文本取中性值 return scores # 從數(shù)據(jù)庫(kù)讀取原始微博數(shù)據(jù) df_weibo pd.read_sql(SELECT id, raw_text FROM weibo_data WHERE cleaned_text IS NULL, engine) # 應(yīng)用清洗函數(shù) df_weibo[cleaned_text] df_weibo[raw_text].apply(clean_weibo_text) # 批量情感分析 texts_for_analysis df_weibo[cleaned_text].tolist() sentiment_scores analyze_sentiment_batch(texts_for_analysis) df_weibo[sentiment_score] sentiment_scores # 將清洗后的數(shù)據(jù)和情感分寫回?cái)?shù)據(jù)庫(kù) # ... (使用df_weibo.to_sql或ORM更新對(duì)應(yīng)記錄)經(jīng)驗(yàn)之談文本清洗沒(méi)有“標(biāo)準(zhǔn)答案”。你需要根據(jù)分析目標(biāo)調(diào)整清洗強(qiáng)度。例如如果關(guān)心用戶情緒保留表情符號(hào)可能更有價(jià)值需要專門的表情分析。SnowNLP的情感分析對(duì)于長(zhǎng)文本和正式文本效果尚可但對(duì)于微博短文本、網(wǎng)絡(luò)流行語(yǔ)、反諷句的識(shí)別能力有限。這是所有情感分析工具的共性局限在解讀結(jié)果時(shí)必須心中有數(shù)。4. 數(shù)據(jù)可視化讓趨勢(shì)與關(guān)聯(lián)一目了然可視化不是為了畫圖而畫圖而是為了回答問(wèn)題。我們的核心問(wèn)題是疫情數(shù)據(jù)的變化與公眾情緒波動(dòng)有關(guān)聯(lián)嗎4.1 疫情趨勢(shì)可視化首先我們繪制疫情本身的發(fā)展趨勢(shì)。import matplotlib.pyplot as plt import seaborn as sns sns.set_style(whitegrid) # 設(shè)置Seaborn樣式 plt.rcParams[font.sans-serif] [SimHei] # 解決中文顯示問(wèn)題 plt.rcParams[axes.unicode_minus] False # 假設(shè)我們已經(jīng)有一個(gè)按日期聚合的疫情DataFrame: df_epidemic_daily fig, axes plt.subplots(2, 1, figsize(14, 10)) # 子圖1新增確診與新增治愈的對(duì)比折線圖 axes[0].plot(df_epidemic_daily[date], df_epidemic_daily[confirmed_new], label新增確診, colorcoral, linewidth2) axes[0].plot(df_epidemic_daily[date], df_epidemic_daily[cured_new], label新增治愈, colorlightgreen, linewidth2) axes[0].set_title(每日新增確診與新增治愈趨勢(shì)對(duì)比) axes[0].set_xlabel(日期) axes[0].set_ylabel(人數(shù)) axes[0].legend() axes[0].fill_between(df_epidemic_daily[date], 0, df_epidemic_daily[confirmed_new], colorcoral, alpha0.3) # 子圖2累計(jì)確診、治愈、死亡的堆疊面積圖 axes[1].stackplot(df_epidemic_daily[date], df_epidemic_daily[confirmed_total], df_epidemic_daily[cured_total], df_epidemic_daily[dead_total], labels[累計(jì)確診, 累計(jì)治愈, 累計(jì)死亡], colors[lightcoral, lightgreen, gray]) axes[1].set_title(累計(jì)病例構(gòu)成變化) axes[1].set_xlabel(日期) axes[1].set_ylabel(累計(jì)人數(shù)) axes[1].legend(locupper left) plt.tight_layout() plt.savefig(epidemic_trend.png, dpi300, bbox_inchestight) plt.show()4.2 微博情感趨勢(shì)與關(guān)聯(lián)分析接下來(lái)分析微博情緒并嘗試與疫情數(shù)據(jù)關(guān)聯(lián)。# 從數(shù)據(jù)庫(kù)讀取按日期和關(guān)鍵詞聚合的情感平均分 df_sentiment_daily pd.read_sql( SELECT DATE(created_at) as date, keyword, AVG(sentiment_score) as avg_sentiment FROM weibo_data WHERE sentiment_score IS NOT NULL GROUP BY DATE(created_at), keyword ORDER BY date , engine) # 假設(shè)我們關(guān)注“疫情”這個(gè)關(guān)鍵詞 df_keyword df_sentiment_daily[df_sentiment_daily[keyword] 疫情].copy() # 合并疫情數(shù)據(jù)與情感數(shù)據(jù)按日期連接 df_merged pd.merge(df_epidemic_daily, df_keyword, ondate, howinner) fig, ax1 plt.subplots(figsize(15, 7)) color tab:red ax1.set_xlabel(日期) ax1.set_ylabel(新增確診數(shù), colorcolor) line1 ax1.plot(df_merged[date], df_merged[confirmed_new], colorcolor, label新增確診, linewidth2) ax1.tick_params(axisy, labelcolorcolor) # 創(chuàng)建第二個(gè)Y軸共享同一個(gè)X軸 ax2 ax1.twinx() color tab:blue ax2.set_ylabel(微博情感平均分, colorcolor) line2 ax2.plot(df_merged[date], df_merged[avg_sentiment], colorcolor, label情感分(疫情關(guān)鍵詞), linestyle--) ax2.tick_params(axisy, labelcolorcolor) # 情感分范圍是0-1可以固定Y軸范圍以便觀察波動(dòng) ax2.set_ylim(0, 1) # 添加圖例 lines line1 line2 labels [l.get_label() for l in lines] ax1.legend(lines, labels, locupper left) plt.title(新增確診趨勢(shì)與“疫情”關(guān)鍵詞微博情感趨勢(shì)對(duì)比) fig.tight_layout() plt.savefig(correlation_analysis.png, dpi300) plt.show()通過(guò)這張雙Y軸圖我們可以直觀地觀察兩條曲線的走勢(shì)。例如可能發(fā)現(xiàn)在新增確診數(shù)大幅攀升后的幾天微博情感平均分會(huì)出現(xiàn)一個(gè)明顯的低谷情緒轉(zhuǎn)向消極。這只是一個(gè)粗略的觀察嚴(yán)謹(jǐn)?shù)南嚓P(guān)性分析還需要計(jì)算統(tǒng)計(jì)指標(biāo)如皮爾遜相關(guān)系數(shù)。5. 項(xiàng)目部署、優(yōu)化與常見(jiàn)問(wèn)題排查5.1 工程化與自動(dòng)化部署一個(gè)學(xué)習(xí)項(xiàng)目不能只停留在Jupyter Notebook里。我將其模塊化并編寫了主調(diào)度腳本。spider_epidemic.py: 疫情數(shù)據(jù)爬蟲模塊。spider_weibo.py: 微博爬蟲模塊。data_processor.py: 數(shù)據(jù)清洗與情感分析模塊。visualizer.py: 可視化繪圖模塊。config.py: 配置文件存放數(shù)據(jù)庫(kù)連接信息、API密鑰如有、關(guān)鍵詞列表等。main_scheduler.py: 主調(diào)度腳本使用schedule庫(kù)定時(shí)運(yùn)行任務(wù)。# main_scheduler.py 示例 import schedule import time from spider_epidemic import main as run_epidemic_spider from spider_weibo import main as run_weibo_spider from data_processor import main as run_data_processor def job(): print(開始每日數(shù)據(jù)任務(wù)...) run_epidemic_spider() # 抓取昨日疫情數(shù)據(jù) run_weibo_spider() # 抓取最新微博 run_data_processor() # 處理新數(shù)據(jù)并分析 print(每日數(shù)據(jù)任務(wù)完成。) # 每天上午9點(diǎn)執(zhí)行 schedule.every().day.at(09:00).do(job) while True: schedule.run_pending() time.sleep(60)可以將這個(gè)腳本放在服務(wù)器上用nohup或systemd在后臺(tái)運(yùn)行實(shí)現(xiàn)全自動(dòng)化數(shù)據(jù)流水線。5.2 性能優(yōu)化與擴(kuò)展思路異步爬蟲對(duì)于微博這類需要大量I/O等待的爬蟲可以使用aiohttpasyncio實(shí)現(xiàn)異步并發(fā)極大提升抓取效率。但要注意目標(biāo)網(wǎng)站的壓力控制并發(fā)數(shù)。增量爬取微博爬蟲不應(yīng)每次都從頭翻頁(yè)??梢杂涗浬洗巫ト〉降淖钚挛⒉㊣D下次請(qǐng)求時(shí)只抓取比這個(gè)ID更新的數(shù)據(jù)。情感分析升級(jí)SnowNLP是基線方案??梢試L試使用更強(qiáng)大的預(yù)訓(xùn)練模型如BERT在自己的標(biāo)注數(shù)據(jù)上微調(diào)以獲得更精準(zhǔn)的情感分類積極、消極、中性甚至情緒識(shí)別憤怒、恐懼、悲傷等。前端展示用Flask或Django搭建一個(gè)簡(jiǎn)單的Web應(yīng)用使用ECharts或Plotly庫(kù)生成交互式圖表讓分析結(jié)果可以通過(guò)網(wǎng)頁(yè)動(dòng)態(tài)查看。5.3 常見(jiàn)問(wèn)題與排查實(shí)錄在開發(fā)和運(yùn)行過(guò)程中我遇到了不少坑這里記錄下最典型的幾個(gè)問(wèn)題1疫情數(shù)據(jù)爬蟲突然返回空數(shù)據(jù)或403錯(cuò)誤。排查首先檢查目標(biāo)網(wǎng)站是否改版API地址或參數(shù)是否變化。其次檢查請(qǐng)求頭User-Agent和Referer是否被屏蔽嘗試更換。最后檢查IP是否被臨時(shí)封禁。解決更新請(qǐng)求頭信息在代碼中加入更長(zhǎng)的隨機(jī)延遲考慮使用付費(fèi)的代理IP服務(wù)針對(duì)高頻率抓取如果網(wǎng)站提供優(yōu)先使用其官方數(shù)據(jù)接口或開放數(shù)據(jù)集。問(wèn)題2微博爬蟲運(yùn)行一段時(shí)間后返回的數(shù)據(jù)全是“加載中”或需要登錄。排查這是最典型的問(wèn)題。微博的未登錄態(tài)訪問(wèn)有嚴(yán)格限制和頻率限制。通過(guò)瀏覽器開發(fā)者工具檢查網(wǎng)絡(luò)請(qǐng)求中是否包含cookies或authorization等認(rèn)證信息。解決方案A簡(jiǎn)單但不穩(wěn)定用Selenium模擬登錄獲取cookies后供Requests使用。但登錄可能觸發(fā)滑塊驗(yàn)證。方案B推薦學(xué)習(xí)研究微博移動(dòng)端或PC端的登錄接口逆向加密過(guò)程實(shí)現(xiàn)程序化登錄獲取令牌(access_token)。這涉及復(fù)雜的逆向工程是爬蟲技術(shù)進(jìn)階的必經(jīng)之路。忠告對(duì)于學(xué)習(xí)項(xiàng)目可以手動(dòng)獲取一次cookies放入代碼中短期使用。長(zhǎng)期運(yùn)行必須考慮自動(dòng)化更新認(rèn)證信息的機(jī)制。問(wèn)題3情感分析結(jié)果不準(zhǔn)確很多反諷句被判斷為積極。排查這是自然語(yǔ)言處理特別是中文短文本情感分析的普遍難題。SnowNLP的訓(xùn)練語(yǔ)料比較通用對(duì)網(wǎng)絡(luò)用語(yǔ)和反諷識(shí)別能力弱。解決降低期望理解工具的局限性將分析結(jié)果視為一種“情緒熱度”的粗略指標(biāo)而非精確的情感判斷。后處理規(guī)則建立一份“負(fù)面詞”詞典如果文本中包含“無(wú)語(yǔ)”、“離譜”等詞即使模型打分高也手動(dòng)調(diào)整為負(fù)面或中性。升級(jí)模型如5.2所述使用更先進(jìn)的模型并在特定領(lǐng)域的語(yǔ)料上微調(diào)。問(wèn)題4可視化圖表中文顯示為方框。排查Matplotlib默認(rèn)字體不包含中文。解決代碼中已給出方案。確保系統(tǒng)存在中文字體如SimHei黑體并通過(guò)plt.rcParams[font.sans-serif]進(jìn)行設(shè)置。在Linux服務(wù)器上部署時(shí)可能需要手動(dòng)安裝中文字體。問(wèn)題5數(shù)據(jù)庫(kù)寫入速度慢。排查是否在循環(huán)中逐條執(zhí)行session.add()和session.commit()解決務(wù)必使用批量操作。對(duì)于Pandas用df.to_sql(..., if_existsappend, indexFalse, chunksize500)。對(duì)于SQLAlchemy ORM使用session.bulk_save_objects(list_of_objects)最后再統(tǒng)一session.commit()。這個(gè)項(xiàng)目從構(gòu)思到實(shí)現(xiàn)幾乎踩遍了數(shù)據(jù)采集和分析各個(gè)階段的常見(jiàn)坑。它最大的價(jià)值不在于得出了某個(gè)驚天動(dòng)地的結(jié)論而在于完整地實(shí)踐了一套數(shù)據(jù)驅(qū)動(dòng)的解決方案。當(dāng)你親手讓散落各處的數(shù)據(jù)經(jīng)過(guò)自己的代碼管道最終變成一幅能講述故事的圖表時(shí)那種感覺(jué)遠(yuǎn)比單純調(diào)用一個(gè)分析API要深刻得多。對(duì)于初學(xué)者我建議不要一開始就追求大而全可以先實(shí)現(xiàn)疫情數(shù)據(jù)爬取和可視化再逐步加入微博爬蟲和情感分析像搭積木一樣把這個(gè)系統(tǒng)構(gòu)建起來(lái)每一步都吃透其中的原理和問(wèn)題這樣收獲才是實(shí)實(shí)在在的。本文還有配套的精品資源點(diǎn)擊獲取