资讯动态

微博定向爬虫与数据可视化:从JSON接口到Flask大屏

发布时间:2026/9/18 1:51:23 来源:尧图企业网站定制
简介一份以Python定向爬虫技术为基础的微博数据可视化毕业设计论文定位清晰主要面向计算机相关专业毕业生、爬虫入门者和社交媒体数据分析爱好者。文档依托西南财经大学论文模板完整覆盖研究背景、研究目的与意义、国内外现状、Python爬虫原理与工具、微博API与直接爬虫两种获取方式、反反爬策略、数据清洗与预处理、可视化设计实验与结果分析等章节并详细介绍了requests、BeautifulSoup、Scrapy等爬虫工具以及matplotlib、seaborn、plotly等可视化库的使用思路章节逻辑严密适合用于毕业设计参考、论文框架搭建和答辩资料准备。压缩包内仅含1个docx文档大小约31KB内容精炼但框架完整。目前已有302人学习下载对于希望快速理解微博数据采集到可视化全流程、并获取论文写作思路的读者具有实用参考价值。1. 微博定向爬虫可视化先想清楚抓什么再动手写代码做微博数据可视化多数人第一步是打开网页按 F12对着 DOM 元素逐个定位抓到什么算什么。只有当数据量上来、字段开始对不齐、页面改版把 XPath 全部打断之后才会意识到“定向爬虫”四个字的分量它要求在采集之前就定义清楚目标抓哪个关键词、什么时间段、要哪些字段、以什么频率翻页全部锁死在方案里。这篇文章要讲的是一套以 Python 为主线的完整路径先通过移动端公开搜索接口把微博 JSON 拉回来再用 pandas 清洗入库最后用 Flask 加 ECharts 做成可视化大屏。适合准备做事件舆情分析、内容运营监控或毕业设计的读者核心思路是不猜 DOM直接命中数据层的 JSON 接口解析成本低得多。2. 定向爬虫的接口设计从搜索关键词到取回微博 JSON2.1 为什么选移动端搜索接口而不是网页 DOM微博网页版的信息流里混杂着广告卡片、推荐位、直播入口和大量埋点脚本每次前端版本迭代都可能调整节点层级把解析逻辑钉在 XPath 上等于给自己埋雷。移动端 m.weibo.cn 的搜索接口以 JSON 返回字段命名一致数据消费成本更低。另一个现实原因是网页版完整信息流通常需要登录态而移动端的综合搜索接口在低频请求下可以直接访问适合做小规模定向采集。下面是一段最小请求代码import requests from urllib.parse import quote def fetch_weibo(keyword: str, page: int 1, timeout: int 10) - dict: params { containerid: 100103type1q quote(keyword), # 综合搜索容器 page_type: searchall, # 返回微博、用户、话题聚合卡片 page: page, # 翻页参数从 1 开始 } headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) } resp requests.get( https://m.weibo.cn/api/container/getIndex, paramsparams, headersheaders, timeouttimeout, ) resp.raise_for_status() return resp.json()quote(keyword)负责把中文关键词转成 URL 编码避免请求串里出现非法字符containerid里的100103type1表示进入综合搜索q后接关键词page_typesearchall让接口同时返回微博、用户和话题三类卡片page控制翻页单页大约返回 10 条数据。请求头里的User-Agent声明为移动端是因为这个接口对 Web 端 UA 的兼容表现不稳定移动端 UA 拿到的返回结构更干净。提示公共接口没有承诺访问期限脚本里务必设置 timeout并记录异常日志方便区分网络问题与内容解析问题。2.2 从嵌套的 data.cards 里拉出 mblog 字段接口返回的 JSON 外层是ok和data状态内层结构为data.cards数组数组里每个card下的mblog才是微博正文。mblog内部又嵌套user和retweeted_status等对象字段层级较深推荐写一个安全的过滤函数而不是每次手动判断索引def get_mblog(cards): for card in cards or []: if card.get(card_type) 9 and card.get(mblog): yield card[mblog]这里过滤掉card_type不等于 9 的卡片因为搜索结果中还有“综合推荐”“查看更多”之类的占位卡片它们本身不是一条可展示的微博。用生成器逐条产出 mblog后续写 CSV 或入库都可以直接循环。拿到 mblog 后最常用的字段可以整理成下面这张对照表目标字段JSON 路径类型说明微博 IDmblog.idstr主键用于去重正文mblog.textstr含 HTML 标签需清洗发布时间mblog.created_atstr相对时间需换算转发数mblog.reposts_countint互动数据评论数mblog.comments_countint互动数据点赞数mblog.attitudes_countint互动数据作者mblog.user.screen_namestr博主昵称这张表就是后期清洗阶段的字段清单采集脚本按这 7 个字段组装行数据而不是把完整 JSON 直接塞进存储层。采集与清洗的边界清晰采集脚本只负责拿数据和落盘清洗脚本只负责转换即使接口增加新字段也不会影响既有处理流程。2.3 翻页、休眠与失败退避让采集任务不轻易挂掉搜索接口的翻页逻辑不复杂第 2 页就把page设为 2但真正影响采集效果的是请求节奏。短时间内连续请求几十页大概率会在某一页收到 418 状态码随机休眠加失败退避是目前比较稳妥的采集节奏。下面这段代码加上了异常处理import time import random def safe_fetch(keyword: str, pages: int 5) - list: rows [] for page in range(1, pages 1): try: data fetch_weibo(keyword, page) except requests.RequestException: time.sleep(10) # 请求失败时先停 10 秒避免重试风暴 continue cards data.get(data, {}).get(cards, []) rows.extend(list(get_mblog(cards))) time.sleep(random.uniform(3, 8)) # 随机等待贴近真实用户行为 return rowsrandom.uniform(3, 8)生成 3 到 8 秒之间的随机等待时间比固定间隔更不容易触发风控请求异常时先休息 10 秒再进入下一页避免在接口未恢复时继续重试放大压力。单关键词的翻页不能无限进行一般取前 20 页即可因为搜索结果的时效性很强翻到后面内容会大量重复。整套逻辑默认遵守平台公开接口的使用要求采集频率控制在低频区间。2.4 依赖清单与 VS Code 调试环境写爬虫最怕代码没跑起来先卡在装环境。这个项目的依赖不多放在 requirements.txt 里即可requests2.31.0 pandas2.0.0 PyMySQL1.1.0 flask3.0.0 openpyxl3.1.0requests 负责请求与异常处理pandas 负责清洗与聚合PyMySQL 负责写库Flask 承担可视化后端openpyxl 用来导出 Excel 备份。在 VS Code 里配置 Python 环境时有两点值得注意先在项目根目录创建虚拟环境python -m venv .venv再在命令面板里选择解释器指向.venv避免污染全局环境调试接口返回时直接在fetch_weibo的 return 处打断点观察resp.json()的 keys比反复 print 整个 JSON 高效得多。3. 清洗与入库把微博半结构化数据变成可统计的二维表3.1 相对时间换算created_at 必须转成 datetime移动端接口返回的created_at不是标准时间格式而是“刚刚”“5分钟前”“昨天 22:10”这类相对文本。直接用字符串入库后面做小时级聚合时无法排序必须先在清洗阶段转换为datetime。下面这段解析函数覆盖了常见前缀import re import datetime def parse_weibo_time(s: str, now: datetime.datetime None) - datetime.datetime: if now is None: now datetime.datetime.now() s s.strip() if 刚刚 in s: return now m re.match(r(\d)分钟前, s) if m: return now - datetime.timedelta(minutesint(m.group(1))) m re.match(r(\d)小时前, s) if m: return now - datetime.timedelta(hoursint(m.group(1))) if s.startswith(昨天): t datetime.datetime.strptime(s[3:], %H:%M) return now - datetime.timedelta(days1) m re.match(r(\d)-(\d) (\d):(\d), s) if m: month, day, hour, minute map(int, m.groups()) return now.replace(monthmonth, dayday, hourhour, minuteminute) return now函数按前缀逐级匹配越靠前的规则优先级越高。“昨天”分支里先把“昨天 ”去掉再按%H:%M解析时分最后用now的日期减一天拼出完整时间。解析完还要做一轮合理性校验如果结果比当前时间晚超过一天说明跨年时把“12-31”这种日期解析到了今年应该整体回退一年。这一条在每年 1 月初跑数据时尤其重要。3.2 HTML 正文清洗与话题、 用户抽取mblog.text里带着完整的 HTML 标签常见形式是a href/n/某账号某账号/a#话题#混排。处理顺序不能乱先反转义再剥标签然后抽话题和 用户最后把这两类内容从正文中移除。下面的函数返回纯净正文、话题列表和 用户列表import re import html def clean_weibo_text(raw: str): text html.unescape(raw) text re.sub(r[^], , text) # 去掉 HTML 标签 topics re.findall(r#([^#])#, text) # 抽取话题标签 at_users re.findall(r([\w\u4e00-\u9fa5\-]), text) # 抽取 用户 text re.sub(r#([^#])#, , text) text re.sub(r[\w\u4e00-\u9fa5\-], , text) return text.strip(), topics, at_usershtml.unescape必须放在去标签之前执行否则转义后的尖括号会干扰正则话题用#([^#])#提取两个井号之外的内容不会被捕获\u4e00-\u9fa5表示中文字符范围覆盖中英文与数字用户名的场景。抽取完毕后把话题和 用户从正文中移除词云图展示时就不会全是大块的蓝色链接文字。3.3 清洗结果规范化长度过滤与互动数修正清洗环节最容易漏掉的不是非法字符而是“看起来正常、实际没意义”的数据。比如转发里带“转发微博”四个字的正文长度极短且没有分析价值营销号的互动数可能异常偏高直接进图表会拉偏整体尺度。常用处理是加一层规则过滤正文长度不低于 5 个字符互动数保留但要在统计时区分真实互动与零互动样本。import pandas as pd def build_dataframe(mblogs, keyword: str) - pd.DataFrame: rows [] for m in mblogs: content, topics, ats clean_weibo_text(m.get(text, )) if len(content) 5: continue rows.append({ id: m.get(id), keyword: keyword, content: content, topic: 、.join(topics), author: m.get(user, {}).get(screen_name, ), reposts: int(m.get(reposts_count, 0)), comments: int(m.get(comments_count, 0)), attitudes: int(m.get(attitudes_count, 0)), created_at: parse_weibo_time(m.get(created_at, )), }) return pd.DataFrame(rows)字段都做了默认值兜底int()强转可以顺带处理个别字段返回字符串的情况。topic用顿号拼接多个话题后续做词云时再按顿号拆分避免一行一个话题导致表结构膨胀。如果一条微博带有retweeted_status说明存在转发关系需要额外补一个is_original字段按需过滤。3.4 写入 MySQL 还是 CSV双写方案更省心清洗后的数据只放在内存里脚本一停数据就丢必须先落盘。小规模实验用 CSV 足够但要做趋势查询和多次增量采集MySQL 更合适。这里采用双写先写 MySQL 作为主存储再导出 CSV 方便用 Excel 快速查看。建表时字符集必须使用utf8mb4emoji 和生僻字才能正常入库CREATE TABLE weibo_post ( id VARCHAR(32) PRIMARY KEY, keyword VARCHAR(64) NOT NULL, content TEXT, topic VARCHAR(255), author VARCHAR(64), reposts INT DEFAULT 0, comments INT DEFAULT 0, attitudes INT DEFAULT 0, created_at DATETIME ) DEFAULT CHARSETutf8mb4;写入采用executemany批量执行SQL 里加上ON DUPLICATE KEY UPDATE每次重复爬到同一条微博时只更新互动数不产生重复行。真实采集时同一个关键词重复抓取是常态这个冲突处理能省去大量人工去重。CSV 导出用df.to_csv(weibo_clean.csv, indexFalse, encodingutf-8-sig)加utf-8-sig是为了让 Windows 下的 Excel 不出现中文乱码。4. 可视化大屏设计Flask 提供数据ECharts 负责呈现4.1 先定图表再定接口四个维度看一组微博数据可视化大屏不是把图表堆满就算完成。这个项目的核心诉求是把采集到的微博数据变成能阅读的结论因此要围绕内容、时间、人物、互动四个维度选图分析问题推荐图表依赖字段大家在聊什么词云topic, content什么时候讨论变多折线图created_at 按小时聚合谁在带动话题条形图author, reposts, comments互动的构成关系散点图reposts, comments, attitudes选型逻辑是先确定问题再确定图而不是反过来。“什么时候讨论变多”本质是时间序列聚合折线图表达能力最直接“谁在带动话题”需要比较大小条形图比饼图更容易阅读。技术选型上有一种常见搭配是用 PyEcharts 在 Python 端直接生成 HTML优点是上手快适合单机演示如果要做出能塞进大屏的企业级数据可视化页面更多项目会采用 Flask 提供 JSON 接口、前端用原生 ECharts 渲染的组合。核心原理一致差别只是 ECharts 配置由哪一端生成。4.2 Flask 数据接口与前端图表联动后端先读清洗后的 CSV 或 MySQL按小时聚合返回 JSON。这里用 pandas 的resample完成聚合核心接口只有十几行from flask import Flask, jsonify import pandas as pd app Flask(__name__) df pd.read_csv(weibo_clean.csv, parse_dates[created_at]) app.route(/api/trend) def api_trend(): series df.set_index(created_at).resample(1h).size() return jsonify({ hours: [t.strftime(%m-%d %H:00) for t in series.index], counts: series.tolist(), })resample(1h)把时间列按小时分桶size()统计每小时的微博条数返回时把 pandas 的Timestamp用strftime转成字符串是因为jsonify对 pandas 时间类型支持不友好。前端拿到 JSON 后把hours和counts分别放进 ECharts 的 xAxis 与 seriesscript srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script div idtrend styleheight: 320px;/div script fetch(/api/trend) .then(r r.json()) .then(data { const chart echarts.init(document.getElementById(trend)); chart.setOption({ title: { text: 微博讨论量小时趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.hours }, yAxis: { type: value }, series: [{ type: line, data: data.counts, smooth: true }] }); }); /script容器 div 必须显式设置高度ECharts 初始化时读不到高度会画出空白画布trigger: axis保证鼠标滑过时同时显示横轴时间和纵轴数量smooth: true让折线圆滑更符合大屏视觉。词云和条形图也大同小异只是 series 的 type 分别换成wordCloud和bar数据字段按图表需要映射即可。4.3 大屏轮播与自适应细节决定图表不翻车把单个图表扩展成大屏布局时有三个细节必须处理自适应、轮播和主题色。ECharts 在窗口尺寸变化后不会自动刷新画布需要在 window 的 resize 事件里调用chart.resize()。如果页面里有多个关键词切换可以维护一个轮播索引每隔 5 秒重新请求接口并覆盖 setOption大屏通常挂在电视或竖屏显示器上字体偏小的问题经常出现全局往 ECharts option 的textStyle里加 fontSize 就能解决。还有两个容易被忽略的点HTML 页面要加 viewport 适配外层容器用 flex 布局让多个图表平均排布否则不同分辨率下图与图会相互挤压深色背景下要把 ECharts 的textStyle.color调整为浅色否则文字会沉到背景里。词云的颜色也尽量控制在三种主色以内超出后视觉上会显得杂乱。5. 两个值得先做的小改造断点续采与数据质量校验断点续采是这个项目最值得优先做的小改造。定向爬虫在跑关键词列表时经常跑到一半被网络异常打断如果没有断点机制重跑会把已抓取的数据再写一遍。常见做法是把已抓取的微博 ID 存进一个集合每次采集开始前加载重复抓到就跳过。import json import os class IdSet: def __init__(self, path: str): self.path path self.ids set() if os.path.exists(path): with open(path, r, encodingutf-8) as f: self.ids set(json.load(f)) def add(self, mid: str): self.ids.add(mid) with open(self.path, w, encodingutf-8) as f: json.dump(list(self.ids), f) def exists(self, mid: str) - bool: return mid in self.ids每次add都全量写盘数据量到几万条以后可以把写盘改成每隔 50 条执行一次减小 IO 压力。这个集合同时承担“重复采集不产生增量”的过滤职责比 SQL 里的主键冲突更早发挥作用。第二个改造是在写入数据库之前做一次质量校验把时间解析和字段类型的问题挡在存储层外面。def validate(df: pd.DataFrame): assert df[id].is_unique, 存在重复微博 ID assert (df[created_at] pd.Timestamp.now()).all(), 出现未来时间 assert (df[[reposts, comments, attitudes]] 0).all().all(), 互动数为负三个断言分别覆盖主键去重是否生效、跨年时间解析是否出错、接口异常返回是否产生负互动数。校验放在清洗脚本末尾作为进入数据库前的最后一道关卡。实际使用时把采集、清洗、导出拆成三个独立脚本用计划任务按顺序调用采集脚本写 IdSet 后直接落盘清洗脚本读 CSV 再写 MySQL导出脚本只负责生成 Excel 和可视化数据。这样任何一环挂了重跑成本都很低。前端轮播脚本里记得把最后一个图表的 setOption 放在 setTimeout 里避免初始化顺序导致第一次切换图表空白。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价