:從數(shù)據(jù)采集到預(yù)警的完整實(shí)現(xiàn)指南)
簡(jiǎn)介這是一份基于Python與MySQL的網(wǎng)絡(luò)輿情分析系統(tǒng)畢業(yè)論文資料包主要面向計(jì)算機(jī)專業(yè)學(xué)生、畢業(yè)設(shè)計(jì)開發(fā)者以及需要開展網(wǎng)絡(luò)言論管理的相關(guān)技術(shù)人員。論文從計(jì)算機(jī)技術(shù)對(duì)信息傳播與言論發(fā)布的影響切入闡明了輿情分析工作的現(xiàn)實(shí)需求并完成了整個(gè)系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)系統(tǒng)采用Python語(yǔ)言開發(fā)以MySQL作為數(shù)據(jù)存儲(chǔ)介質(zhì)功能覆蓋言論分析、言論管理、用戶管理等模塊同時(shí)支持以城市或地區(qū)為關(guān)鍵詞進(jìn)行本地相關(guān)負(fù)面評(píng)論的快速檢索與查看為網(wǎng)絡(luò)管理部門提供了高效、可落地的監(jiān)測(cè)思路。資源包共1個(gè)文件為docx格式文檔整體大小約1.73MB文檔中除論文正文外還包含封面、獨(dú)創(chuàng)性聲明、中英文摘要等標(biāo)準(zhǔn)寫作模板便于使用者對(duì)照格式要求進(jìn)行修改和提交。目前已有1262人學(xué)習(xí)下載適合正在撰寫輿情分析類畢業(yè)設(shè)計(jì)或希望系統(tǒng)了解PythonMySQL開發(fā)流程的讀者能夠幫助快速搭建論文框架并理解從數(shù)據(jù)庫(kù)設(shè)計(jì)到業(yè)務(wù)功能實(shí)現(xiàn)的關(guān)鍵環(huán)節(jié)。1. 基于python的網(wǎng)絡(luò)輿情分析系統(tǒng)一條從采集到預(yù)警的數(shù)據(jù)鏈路基于python的網(wǎng)絡(luò)輿情分析系統(tǒng)乍看是課程設(shè)計(jì)或畢設(shè)題目實(shí)際是一條從數(shù)據(jù)采集、清洗入庫(kù)、文本分析到可視化預(yù)警的完整數(shù)據(jù)鏈路。它解決的并不是“爬蟲能爬到多少數(shù)據(jù)”而是“拿到數(shù)據(jù)之后怎么變成可讀的輿情結(jié)論”某事件在哪個(gè)平臺(tái)發(fā)酵、負(fù)面占比多少、熱度峰值出現(xiàn)在幾點(diǎn)、源頭是哪篇文章。適合三類人準(zhǔn)備做畢設(shè)但不想只交 demo 的學(xué)生、要給部門搭輕量輿情監(jiān)控的運(yùn)維或后端工程師、以及需要給論文補(bǔ)“數(shù)據(jù)庫(kù)算法”完整閉環(huán)的研究者。我給你的一個(gè)反直覺結(jié)論是這個(gè)系統(tǒng)最耗時(shí)間的不是算法調(diào)優(yōu)而是數(shù)據(jù)采集穩(wěn)定性和文本清洗情感模型反而能用詞典法先跑通。2. 數(shù)據(jù)層設(shè)計(jì)與采集實(shí)現(xiàn)先把輿情數(shù)據(jù)存進(jìn) MySQL2.1 數(shù)據(jù)源與采集方案先劃定邊界再寫爬蟲做輿情系統(tǒng)前先想清楚數(shù)據(jù)源邊界。常見做法是優(yōu)先選三種新聞門戶的滾動(dòng)列表、公開評(píng)論區(qū)和搜索引擎的新聞聚合結(jié)果這三類頁(yè)面結(jié)構(gòu)相對(duì)穩(wěn)定、不需要登錄、字段也夠用。不要把目標(biāo)一開始就定成“監(jiān)控全網(wǎng)”數(shù)據(jù)源越泛清洗規(guī)則越難寫后期論文里也沒法交代“數(shù)據(jù)從哪來(lái)、覆蓋范圍是什么”。采集層我首選 requests BeautifulSoup不用一上來(lái)就上 Scrapy。原因很簡(jiǎn)單小規(guī)模輿情系統(tǒng)的采集目標(biāo)是“每天幾千到幾萬(wàn)條”單機(jī)多線程請(qǐng)求足夠Scrapy 的中間件、Pipeline 在數(shù)據(jù)量上去之后才是收益前期只會(huì)增加調(diào)試成本。下面這個(gè)腳本是一個(gè)新聞列表頁(yè)的最小采集實(shí)現(xiàn)import requests from bs4 import BeautifulSoup import hashlib, json, time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://news.sina.com.cn/ } def fetch_news_list(url, timeout10): resp requests.get(url, headersHEADERS, timeouttimeout) # 不要把編碼寫死成 utf-8優(yōu)先用 apparent_encoding 兜底 resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) items [] for a in soup.select(a): href a.get(href, ).strip() title a.get_text(stripTrue) if not title or len(title) 8: continue if not href.startswith(http): continue # 用 md5(url) 作為去重指紋比直接存 url 做索引快得多 uid hashlib.md5(href.encode(utf-8)).hexdigest() items.append({id: uid, title: title, url: href}) return items if __name__ __main__: base_url https://news.sina.com.cn/roll/#page_{} for page in range(1, 3): data fetch_news_list(base_url.format(page)) with open(fnews_{page}.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) time.sleep(1) # 限速是禮貌也是反封的第一步邏輯說(shuō)明resp.encoding 用 apparent_encoding 是為了避免某些頁(yè)面 gbk 編碼導(dǎo)致標(biāo)題亂碼md5 指紋是為后面 MySQL 去重做準(zhǔn)備url 動(dòng)輒幾百字符直接建索引會(huì)浪費(fèi)空間。time.sleep(1) 是控制請(qǐng)求頻率的最低成本手段輿情采集不是搶購(gòu)不需要高并發(fā)。這個(gè)腳本只輸出 json還沒入庫(kù)先看數(shù)據(jù)長(zhǎng)什么樣再?zèng)Q定表結(jié)構(gòu)。2.2 MySQL 表設(shè)計(jì)輿情主表、熱度表、詞典表數(shù)據(jù)庫(kù)選型上MySQL 是輿情系統(tǒng)最穩(wěn)的起步選擇事務(wù)保證寫入不丟、SQL 查詢方便、千萬(wàn)級(jí)數(shù)據(jù)加索引仍然可查。PostgreSQL 也很好但如果你要給別人復(fù)現(xiàn)MySQL 的普及度更高。不要在這個(gè)階段引入 MongoDB輿情數(shù)據(jù)的核心操作是按時(shí)間和來(lái)源做聚合關(guān)系型數(shù)據(jù)庫(kù)的 GROUP BY 比文檔數(shù)據(jù)庫(kù)更順手。建表時(shí)我推薦拆三張表新聞文章表存原始內(nèi)容熱度統(tǒng)計(jì)表存按小時(shí)聚合后的數(shù)據(jù)詞典表存自定義分詞和情感詞。拆表的原因是統(tǒng)計(jì)任務(wù)不應(yīng)該反復(fù)掃全表聚合結(jié)果單獨(dú)存報(bào)表直接查。下面是第一張表的建表語(yǔ)句CREATE DATABASE IF NOT EXISTS yq DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE yq; CREATE TABLE news_article ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, uid CHAR(32) NOT NULL COMMENT md5(url) 去重指紋, title VARCHAR(255) NOT NULL, content MEDIUMTEXT, source VARCHAR(64) DEFAULT , publish_time DATETIME NOT NULL, fetch_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, url VARCHAR(512) DEFAULT , comment_cnt INT DEFAULT 0, like_cnt INT DEFAULT 0, UNIQUE KEY uk_uid (uid), KEY idx_publish_time (publish_time), KEY idx_source (source) ) ENGINEInnoDB;參數(shù)說(shuō)明uid 用 CHAR(32) 而不是 VARCHAR(512) 存 URL是索引性能和存儲(chǔ)空間的雙重考慮publish_time 上建普通索引不建聯(lián)合索引因?yàn)楹罄m(xù)查詢基本是“某個(gè)時(shí)間范圍 某個(gè)來(lái)源”兩個(gè)單索引足夠。content 用 MEDIUMTEXT 而不是 TEXT是給長(zhǎng)文留余地。這里最容易踩的坑是直接在 url 字段上建 UNIQUE KEYutf8mb4 下 512 字符會(huì)超出索引長(zhǎng)度限制所以指紋字段必須單獨(dú)建。2.3 數(shù)據(jù)入庫(kù)與冪等去重同一篇新聞重復(fù)入庫(kù)的問(wèn)題采集腳本跑起來(lái)之后會(huì)遇到第一個(gè)實(shí)際麻煩頁(yè)面刷新后同一篇新聞反復(fù)抓到。如果不做去重news_article 表會(huì)膨脹得很快后面統(tǒng)計(jì)出的“輿情數(shù)量”全是虛的。解決思路不是查一遍再插而是用數(shù)據(jù)庫(kù)的唯一索引保證冪等。import pymysql conn pymysql.connect( host127.0.0.1, port3306, userroot, password123456, databaseyq, charsetutf8mb4, autocommitFalse ) def save_batch(rows): sql INSERT IGNORE INTO news_article (uid, title, content, source, publish_time, url) VALUES (%s, %s, %s, %s, %s, %s) with conn.cursor() as cur: # executemany 批量寫入比逐條 insert 快一個(gè)量級(jí) cur.executemany(sql, rows) conn.commit()邏輯說(shuō)明INSERT IGNORE 配合 uk_uid 唯一索引重復(fù)的 uid 會(huì)被數(shù)據(jù)庫(kù)直接丟棄不報(bào)錯(cuò)、不影響批量導(dǎo)入。這樣遇上去重邏輯時(shí)不需要先 SELECT 再 INSERT減少一次網(wǎng)絡(luò)往返。常見誤區(qū)是“我代碼里先查一遍再插不是更穩(wěn)嗎”但實(shí)際上高并發(fā)下這種 check-then-insert 存在競(jìng)態(tài)兩條線程同時(shí)查到“不存在”然后同時(shí)插入還是會(huì)重復(fù)。把去重交給數(shù)據(jù)庫(kù)唯一索引是輿情數(shù)據(jù)入庫(kù)最省心的做法。3. 輿情分析核心實(shí)現(xiàn)分詞、情感判定、熱點(diǎn)聚類一條鏈路3.1 中文分詞與關(guān)鍵詞提取為什么 jieba 是默認(rèn)起點(diǎn)輿情分析的第一步是分詞。中文分詞的難點(diǎn)在于歧義和新詞比如“西安交大”可能被切成“西安”和“交大”“yyds”這種網(wǎng)絡(luò)熱詞在默認(rèn)詞典里根本不存在。jieba 雖然是老牌庫(kù)但勝在可控支持自定義詞典、支持詞性標(biāo)注、部署成本極低。不要一上來(lái)就上 HanLP 或 LTP它們的模型更大、精度更高但輿情分析的前期任務(wù)是快速建立基線jieba 足夠。import jieba import jieba.analyse # 自定義詞必須優(yōu)先加載否則品牌詞會(huì)被切開 jieba.load_userdict(custom_dict.txt) text 某手機(jī)品牌發(fā)布了新款旗艦機(jī)但網(wǎng)友對(duì)該機(jī)型的續(xù)航表現(xiàn)吐槽較多 # 精確模式分詞適合做關(guān)鍵詞提取和情感判定 words jieba.lcut(text) print(words) # 基于 TF-IDF 提取 Top5 關(guān)鍵詞用于后續(xù)熱點(diǎn)聚類 keywords jieba.analyse.extract_tags(text, topK5, withWeightTrue) print(keywords)參數(shù)說(shuō)明lcut 走的是精確模式不會(huì)把“手機(jī)品牌”繼續(xù)切成“手機(jī)”和“品牌”對(duì)情感分析更友好。extract_tags 的 topK 控制每個(gè)文本保留幾個(gè)關(guān)鍵詞我一般設(shè) 5設(shè)太多會(huì)把“的”“了”這類停用詞帶回結(jié)果。custom_dict.txt 里每行格式是“詞 詞頻 詞性”比如“某手機(jī)品牌 100 n”這個(gè)詞頻不是必須準(zhǔn)確但給一個(gè)較大的數(shù)能提高詞被合并的概率。如果發(fā)現(xiàn)輿情文案里品牌詞總被切開優(yōu)先檢查自定義詞典而不是換分詞庫(kù)。3.2 情感分析基于情感詞典的正面、負(fù)面、中性判定情感分析在輿情系統(tǒng)里是核心模塊??梢杂?Snownlp 直接跑但我更推薦自寫一個(gè)詞典打分器原因是詞典法可解釋性強(qiáng)論文里能畫清楚流程圖調(diào)閾值、加否定詞都直觀后期數(shù)據(jù)量夠了想換成深度學(xué)習(xí)詞典法還能作為標(biāo)注基線。Snownlp 的問(wèn)題是模型是通用的對(duì)“某品牌降價(jià)”這種語(yǔ)境會(huì)誤判為純負(fù)面而實(shí)際上消費(fèi)者可能是在夸性價(jià)比。import jieba POS_DICT {好評(píng): 2, 穩(wěn)定: 1, 流暢: 1, 性價(jià)比: 1} NEG_DICT {翻車: -2, 卡頓: -1, 續(xù)航崩: -2, 吐槽: -1} DEGREE_DICT {非常: 1.5, 很: 1.2, 有點(diǎn): 0.8} NEG_WORDS {不, 沒, 無(wú)} def sentiment_score(text): words jieba.lcut(text) score 0.0 degree 1.0 # 程度副詞權(quán)重 neg 1.0 # 否定詞權(quán)重 for w in words: if w in NEG_WORDS: neg -1.0 elif w in DEGREE_DICT: degree DEGREE_DICT[w] elif w in POS_DICT: score degree * neg * POS_DICT[w] degree, neg 1.0, 1.0 elif w in NEG_DICT: score degree * neg * NEG_DICT[w] degree, neg 1.0, 1.0 return score print(sentiment_score(這款手機(jī)續(xù)航非常穩(wěn)定)) # 正分 print(sentiment_score(續(xù)航一點(diǎn)都不穩(wěn)定)) # 負(fù)分邏輯說(shuō)明這個(gè)打分器的核心是“程度副詞 否定詞”的窗口機(jī)制。讀到“非?!卑?degree 置為 1.5讀到“不”把 neg 置為 -1當(dāng)下一個(gè)情感詞出現(xiàn)時(shí)把兩者乘進(jìn)去。詞打分乘以負(fù)權(quán)重后整體方向反轉(zhuǎn)這就是為什么“一點(diǎn)都不穩(wěn)定”能被打成負(fù)分。參數(shù)說(shuō)明POS_DICT 和 NEG_DICT 的權(quán)重絕對(duì)值代表情感強(qiáng)度建議范圍 1~3不要給太大否則幾條極端詞就能淹沒整篇文章的判斷。這個(gè)簡(jiǎn)易版的問(wèn)題在于多個(gè)否定詞疊加如“不是不好”會(huì)被誤判實(shí)際項(xiàng)目中我加了連續(xù)否定詞窗口讀到連續(xù)兩個(gè)否定詞就重置為正向。3.3 熱點(diǎn)聚類與話題追蹤用 TF-IDF 向量化再做聚類輿情系統(tǒng)除了看單篇文章情感還要回答“當(dāng)前大家在討論什么”。常見做法是把一段時(shí)間內(nèi)的標(biāo)題向量化再做聚類。TF-IDF 向量化是為了把文本轉(zhuǎn)成數(shù)字KMeans 聚類是為了把相似話題歸到同一組。為什么不直接用關(guān)鍵詞匹配因?yàn)橥粋€(gè)話題的表達(dá)方式太多“某品牌手機(jī)發(fā)布會(huì)”和“某某旗艦機(jī)發(fā)布”在字面上不重疊但向量空間里距離很近。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans import numpy as np titles [ 某品牌發(fā)布新款旗艦機(jī)售價(jià)大幅下調(diào), 旗艦機(jī)價(jià)格戰(zhàn)開打某廠商跟進(jìn), 某品牌手機(jī)續(xù)航測(cè)試結(jié)果出爐, 發(fā)布會(huì)現(xiàn)場(chǎng)體驗(yàn)新機(jī)手感輕薄, ] # ngram_range(1,2) 保留詞組min_df2 過(guò)濾只出現(xiàn)一次的詞 vectorizer TfidfVectorizer(max_features10000, ngram_range(1, 2), min_df2) X vectorizer.fit_transform(titles) km KMeans(n_clusters2, random_state42, n_init10) labels km.fit_predict(X) # 每個(gè)聚類取 TF-IDF 權(quán)重最高的詞作為話題標(biāo)簽 feature_names vectorizer.get_feature_names_out() for i in range(km.n_clusters): centroid_idx km.cluster_centers_[i].argsort()[::-1][:5] words [feature_names[j] for j in centroid_idx] print(fCluster {i}: {, .join(words)})參數(shù)說(shuō)明n_clusters2 是基于“4 條標(biāo)題最多兩個(gè)話題”的人為假設(shè)真實(shí)場(chǎng)景里需要用輪廓系數(shù)或手肘法確定min_df2 是把只出現(xiàn)一次的詞過(guò)濾掉避免單個(gè)文本的獨(dú)特詞影響聚類n_init10 是讓 KMeans 多跑幾次取最優(yōu)避免隨機(jī)初始化帶來(lái)的結(jié)果抖動(dòng)。熱點(diǎn)聚類的時(shí)效性要注意輿情系統(tǒng)的聚類窗口我一般設(shè) 4 小時(shí)或 24 小時(shí)窗口太短樣本不夠太長(zhǎng)話題已經(jīng)變了好幾輪。這個(gè)方案適合論文里的定性分析如果要實(shí)時(shí)追蹤熱點(diǎn)演變就得換增量聚類算法但那是后話。4. 可視化看板與預(yù)警規(guī)則讓輿情結(jié)果能看懂、能報(bào)警4.1 用 Flask ECharts 搭建最小輿情看板輿情分析的結(jié)果最終要給兩類人看一類是技術(shù)負(fù)責(zé)人關(guān)心數(shù)據(jù)量和模型準(zhǔn)確率另一類是業(yè)務(wù)或領(lǐng)導(dǎo)只看趨勢(shì)圖和預(yù)警消息。給業(yè)務(wù)看的東西必須可讀我的做法是 Flask 提供 JSON 接口前端用 ECharts 渲染前后端分離但放在同一個(gè)項(xiàng)目里省去跨域配置的麻煩。from flask import Flask, jsonify import pymysql, json app Flask(__name__) app.route(/api/trend) def trend(): 返回最近 24 小時(shí)輿情數(shù)量趨勢(shì) conn pymysql.connect(host127.0.0.1, userroot, password123456, databaseyq, charsetutf8mb4) with conn.cursor() as cur: cur.execute( SELECT DATE_FORMAT(publish_time, %Y-%m-%d %H:00) AS hour, COUNT(*) AS cnt FROM news_article WHERE publish_time NOW() - INTERVAL 24 HOUR GROUP BY DATE_FORMAT(publish_time, %Y-%m-%d %H:00) ORDER BY hour ) rows cur.fetchall() conn.close() return jsonify([{hour: r[0], cnt: r[1]} for r in rows]) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)邏輯說(shuō)明這個(gè)接口的參數(shù)設(shè)計(jì)值得講一下——DATE_FORMAT 按小時(shí)粒度聚合把時(shí)間戳變成整點(diǎn)字符串前端 ECharts 的 x 軸可以直接用用 NOW() - INTERVAL 24 HOUR 而不是在 Python 里算時(shí)間是為了讓時(shí)間判斷完全交給數(shù)據(jù)庫(kù)避免應(yīng)用服務(wù)器與數(shù)據(jù)庫(kù)服務(wù)器時(shí)區(qū)不一致導(dǎo)致“少一小時(shí)數(shù)據(jù)”的詭異問(wèn)題。這也是輿情系統(tǒng)里很常見的一種“玄學(xué) bug”最后查出來(lái)是時(shí)區(qū)沒對(duì)齊。接口只返回 JSON圖表渲染交給前端ECharts 的折線圖配置網(wǎng)上有很多模板重點(diǎn)是數(shù)據(jù)接口穩(wěn)定。4.2 輿情預(yù)警規(guī)則閾值、速率、突發(fā)敏感詞三種觸發(fā)有了趨勢(shì)數(shù)據(jù)下一步是預(yù)警。輿情預(yù)警不能只做一個(gè)“超過(guò) 100 條就報(bào)警”的靜態(tài)閾值那樣不是預(yù)警是噪音。我一般設(shè)計(jì)三層規(guī)則第一層是數(shù)量閾值比如單個(gè)時(shí)間窗輿情總數(shù)超過(guò)歷史均值 1.5 倍第二層是負(fù)面占比比如一小時(shí)內(nèi)負(fù)面情感占比超過(guò) 30%第三層是敏感詞突發(fā)比如自定義敏感詞在十分鐘內(nèi)出現(xiàn)次數(shù)超過(guò)日常均值 5 倍。這三層規(guī)則從“量變”到“質(zhì)變”到“具體事件”覆蓋了大部分輿情場(chǎng)景。def check_alert(total_now, total_hist, neg_rate_now, neg_th0.3, rate_th1.5): alerts [] # 規(guī)則1總量突增 if total_hist 0 and total_now / total_hist rate_th: alerts.append(總量突增) # 規(guī)則2負(fù)面占比超閾值 if neg_rate_now neg_th: alerts.append(負(fù)面占比過(guò)高) return alerts參數(shù)說(shuō)明rate_th1.5 意味著當(dāng)前時(shí)間窗輿情數(shù)比歷史同期多 50% 才報(bào)警。這個(gè)值不能拍腦袋定死需要觀察一周數(shù)據(jù)再調(diào)如果每天都有幾個(gè)時(shí)段誤報(bào)就調(diào)高到 2.0如果該報(bào)警沒報(bào)漏報(bào)就調(diào)低到 1.3。neg_th0.3 是負(fù)面占比閾值這個(gè)值受數(shù)據(jù)源影響很大如果是采集新聞源負(fù)面占比天然低0.3 就偏高如果是采集社交公開數(shù)據(jù)負(fù)面情緒本就多0.5 都不一定報(bào)警。所以預(yù)警規(guī)則一定要做成配置項(xiàng)放數(shù)據(jù)庫(kù)或配置文件里不要硬編碼在代碼里。4.3 日?qǐng)?bào)導(dǎo)出把數(shù)據(jù)庫(kù)查詢結(jié)果落成 Excel 文件輿情系統(tǒng)的最終交付物往往是一份日?qǐng)?bào)或周報(bào)今天總輿情量、負(fù)面量、Top 熱點(diǎn)話題、預(yù)警事件列表。用 pandas 加 openpyxl 直接導(dǎo)出 Excel 是最穩(wěn)妥的交付方式業(yè)務(wù)方可以在 Excel 里二次篩選領(lǐng)導(dǎo)也能直接看。除了 pandas還要裝 openpyxl光 pandas 只能寫 csvExcel 格式的導(dǎo)出需要這個(gè)庫(kù)。import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://root:123456127.0.0.1:3306/yq?charsetutf8mb4) df pd.read_sql( SELECT DATE(publish_time) AS day, COUNT(*) AS total_cnt, SUM(CASE WHEN sentiment -1 THEN 1 ELSE 0 END) AS neg_cnt FROM news_article WHERE publish_time CURDATE() - INTERVAL 7 DAY GROUP BY DATE(publish_time) , engine) df[neg_ratio] (df[neg_cnt] / df[total_cnt]).round(4) df.to_excel(輿情日?qǐng)?bào).xlsx, indexFalse, sheet_name7日輿情趨勢(shì))邏輯說(shuō)明這里用的 SQLAlchemy 連接串代替了之前的 pymysql 直連主要是讓 pandas 的 read_sql 能直接拿 DataFrame省去逐行轉(zhuǎn)換。CASE WHEN 在 SQL 里做條件計(jì)數(shù)比先把全表查回來(lái)再在 Python 里 groupby 更省內(nèi)存。注意 to_excel 的 sheet_name 參數(shù)如果你不指定默認(rèn)叫 Sheet1日?qǐng)?bào)交付時(shí)最好把 sheet 名字改成業(yè)務(wù)能看懂的內(nèi)容。Excel 導(dǎo)出是輿情系統(tǒng)里“做了沒人夸、漏了被人罵”的功能但它恰恰是論文里“系統(tǒng)應(yīng)用”章節(jié)最好寫的部分。5. 輿情系統(tǒng)落地避坑記錄5 條壓測(cè)才暴露的數(shù)據(jù)庫(kù)與調(diào)度問(wèn)題5.1 采集端被限制或返回亂碼現(xiàn)象、成因與處理現(xiàn)象采集腳本跑了十幾分鐘后返回的頁(yè)面從正常列表變成驗(yàn)證頁(yè)或者標(biāo)題全是亂碼。第一次遇到時(shí)我以為是反爬查了半天才發(fā)現(xiàn)是編碼問(wèn)題。成因有兩類一是沒有帶 Referer 或 User-Agent被服務(wù)器識(shí)別為腳本二是頁(yè)面本身是 gbk 編碼代碼里 resp.encoding 固定寫成 utf-8導(dǎo)致標(biāo)題亂碼。解決頭部信息至少帶真實(shí)瀏覽器的 UAReferer 填首頁(yè)地址編碼不要寫死用 resp.apparent_encoding。采集頻率用限速控制不做并發(fā)轟炸大多數(shù)新聞?wù)镜娜萑潭缺认胂笾懈邌?wèn)題出在頻率不穩(wěn)定時(shí)快時(shí)慢才容易被限制。5.2 數(shù)據(jù)庫(kù)連接池被打滿與慢查詢另一個(gè)隱蔽坑現(xiàn)象多線程采集開起來(lái)后程序報(bào) “Too many connections”第二天看數(shù)據(jù)庫(kù)統(tǒng)計(jì)接口響應(yīng)時(shí)間從 50ms 漲到 3 秒。原因代碼里每個(gè)線程每次寫入都新建 pymysql 連接用完后只 close 了游標(biāo)沒有關(guān)連接聚合查詢 group by publish_time 時(shí)沒有索引。解決全局維護(hù)一個(gè)連接池用 pymysql 的 connections 模塊或直接改用 SQLAlchemy 的連接池給 publish_time 和 source 建普通索引。這個(gè)問(wèn)題的隱蔽性在于單線程時(shí)一切正常一旦并發(fā)上去連接數(shù)翻倍MySQL 默認(rèn) max_connections 是 151很快就會(huì)被耗盡。5.3 情感詞典翻車語(yǔ)境遷移時(shí)準(zhǔn)確率驟降現(xiàn)象情感分析模型在測(cè)試集上準(zhǔn)確率 78%上線后發(fā)現(xiàn)“某品牌續(xù)航翻車”被判成中性。原因是詞典里沒收錄“翻車”這個(gè)詞或者“翻車”被 jieba 切成了“翻”和“車”。解決把業(yè)務(wù)場(chǎng)景里出現(xiàn)的高頻詞人工加入詞典和情感詞表比如數(shù)碼領(lǐng)域要加“續(xù)航、快充、發(fā)熱、掉電”食品領(lǐng)域要加“衛(wèi)生、口感、配料”。我后來(lái)養(yǎng)成的習(xí)慣是每周從數(shù)據(jù)庫(kù)里隨機(jī)抽 50 條新輿情人工標(biāo)注后對(duì)比模型結(jié)果把誤判的詞補(bǔ)進(jìn)詞典。這一步是純?nèi)斯せ畹珔s是詞典法準(zhǔn)確率的直接上限。5.4 預(yù)警規(guī)則太靈敏導(dǎo)致的告警疲勞現(xiàn)象預(yù)警功能上線第一天釘釘群被刷了 40 條消息第二天沒人看真出事時(shí)反而被忽略。原因是閾值設(shè)得太低加上沒有做同源合并——同一個(gè)事件被多個(gè)新聞?wù)巨D(zhuǎn)載每個(gè)站點(diǎn)都觸發(fā)一次預(yù)警。解決預(yù)警規(guī)則里加靜默期同一事件 30 分鐘內(nèi)只推送一次閾值要從歷史數(shù)據(jù)的分位數(shù)算而不是拍腦袋定。比如統(tǒng)計(jì)過(guò)去 30 天每天同時(shí)段的輿情量取 95 分位數(shù)作為閾值基線遠(yuǎn)比你拍一個(gè)“100 條”靠譜。告警疲勞是輿情系統(tǒng)最常見的“看起來(lái)做了很多、實(shí)際沒人用”的原因。5.5 文本清洗的疏漏HTML 標(biāo)簽和表情符號(hào)污染情感分?jǐn)?shù)現(xiàn)象有幾天負(fù)面率異常偏高抽了幾條數(shù)據(jù)發(fā)現(xiàn)內(nèi)容是“續(xù)航真不錯(cuò)”這明顯是矛盾表達(dá)但情感打分器把“不錯(cuò)”算成正向表情符號(hào)又被清洗邏輯刪掉。原因表情符號(hào)其實(shí)攜帶很強(qiáng)的情感信息不能簡(jiǎn)單只按文本詞典打分需要單獨(dú)提取表情符號(hào)做二次修正。解決清洗時(shí)把 HTML 實(shí)體和標(biāo)簽去掉但把表情符號(hào)單獨(dú)存一列后續(xù)作為情感分析的一個(gè)附加特征對(duì)“文字正向 表情負(fù)向”的組合給一個(gè)負(fù)向偏移。這類問(wèn)題不靠算法解決靠的是你在清洗階段多留一個(gè)心眼。6. 評(píng)估輿情系統(tǒng)到底準(zhǔn)不準(zhǔn)從留存語(yǔ)料到冷啟動(dòng)調(diào)參6.1 用 200 條標(biāo)注語(yǔ)料跑一套半小時(shí)的留存評(píng)估輿情系統(tǒng)上線前我建議先留出 200 條已經(jīng)人工標(biāo)注的輿情文本永遠(yuǎn)不要用這些數(shù)據(jù)去調(diào)情感詞典——它們只用來(lái)評(píng)估。評(píng)估指標(biāo)看三個(gè)準(zhǔn)確率、召回率、F1尤其要看負(fù)向樣本的召回率。輿情場(chǎng)景里負(fù)面事件漏報(bào)的代價(jià)遠(yuǎn)大于誤報(bào)所以我不只看總體準(zhǔn)確率而是單獨(dú)算“負(fù)向 F1”。def evaluate(y_true, y_pred): # y_true/y_pred 都是 list元素為 1 或 01 表示負(fù)面 tp sum(1 for t, p in zip(y_true, y_pred) if t 1 and p 1) fp sum(1 for t, p in zip(y_true, y_pred) if t 0 and p 1) fn sum(1 for t, p in zip(y_true, y_pred) if t 1 and p 0) precision tp / (tp fp 1e-9) recall tp / (tp fn 1e-9) f1 2 * precision * recall / (precision recall 1e-9) return precision, recall, f1邏輯說(shuō)明我加 1e-9 只是防除零實(shí)際數(shù)據(jù)里這個(gè)分母幾乎不會(huì)為零。加這一行的真實(shí)原因是之前有一次評(píng)估線上輿情數(shù)據(jù)某個(gè)時(shí)段全是正向樣本tpfp 等于 0直接 ZeroDivisionError日志里留下一堆報(bào)錯(cuò)。負(fù)向樣本在真實(shí)輿情里占比通常只有 10% 左右所以只看總體準(zhǔn)確率沒有意義——模型全預(yù)測(cè)正向也能有 90% 準(zhǔn)確率但輿情系統(tǒng)等于廢了。6.2 兩小時(shí)冷啟動(dòng)調(diào)參從詞典到閾值的一輪循環(huán)冷啟動(dòng)階段沒有標(biāo)注數(shù)據(jù)怎么調(diào)我的習(xí)慣是先用詞典法跑通全流程把系統(tǒng)輸出的每條結(jié)果人工抽檢 50 條記錄錯(cuò)誤類型如果是詞典缺詞補(bǔ)詞典如果是閾值不合理調(diào)整閾值如果是“不”“沒”這類否定詞處理不對(duì)檢查否定詞窗口。這一輪循環(huán)下來(lái)情感分析的負(fù)向 F1 能從 0.3 漲到 0.7 左右已經(jīng)達(dá)到輿情系統(tǒng)可用基線。還有一個(gè)重要習(xí)慣任何模型改動(dòng)都必須先跑一遍 6.1 的留存語(yǔ)料對(duì)比新舊結(jié)果再上生產(chǎn)。這個(gè)習(xí)慣幫我擋過(guò)不少“改了個(gè)詞典全站預(yù)警刷屏”的翻車事故。輿情系統(tǒng)的效果不是靠一個(gè)模型撐起來(lái)的而是靠數(shù)據(jù)鏈路每一環(huán)的細(xì)節(jié)堆出來(lái)的一步步把每個(gè)環(huán)節(jié)做扎實(shí)結(jié)果自然會(huì)說(shuō)服人。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取