ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

用Python搭建微博舆情监控系统:从爬虫到可视化全流程

用Python搭建微博舆情监控系统:从爬虫到可视化全流程 简介面向专科与本科毕业设计场景的一份原创论文文档《基于Python的微博网络舆情监控系统设计与实现》以微博网络舆情为研究对象围绕数据采集、数据清洗、文本特征提取、情感分析和可视化展示构建了完整系统流程可用于Python课程设计、毕业论文框架搭建及答辩材料参考尤其适合计算机及相关专业学生快速切入舆情分析方向。压缩包内仅有1个docx文档全文约万字大小33KB文档包含摘要、关键词、第一章至第五章的完整章节具体涵盖研究背景、国内外现状、需求分析、系统架构设计、数据采集模块、数据预处理、情感分析及系统实现与可视化模块目录结构规范清晰。目前已有375人浏览学习。文中以Flask/Django为技术背景给出开发环境与模块实现说明在情感分析和文本特征提取部分提供了较为完整的论述既可作为毕业设计写作模板也可作为降重与结构组织的范例能帮助读者迅速理清舆情监控系统的整体设计思路与论文撰写脉络。1. 舆情监控系统为什么偏偏用Python搭微博至今仍是中文互联网舆论场中最具风向标价值的公开信息源热点事件、消费趋势、政策反馈几乎都会在微博上率先发酵。但“监控微博舆情”这件事的真正难点不在于“刷微博”而在于把信息流变成结构化指标一条爆款微博的转发曲线、负面评论占比、传播路径上哪些账号起了放大器作用这背后是从采集、清洗、NLP处理到可视化的完整链路。用Python做这条链路靠的不是单点库而是十几个库之间几乎无缝的集成——requests负责采集pandas做清洗SnowNLP算情感Flask搭结果页这在其他语言里往往要拼凑几套生态才能完成。本文要解决的问题很具体从零搭建一个能跑的微博舆情监控原型覆盖关键词追踪、热度趋势统计、情感倾向分析和可视化看板到手把手教你规避微博的反爬机制、处理登录态和Cookie失效以及如何用APScheduler让系统定时自动地抓一轮新微博。整个过程不需要分布式架构不依赖昂贵的商业API一台普通云服务器加一个Python环境就能扛住中小规模监控任务。这套方案适合想给业务加“舆情商情”能力的后端工程师也适合做毕业设计的学生以及正在做数据分析相关技术选型的开发者——你拿到的不是某个项目的照抄代码而是一套可以按自己需求改装的完整设计思路。2. 微博舆情数据采集从零实现一个健壮的爬虫端微博的网页端和数据接口经历过多次改版2022年以后手机版(m.weibo.cn)的接口稳定性明显优于PC端也是目前个人开发者抓取微博数据的主流入口。采集层是整个监控系统的基础如果数据不完整、字段缺失或触发封禁后续的分析再漂亮也是空谈。2.1 需要抓哪些字段存在什么结构里设计微博舆情监控第一步不是写爬虫而是想清楚“舆情分析需要哪些字段”。只存一条微博正文是不够的因为热度判断依赖转发数、评论数、点赞数时间线分析依赖发布时间戳负面情感占比依赖评论文本。我通常会把单条微博设计成如下表结构字段名类型说明用于什么分析weibo_idVARCHAR(32)微博唯一ID主键去重、增量更新contentTEXT清洗后的正文文本关键词提取、情感分析user_nameVARCHAR(64)发布者昵称KOL识别、传播路径分析followers_countINT发布者粉丝数账号权重评估reposts_countINT转发数热度等级判定comments_countINT评论数互动率计算likes_countINT点赞数热度综合评分created_atDATETIME发布时间热度时间序列、趋势分析keyword_matchedVARCHAR(32)命中的监控关键词多主题分类监控存储选型上SQLite在原型阶段完全够用量大了换成MySQL时只需要改连接串。建表语句我一般写成这样保证重复执行不会报错CREATE TABLE IF NOT EXISTS weibo_posts ( weibo_id VARCHAR(32) PRIMARY KEY, content TEXT, user_name VARCHAR(64), followers_count INT DEFAULT 0, reposts_count INT DEFAULT 0, comments_count INT DEFAULT 0, likes_count INT DEFAULT 0, created_at DATETIME, keyword_matched VARCHAR(32), INDEX idx_created_at (created_at), INDEX idx_keyword (keyword_matched) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;使用InnoDB是因为后续做增量插入时要用INSERT IGNORE或ON DUPLICATE KEY UPDATEMyISAM不支持事务约束容易出数据一致性问题。utf8mb4字符集非选不可微博正文里的emoji表情用utf8mb3会直接截断报错。2.2 m.weibo.cn接口解析与登录态处理m.weibo.cn的热门微博搜索接口请求路径为/api/container/getIndex核心参数有三个containerid是话题内容的容器IDsince_id控制翻页游标type固定为搜索类型。默认情况下直接请求会跳转登录返回数据不是JSON。解决思路是模拟浏览器登录一次拿到Cookie之后把Cookie持久化到本地文件定时刷新。先说怎么拿Cookie用Chrome打开微博手机版首页F12打开开发者工具在Network面板勾选Preserve log手动登录一次找到任意一条请求把请求头里的Cookie完整复制出来。注意不要漏掉SUB和SUBP这两个核心字段缺了它们接口会返回-100错误。我的做法是把Cookie存到环境变量里不写进代码避免机器码泄露到仓库:import os import json import time import requests COOKIE os.getenv(WEIBO_COOKIE) HEADERS { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148, Cookie: COOKIE, Referer: https://m.weibo.cn/, X-Requested-With: XMLHttpRequest, } def fetch_weibo_by_keyword(keyword: str, page: int 1) - list: params { containerid: f100103type1q{keyword}, page_type: searchall, page: page, } resp requests.get( https://m.weibo.cn/api/container/getIndex, paramsparams, headersHEADERS, timeout10, ) resp.raise_for_status() data resp.json() cards data.get(data, {}).get(cards, []) result [] for card in cards: mblog card.get(mblog, {}) if not mblog: continue result.append({ weibo_id: mblog.get(idstr), content: mblog.get(text), user_name: mblog.get(user, {}).get(screen_name), followers_count: mblog.get(user, {}).get(followers_count), reposts_count: mblog.get(reposts_count), comments_count: mblog.get(comments_count), likes_count: mblog.get(likes_count), created_at: mblog.get(created_at), }) return result这串代码的核心逻辑就三步拼参数、发GET请求、拆cards列表。containerid里q后面直接拼关键词中文不需要手动URL编码requests库会自动处理。page从1递增微博搜索接口通常返回前10页结果再做增量监控时每页按时间倒序新微博永远在第一页所以增量抓取只需要抓第一页即可。这里容易踩的坑有两个。第一是请求频率这个接口对单IP的限流是每分钟60次左右正常翻页没问题但你要同时监控10个关键词每轮请求就会达到10次加上翻页很容易触发风控。第二是数据里的text字段是HTML片段包含a href...和span标签直接放进分析前必须做清洗。2.3 评论数据补采与清洗流程微博的评论是和主微博分开的接口想要做语义分析必须有评论文本。评论接口路径是/api/container/getIndex但containerid参数完全不同格式为100103type61weibo_idxxxvidxxxxweibo_id取自主表数据。另一个坑是评论接口的翻页参数不叫page而是since_id而且返回体里的max_id才代表的是下一页游标新手经常混淆。采集后清洗这一环直接决定情感分析的准确率我是这样处理的import re import html def clean_weibo_text(raw: str) - str: text html.unescape(raw) text re.sub(r[^], , text) # 去HTML标签和链接 text re.sub(r#\S#, , text) # 去掉话题占位符 text re.sub(r//\S:, , text) # 去掉转发时带的原作者 text re.sub(r\s, , text).strip() return text[:500] def clean_comment(raw: str) - str: text html.unescape(raw) text re.sub(r[^], , text) text re.sub(r回复\S:, , text) # 去掉他人的前缀 return text.strip()清洗正则里[^]负责去掉所有HTML标签这些是微博正文里的链接和表情图片//\S:匹配转发转发时的原始作者前缀避免情感分析时把“张三://李四”这种内容也算进去。截断到500字是因为情感分析模型对超长文本的效果会衰减微博本身的正文限制是140字但转发嵌套后文本会变长。清洗完的数据我会额外做一次空值过滤把内容长度小于5的样本剔除这类样本大多是纯图片微博正文只有“分享图片”四个字进入NLP是纯噪音。3. 舆情指标分析和情感识别从文本到热度的关键一跃数据落库之后就需要回答“这条微博值不值得关注”这个核心问题。热度不是一个单指标而是时效性、传播速度、互动深度三个维度的综合评分。同时情感倾向也是舆情系统必不可少的一环这需要一个能稳定跑在CPU机器上的情感分析方案且不能上GPU。3.1 热度评分与趋势聚合的算法设计把三条指标合成一个0到100的热度分常见的加权公式是score log10(reposts comments likes 1) * 40 log10(followers_of_author 1) * 20 freshness_factor * 40freshness_factor用两条规则算出只抓最近24小时内的数据超过24小时时效分迅速衰减用1/(1age_hours*0.08)计算。这个公式比单纯看微博总互动量更倾向于识别“刚爆发”的事件因为一个百万粉丝的大V发一条互动过万的微博24小时后时效分降到接近0热度自然让位给真正的新事件。趋势聚合则是把时间离散化按小时或半小时做桶统计每个桶内的微博条数和平均热度SELECT DATE_FORMAT(created_at, %Y-%m-%d %H:00) AS hour_bucket, COUNT(*) AS post_count, ROUND(AVG( LOG10(reposts_count comments_count likes_count 1) * 40 LOG10(followers_count 1) * 20 ), 2) AS avg_score FROM weibo_posts WHERE created_at DATE_SUB(NOW(), INTERVAL 24 HOUR) GROUP BY hour_bucket ORDER BY hour_bucket;LOG10取对数是为了压制极端值比如一条转发十万的顶流微博会把平均值拉爆但取对数之后100000和1000之间的差距只是5比4更加平滑。DATE_FORMAT把created_at截断到小时这样在ECharts折线图里就是24个点趋势一目了然。3.2 情感分析SnowNLP与词典规则的双路方案Python的舆情分析中最常用的情感判断库是SnowNLP它底层是一个朴素贝叶斯分类器输出0到1的分数越接近1代表越正向。但它训练语料以电商评论为主微博文本尤其是网络流行语和反讽句式准确率会往下掉。我的做法是双路结合——SnowNLP给基础分再用词典规则修正。from snownlp import SnowNLP import re negative_boost_words [ 无语, 恶心, 抵制, 失望, 违法, 封杀, 抗议, 愤怒, 严查, 滚出 ] def sentiment_analyze(text: str) - dict: if not text or len(text.strip()) 2: return {sentiment_label: neutral, sentiment_score: 0.5} base_score SnowNLP(text).sentiments extra_penalty 0.0 for word in negative_boost_words: if word in text: extra_penalty 0.08 final_score max(0.0, min(1.0, base_score - extra_penalty)) if final_score 0.55: label positive elif final_score 0.35: label negative else: label neutral return { sentiment_label: label, sentiment_score: round(final_score, 4), }注意这里为什么是减而不是加舆情监控更注重负面信息的召回率宁可让一些疑似负面的样本被标成负面再做人工复核。每条负面词叠加0.08的惩罚值三条负面词就能把一条中间偏正的评价拉到负面区间。阈值0.55和0.35不是拍脑袋写的是针对微博语料的人工抽样调参——标注了300条微博测试后这个阈值组合的F1值最优。不同业务场景的负面包围率要求不同比如金融监控会把阈值放宽到0.45娱乐舆情会收严到0.60建议你根据自己标注集重新调优。这里也要讲清楚边界中文社交媒体的反讽识别属于NLP的开放难题单纯靠词典叠加无法识别“演技太好了哈哈”这种讽刺句式想要更准需要引入BERT类模型但这超出了“单机可跑”的设计前提。在工程化落地时这种双路模型已经能覆盖大部分监控需求。3.3 基于TF-IDF的关键词摘要提取热度高的微博只告诉你了“发生了什么”还需要知道“大家在讨论什么”。TF-IDF在这里很有效它可以从一批文本里找到区分度最高的词从而自动形成这一轮舆情的事件摘要不需要人工去读每一条微博。scikit-learn提供了现成的实现我的处理流程是from sklearn.feature_extraction.text import TfidfVectorizer import jieba import pandas as pd def extract_hot_terms(df: pd.DataFrame, top_k: int 10) - list: df df.dropna(subset[content]) # 用jieba做中文分词去掉单字和停用词 df[tokenized] df[content].apply( lambda x: .join( w for w in jieba.cut(str(x)) if len(w.strip()) 1 and w not in STOP_WORDS ) ) vectorizer TfidfVectorizer(max_features200) tfidf_matrix vectorizer.fit_transform(df[tokenized].tolist()) feature_names vectorizer.get_feature_names_out() scores tfidf_matrix.sum(axis0).A1 word_score_pairs list(zip(feature_names, scores)) word_score_pairs.sort(keylambda x: x[1], reverseTrue) return word_score_pairs[:top_k]关键点是把每条微博的分词结果合成一个语料库级别的TF-IDF矩阵再按所有文档累加得分排序。因为TF-IDF本身能压制“微博”“转发”这类高频但信息量低的词剩下的词就是这一轮迭代的有效关键词。这里需要维护一份停用词表把“简直”“真的”“我们”这类虚词过滤掉不然前几名永远是语气词。如果嫌sklearn体积偏大也可以直接用Python标准库的collections.Counter配合jieba分词做纯词频统计效果会差一些但部署更轻。我在超低配服务器1核1G上会选择后者词频TOP10和TF-IDF TOP10的重合度通常有六成频率特征已能支撑日常舆情告警。4. 舆情看板可视化用FlaskECharts搭出全交互后台舆情分析的结果只有展示给人才有用这一章讲的是如何把上面抓取和处理好的数据快速变成一个能看、能筛选、能往下翻的Web界面。这里的选型思路是用Flask提供数据API前端用ECharts渲染图表。4.1 Flask后端提供数据API先把数据库查询封装成几个独立的API接口前端页面通过Ajax拉取。API设计突出轻量不求花哨from flask import Flask, jsonify, request import pymysql import json app Flask(__name__) DB_CONFIG { host: 127.0.0.1, user: root, password: yourpassword, database: weibo_monitor, charset: utf8mb4, cursorclass: pymysql.cursors.DictCursor, } def get_db(): return pymysql.connect(**DB_CONFIG) app.route(/api/trend) def trend(): keyword request.args.get(keyword, ) hours int(request.args.get(hours, 24)) sql SELECT DATE_FORMAT(created_at, %%Y-%%m-%%d %%H:00) AS hour_bucket, COUNT(*) AS post_count, ROUND(AVG(LOG10(reposts_count comments_count likes_count 1) * 40), 2) AS avg_score FROM weibo_posts WHERE (%(keyword)s OR keyword_matched %(keyword)s) AND created_at DATE_SUB(NOW(), INTERVAL %(hours)s HOUR) GROUP BY hour_bucket ORDER BY hour_bucket with get_db() as conn: with conn.cursor() as cursor: cursor.execute(sql, {keyword: keyword, hours: hours}) rows cursor.fetchall() return jsonify({code: 0, data: rows}) app.route(/api/sentiment_pie) def sentiment_pie(): keyword request.args.get(keyword, ) sql SELECT sentiment_label, COUNT(*) AS cnt FROM weibo_posts WHERE (%(keyword)s OR keyword_matched %(keyword)s) GROUP BY sentiment_label with get_db() as conn: with conn.cursor() as cursor: cursor.execute(sql, {keyword: keyword}) rows cursor.fetchall() return jsonify({code: 0, data: rows})这里有个容易写错的地方%%转义。pymysql默认打开参数化查询SQL里的%Y会被当作占位符解析必须写作%%Y才能把真正的格式符传给MySQL。如果报TypeError: not all arguments converted during string formatting之类的错基本就是这里的问题。另外所有SQL都走参数化%(keyword)s这种写法不拼接字符串这是防止SQL注入的最低限做法舆情系统拿到的是外部网络请求这一点不能省。4.2 ECharts前端图表与自动刷新前端页面不引入框架一个HTML文件加CDN的ECharts满足监控看板需求就在够用的基础上保持简单。核心是两条图表容器尺寸要做到自适应数据接口轮询周期默认30秒。const chart echarts.init(document.getElementById(trendChart)); const sentimentChart echarts.init(document.getElementById(sentimentChart)); async function loadData() { const keyword document.getElementById(keywordInput).value; try { const [trendResp, pieResp] await Promise.all([ fetch(/api/trend?keyword${encodeURIComponent(keyword)}).then(r r.json()), fetch(/api/sentiment_pie?keyword${encodeURIComponent(keyword)}).then(r r.json()) ]); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: trendResp.data.map(d d.hour_bucket) }, yAxis: [ { type: value, name: 微博条数 }, { type: value, name: 热度均分 } ], series: [ { name: 微博数, type: bar, data: trendResp.data.map(d d.post_count) }, { name: 热度均分, type: line, yAxisIndex: 1, data: trendResp.data.map(d d.avg_score) } ] }); sentimentChart.setOption({ tooltip: { trigger: item }, series: [{ type: pie, data: pieResp.data.map(d ({ name: d.sentiment_label, value: d.cnt })) }] }); } catch (err) { console.error(数据加载失败:, err); } } setInterval(loadData, 30000); loadData();ECharts支持双Y轴配置左侧显示微博条数柱状图右侧显示热度均分折线图这样两个指标可以呈现在同一时间轴上对比。轮询用最普通的setInterval页面每次刷新数据都会把图表整体重绘数据量小了没有性能问题。如果你要监控的关键词多于5个建议给每个关键词单独开一张看板页因为单页里图表数量过多时ECharts在低端浏览器上的渲染会卡顿。页面骨架部分有一个输入框筛选按钮点击后触发一次loadData配合30秒的自动轮询直观体验就是“不用刷新页面挂着就行”。4.3 告警规则怎么嵌入看板系统舆情监控不只是看数据还要在异常出现时通知人。最轻量的做法是写一个定时任务脚本每次抓完数据后扫描最近一小时的统计结果发现负向比例超标就发送钉钉机器人Webhook通知微信班级群和企业微信群都能用类似方式接入。判定告警的阈值设计要基于历史基线不要用固定值因为不同关键词的负面率天然不同例如“某餐饮品牌”平时负面率8%“某手机品牌”平时负面率20%如果都用10%做阈值后者会天天告警直接把你短信通道打爆。我一般维护一张关键词基线表用最近7天的平均负向率乘以1.8作为当日告警阈值def check_alert(sentiment_list: list, baseline_negative_rate: float) - bool: negative_count sum(1 for item in sentiment_list if item negative) current_rate negative_count / len(sentiment_list) if sentiment_list else 0 threshold min(0.5, baseline_negative_rate * 1.8) return current_rate thresholdbaseline_negative_rate在每次跑批时同步刷新这样系统在运行两三周后会自己慢慢适应不同关键词的舆论底数。下限min(0.5, ...)起保护作用防止基线本身因异常舆情被拉得太高导致告警失效。这种自适应阈值方案比固定阈值更适合舆情监控场景因为舆论环境本身在持续变化。5. 定时采集和自动化部署把监控挂到服务器上开发机和服务器是两个世界。你在自己电脑上能跑通脚本不代表放到Linux云服务器上还能稳定运行——这里有环境、编码、进程守护、Cookie过期四座大山要翻。这一章直击“怎么让它7x24小时自己跑起来”的全部细节。5.1 用APScheduler实现多关键词定时巡航抓取任务用APScheduler比用crontab更合理因为任务之间有依赖关系——要先抓新微博然后清洗然后算情感最后写库完全可以用Python函数串联成一个任务链而不会分散在多个脚本里。而且APScheduler支持任务错过后的补偿操作休眠期间跳过的任务会在下次启动时执行一次cron做不到这个。from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.interval import IntervalTrigger import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) KEYWORDS [Python招聘, 苹果发布会, 某品牌塌房] def run_pipeline(): for kw in KEYWORDS: try: new_posts fetch_weibo_by_keyword(kw, page1) cleaned [clean_weibo_text(p[content]) for p in new_posts] for post, clean_text in zip(new_posts, cleaned): post[content] clean_text score_info sentiment_analyze(clean_text) post[sentiment_label] score_info[sentiment_label] post[sentiment_score] score_info[sentiment_score] save_to_db(post) logging.info(f[{kw}] 抓取完成新增/更新 {len(new_posts)} 条) except Exception as e: logging.warning(f[{kw}] 抓取失败: {e}, exc_infoTrue) if __name__ __main__: scheduler BlockingScheduler() scheduler.add_job( run_pipeline, triggerIntervalTrigger(minutes10), idweibo_monitor, misfire_grace_time300, coalesceTrue, ) scheduler.start()misfire_grace_time300的含义是任务原定执行时间点如果错过了5分钟以内还可以补跑超过5秒则跳过本轮coalesceTrue的作用是把错过的多轮任务合并成一次执行避免服务器休眠8小时后一次性跑48轮无意义抓取。两个参数一起设置正好堵住服务器重启和休眠这两个最常见的定时任务漏洞。这里抓取时只拉第一页每条监控关键词每轮最多消耗一次接口请求10分钟一轮24小时144次请求远低于接口限流阈值。日志输出也要配套logging.basicConfig直接输出到控制台如果你用systemd管理服务标准输出会自动被journald收走用journalctl -u weibo-monitor -f就能实时翻日志。这比自己在代码里写日志轮转简单得多。5.2 Linux服务器上的Python环境配置与依赖管理服务器上的Python安装是个高频踩坑项目。Ubuntu 22.04系统自带的python3是3.10版本对本文用到的这几个库都友好完全没必要自己再编译一套Python。需要安装的东西是pip和venv模块一条命令搞定sudo apt update sudo apt install -y python3 python3-pip python3-venv python3 --version用系统自带的python3是明智的尽量避免源码编译安装。如果你必须装新版Python比如跑BERT类模型需要3.11以上的版本也不要整个系统替换而是用apt官方源里的python3.11包sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update sudo apt install -y python3.11 python3.11-venv新建虚拟环境把依赖全部装进项目目录这是多项目共存的基础mkdir -p /opt/weibo-monitor cd /opt/weibo-monitor python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install requests pandas pymysql flask snownlp scikit-learn jieba apscheduler gunicorn pip freeze requirements.txtrequirements.txt生成好以后下次换机器部署时只需一条pip install -r requirements.txt就能还原。这里要注意的是scikit-learn的安装体积比较大如果你的服务器内存小于1G建议改用纯词频统计方案把scikit-learn从依赖列表里去掉能省下400M以上的内存占用。Flask开发服务器app.run()只能应付单机单进程调试部署时换成gunicornvenv/bin/gunicorn -w 2 -b 0.0.0.0:5000 app:app --daemon-w 2表示2个worker进程考虑到看板页面本身并发量极低2个worker足够。如果你同时监控的关键词数量大、表格查询多可以调高到4个注意内存有限的话这是一个取舍问题。5.3 进程守护与重启自恢复gunicorn的--daemon参数只是让它脱离终端运行而已机器一重启照样不会自动拉起。生产环境的正确做法是写systemd服务文件让进程在崩溃和重启后自动恢复[Unit] DescriptionWeibo Monitor Service Afternetwork.target [Service] WorkingDirectory/opt/weibo-monitor ExecStart/opt/weibo-monitor/venv/bin/gunicorn -w 2 -b 0.0.0.0:5000 app:app Restartalways RestartSec10 EnvironmentWEIBO_COOKIE你持久化后的Cookie值 [Install] WantedBymulti-user.target关键见这三行Restartalways进程退出后不区分错误类型全部拉起。网络抖动导致requests抛异常、数据库连接断开都由它兜底。RestartSec10重启前等10秒防止频繁崩溃时陷入重启死循环。EnvironmentWEIBO_COOKIE...Cookie放在服务文件里比写在代码里更灵活换Cookie不用改代码直接改这个文件然后systemctl daemon-reload和systemctl restart weibo-monitor即可。启动和查看状态sudo systemctl daemon-reload sudo systemctl enable weibo-monitor sudo systemctl start weibo-monitor sudo systemctl status weibo-monitorenable是设置开机自启status输出里如果能看到Active: active (running)说明服务正常。查看应用日志用journalctl -u weibo-monitor -n 100看最后100行排查刚才的抓取是否成功。这里有一个完整的持续运行闭环systemd保证进程活着APScheduler保证任务按时触发gunicorn保证看板页面随时可访问三层各司其职。遇到异常先看journalctl然后手动执行一遍run_pipeline()复现90%的问题都能在日志堆栈里定位到。6. 增量抓取策略与反封号的六个细节系统上线一段时间后“数据量涨上去了”并不是最值得开心的事真正的麻烦是采集端开始不稳定一会儿抓不到新微博了一会儿账号被临时限制。这一章专门讲运维层面的坑每一节都是我在真实环境里踩过后总结的处理方案。6.1 增量更新靠主键去重而不是时间过滤初版代码通常写成“每轮全量抓一遍然后按发布时间过滤最近N分钟”这有两个问题第一全量翻页浪费接口配额被封风险指数上涨第二接口返回的created_at字段是相对时间比如“3分钟前”转换时偶尔出错会把新微博误判成旧数据。更稳妥的做法是主键去重def save_to_db(post: dict) - None: sql INSERT INTO weibo_posts (weibo_id, content, user_name, followers_count, reposts_count, comments_count, likes_count, created_at, keyword_matched) VALUES (%(weibo_id)s, %(content)s, %(user_name)s, %(followers_count)s, %(reposts_count)s, %(comments_count)s, %(likes_count)s, %(created_at)s, %(keyword_matched)s) ON DUPLICATE KEY UPDATE reposts_count VALUES(reposts_count), comments_count VALUES(comments_count), likes_count VALUES(likes_count) conn get_db() try: with conn.cursor() as cursor: cursor.execute(sql, post) conn.commit() except Exception: conn.rollback() raise finally: conn.close()ON DUPLICATE KEY UPDATE只更新互动数据不碰正文和发布时间。之所以不整体覆盖是因为微博的老内容也可能被搜索接口再次返回正文和发布时间不会变变了反而是异常。每轮采集完顺手清一次历史数据只保留最近30天可以防止表无限膨胀拖慢查询DELETE FROM weibo_posts WHERE created_at DATE_SUB(NOW(), INTERVAL 30 DAY);不要频繁执行这条SQL每周一次没问题。MySQL的DELETE在数据量大时会锁表不要放在高请求时段执行建议挂在每周日凌晨三点的定时任务里。6.2 Cookie保鲜半自动化的运维方案微博Cookie的有效期视你登录设备的情况而定从几天到半个月都有。等它过期了再手动换系统中间就有一段沉默期那段时间的舆情完全抓不到。一个常见的半自动化做法是用requests.Session配合selenium来续期Selenium每次打开浏览器帮你重新扫码登录然后从浏览器里把新Cookie抓出来回写数据库。但Selenium依赖Chrome和浏览器驱动部署重量级高不一定值得。更轻量的做法是把Cookie过期检测前置到每次请求前def safe_fetch(keyword): resp requests.get(url, paramsparams, headersHEADERS, timeout10) if resp.status_code 200: data resp.json() if data.get(ok) -100: send_alert(微博Cookie已失效请及时替换) raise RuntimeError(Cookie expired) return resp.json()请求之前主动探测Cookie有效性比等触发风控再补救更有意义。ok -100是微博接口的通用失效返回码一旦捕获到立刻发告警到钉钉/企业微信运维人员只需要把新Cookie替换进systemd环境变量和服务配置文件里一条命令重启服务就算完成替换。这整个过程通常五分钟之内搞定相比每一轮失败时控制台刷日志这是一条有生命力的运维路径。6.3 反爬的六个实际参数与风险边界微博对爬虫的杀手锏不是封IP而是封账号和降权。IP代理池方案成本高且对个人项目不友好真正能扛住微博风控的往往是“把自己伪装成正常用户”的策略这里有六个参数值得逐一说明请求频率。每抓20条微博数据等待3到5秒。采样要带有随机性time.sleep(random.uniform(3, 5))比固定等待更接近人工节奏。User-Agent伪装。不要用Python默认的python-requests/2.x那是最明显的机器特征。每次请求从预先准备的UA列表里随机取一个iPhone和Android的UA都要混着用。Referer头补全。微博接口校验Referer正常的搜索行为必然是从微博页面内发出的请求如果你不带Referer容易被风控捕捉。单账号请求量。控制在每10分钟最多30次接口调用超过这个频率就要分账号轮询。微博的搜索接口风控阈值一般比个人中心接口高但保持克制是最好的维护方式。夜间降速。凌晨2点到6点是舆情低谷期也是风控最敏感的时段有条件的话这个时间段直接把抓取任务跳过不会对监控质量有任何影响。内存留白。数据量大了以后pandas处理全量数据会占满内存导致采集和分析任务互相拖累。建议用pd.read_sql的分页参数控制每次从数据库读出的数据量比如每页1000条循环处理不要一次性全量load。做到以上六点单账号跑了三周没有遇到硬封禁只出现过一次临时要求验证码第二天自动恢复。如果你监控的关键词超过30个还是建议自己做账号池轮询多账号分摊请求量降低单账号被盯上的概率。风控策略每天都在变这套参数不是万能解药但它是成本最低、维护最简单的基础防线。本文还有配套的精品资源点击获取
返回列表