ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SVM与随机森林的上市公司新闻文本分类实战:从爬虫到模型部署

基于SVM与随机森林的上市公司新闻文本分类实战:从爬虫到模型部署 简介这份资源是面向金融数据分析初学者与文本挖掘实践者的完整项目源码围绕上市公司新闻的抓取、文本分析与分类预测展开帮助读者理解从数据采集到模型落地的全流程。项目通过爬虫脚本从财经、每经网、金融界、中国证券网、证券时报网等平台获取历史新闻再经分词、停用词过滤、财经词典辅助等预处理提取词袋与TF-IDF特征并训练SVM、随机森林等分类器实现新闻自动归类。资源包共21个文件以17个Python源代码文件为主覆盖爬虫、文本处理、特征提取、模型训练与评估等模块另含3个txt文本文件财经词典与中文停用词表及1个许可证文件压缩包约180KB结构清晰便于按模块学习。目前已有350人学习下载适合希望掌握Python文本分析与机器学习分类实战的读者参考复用。1. 从 21 个文件说起这套上市公司新闻文本分析与分类预测源码能跑出什么拿到一个压缩包先别急着解压看代码我习惯先扫一遍文件清单。这个包一共 21 个文件17 个 Python 源码、2 个文本词典、1 个 LICENSE、1 个 readme结构非常清晰Crawler/负责抓Text_Analysis/负责算run_main.py是总入口。它解决的是一个很具体的问题——把财经、每经、金融界、中国证券网、证券时报网这几家平台的上市公司历史新闻批量抓下来做中文分词、去停用词、特征提取再用 SVM 和随机森林训练分类器最终对新闻做自动归类预测。适合谁正在做 Python 数据分析、文本挖掘、机器学习课程设计的学生以及想跑通「爬虫→NLP→分类模型」完整链路的初级算法工程师。它不是一个生产级系统但作为一套可复现的端到端练手项目链路完整度是够的。2. 拆开 Crawler 目录五个爬虫脚本的选型逻辑与启动方式2.1 为什么是这五家而不是随便抓Crawler/下对应五个平台各一个模块crawler_cnstock.py、crawler_tushare.py、crawler_nbd.py、crawler_stcn.py、crawler_jrj.py另外还有crawler_sina.py。每个模块配一个run_crawler_xxx.py作为独立启动入口这种「一个平台一个模块 一个 run 脚本」的拆法好处是某个平台改版时只动那一个文件不会牵连全局。选这五家是有讲究的。中国证券网和证券时报网偏官方公告与政策解读文本规范、噪声少每经网和金融界偏市场化报道标题党多、口语化重Tushare 严格说不是爬虫目标它是财经数据接口crawler_tushare.py大概率是拉股票列表或行情做辅助关联。这几类语料混在一起正好覆盖了「规范文本 噪声文本」两种分布训练出来的分类器泛化能力比单一来源强。如果你只抓一家模型很容易过拟合到那家的写作风格上。2.2 启动爬虫前必须确认的三件事直接python run_crawler_cnstock.py大概率会翻车先做三件事。第一确认依赖。常见做法是建虚拟环境再装python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install requests beautifulsoup4 lxml pandas tushare jieba scikit-learnrequests负责请求beautifulsoup4lxml负责解析 HTMLjieba是后面分词要用的scikit-learn提供 SVM 和随机森林。Tushare 单独装因为它有自己的一套 token 机制。第二确认目标网站的页面结构。爬虫模块里通常写死了 CSS 选择器或 XPath比如soup.select(.news-list li a)这种。网站一改版选择器就失效报错往往是AttributeError: NoneType object has no attribute text。遇到这个打开浏览器 F12 重新定位列表容器和标题节点把选择器换掉即可。第三控制请求频率。财经网站对高频访问敏感脚本里如果没有time.sleep()跑几百条就可能被限流甚至封 IP。我一般会在循环里加import time import random for page in range(1, 51): url fhttps://example.com/news?page{page} resp requests.get(url, headersheaders, timeout10) # 解析逻辑... time.sleep(random.uniform(1.5, 3.5)) # 随机间隔降低被识别为机器的概率random.uniform(1.5, 3.5)比固定sleep(2)更稳固定间隔反而容易被风控识别出规律。timeout10必须有否则某个请求卡死整个脚本就挂在那了。2.3 抓下来的数据长什么样爬虫模块一般会把结果写成 CSV 或直接存成列表供后续调用。字段通常包括标题、正文、发布时间、来源平台、股票代码。这里有个容易忽略的点正文里往往混着 HTML 标签、nbsp;、全角空格、编辑署名等噪声。如果爬虫阶段不清后面分词阶段就得补建议在爬虫模块里就做一次粗清洗import re def clean_text(raw): raw re.sub(r[^], , raw) # 去 HTML 标签 raw raw.replace(nbsp;, ).replace(\u3000, ) raw re.sub(r\s, , raw) # 合并连续空白 raw re.sub(r编辑[:].*?, , raw) # 去编辑署名 return raw.strip()re.sub(r[^], , raw)是通用去标签\u3000是全角空格中文网页里极常见。编辑署名那条正则按实际文本调整不同平台格式不一样。3. Text_Analysis 里的文本挖掘链路从停用词到 TF-IDF 特征矩阵3.1 两个词典文件决定了预处理质量Chinese_Stop_Words.txt和finance_dict.txt是这套源码里最容易被低估的两个文件。停用词表负责过滤「的、是、了、在」这类高频但无区分度的词财经词典则是自定义词典喂给 jieba 用的保证「市盈率」「资产负债率」「归母净利润」这类术语不被切碎。jieba 加载自定义词典的方式import jieba jieba.load_userdict(finance_dict.txt) # 财经术语不被切碎 def load_stopwords(path): with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) stopwords load_stopwords(Chinese_Stop_Words.txt)load_userdict接受的文件格式是「词语 词频 词性」三列词频和词性可省略。如果你发现「上市公司」被切成「上市」「公司」就是词典没生效或词条没加进去。停用词用set而不是list因为后面过滤时是if word not in stopwords集合查找是 O(1)列表是 O(n)几万条文本下差距很明显。3.2 text_processing.py 与 text_mining.py 的分工按命名推断text_processing.py管清洗和分词text_mining.py管特征提取和向量化。典型的分词函数长这样import jieba.posseg as pseg def segment(text, stopwords): words [] for word, flag in pseg.cut(text): # 只保留名词、动词、形容词过滤虚词 if flag in (n, vn, v, a) and word not in stopwords and len(word) 1: words.append(word) return wordspseg.cut带词性标注flag是词性。只留n名词、vn动名词、v动词、a形容词是因为新闻分类真正有区分度的是实词虚词和标点只会增加维度噪声。len(word) 1过滤单字中文单字歧义太大。特征提取这一步源码描述里提到了词袋和 TF-IDF。实际用 TF-IDF 更合理因为它能压低「公司」「股票」这种到处都出现的词的权重from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizerlambda x: x, # 传入的已经是分好词的列表 preprocessorlambda x: x, token_patternNone, max_features5000, # 只保留权重最高的 5000 个词 ngram_range(1, 2) # 一元词 二元词组 ) X vectorizer.fit_transform(tokenized_corpus)max_features5000是控制维度的关键不设的话词表可能上万维训练慢且容易过拟合。ngram_range(1, 2)让「新能源」「汽车」也能组合成「新能源汽车」这样的二元特征对新闻分类有实际提升。注意tokenizer和preprocessor都设成恒等函数是因为输入已经是分好词的列表不能再让 vectorizer 自己切。3.3 标签从哪来分类预测要有标签才能训练。这套源码的标签来源通常有两种一是按新闻来源平台打标财经 vs 每经 vs 金融界二是按股票行业板块打标。前者简单爬虫时顺手就记了后者需要额外关联股票代码和行业表。如果你拿到的数据没有标签列先确认run_main.py里是怎么构造y的别急着跑模型。4. 训练 SVM 与随机森林参数怎么设、结果怎么看4.1 两个分类器的定位差异SVM 在文本这种高维稀疏数据上表现稳定尤其是线性核训练快、不容易过拟合。随机森林是集成方法对特征尺度不敏感、能输出特征重要性但维度上万时训练比 SVM 慢。源码同时用这两个意图很明显做对比实验看哪个在当前语料上更好。SVM 的典型配置from sklearn.svm import LinearSVC from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) svm LinearSVC(C1.0, max_iter2000) svm.fit(X_train, y_train) y_pred svm.predict(X_test) print(classification_report(y_test, y_pred))LinearSVC比SVC(kernellinear)快得多文本分类首选。C1.0是正则强度C 越大越容易过拟合文本任务一般从 0.1 到 10 之间调。stratifyy保证训练集和测试集里各类别比例一致类别不均衡时必须有否则某类可能全跑测试集里去了。随机森林from sklearn.ensemble import RandomForestClassifier rf RandomForestClassifier( n_estimators200, # 树的数量 max_depthNone, # 不限制深度让树充分生长 min_samples_leaf2, # 叶子最少 2 个样本防过拟合 n_jobs-1, # 用满所有 CPU 核 random_state42 ) rf.fit(X_train, y_train)n_estimators200是精度和耗时的平衡点再往上收益递减。min_samples_leaf2是我一般会加的默认 1 时每棵树容易长出只含一个样本的叶子噪声全被记住。n_jobs-1并行训练能省不少时间。4.2 评估指标别只看准确率classification_report会输出 precision、recall、f1-score。新闻分类如果类别不均衡比如某平台新闻量远大于其他准确率会骗人——全预测成多数类也能有 70%。重点看 macro avg 的 f1它给每个类别等权重。如果某类 recall 特别低说明模型把这类样本大量误判成别的类要么加这类数据要么调class_weightbalanced。4.3 特征重要性反查随机森林训练完可以看哪些词贡献最大import numpy as np feature_names vectorizer.get_feature_names_out() importances rf.feature_importances_ top_idx np.argsort(importances)[-20:][::-1] for i in top_idx: print(feature_names[i], round(importances[i], 4))这一步很实用。如果 top 词里出现「编辑」「转载」「来源」这种说明清洗没做干净模型在学噪声而不是内容。我见过最离谱的一次top1 特征是「责任编辑」因为某平台每篇都有这四个字模型直接靠它分类。5. 避坑与排查这套源码跑不通时先查这五处5.1 现象爬虫返回空列表程序不报错但没数据原因目标网站改版CSS 选择器失效select()返回空列表循环体不执行自然没数据也不报错。解决打开目标页面 F12重新定位新闻列表的容器节点更新选择器。加一行print(len(items))在解析后能第一时间发现是 0 条。5.2 现象jieba 分词后全是单字术语被切碎原因finance_dict.txt没加载或文件编码不是 UTF-8load_userdict静默失败。解决确认jieba.load_userdict在分词之前调用且文件路径正确。用jieba.lcut(市盈率)单独测一下如果输出[市, 盈, 率]就是词典没生效。5.3 现象TF-IDF 矩阵维度上万训练极慢甚至内存溢出原因max_features没设或设得太大词表爆炸。解决设max_features5000到 10000 之间同时用min_df2过滤只出现一次的词。min_df2能砍掉大量拼写错误和低频噪声词。5.4 现象模型准确率虚高但换一批数据就崩原因训练集和测试集来自同一时间段、同一平台分布太像模型记住了平台特征而非内容特征。解决按时间切分而非随机切分用早期数据训练、后期数据测试。或者按平台切分用 A 平台训练、B 平台测试看真实泛化能力。5.5 现象Tushare 模块报 token 错误原因crawler_tushare.py需要 Tushare 的 API token源码里通常是占位符。解决去 Tushare 注册获取 token替换脚本里的占位字符串。如果只是跑文本分类这个模块可以暂时跳过不影响主链路。6. 让这套源码真正可用从跑通到跑好的三个进阶动作跑通run_main.py只是起点。我拿到这类项目习惯做三件事让它从「能跑」变成「能用」。第一件把爬虫结果落盘成中间层。别让爬虫和训练耦合在一次运行里爬完存成 CSV训练时读 CSV。这样调模型参数时不用反复爬省时间也少给目标网站添麻烦。存的时候用df.to_csv(news_raw.csv, indexFalse, encodingutf-8-sig)utf-8-sig带 BOMExcel 打开不乱码。第二件加一个基线对比。别只跑 SVM 和随机森林就下结论加一个朴素贝叶斯或逻辑回归做基线。如果复杂模型比基线高不了两三个点说明特征质量才是瓶颈该回去优化分词和停用词表而不是继续调模型参数。我一般会用一个简单的MultinomialNB做基线几行代码的事from sklearn.naive_bayes import MultinomialNB nb MultinomialNB(alpha0.1) nb.fit(X_train, y_train) print(NB baseline:, nb.score(X_test, y_test))alpha0.1是平滑系数文本分类常用 0.01 到 1 之间。基线跑出来如果和 SVM 差距在 3 个点以内优先查特征而不是换模型。第三件把模型和向量器一起持久化。训练完不保存下次预测又得重训浪费时间。用 joblibimport joblib joblib.dump(vectorizer, tfidf.pkl) joblib.dump(svm, svm_model.pkl) # 预测新新闻时 vectorizer joblib.load(tfidf.pkl) model joblib.load(svm_model.pkl) new_vec vectorizer.transform([segment(new_text, stopwords)]) print(model.predict(new_vec))注意预测时transform而不是fit_transform用训练好的向量器否则词表对不上维度不一致直接报错。这个坑我踩过不止一次训练时fit_transform、预测时手滑写成fit_transform结果每次预测词表都在变模型输出全是乱的。从那以后我每次保存模型都会顺手写一个predict.py单独测一条新样本确认加载、向量化、预测整条链路通再收工。希望帮到你。本文还有配套的精品资源点击获取
返回列表