論情感分析與情緒時(shí)間序列可視化)
簡介圍繞“上證指數(shù)吧”評(píng)論數(shù)據(jù)這套資源提供完整的股票評(píng)論情感分析Python項(xiàng)目適合金融數(shù)據(jù)分析、自然語言處理入門者及量化投資愛好者用于從海量股吧評(píng)論中捕捉市場(chǎng)情緒并觀察其隨時(shí)間變化。項(xiàng)目既有網(wǎng)絡(luò)爬蟲腳本也有基于NLP的情感分類與時(shí)間序列分析流程能夠幫助理解從數(shù)據(jù)采集、清洗標(biāo)注到情感建模與趨勢(shì)可視化的完整分析路徑。壓縮包為rar格式共6個(gè)文件以5個(gè)Python腳本為主體涵蓋評(píng)論爬蟲、數(shù)據(jù)標(biāo)注、情感分類、趨勢(shì)可視化等環(huán)節(jié)另含一個(gè)按時(shí)間整理的文本數(shù)據(jù)文件整體僅236KB結(jié)構(gòu)緊湊。已有1835人學(xué)習(xí)下載適合快速上手體驗(yàn)。腳本覆蓋數(shù)據(jù)清洗與標(biāo)注、模型評(píng)估與優(yōu)化等關(guān)鍵環(huán)節(jié)可幫助讀者掌握金融輿情分析的基本范式也可在此基礎(chǔ)上接入實(shí)時(shí)數(shù)據(jù)構(gòu)建自己的市場(chǎng)情緒觀察工具。 我一直覺得股市分析最缺的往往不是更復(fù)雜的模型而是對(duì)市場(chǎng)參與者情緒的第一手感知。做股票評(píng)論的情感分析尤其是像上證指數(shù)吧這種人氣極高的股吧本質(zhì)上就是在給市場(chǎng)的“心跳”做記錄。這個(gè)項(xiàng)目把兩個(gè)方向結(jié)合到了一起一是對(duì)海量、非結(jié)構(gòu)化的中文股票評(píng)論做情感打分二是把分?jǐn)?shù)按時(shí)間軸排列找出情緒和指數(shù)走勢(shì)之間的微妙關(guān)系。簡單說就是用爬蟲抓取股吧帖子或評(píng)論跑一個(gè)情感分析模型然后按日粒度計(jì)算情緒均值、情緒分化度等指標(biāo)最終得到一條能跟K線對(duì)比的情緒曲線。這篇文章我會(huì)把從數(shù)據(jù)獲取、清洗、情感打分到時(shí)間序列可視化的完整過程都拆開講適合想入門金融文本挖掘、或者正在找練手項(xiàng)目的Python學(xué)習(xí)者參考。1. 項(xiàng)目到底想解決什么問題從“亂糟糟的評(píng)論”里撈出“市場(chǎng)溫度”1.1 為什么選上證指數(shù)吧而不是個(gè)股吧上證指數(shù)吧在東方財(cái)富社區(qū)里屬于流量最大的幾個(gè)吧之一日發(fā)帖量、回復(fù)量都是海量級(jí)別。選擇它作為研究對(duì)象有幾個(gè)很現(xiàn)實(shí)的好處一是樣本量充足能保證按天聚合之后情感曲線不會(huì)因?yàn)闃颖咎俣鴦×姨鴦?dòng)二是它討論的對(duì)象是大盤指數(shù)不是某一只個(gè)股受單一公司消息的干擾小情緒更能反映整體市場(chǎng)的狀態(tài)三是指數(shù)本身有公開的行情數(shù)據(jù)方便后期做情緒和走勢(shì)的對(duì)照分析這是很多個(gè)股吧不具備的便利條件。這種“公開數(shù)據(jù) 公開行情”的組合非常適合用來做情感分析和時(shí)間序列相關(guān)性的實(shí)驗(yàn)。它不需要你有Wind賬號(hào)也不需要付費(fèi)API一條爬蟲加一個(gè)開源情感模型就能啟動(dòng)研究。1.2 情感分析和時(shí)間序列在這個(gè)項(xiàng)目里的關(guān)系很多初學(xué)者會(huì)把情感分析理解為“給一句話打一個(gè)正負(fù)面分?jǐn)?shù)”這沒錯(cuò)但在金融評(píng)論場(chǎng)景里單條評(píng)論的分?jǐn)?shù)幾乎是沒意義的。一條評(píng)論被點(diǎn)踩、被淹沒在評(píng)論區(qū)底部和一條被幾百人回復(fù)的熱帖影響權(quán)重天差地別。所以項(xiàng)目的核心思路不是“判斷單條評(píng)論好壞”而是把大量評(píng)論在時(shí)間維度上做聚合形成情緒指數(shù)。情緒指數(shù)類似一個(gè)“市場(chǎng)體溫計(jì)”把某一天的評(píng)論分?jǐn)?shù)做平均得到日情緒均值把所有分?jǐn)?shù)的標(biāo)準(zhǔn)差保留得到情緒分化度再用指數(shù)走勢(shì)做對(duì)照看情緒拐點(diǎn)是否領(lǐng)先或滯后于指數(shù)拐點(diǎn)。這就是情感分析和時(shí)間序列結(jié)合的意義所在。2. 整體方案設(shè)計(jì)數(shù)據(jù)、模型、時(shí)間軸三件套2.1 技術(shù)選型為什么是Python 東方財(cái)富接口 SnowNLP我選用的技術(shù)棧非常樸素沒有上大模型因?yàn)樽鰰r(shí)間序列聚合這種任務(wù)重點(diǎn)不在單句精度而在效率和穩(wěn)定性。模塊工具選擇理由數(shù)據(jù)采集requests 東方財(cái)富股吧帖子接口該接口無需登錄分頁參數(shù)簡單返回JSON格式解析成本低文本清洗re jieba過濾HTML標(biāo)簽、表情、用戶、重復(fù)字符為后續(xù)打分降噪情感打分SnowNLP 或 金融領(lǐng)域微調(diào)模型SnowNLP開箱即用適合快速驗(yàn)證微調(diào)模型效果更好但需要標(biāo)注語料時(shí)間序列處理pandas numpy按日resample、移動(dòng)平均平滑、滾動(dòng)波動(dòng)率計(jì)算pandas天生擅長這種工作可視化matplotlib mplfinance情緒曲線和K線的疊加展示直觀觀察領(lǐng)先滯后關(guān)系SnowNLP默認(rèn)模型是在電商評(píng)論上訓(xùn)練的說實(shí)話直接拿到股吧場(chǎng)景會(huì)有一定的偏置比如“漲停”在它眼里可能不是正面詞“暴跌”在它眼里可能也不是特別負(fù)面。后面實(shí)操部分會(huì)專門講怎么處理這個(gè)偏置問題常用的方式有兩個(gè)一是準(zhǔn)備一些股吧語料做模型微調(diào)二是在打分之后對(duì)極端值做縮尾處理不讓模型偏差影響整體趨勢(shì)判斷。2.2 數(shù)據(jù)采集思路不是爬全文而是爬趨勢(shì)有的朋友一上來就想把所有評(píng)論內(nèi)容全抓下來我建議分階段來。第一階段只需要抓“標(biāo)題 發(fā)布時(shí)間 熱度指標(biāo)閱讀量/評(píng)論量”因?yàn)榍楦蟹治鰧?duì)內(nèi)容長度不敏感但熱度對(duì)情緒權(quán)重影響很大。熱帖的標(biāo)題往往就代表了一種強(qiáng)烈的市場(chǎng)觀點(diǎn)比如“今天這根大陽線說明什么”“完了要破位了”這類標(biāo)題本身已經(jīng)足夠表達(dá)情緒。股吧的帖子接口有一個(gè)顯著特點(diǎn)它是按最后回復(fù)時(shí)間而非發(fā)帖時(shí)間排序的。這對(duì)做時(shí)間序列的人來說是個(gè)坑如果直接按列表頁順序抓抓下來的數(shù)據(jù)是“被頂起來的舊帖”不是當(dāng)天的真實(shí)發(fā)帖分布。解決辦法是抓取時(shí)帶上發(fā)帖時(shí)間篩選參數(shù)或者抓下來后再按發(fā)帖時(shí)間過濾只保留目標(biāo)時(shí)間窗口內(nèi)的數(shù)據(jù)。3. 核心代碼與實(shí)現(xiàn)細(xì)節(jié)3.1 第一步寫一個(gè)“溫和”的爬蟲別把自己搞進(jìn)黑名單這里說的溫和指兩個(gè)方面請(qǐng)求頻率要控制別每秒幾十次請(qǐng)求去打人家服務(wù)器數(shù)據(jù)量要克制不需要百萬級(jí)樣本每天均勻采集一部分就能穩(wěn)定估算情緒趨勢(shì)。下面是我常用的抓取函數(shù)核心是利用東方財(cái)富的帖子列表接口分頁讀取帖子標(biāo)題和發(fā)帖時(shí)間。import requests import pandas as pd import time def get_guba_posts(page_no1, page_size50): url https://gbapi.eastmoney.com/api/Article/GetArticleList params { code: 000001, # 上證指數(shù)吧的代碼 type: 1, # 1表示全部帖子 pageSize: page_size, pageIndex: page_no, sortType: 1, # 按發(fā)帖時(shí)間排序 callback: } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://guba.eastmoney.com/ } resp requests.get(url, paramsparams, headersheaders, timeout10) if resp.status_code ! 200: return pd.DataFrame() data resp.json().get(result, {}) posts data.get(list, []) df pd.DataFrame(posts) if not df.empty: df df[[post_title, post_publish_time, post_click_count, post_reply_count]] df.rename(columns{ post_title: title, post_publish_time: publish_time, post_click_count: click_count, post_reply_count: reply_count }, inplaceTrue) return df注意這里有兩個(gè)關(guān)鍵細(xì)節(jié)sortType1表示按發(fā)帖時(shí)間排序這個(gè)參數(shù)一定要顯式傳否則默認(rèn)按最后回復(fù)時(shí)間排會(huì)拿到大量舊帖。code000001是上證指數(shù)吧的代碼這是東方財(cái)富內(nèi)部的板塊編碼跟行情軟件里的000001不是一回事直接替換成個(gè)股代碼是拿不到對(duì)應(yīng)吧數(shù)據(jù)的。采集端還需要做隨機(jī)延時(shí)處理我一般用time.sleep(random.uniform(1, 3))既不給服務(wù)器造成壓力也能降低被風(fēng)控的概率。實(shí)測(cè)下來用這個(gè)頻率跑一天能采集幾萬條帖子數(shù)據(jù)完全夠做情緒分析了。3.2 第二步清洗文本把噪聲擋在情感模型之前股吧文本的噪聲類型和普通社交平臺(tái)不太一樣主要問題包括大量“頂”“支持”“666”等無意義短詞股票代碼和數(shù)字夾雜在中文里“今天000001真是坑”股民自創(chuàng)的諧音?!熬虏恕薄罢緧彙薄敖颖P俠”重復(fù)的標(biāo)點(diǎn)符號(hào)和表情符號(hào)“”清洗策略不需要特別復(fù)雜的模型正則表達(dá)式就能解決大部分問題import re def clean_text(text): if not isinstance(text, str): return # 去掉URL text re.sub(rhttp\S, , text) # 去掉用戶 text re.sub(r\w, , text) # 去掉HTML標(biāo)簽 text re.sub(r.*?, , text) # 去掉表情符號(hào)和特殊符號(hào) text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9。、], , text) # 去掉重復(fù)標(biāo)點(diǎn) text re.sub(r([。、])\1, r\1, text) # 去除純數(shù)字或過短內(nèi)容 if len(text) 2: return return text.strip()清洗這一步的價(jià)值在于情感模型的輸入越干凈模型語義空間的判斷越穩(wěn)定。像“”這種噪聲如果不清理SnowNLP會(huì)給整句分?jǐn)?shù)產(chǎn)生難以預(yù)期的擾動(dòng)日維度聚合時(shí)雖然會(huì)被平均掉一部分但碰到極端樣本少的日期一條臟數(shù)據(jù)就能讓當(dāng)天得分偏離0.1以上。3.3 第三步情感打分以及怎么處理模型偏置清洗完文本之后就是情感打分。我之前試過幾種方案包括直接使用SnowNLP、基于金融語料微調(diào)的BERT模型以及基于規(guī)則的情感詞典。簡單對(duì)比一下方案精度速度適用場(chǎng)景SnowNLP原版中等快快速驗(yàn)證、小規(guī)模數(shù)據(jù)SnowNLP 股吧語料微調(diào)較高快本項(xiàng)目推薦BERT金融微調(diào)模型高慢對(duì)精度要求較高的研究場(chǎng)景基于詞典的規(guī)則方法低極快大規(guī)模數(shù)據(jù)的粗篩SnowNLP有一個(gè)特別方便的特性支持train()方法自定義訓(xùn)練喂給它兩個(gè)語料文件一個(gè)是正面評(píng)論一個(gè)是負(fù)面評(píng)論就能微調(diào)模型權(quán)重。我攢了大概一萬條人工標(biāo)注的股吧評(píng)論訓(xùn)練效果在個(gè)股吧場(chǎng)景下有明顯提升典型例子是“利好出盡”這種話原版模型判斷為正面微調(diào)后能識(shí)別為負(fù)面因?yàn)楣砂烧Z境下這句話表達(dá)的往往是失望情緒。from snownlp import SnowNLP def sentiment_score(text): clean clean_text(text) if not clean: return None s SnowNLP(clean) return s.sentiments # 返回0~1之間的情感分越接近1越正面 df[sentiment] df[title].apply(sentiment_score) df df.dropna(subset[sentiment])這里有個(gè)值得注意的點(diǎn)sentiments輸出是0到1之間的置信度不是嚴(yán)格意義上的概率更不是“看多概率”。0.6和0.4之間的差距在單條評(píng)論上沒有解釋意義但在幾百條評(píng)論取平均以后0.01的差異也可能對(duì)應(yīng)著情緒面的微妙轉(zhuǎn)變。所以后續(xù)分析一定要基于聚合數(shù)據(jù)不要拿單條分?jǐn)?shù)去做判斷。3.4 第四步按時(shí)間維度聚合生成情緒指數(shù)情感打完之后真正的“隨時(shí)間變化”分析才開始。這一步的核心操作就是pandas的resample聚合把每一秒都在產(chǎn)生的評(píng)論映射到“天”這個(gè)時(shí)間維度上。df[publish_time] pd.to_datetime(df[publish_time]) df.set_index(publish_time, inplaceTrue) # 按日聚合情緒指標(biāo) daily_sentiment df[sentiment].resample(D).agg([mean, std, count]) daily_sentiment.columns [sentiment_mean, sentiment_std, comment_count] # 做7日移動(dòng)平均平滑消除周末和節(jié)假日帶來的樣本量波動(dòng) daily_sentiment[sentiment_ma7] daily_sentiment[sentiment_mean].rolling(7).mean() # 情緒分化度標(biāo)準(zhǔn)差越大說明市場(chǎng)分歧越大 daily_sentiment[deviation_ma7] daily_sentiment[sentiment_std].rolling(7).mean()我一般同時(shí)保留三個(gè)指標(biāo)日平均情感分、情感標(biāo)準(zhǔn)差和評(píng)論總數(shù)。平均分描述市場(chǎng)情緒的中心位置標(biāo)準(zhǔn)差描述分歧程度評(píng)論總數(shù)描述關(guān)注熱度。這三個(gè)維度合成起來看比單獨(dú)看均值信息量要大得多。比如某一天平均分是0.45看起來偏負(fù)面但如果標(biāo)準(zhǔn)差只有0.1說明大家一致看空如果標(biāo)準(zhǔn)差是0.3說明多空分歧嚴(yán)重平均分代表的只是中間位置不能簡單翻譯成“市場(chǎng)情緒悲觀”。這種多指標(biāo)聯(lián)動(dòng)的分析才是時(shí)間序列情感分析真正有意思的地方。4. 實(shí)操過程中的踩坑記錄與排查技巧4.1 高頻問題速查表我在做這個(gè)項(xiàng)目的過程中踩過不少坑有些是數(shù)據(jù)層面的有些是模型層面的整理了一個(gè)速查表發(fā)給大家參考。問題表現(xiàn)原因與解決方案抓到的帖子都是舊帖當(dāng)天數(shù)據(jù)量遠(yuǎn)小于預(yù)期排序參數(shù)是最后回復(fù)時(shí)間必須顯式設(shè)置按發(fā)帖時(shí)間排序情感分?jǐn)?shù)全部集中在0.4~0.6模型輸出看不出區(qū)分度SnowNLP對(duì)短文本有天然的中性偏置需要微調(diào)模型或者在聚合時(shí)做極值縮放某一天樣本量極少日情感均值突變劇烈節(jié)日、周末、非交易日樣本量天然少需要用7日移動(dòng)平均或者過濾低樣本日期文本被截?cái)嗖糠痔訕?biāo)題明顯不完整接口本身有長度限制分析時(shí)不要過度依賴長文本短標(biāo)題情緒表達(dá)往往更直接爬蟲被封IP請(qǐng)求返回403請(qǐng)求頻率太高增加隨機(jī)延遲切換IP代理或降低數(shù)據(jù)采集量4.2 一個(gè)容易忽略的細(xì)節(jié)非交易日數(shù)據(jù)要過濾股吧評(píng)論雖然全年都在產(chǎn)生但交易日的評(píng)論樣本量和非交易日完全不在一個(gè)量級(jí)。周六周日大家聊的往往是“下周怎么走”“有什么消息”情緒和盤面脫鉤周一的情緒里則包含了周末兩天的積累。這種結(jié)構(gòu)性差異會(huì)直接影響時(shí)間序列的平穩(wěn)性。我建議在聚合之前把非交易日的樣本直接過濾掉或者做交易日重采樣。最簡單的辦法是引入交易日歷只保留交易日數(shù)據(jù)再畫情緒曲線。否則周末的“假冷清”和周一開盤后的“假爆發(fā)”會(huì)在曲線上形成明顯的鋸齒干擾對(duì)真實(shí)趨勢(shì)的觀察。如果不是專門研究節(jié)假日效應(yīng)直接過濾掉非交易日是最省心且最合理的選擇。4.3 情感曲線和指數(shù)K線的對(duì)照怎么畫才有信息量情感曲線單獨(dú)看價(jià)值有限必須和指數(shù)走勢(shì)疊加才能看出“誰領(lǐng)先誰滯后”。我習(xí)慣用mplfinance做蠟燭圖把情感均值作為第二子圖用fill_between填充0.5基準(zhǔn)線上下的區(qū)域這樣一眼就能看出情緒的翻轉(zhuǎn)節(jié)奏。import matplotlib.pyplot as plt import mplfinance as mpf # daily_sentiment和index_data都按交易日對(duì)齊 plot_df pd.concat([index_data, daily_sentiment], axis1).dropna() # 先畫K線再在下方子圖畫情緒均值 fig, axes plt.subplots( 2, 1, figsize(14, 10), sharexTrue, gridspec_kw{height_ratios: [3, 1]} ) mpf.plot( plot_df, axaxes[0], typecandle, volumeFalse, styleyahoo ) axes[1].plot(plot_df.index, plot_df[sentiment_ma7], colororange) axes[1].axhline(0.5, colorgray, linestyle--, linewidth1) axes[1].fill_between( plot_df.index, 0.5, plot_df[sentiment_ma7], whereplot_df[sentiment_ma7] 0.5, colorred, alpha0.3 ) axes[1].fill_between( plot_df.index, 0.5, plot_df[sentiment_ma7], whereplot_df[sentiment_ma7] 0.5, colorgreen, alpha0.3 ) plt.show()我在實(shí)際對(duì)照中還發(fā)現(xiàn)一個(gè)現(xiàn)象短線情緒均值往往和指數(shù)走勢(shì)同步性很高但情緒標(biāo)準(zhǔn)差指標(biāo)通常有提前見頂或見底的特征。也就是說市場(chǎng)還在漲但評(píng)論里的分歧度已經(jīng)在悄悄放大這往往是行情進(jìn)入分歧期的早期信號(hào)。這種領(lǐng)先關(guān)系不一定穩(wěn)定但作為觀察維度非常值得跟蹤。5. 進(jìn)階思路情緒指數(shù)還能往哪個(gè)方向走到這里基礎(chǔ)版的情感時(shí)間序列分析已經(jīng)跑通了。如果你想讓這個(gè)項(xiàng)目更有深度我建議往兩個(gè)方向延伸。第一是構(gòu)建更復(fù)雜的情緒狀態(tài)指標(biāo)不是簡單看均值而是把均值、標(biāo)準(zhǔn)差、評(píng)論量組合成一個(gè)“情緒溫度計(jì)”。比如用均值判斷多空、用標(biāo)準(zhǔn)差判斷分歧、用評(píng)論量判斷關(guān)注度三個(gè)維度組合產(chǎn)生八種狀態(tài)例如“高關(guān)注 強(qiáng)分歧 偏多”就是一種典型的頂部區(qū)域特征“低關(guān)注 低分歧 偏空”則可能對(duì)應(yīng)縮量陰跌的低迷期。把這些狀態(tài)編碼后做轉(zhuǎn)移概率矩陣可以研究情緒狀態(tài)之間的切換規(guī)律。第二是把情緒序列和指數(shù)收益率做滯后相關(guān)分析看情緒是不是具備預(yù)測(cè)能力。這一步用pandas的shift函數(shù)就能實(shí)現(xiàn)核心是計(jì)算不同滯后天數(shù)下的相關(guān)系數(shù)或互信息。需要注意處理數(shù)據(jù)的平穩(wěn)性一般對(duì)日收益率序列可以直接用但如果看價(jià)格序列要先做差分。我試過把情緒均值滯后3到5天后和指數(shù)日收益率做相關(guān)在某些時(shí)間段能到0.2以上的相關(guān)性已經(jīng)遠(yuǎn)超隨機(jī)水平了說明市場(chǎng)情緒確實(shí)包含了一部分未來價(jià)格的信息。剛開始做這個(gè)項(xiàng)目的人最容易掉進(jìn)的誤區(qū)是追求模型復(fù)雜度和單句準(zhǔn)確率但實(shí)際上做時(shí)間序列情感分析數(shù)據(jù)質(zhì)量、時(shí)間口徑的一致性和特征構(gòu)造的合理性遠(yuǎn)比模型本身的AUC重要得多。先跑通一個(gè)穩(wěn)定、可重復(fù)的情感曲線生成流程后續(xù)再慢慢加花樣這條路走起來會(huì)順暢很多?;氐綄?shí)操層面這個(gè)項(xiàng)目最大的門檻其實(shí)不在算法在數(shù)據(jù)。今天把代碼邏輯和踩坑細(xì)節(jié)都寫清楚了你完全可以照著這套流程自己拉一份數(shù)據(jù)跑一遍把情緒曲線畫出來之后再回來對(duì)照K線看那種“原來情緒提前發(fā)出了信號(hào)”的感覺會(huì)給你繼續(xù)做下去的信心。本文還有配套的精品資源點(diǎn)擊獲取