我的毕业设计:用SVM给微博评论‘看相’,从爬虫到部署的踩坑实录
我的毕业设计:用SVM给微博评论"看相",从爬虫到部署的踩坑实录
去年这个时候,我和所有大四同学一样,正在为毕业设计焦头烂额。选题会上,导师那句"做点能落地的机器学习应用"让我灵光一闪——为什么不试试给微博评论做情感分析?既能用到课堂上学过的SVM算法,又能解决实际问题。没想到这个决定让我开启了四个月的"打怪升级"之路,从数据爬取到模型部署,几乎每个环节都踩了坑。如果你也在为机器学习项目发愁,这篇血泪史或许能帮你少走弯路。
1. 数据获取:当爬虫遇上反爬
1.1 微博API的"温柔陷阱"
最初天真的我以为直接调用微博开放API就能轻松获取数据。注册开发者账号时还暗自窃喜,直到发现免费接口每天只有200条的调用限额——这连训练集的零头都不够。更坑的是,API返回的JSON数据里夹杂着各种表情符号和话题标签,光是清洗这些"噪声"就让我写了三天的正则表达式。
# 示例:处理微博特殊符号的正则 import re def clean_weibo_text(text): # 去除话题标签 text = re.sub(r'#.*?#', '', text) # 去除@用户 text = re.sub(r'@[\w\u4e00-\u9fff]+', '', text) # 去除网页链接 text = re.sub(r'http[s]?://\S+', '', text) return text.strip()1.2 转向requests+selenium的"土办法"
迫不得已,我转向了requests直接爬取网页的方案。但很快发现微博的异步加载机制让简单requests.get()只能拿到空壳HTML。这时候selenium派上了用场,配合ChromeDriver模拟浏览器行为。不过新问题接踵而至:
- 登录验证:微博的滑动验证码让我折腾了两天,最终选择手动登录后保存cookies
- 频率限制:连续请求20次就会被封IP,不得不加上随机间隔和代理IP池
- 页面结构变更:某次微博前端改版直接导致我的xpath定位全部失效
提示:爬虫代码一定要写异常处理和日志记录!我在凌晨三点被爬虫中断惊醒的血泪教训。
2. 文本预处理:当jieba遇上网络用语
2.1 中文分词的"水土不服"
本以为调用jieba.cut()就能万事大吉,实际处理微博评论时才发现:
- 网络用语:"yyds"、"绝绝子"等词直接被切成单字
- 中英文混排:"iPhone13香"被错误分割
- 表情符号:"[笑cry]"等微博特有表情影响分词效果
解决方案是自定义词典和调整分词模式:
import jieba # 加载自定义词典 jieba.load_userdict("weibo_lexicon.txt") # 调整分词模式 text = "iPhone13真的yyds![笑cry]" print(jieba.lcut(text, HMM=True)) # 开启HMM模式识别未登录词2.2 特征工程的"维度灾难"
经过分词后,我得到了超过5万个特征词——这直接导致后续SVM训练时内存爆炸。通过以下方法终于将特征压缩到3000维左右:
- 停用词过滤:不仅使用标准停用词表,还针对微博特点新增了"转发"、"微博"等高频低价值词
- TF-IDF筛选:保留TF-IDF值最高的前3000个特征词
- n-gram组合:加入部分bigram特征捕捉短语语义(如"不太行")
3. 模型训练:当SVM遇上不平衡数据
3.1 标签获取的"主观困境"
手动标注5000条数据后,我发现了情感分析最棘手的问题——很多评论难以明确归类。比如:
- "这家餐厅环境5星,服务0分"(到底是正面还是负面?)
- "笑死,这就是你说的惊喜?"(反讽语气如何判断)
- "[吃瓜]"(纯表情如何标注)
最终决定采用三分类:正面(1)、负面(-1)、中性(0),并邀请同学交叉验证标注结果。
3.2 SVM调参的"玄学艺术"
本以为sklearn的SVM开箱即用,实际调参过程却让我怀疑人生:
| 参数组合 | 准确率 | 问题 |
|---|---|---|
| 默认参数 | 68.2% | 对负面样本识别率极低 |
| kernel='rbf', C=1 | 72.5% | 训练时间过长 |
| kernel='linear', C=0.5 | 75.1% | 对中性样本过拟合 |
| kernel='linear', class_weight='balanced' | 78.3% | 最终选择方案 |
关键发现是必须设置class_weight来处理样本不平衡问题,负面评论数量只有正面的三分之一。
from sklearn.svm import SVC model = SVC(kernel='linear', class_weight='balanced', # 自动调整类别权重 probability=True) # 需要预测概率 model.fit(X_train, y_train)4. 系统部署:当Flask遇上生产环境
4.1 从Jupyter到Web服务的鸿沟
本地训练好的模型准确率达到78%让我沾沾自喜,直到尝试把它变成可用的Web服务:
- 环境依赖:训练时用的Python 3.8,服务器却是3.6,导致部分库不兼容
- 内存泄漏:最初每请求一次就加载一次模型,很快耗尽内存
- 并发瓶颈:用Flask开发服务器直接部署,10个并发请求就崩溃
解决方案:
- 使用gunicorn作为WSGI服务器
- 实现模型单例模式,启动时加载一次
- 添加缓存机制减少重复计算
from flask import Flask import pickle app = Flask(__name__) # 全局加载模型 with open('svm_model.pkl', 'rb') as f: model = pickle.load(f) @app.route('/predict', methods=['POST']) def predict(): text = request.form['text'] # ...预处理逻辑... proba = model.predict_proba([processed_text]) return jsonify({'result': proba.tolist()})4.2 云服务器上的"神秘bug"
把代码部署到学生优惠的云服务器后,出现了本地从未遇到的诡异问题:
- 中文显示乱码(解决方案:在Nginx配置中添加charset utf-8)
- 分词速度慢10倍(原因:服务器没有CPU优化编译的jieba版本)
- 凌晨自动崩溃(罪魁祸首:crontab里有个忘记删除的测试任务)
最终系统架构如下:
用户浏览器 → Nginx反向代理 → Gunicorn → Flask应用 ↑ Redis缓存这个毕业设计最终拿了优秀,但比成绩更重要的是解决问题的过程。记得在模型准确率卡在75%的那两周,我几乎翻遍了所有相关论文和GitHub项目;在部署遇到编码问题时,凌晨三点在Stack Overflow上遇到了一位同样熬夜的国内开发者。这些经历让我明白,真实的机器学习项目永远不是教科书上的标准流程,而是一个不断遇到问题、解决问题的循环。如果你也在做类似项目,我的建议是:尽早开始写单元测试,多用版本控制保存关键节点,最重要的是——保持耐心,每个bug都是进步的机会。
