☰
微博数据三件套实战:用户信息、好友关系与转发关系解析
2026/10/3 9:34:37 网站建设 项目流程

简介:这份微博社交网络数据集面向社交网络分析、用户行为研究与信息传播建模的学习者和研究者,包含用户信息、好友关注关系与转发关系三类核心数据,可用于人口统计特征分析、网络中心性与社区发现、信息扩散路径追踪等任务,适合具备一定SQL与数据分析基础的中高级读者。压缩包共2个文件,以sql数据库文件和md说明文档为主,整体约22.26MB,其中sql文件以结构化查询语言存储建表与数据记录,便于在数据库管理系统中恢复、查询与清洗,md文档则说明数据格式、来源与使用许可。目前已有168人学习下载。通过该数据集,读者可完整复现用户属性统计、关注网络构建与转发传播分析等流程,并借助说明文档规范使用数据,为推荐系统、舆情监控与精准营销等场景提供实证基础。

1. 微博数据三件套:用户信息、好友关系、转发关系到底能做什么

拿到一个叫「微博数据(用户信息,好友关系,转发关系).zip」的压缩包,第一反应不该是解压看文件,而是先想清楚这三类数据各自是什么形态、能拼出什么。用户信息是节点属性表,好友关系是关注/粉丝构成的有向边表,转发关系是内容传播链上的有向边表。三者叠在一起,就是一张带属性的社交传播图。做社交网络分析、舆情溯源、传播路径还原、KOL影响力建模,都绕不开这三张表。适合谁?做数据挖掘的学生、做舆情系统的后端、想练图数据库和社区发现的工程师。微博签到数据、微博图片下载这些热词背后,本质都是同一套采集与建模链路的不同切面。这一篇不讲空泛概念,只讲拿到 zip 之后怎么把三张表跑通、参数怎么设、哪里会翻车。

2. 三张表的结构拆解与字段映射

2.1 用户信息表:哪些字段真正有用

用户信息表通常是一行一个用户,字段包括 uid、昵称、性别、所在地、认证类型、粉丝数、关注数、微博数、注册时间等。真正做图分析时,昵称和所在地是脏数据重灾区,uid 才是唯一主键。认证类型(蓝V、黄V、普通)在影响力建模里是强特征,粉丝数和关注数的比值能粗略判断账号类型。注册时间可以用来做账号年龄分层,过滤掉批量注册的水军号。

我一般会先做字段可用性筛查,把空值率超过 60% 的列直接标记为低置信字段,不参与后续建模。比如所在地字段,很多人填「其他」或乱填,实际可用率可能不到一半。粉丝数和关注数虽然数值型,但要注意有些账号设置了隐私,返回的是 -1 或 0,这类要单独标记而不是当真实值用。

import pandas as pd # 读取用户信息表,假设是 csv 格式 users = pd.read_csv("user_info.csv", dtype={"uid": str}) # 字段空值率统计 null_rate = users.isnull().mean().sort_values(ascending=False) print(null_rate) # 标记低置信字段 low_conf_cols = null_rate[null_rate > 0.6].index.tolist() print("低置信字段:", low_conf_cols) # 粉丝数异常值处理:-1 表示隐私不可见 users["fans_valid"] = users["fans_count"].apply(lambda x: x if x >= 0 else None)

这段代码先做空值率排序,把超过 60% 空值的列挑出来。dtype={"uid": str}是关键,uid 如果被 pandas 读成 int,后续和关系表做 join 时可能因为精度丢失对不上。粉丝数用 apply 把负数转成 None,避免后续求均值时被污染。参数上,60% 这个阈值不是固定的,数据量小可以放宽到 50%,数据量大可以收紧到 70%,看你对字段完整度的容忍度。

2.2 好友关系表:有向边怎么存怎么查

好友关系表一般是两列:from_uid 和 to_uid,表示 from 关注了 to。这是有向边,不能当无向图处理。存储上,小规模(百万级边)用 CSV 或 Parquet 就够,大规模(亿级)建议直接进图数据库或做邻接表压缩。查询时最常见的需求是「某人的二度人脉」和「共同关注」,这两个操作在纯 pandas 里做会非常慢,必须换工具。

我一般会先把边表去重,因为采集时可能重复抓取同一条关注关系。去重后统计每个节点的出度和入度,出度就是关注数,入度就是粉丝数,可以和用户信息表交叉验证。如果两边对不上,说明采集有缺失或用户信息表过期了。

import pandas as pd edges = pd.read_csv("follow_edges.csv", dtype={"from_uid": str, "to_uid": str}) # 去重 edges = edges.drop_duplicates(subset=["from_uid", "to_uid"]) # 计算出度和入度 out_degree = edges.groupby("from_uid").size().rename("out_deg") in_degree = edges.groupby("to_uid").size().rename("in_deg") # 和用户信息表交叉验证 users = users.set_index("uid") users = users.join(out_degree, how="left").join(in_degree, how="left") users[["fans_count", "in_deg"]].describe()

