ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微博数据三件套:从清洗到Neo4j建图与传播分析实战

微博数据三件套:从清洗到Neo4j建图与传播分析实战 简介这份数据集面向社交网络分析、数据科学与舆情研究方向的开发者与研究者围绕微博平台的用户信息、好友关注与转发传播三类核心数据提供可直接导入数据库的完整样本适合开展用户画像、网络结构挖掘与信息扩散建模等定量研究。压缩包共2个文件以SQL数据库脚本与Markdown说明文档为主整体约22.26MB其中SQL文件包含建表与数据记录便于在数据库管理系统中恢复并执行查询统计说明文档则交代数据格式、来源与使用许可。已有168人学习下载。借助用户属性字段可分析性别比、地域分布与影响力通过关注关系能计算连通性、中心性与社区结构转发记录则支撑传播路径与扩散模式探究为推荐系统、精准营销与舆情监控提供实证基础。1. 微博数据三件套用户信息、好友关系、转发关系到底能拿来做什么如果你手头正好有一个名为「微博数据用户信息好友关系转发关系.zip」的数据包或者你正打算自己采集这样一套数据那你大概率已经意识到单独一份用户表没什么意思真正有价值的是把用户、关注、转发这三层关系叠在一起看。用户信息回答「谁在说」好友关系回答「谁听谁的」转发关系回答「信息怎么跑」。这三者合起来才构成一张能跑社区发现、影响力传播、水军识别的图。我见过太多人拿到这类数据后第一步就翻车直接pd.read_csv然后df.head()发现中文乱码、ID 是科学计数法、时间字段是字符串然后开始怀疑数据本身有问题。其实问题出在加载环节。这篇笔记就按「数据长什么样 → 怎么清洗入库 → 怎么建图分析 → 坑在哪」的顺序把微博三件套从 zip 到可用图结构的完整路径讲清楚。适合做社交网络分析、舆情研究、或者单纯想练手图数据库的工程师新手能跟着命令走熟手可以直接跳到参数和避坑部分。2. 先搞清楚三张表的结构再动手字段、主键与关联逻辑2.1 用户信息表哪些字段真正有用微博用户信息表通常包含 uid、昵称、性别、所在地、粉丝数、关注数、微博数、认证类型、注册时间等字段。但实际拿到手的数据往往字段名不统一常见的有id/uid/user_id混用followers_count/fans_count混用。我一般先做一次字段映射把不同来源的列名统一成一套内部标准。下面这段代码做两件事统一列名以及把明显无用的列比如头像 URL、个性签名先剔掉减少后续内存占用。import pandas as pd # 原始列名到标准列名的映射按你实际拿到的表头改 COL_MAP { id: uid, user_id: uid, 昵称: screen_name, screen_name: screen_name, followers_count: fans_cnt, fans_count: fans_cnt, friends_count: follow_cnt, follow_count: follow_cnt, statuses_count: post_cnt, 微博数: post_cnt, created_at: reg_time, 注册时间: reg_time, } def load_users(path): df pd.read_csv(path, dtype{uid: str}, encodingutf-8-sig) df df.rename(columns{k: v for k, v in COL_MAP.items() if k in df.columns}) keep [uid, screen_name, fans_cnt, follow_cnt, post_cnt, reg_time] df df[[c for c in keep if c in df.columns]] df[uid] df[uid].astype(str).str.strip() return df.drop_duplicates(subsetuid)逻辑说明dtype{uid: str}是关键微博 uid 是 10 位数字pandas 默认会读成 int64导出时可能变成科学计数法或者丢失精度。encodingutf-8-sig处理带 BOM 的 CSV这是 Windows 下 Excel 导出的常见格式。drop_duplicates按 uid 去重因为采集过程中同一用户可能被多次写入。参数方面fans_cnt、follow_cnt、post_cnt这三个数值字段后续会用来做影响力排序建议读入后统一转成 int遇到空值填 0 而不是 NaN否则排序时会出问题。2.2 好友关系表有向边还是无向边好友关系表一般两列from_uid和to_uid表示 from 关注了 to。这是一个有向图。但很多人会把它当成无向边处理导致后续计算度中心性时结果翻倍。我的做法是建图时保留有向需要无向分析时再显式转换而不是一开始就丢掉方向。def load_follows(path): df pd.read_csv(path, dtype{from_uid: str, to_uid: str}, encodingutf-8-sig) df.columns [from_uid, to_uid][: len(df.columns)] df[from_uid] df[from_uid].str.strip() df[to_uid] df[to_uid].str.strip() # 去掉自环微博不允许自己关注自己但采集数据里偶尔出现 df df[df[from_uid] ! df[to_uid]] return df.drop_duplicates()这里有个容易忽略的点好友关系表的数据量通常是用户表的几十倍甚至上百倍。一个百万用户的数据集关注边可能上亿。所以drop_duplicates之后建议直接落成 Parquet 格式比 CSV 小很多读取也快。df_follows.to_parquet(follows.parquet, indexFalse)Parquet 按列存储后续只读from_uid或to_uid时不需要加载全部数据对内存友好。2.3 转发关系表时间戳和层级信息别丢转发关系表一般包含uid转发者、retweeted_uid被转发者、mid微博 ID、retweeted_mid原微博 ID、time转发时间。这张表是三层数据里信息密度最高的因为它同时携带了社交关系和传播时序。def load_retweets(path): df pd.read_csv(path, dtype{uid: str, retweeted_uid: str, mid: str, retweeted_mid: str}, encodingutf-8-sig) df[time] pd.to_datetime(df[time], errorscoerce) df df.dropna(subset[time]) df df[df[uid] ! df[retweeted_uid]] return dferrorscoerce把解析不了的时间变成 NaT然后统一丢掉。微博时间格式在不同采集批次里可能不一致有的是2023-01-01 12:00:00有的是时间戳建议先检查一遍再决定用哪种解析方式。三张表通过 uid 关联用户表是节点属性好友关系是关注边转发关系是传播边。关联逻辑清楚了后面建图才不会乱。3. 从 zip 到图数据库清洗、入库、建索引的完整命令3.1 解压与编码检查先别急着读拿到 zip 之后第一步不是解压完直接读而是先看文件列表和编码。unzip -l 微博数据用户信息好友关系转发关系.zip看清楚里面有几个文件、什么格式。常见的是三个 CSV但也可能是三个文件夹各放一批分片。如果是分片后续读取要用glob合并。编码检查用file命令file -i users.csv如果输出charsetutf-8就正常如果是iso-8859-1或者unknown-8bit读的时候要指定encodinggbk或encodinggb18030。中文微博数据里 GBK 编码并不少见尤其是早期采集的数据。3.2 用 pandas 做第一轮清洗清洗的核心目标是三件事去重、去空、类型对齐。下面是一个完整的清洗流程把三张表处理成可以入库的状态。import pandas as pd def clean_all(users_path, follows_path, retweets_path): users load_users(users_path) follows load_follows(follows_path) retweets load_retweets(retweets_path) # 只保留在用户表里出现过的 uid避免悬空边 valid_uids set(users[uid]) follows follows[follows[from_uid].isin(valid_uids) follows[to_uid].isin(valid_uids)] retweets retweets[retweets[uid].isin(valid_uids) retweets[retweeted_uid].isin(valid_uids)] # 数值字段填充 for col in [fans_cnt, follow_cnt, post_cnt]: if col in users.columns: users[col] pd.to_numeric(users[col], errorscoerce).fillna(0).astype(int) return users, follows, retweets逻辑说明valid_uids这一步很关键。采集数据里经常出现关注边指向一个不在用户表里的 uid这种悬空边在建图时会报错或者产生孤立节点。先过滤掉后面省很多事。参数方面如果你的数据量超过内存isin这一步会吃很多内存。替代方案是用 DuckDB 做 join或者分块处理。我一般百万级以下用 pandas千万级以上直接上 DuckDB。3.3 导入 Neo4j 建图节点、边、索引图数据库选 Neo4j 是最常见的做法Cypher 语法直观社区版免费够用。导入方式有两种小数据量用LOAD CSV大数据量用neo4j-admin import。这里讲LOAD CSV因为更灵活。先把清洗后的数据导出成 CSVusers.to_csv(clean_users.csv, indexFalse, encodingutf-8) follows.to_csv(clean_follows.csv, indexFalse, encodingutf-8) retweets.to_csv(clean_retweets.csv, indexFalse, encodingutf-8)然后在 Neo4j Browser 里执行// 建用户节点 LOAD CSV WITH HEADERS FROM file:///clean_users.csv AS row CREATE (:User { uid: row.uid, name: row.screen_name, fans: toInteger(row.fans_cnt), follows: toInteger(row.follow_cnt), posts: toInteger(row.post_cnt) }); // 建索引加速后续匹配 CREATE INDEX user_uid IF NOT EXISTS FOR (u:User) ON (u.uid);索引必须在导入边之前建好否则每插入一条边都要全表扫描速度差几十倍。// 建关注边 LOAD CSV WITH HEADERS FROM file:///clean_follows.csv AS row MATCH (a:User {uid: row.from_uid}) MATCH (b:User {uid: row.to_uid}) CREATE (a)-[:FOLLOWS]-(b);转发边类似但多一个时间属性LOAD CSV WITH HEADERS FROM file:///clean_retweets.csv AS row MATCH (a:User {uid: row.uid}) MATCH (b:User {uid: row.retweeted_uid}) CREATE (a)-[:RETWEET {time: row.time, mid: row.mid}]-(b);如果数据量很大LOAD CSV会慢。常见做法是先用neo4j-admin import做全量导入后续增量再用 Cypher。neo4j-admin import要求 CSV 有特定表头格式需要提前准备。3.4 验证导入结果三个必查指标导入完成后不要直接开始分析先跑三个查询确认数据完整性MATCH (u:User) RETURN count(u) AS user_cnt; MATCH ()-[r:FOLLOWS]-() RETURN count(r) AS follow_cnt; MATCH ()-[r:RETWEET]-() RETURN count(r) AS retweet_cnt;把这三个数和清洗后的 DataFrame 行数对比一致就说明导入没丢数据。如果不一致大概率是 CSV 里有空行或者 MATCH 没匹配上。4. 转发关系建传播图时间窗口、层级深度与影响力排序4.1 按时间窗口切分传播链转发关系的核心价值在于还原传播过程。同一条原微博retweeted_mid下的所有转发记录按时间排序就构成一条传播链。def build_cascade(retweets, mid): chain retweets[retweets[retweeted_mid] mid].sort_values(time) chain chain.reset_index(dropTrue) chain[rank] chain.index return chain[[uid, retweeted_uid, time, rank]]rank表示该用户是第几个转发的。这个字段后续可以用来算传播速度比如前 100 个转发用了多长时间就能粗略估计这条微博的爆发力。参数方面时间窗口的选择取决于你的分析目标。做舆情爆发检测窗口设 1 小时做长尾传播研究窗口设 24 小时。没有固定值但建议至少跑两三个窗口对比。4.2 计算传播深度和广度传播深度用层级表示原微博是第 0 层直接转发是第 1 层转发转发是第 2 层以此类推。广度就是每层的节点数。import networkx as nx def cascade_tree(retweets, mid): chain retweets[retweets[retweeted_mid] mid] G nx.DiGraph() for _, row in chain.iterrows(): G.add_edge(row[retweeted_uid], row[uid]) return G def cascade_stats(G): if len(G) 0: return {} depths nx.shortest_path_length(G, sourcelist(G.nodes)[0]) return { nodes: G.number_of_nodes(), edges: G.number_of_edges(), max_depth: max(depths.values()) if depths else 0, avg_depth: sum(depths.values()) / len(depths) if depths else 0, }这里用networkx的shortest_path_length算深度。注意传播图可能不是树因为同一个用户可以多次转发同一条微博虽然微博产品上不常见但数据里可能有。所以用最短路径而不是树深度。4.3 影响力排序PageRank 和转发数结合单看粉丝数排序会高估僵尸号。我一般用 PageRank 在关注图上算一版再用转发次数加权算一版两个排名取交集。def influence_rank(follows_df, retweets_df): G nx.from_pandas_edgelist(follows_df, from_uid, to_uid, create_usingnx.DiGraph()) pr nx.pagerank(G, alpha0.85) rt_cnt retweets_df.groupby(retweeted_uid).size().to_dict() scores {} for uid in set(pr.keys()) | set(rt_cnt.keys()): scores[uid] { pagerank: pr.get(uid, 0), retweet_cnt: rt_cnt.get(uid, 0), } return scoresalpha0.85是 PageRank 的标准阻尼系数一般不改。如果你的图非常大千万节点以上nx.pagerank会慢建议换scipy.sparse或者graph-tool。5. 避坑与排查微博数据清洗建图最常见的五个翻车点5.1 uid 被读成科学计数法现象导出的 CSV 里 uid 变成1.23E09导入 Neo4j 后匹配不上。原因pandas 默认把纯数字列读成 int64 或 float64导出时超过一定位数就变科学计数法。解决读入时强制dtype{uid: str}导出时确保该列是字符串类型。如果已经读错了用df[uid].astype(int64).astype(str)救回来但前提是精度没丢。5.2 中文乱码导致昵称全变问号现象screen_name列全是???或者乱码字符。原因CSV 编码是 GBK但用 UTF-8 读了。解决先file -i确认编码然后用对应编码读。如果已经读错重新读一遍不要试图在乱码基础上修复。5.3 悬空边导致 Neo4j 导入报错现象LOAD CSV执行到一半报Node not found。原因关注边或转发边里的 uid 在用户表里不存在。解决导入前用isin过滤只保留两端都在用户表里的边。这一步在 pandas 里做比在 Cypher 里做快得多。5.4 时间字段格式不统一现象pd.to_datetime报ValueError或者解析出一堆 NaT。原因不同批次采集的时间格式不一样有的是字符串有的是 Unix 时间戳。解决先抽样看几种格式然后写一个兼容函数def parse_time(val): if pd.isna(val): return pd.NaT val str(val).strip() if val.isdigit(): return pd.to_datetime(int(val), units) return pd.to_datetime(val, errorscoerce)5.5 大图 PageRank 内存溢出现象跑nx.pagerank时进程被 kill。原因networkx 的 PageRank 实现是纯 Python 的千万节点级别内存扛不住。解决换scipy.sparse手写迭代或者用igraph的pagerank。如果只是要排名也可以先用度中心性粗筛再对 Top N 节点算精确 PageRank。6. 一个实用技巧用转发时间差识别水军账号最后一章讲一个我实际用过的技巧用转发时间差做水军初筛。正常用户转发一条微博时间分布是分散的水军账号往往在极短时间内集中转发同一条内容。具体做法对每条原微博计算所有转发记录的时间间隔如果某个账号的转发时间与相邻转发的时间差小于 2 秒且该账号在多个原微博下都有这种行为就标记为可疑。def detect_bot(retweets, threshold_sec2, min_hits3): retweets retweets.sort_values([retweeted_mid, time]) retweets[gap] retweets.groupby(retweeted_mid)[time].diff().dt.total_seconds() suspicious retweets[retweets[gap] threshold_sec] hit_cnt suspicious.groupby(uid).size() return hit_cnt[hit_cnt min_hits].index.tolist()threshold_sec设 2 秒是个经验值设太小漏报设太大误报。min_hits设 3 表示至少在 3 条不同微博下都有集中转发行为才标记。这个方法的误报率不低但作为初筛足够用筛出来的账号再人工看一遍或者结合其他特征做二次判断。我自己踩过的坑是一开始阈值设了 1 秒结果漏掉了一大批用脚本但加了随机延迟的账号。后来改成 2 秒召回率明显提升但误报也多了。所以这个参数没有标准答案得根据你的数据特点调。另一个教训是别把时间差当成唯一证据它只是一个信号最终判断还是要结合账号注册时间、昵称模式、转发内容相似度一起看。希望帮到你。本文还有配套的精品资源点击获取
返回列表