去重用drop_duplicates指定两列组合,比全列去重更精准。出度入度分别 groupby 后 join 回用户表,how="left"保证不丢用户。最后 describe 看 fans_count 和 in_deg 的分布差异,如果差异巨大,说明用户信息表的粉丝数可能是缓存值,以边表实时计算为准。参数上,如果边表超过内存,改用 dask 或直接上 Neo4j 的 LOAD CSV。

2.3 转发关系表:传播链的时间维度

转发关系表通常包含原微博 mid、转发用户 uid、被转发用户 uid、转发时间戳。这张表的核心价值是时间序列上的传播路径。字段里 mid 是内容标识,uid 是传播节点,时间戳决定传播顺序。做传播树还原时,需要根据时间戳和转发关系推断父子结构,但微博的转发关系不一定记录直接父节点,很多时候只记录原微博,这时候传播树是有损的。

常见做法是:如果表里有 parent_mid 或 root_mid 字段,直接用;如果没有,只能按时间窗口近似还原,比如同一 mid 下,按时间排序,每个转发节点挂到最近的前一个转发节点上。这种近似在传播早期误差小,后期会形成链式结构而非树状,分析时要注明假设。

import pandas as pd reposts = pd.read_csv("repost_edges.csv", dtype={"mid": str, "uid": str}) reposts["timestamp"] = pd.to_datetime(reposts["timestamp"], unit="s") # 按 mid 分组,按时间排序 reposts = reposts.sort_values(["mid", "timestamp"]) # 近似还原传播链:每个节点挂到同 mid 下前一个节点 reposts["parent_uid"] = reposts.groupby("mid")["uid"].shift(1) # 统计每条微博的传播深度 depth = reposts.groupby("mid")["parent_uid"].apply(lambda x: x.notnull().sum()) print(depth.describe())

pd.to_datetime的 unit 参数要看原始时间戳是秒还是毫秒,秒用 "s",毫秒用 "ms",搞错会得到 1970 年。shift(1)是核心,把同组上一行 uid 挪到当前行作为父节点。传播深度统计用 notnull 计数,第一条转发没有父节点所以是 NaN。这个近似方法在转发量小于 1000 时比较可靠,超过之后链式误差累积,建议只用于趋势分析不做精确树结构。

3. 从 zip 到可查询图:本地跑通的最小链路

3.1 环境准备与依赖安装

本地跑通不需要分布式,一台 16G 内存的机器足够处理百万级节点和千万级边。核心依赖就三个:pandas 做表格处理,networkx 做小规模图分析,pyarrow 做 Parquet 读写加速。如果边表超过 500 万行,加一个 dask 做分块。图数据库可选 Neo4j,但本地验证阶段用 networkx 更快。

pip install pandas networkx pyarrow dask[dataframe] matplotlib

安装完先验证版本,pandas 建议 2.x,networkx 建议 3.x。版本不匹配时 networkx 的 from_pandas_edgelist 接口参数会变,容易报 TypeError。matplotlib 只用于最后画图,不装也行。

3.2 三表关联与图构建

把用户信息作为节点属性,好友关系作为边,转发关系作为另一组边,构建一张异构图。networkx 支持有向图 DiGraph,节点属性用字典挂上去。构建时注意 uid 类型统一为 str,否则 join 会静默失败。

import networkx as nx import pandas as pd users = pd.read_csv("user_info.csv", dtype={"uid": str}) follow = pd.read_csv("follow_edges.csv", dtype={"from_uid": str, "to_uid": str}) repost = pd.read_csv("repost_edges.csv", dtype={"mid": str, "uid": str}) G = nx.DiGraph() # 添加节点及属性 for _, row in users.iterrows(): G.add_node(row["uid"], fans=row.get("fans_count", 0), verified=row.get("verified_type", "none")) # 添加关注边 for _, row in follow.iterrows(): G.add_edge(row["from_uid"], row["to_uid"], edge_type="follow") # 添加转发边(转发关系是 uid 到 mid,这里把 mid 也作为节点) for _, row in repost.iterrows(): G.add_node(row["mid"], node_type="content") G.add_edge(row["uid"], row["mid"], edge_type="repost", ts=row["timestamp"]) print(G.number_of_nodes(), G.number_of_edges())

逐行 iterrows 在百万级数据上很慢,生产环境应该用nx.from_pandas_edgelist批量加边,但那样加不了边属性。折中方案是先用 from_pandas_edgelist 建骨架,再用 set_edge_attributes 批量挂属性。这里为了展示逻辑用 iterrows,实际跑的时候记得换。mid 作为内容节点加入图后,图就变成了用户-内容异构图,后续可以做二部图投影。

3.3 用 Parquet 加速重复读取

CSV 读取慢且占内存,第一次读完就转 Parquet,后续读取快 5 到 10 倍。Parquet 还保留 dtype,不会出现 uid 被读成 int 的问题。

import pandas as pd for name in ["user_info", "follow_edges", "repost_edges"]: df = pd.read_csv(f"{name}.csv", dtype=str) df.to_parquet(f"{name}.parquet", index=False) # 后续读取 users = pd.read_parquet("user_info.parquet")

dtype=str全列按字符串读,避免混合类型推断出错。to_parquet 的 index=False 去掉行号列,省空间。Parquet 文件通常比 CSV 小 60% 到 80%,读取速度快一个数量级。注意 Parquet 不支持原地追加,增量数据要写新文件再合并。

4. 避坑与排查:三张表关联时最容易翻车的五件事

4.1 uid 类型不一致导致 join 结果为空

现象:用户表和关系表做 merge,结果行数为 0 或远小于预期。原因:一个表 uid 是字符串,另一个被 pandas 推断成 int64,join 时类型不匹配。解决:所有读取操作强制dtype={"uid": str},或者在 merge 前统一astype(str)。这个坑血泪经验最多,因为 pandas 不报错,只是静默返回空结果。

4.2 转发关系表缺少父节点字段

现象:想还原传播树,发现表里只有原微博 mid 和转发者 uid,没有直接父节点。原因:采集时只抓了转发动作,没抓转发链的层级关系。解决:用时间排序加 shift 近似还原,或者只做传播时间序列分析,放弃精确树结构。如果业务强依赖树结构,需要重新采集带 parent_mid 的数据。

4.3 粉丝数和入度对不上

现象:用户信息表显示某账号 10 万粉丝,但边表里入度只有 200。原因:用户信息表的粉丝数是微博平台缓存值,边表是实际采集到的关注关系,采集不完整或用户信息过期。解决:以边表实时计算为准,用户信息表的粉丝数只做参考。如果差异超过一个数量级,标记该账号数据可信度低。

4.4 时间戳单位搞错导致传播顺序全乱

现象:按时间排序后,转发时间集中在 1970 年或 50000 年。原因:时间戳是毫秒却被当成秒解析,或者反过来。解决:先看时间戳数值范围,10 位数是秒,13 位数是毫秒。pd.to_datetime的 unit 参数必须匹配。不确定就先转成 datetime 看最小值,明显不对就换 unit。

4.5 大图内存溢出

现象:networkx 构建百万节点图时内存爆掉。原因:networkx 的 Python 对象开销大,每个节点和边都是字典。解决:超过 50 万节点改用 igraph 或 graph-tool,或者直接用 Neo4j 存储,networkx 只做采样分析。另一个办法是只加载子图,比如只取粉丝数前 10% 的节点及其边。

5. 进阶:用转发关系做传播路径还原与影响力排序

传播路径还原的核心是构建传播树,然后计算每个节点的传播贡献。如果表里有 parent_mid,直接建树;没有就用时间近似。建完树后,用 PageRank 或 HITS 做影响力排序,但要注意转发图的边方向是「转发者指向被转发内容」,做 PageRank 时需要反向或调整阻尼系数。

import networkx as nx import pandas as pd repost = pd.read_parquet("repost_edges.parquet") repost["timestamp"] = pd.to_datetime(repost["timestamp"], unit="s") repost = repost.sort_values(["mid", "timestamp"]) # 构建传播树:每个 mid 一棵树 trees = {} for mid, group in repost.groupby("mid"): tree = nx.DiGraph() prev_uid = None for _, row in group.iterrows(): tree.add_node(row["uid"]) if prev_uid is not None: tree.add_edge(prev_uid, row["uid"]) prev_uid = row["uid"] trees[mid] = tree # 计算每棵树的深度和宽度 for mid, tree in list(trees.items())[:5]: depth = nx.dag_longest_path_length(tree) if nx.is_directed_acyclic_graph(tree) else -1 print(f"mid={mid}, nodes={tree.number_of_nodes()}, depth={depth}")

这段代码按 mid 分组,每组内按时间排序,顺序连边形成链式传播树。nx.dag_longest_path_length算最长路径即传播深度,但链式结构一定是有向无环的,所以不会返回 -1。实际传播树应该是分支结构,这里近似成链是简化,如果要还原分支,需要根据转发时的评论关系或 @关系补充边。

影响力排序用 PageRank 时,把传播树反向,让被转发者指向转发者,这样 PageRank 高的是被大量转发的源头。参数上,alpha 默认 0.85,传播链短可以调到 0.7 让权重更分散。另一个技巧是用转发时间间隔做边权重,间隔越短说明传播越即时,权重越高。

# 反向传播树做 PageRank reverse_tree = tree.reverse() pr = nx.pagerank(reverse_tree, alpha=0.85) top_nodes = sorted(pr.items(), key=lambda x: x[1], reverse=True)[:10] print(top_nodes)

反向用tree.reverse(),PageRank 的 alpha 控制阻尼,0.85 是经典值。top_nodes 取前 10 个高影响力节点。这个排序结果可以和用户信息表的认证类型交叉验证,如果高影响力节点里蓝V占比高,说明传播由官方账号驱动;如果普通账号占比高,说明是草根引爆。

我自己的习惯是,每次拿到新的微博数据集,先跑一遍三表关联和传播深度统计,看数据完整度再决定做不做精细分析。数据质量不行的时候,再花哨的算法都是空中楼阁。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询