做社交网络分析这几年,我最常被问到的问题就是:那些大V到底是怎么影响普通用户的?一个话题是怎么从某个小圈子火起来的?团队内部谁是真正的信息枢纽?说实话,这些问题用传统的关系型数据库和统计报表很难回答,但如果你把目光转向图数据挖掘,把用户当成节点、把关注和互动当成边,很多模糊的直觉就能变成可量化、可验证的结论。
这篇文章是我在完成一个模拟项目X(一个基于Python抓取公开社交页面、构建用户关系网络并做图挖掘分析的个人项目)之后的完整复盘。项目标题叫“社交媒体用户关系网络分析:基于Python的先进爬虫技术与图数据挖掘实战”,听起来有点长,但拆开看其实就两件事:一是用爬虫拿到数据,二是用图挖掘从数据里捞到有价值的结构信息。我会从整体设计思路、爬虫架构、图数据库选型、核心算法到踩坑记录,把整个流程完整地过一遍。如果你正准备做类似的数据分析项目,或者已经在做但卡在了某个环节,这篇内容应该能帮你少走不少弯路。
1. 整体设计与技术选型思路
1.1 从标题拆解核心需求
拿到这个标题的时候,我第一反应是把它拆成三个核心问题:
第一,数据从哪来?社交媒体用户关系网络,说到底是一张巨大的有向图,但图的构建必须依赖真实的数据。公开页面上的关注列表、粉丝列表、转发记录、评论关系,这些都是原始素材。可问题在于,这些数据分散且量大,手动一页页复制显然不现实,所以必须用爬虫来批量采集。
第二,数据怎么组织?用户和用户之间的关系天然适合用图模型表达,谁关注了谁就是一条有向边。但我们总不能把所有数据都塞进一个大JSON文件里,然后到处复制。需要一个既能存储图结构、又能高效查询的数据库方案,同时还要能把图数据导出给算法库使用。
第三,价值怎么提炼?光有图和数据库还不够,图数据挖掘的目的是找出隐藏的模式:哪些用户是意见领袖,哪些社区内部联系紧密,信息更倾向于沿着什么路径传播。这需要中心性分析、社区发现、影响力传播等算法来帮忙。
这三个问题其实是层层递进的关系:采集是地基,存储是承重墙,挖掘是最后的装修。任何一个环节出问题,后面都做不下去。
1.2 Python技术栈的选型逻辑
为什么选择Python而不是其他语言,或者为什么不直接用现成平台?我的实际体会是:Python在图分析和数据处理这个领域有最完整的生态,不需要自己从零造轮子。
爬虫层面,请求库用的是requests,解析HTML用的是BeautifulSoup和lxml,如果遇到需要异步并发采集的场景,还有httpx和aiohttp可以上。模拟项目X的数据量不算特别大,大约几万个节点,所以单机爬虫完全够用。如果你要抓的是千万级以上的数据,那可能要上Scrapy配合分布式部署,但对大多数个人项目来说,requests加限速策略就已经很稳了。
图分析层面,Python有两个方向可走:一个是NetworkX,适合分析中小规模图,灵活、API丰富、社区活跃;另一个是Neo4j,适合大规模图数据的存储和查询,配合Cypher查询语言做复杂路径分析很方便。我在项目里两个都用到了:分析算法走NetworkX,数据持久化走Neo4j。
选择这些工具的唯一标准就是:够用、好调试、出问题能在网上找到答案。别迷信花哨的技术栈,能解决问题才是第一位的。
1.3 为什么用图而不用传统表格
这是很多刚接触项目的人会有的疑惑:用户之间的关注关系,用两张表(用户表、关系表)也能存,为什么要特意用图数据库和图算法?
我打个比方:如果用户关系是一堆环节扣在一起的锁链,表格只是把这堆锁链平铺在地上,你能看到每一环,但看不出哪些环连成了什么样的形状。而图模型直接保留了锁链的连接方式,你一眼就能看到哪里是核心节点、哪里只是末端链路。
具体到分析场景,很多问题天然就是图问题。比如寻找“连接两个社区的关键桥梁”,在表格里你得写多表联查,逻辑复杂且效率低;但在图里它就是一次“最短路径”或者“介数中心性”计算,几行代码就出结果。这也是项目标题里把“图数据挖掘”作为重点的原因,关系网络的本质就是图,绕开图去分析反而是舍近求远。
2. 核心细节解析与实操要点
2.1 明确可采集的数据边界
在写任何爬虫之前,务必把“能抓什么”和“该抓什么”搞清楚。模拟项目X当时选择采集的目标是一家主流社交媒体平台上的公开用户页面数据,包括用户名、用户简介、关注列表、粉丝列表以及部分公开互动记录。这里的“公开”两个关键词很重要:只有用户主动公开的信息才在采集范围内,私密账号、非公开内容一概不碰。
合规方面有几个最基本的红线:第一,严格遵守目标平台的robots.txt文件设置;第二,控制请求频率,不给对方服务器造成压力;第三,采集到的数据不涉及个人隐私信息,比如手机号、真实姓名、家庭住址,这些即使在公开页面出现也不能入库;第四,采集行为本身只用于学术分析或个人研究,不用于商业化的用户画像或营销。如果你准备发布相关项目,也建议把所有采集代码和数据都脱敏处理后再展示。
另外有一个容易被忽略的细节:平台方可能会在页面里设置“陷阱”字段,用来识别爬虫。识别这些字段不难,难的是每天都去检查页面结构是否变了。所以我建议在爬虫代码里把页面解析逻辑和字段提取逻辑分开,这样一旦页面改版,只需要修改一小块解析代码,不至于整套流程瘫痪。
2.2 数据模型设计:节点与边的定义
关系网络图的数据模型设计是整个项目最重要的地基。我在模拟项目X里定义了这样一套模型:
节点(Node)代表用户,节点属性包括user_id、screen_name、follower_count、friend_count、verified、created_at等。边(Edge)代表用户之间的互动或关注关系,边属性包括relation_type(关注、转发、评论、@提及)、weight(互动次数)、timestamp(最近一次互动时间)。
比较关键的设计决策是:我只保留了“关系类型”和“权重”两个核心属性,其他诸如转发内容本身、评论的完整文本,就没有放进图里,而是另外存了一份原始数据表。原因是图模型擅长分析结构,不擅长做全文检索,如果想把文本和结构混在一起,查询效率会大打折扣。
关于边的方向,用户关注的边是天然有向的——A关注B,和B关注A是两回事。所以我将图定义为有向图。但后续做社区发现和节点影响力分析时,有些算法默认不支持有向图,比如某些版本的Louvain聚类,这时可以临时把有向图转成无向图处理。这个“先有向、后无向”的切换逻辑,属于实操中默认的处理方式,我在后面算法部分会详细说明。
2.3 爬虫架构设计与限速策略
模拟项目X的爬虫用了非常经典的模块化设计:URL管理器负责维护待采集和已采集的URL队列;下载器负责发送HTTP请求并接收响应;解析器负责从HTML或JSON中提取用户数据和关系数据;存储模块负责把数据写入数据库和本地文件。这四个模块互相解耦,哪怕只改其中一个,也不会影响其他模块。
限速策略是爬虫能长期稳定运行的命脉。我当时的策略很简单也很有效:每个请求之间至少要间隔1到2秒,高峰期自动拉大间隔到5秒以上;单日请求总量不设顶,但一旦某段时间响应码连续出现非200情况,立刻暂停采集,等一段时间再恢复。这种“慢就是快”的节奏,很多人一开始理解不了,但实测下来反而是总体耗时最短的方案。
另外强烈建议:即使你只是短时间跑一下,也一定要写一个可中断、可续跑的逻辑。因为社交平台数据量巨大,一次任务往往要跑好几个小时,中间可能断网、报错或者被限流。如果爬虫不允许断点续传,意味着所有已采集的数据都要重来,那才是真的灾难。我通常使用本地JSON文件记录已完成的任务列表,每次重启时先加载这个列表,跳过已经采集过的用户。
2.4 账号管理与Headers模拟进阶
如果你只抓几个公开页面,用默认的User-Agent就够了。但当你抓到几千上万个用户页面的时候,目标平台一定会注意到你的存在。这时候最基本的对策就是维护一个请求头池,里面包含不同类型的User-Agent、Accept-Language和Referer,每次请求随机挑选一组。
比请求头更管用的是cookie的有效期管理。很多社交平台对未登录用户展示的信息有限,登录后可见字段更多。但登录态的cookie很容易过期,而且同一个cookie短时间内大量使用也会触发风控。我的做法是准备多个账号,定期轮换cookie,同时把每个账号的请求频率尽可能控制在接近真人行为。注意,这里说的账号轮换只适用于单机小规模采集,如果数据量再大,建议考虑更正规的数据获取渠道。
页面内容方面,大部分现代社交平台已经全面转向JSON接口,而不是传统的服务端渲染HTML。这意味着你在浏览器里看到的动态内容,往往是通过API接口异步加载的。实际采集时直接请求这些JSON接口,比解析HTML要稳定得多。判断方法非常简单:打开浏览器开发者工具,在Network面板里看XHR请求,凡是返回JSON数据的,就是你需要找的接口。
3. 实操过程与核心环节实现
3.1 环境准备与依赖工具清单
模拟项目X最终跑在Python 3.10环境下,主要依赖包如下:
- requests:发送HTTP请求,是所有采集动作的基础。
- BeautifulSoup4 + lxml:解析旧版HTML页面,在新版JSON接口项目中用得较少,但留着备用。
- networkx:构建图结构、计算中心性指标、执行社区发现算法。
- python-louvain:执行Louvain社区发现的核心库。
- pyvis:快速生成可在浏览器中交互展示的网络图。
- neo4j-driver:连接Neo4j图数据库,进行Cypher查询和数据写入。
安装指令可以直接用pip完成,这里不再赘述。如果过程中遇到依赖冲突,建议用venv或conda隔离出一个干净的环境,不要直接在全局环境硬装,尤其是networkx和scipy之间偶尔会有版本兼容问题,单独隔离环境能省去不少麻烦。
3.2 爬虫核心代码的结构演示
下面的代码展示了爬虫中最核心的请求和响应处理逻辑。我不打算贴完整几百行的项目代码,只把关键模块展示出来,并逐一解释为什么这样写。
import requests import json import random import time from typing import Dict, List, Optional HEADERS_POOL = [ { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": "https://www.example.com/" }, { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15", "Accept-Language": "en-US,en;q=0.8", "Referer": "https://www.example.com/" } ] class SocialCrawler: def __init__(self, max_retry: int = 3): self.session = requests.Session() self.max_retry = max_retry def _get_random_headers(self) -> Dict[str, str]: return random.choice(HEADERS_POOL) def fetch_json(self, url: str, params: Optional[Dict] = None) -> Optional[Dict]: for attempt in range(1, self.max_retry + 1): try: resp = self.session.get( url, params=params, headers=self._get_random_headers(), timeout=15 ) if resp.status_code == 200: return resp.json() elif resp.status_code in (403, 429): wait_time = 60 * attempt print(f"触发限流,等待 {wait_time} 秒后重试") time.sleep(wait_time) else: print(f"请求失败: {resp.status_code}") except Exception as e: print(f"请求异常: {e}") time.sleep(5 * attempt) return None def close(self): self.session.close()这段代码看起来简单,但有几个点是决定成败的:第一,使用同一个requests.Session实例可以在多次请求之间维持HTTP连接池,减少TCP握手开销;第二,对403和429状态码做专门处理,这两类响应说明你已经触发了平台的风控,必须停下来等待而不是继续硬撞;第三,重试的时间采用线性退避而不是固定时间,这是基于我自己的经验,如果对方限制比较严格,固定时间的重试很快就会被识别。
3.3 从用户页到关系边的数据链路
有了爬虫基础能力后,接下来要解决的是“怎么从零构造出一张关系网络”。我的执行顺序是这样的:
第一步,选定种子用户。种子用户可以是某个领域的大V,也可以是随机抽取的中小博主。种子质量直接影响后续网络的覆盖度,建议选5到10个不同圈子的人,避免整个图扎堆在一个社区里。
第二步,通过种子用户的关注列表向外扩展。每抓到一个新用户,就把他的关注列表记录下来,形成“种子用户→关注对象”的有向边。然后再从关注对象里挑一部分,继续抓他们的关注列表。这个过程在技术圈叫BFS(广度优先搜索)式爬取。
第三步,按需采集粉丝列表。粉丝列表的接口通常比关注列表更容易被限制,而且单个大V的粉丝数量可能是几十万甚至上百万,全部采集是不现实的。我采取的策略是只采集种子用户及其核心关注对象的粉丝列表,并设定每个用户粉丝列表最多取前1000条。这个阈值不是因为技术上做不到更多,而是因为分析几千个用户的结构特征和关系路径,已经足够回答项目核心问题了。
第四步,清洗与去重。不同接口返回的用户名格式可能不一致,有的带昵称、有的不带;有的user_id是字符串、有的是整数。我在入库前做了统一格式化:user_id全部转成字符串,昵称去掉首尾空格,同时对每条边做唯一性校验,避免重复建边。
import networkx as nx def build_graph_from_relations(relations): G = nx.DiGraph() for relation in relations: source = relation["source_user_id"] target = relation["target_user_id"] if source == target: continue G.add_edge(source, target) return G这里有个很容易踩的小坑:很多人在建图时会把重复边直接覆盖,但正确的做法是先把权重累加起来,再决定是否保留多条并行边。我实际项目中更多使用的是“聚合权重”的方式,也就是如果A关注B,并且A也转发过B的内容,那么A到B的这条边权重就是2,表示多重关系。
3.4 图数据的Neo4j持久化操作
图分析完成后,如果项目需要对外展示或持续查询,往往要把结果存入图数据库。Neo4j是我用的最多的选择,它对流式写入和路径查询都很友好。
写入Neo4j的核心过程是Cypher语句,下面这段是我在项目中反复使用的批量导入模式:
UNWIND $batch as row MERGE (u:User {user_id: row.source_id}) MERGE (v:User {user_id: row.target_id}) MERGE (u)-[r:FOLLOWS]->(v) ON CREATE SET r.weight = row.weight ON MATCH SET r.weight = r.weight + row.weight这里使用MERGE而不是CREATE,原因很简单:避免重复导入的时候产生重复节点和重复边。UNWIND加参数化查询,可以把几千条边一次写入,比逐条CREATE快非常多。如果你有百万级以上的边要入库,建议使用Neo4j的neo4j-admin import工具,那个走的是批量文件导入通道,速度是普通Cypher写入的几十倍。
3.5 图挖掘核心算法的实测过程
图构建好之后,最让人兴奋的部分来了:把数据喂给算法,看它们能得出什么结论。
我在模拟项目X中主要用了三个算法,分别是PageRank、介数中心性和Louvain社区发现。
PageRank的计算过程非常直观。它假设一个用户在关系网络中“跟随”另一个用户,就相当于一次投票,PageRank值越高的用户,说明被越多高权重用户关注和互动。光看粉丝数有时会失真,因为僵尸粉可能占到一半以上,但PageRank因为考虑的是传递权重,反而能更接近真实影响力。
介数中心性回答的是另一个问题:哪些用户虽然在粉丝数量上不算顶尖,却在信息流动中起到关键桥梁作用。算法原理是统计所有节点对之间的最短路径,看某节点出现在多少条最短路径上。出现越多,介数中心性越高。这个指标用来识别隐藏的关键节点特别有效,很多看起来默默无闻的运营人员,实际上掌控着大量信息中转。
Louvain社区发现则是把关系紧密的用户自动划分为一个个小组。它基于模块度优化,通过不断迭代把节点归并到让社区结构更清晰的模块中。项目中跑出来的社区效果非常好,有些社区我一眼就认出来是某个兴趣领域的人群,有些社区则需要结合用户标签才能判断主题。
代码调用方式很简单:
import networkx as nx from networkx.algorithms import community pagerank_scores = nx.pagerank(G, alpha=0.85) betweenness_scores = nx.betweenness_centrality(G, k=200) communities = community.louvain_communities(G, resolution=1.0)这里有两个参数值得解释一下。alpha=0.85是PageRank算法中常见的阻尼系数,它的含义是“用户在关系网络中继续沿着边跳转的概率”,剩下的0.15概率会随机跳到任意节点。这个值不是拍脑袋定的,而是一组实验后证明效果较稳定的默认值。k=200表示只对200个随机节点对计算最短路径来做介数中心性估计,因为全量计算的时间复杂度太高,对于几万节点的图来说完全不现实。
3.6 结果可视化的呈现技巧
分析结果如果只是藏在代码里,很难让人直观感受到价值,所以我用pyvis和Gephi做了两层可视化。
pyvis的优势是可以生成独立的HTML文件,在浏览器里交互查看,适合快速分享给同事或朋友。做法上,我先把NetworkX的图直接传给pyvis,再按PageRank值调整节点大小,按社区标签调整节点颜色。这样图一打开,一眼就能看到哪些节点是核心、哪些社区是主体。
Gephi更适合做深度美化。它可以对布局做力导向调整,也能把社区聚类的效果渲染得更有层次感。我的建议是:如果只是自己看,用pyvis足够;如果要写报告或者做分享,用Gephi导出的图更专业。
可视化有一个容易被忽略的操作要点:在展示大规模网络时,务必设置好节点大小和颜色的映射阈值,否则所有小节点挤在一起,整个图只会变成一团墨,看不清任何结构。我当时在Gephi里把PageRank排名前100的节点单独选中放大,做了局部高亮,效果比全图展示好得多。
4. 常见问题与排查技巧实录
4.1 爬虫请求被频繁拦截的解决方案
网上很多人讨论爬虫被拦截的问题,说来说去无外乎几点:请求频率太高、请求头不完整、cookie失效、行为异常。我在模拟项目X期间也遇到过几次,最有价值的一条经验是:一旦开始被拦截,首先不要急着加大访问间隔,而是先检查你的请求头里有没有遗漏关键字段。
有一次我连续几小时采集都正常,突然所有请求都返回了验证码页面。排查后发现并不是因为我频率提高,而是因为某个新接口要求额外的自定义Header字段,如果该字段存在,请求就是合法的;如果缺失,则直接被当成爬虫拦截。加上这个字段之后,一切恢复正常。
更高的排查技巧是查看返回的HTML里是否包含特定提示文字。多数平台拦截后返回的页面文案都是固定的类模板,比如“当前网络环境异常”或要求完成安全验证之类的话。把这些关键词加入爬虫日志的告警条件,一旦命中就立刻通知自己,能有效避免长时间无效抓取。
4.2 图数据导入Neo4j的性能优化
在把几十万条边导入Neo4j时,我曾经犯过一个低级的错误:使用逐条CREATE加MERGE写循环,结果跑了整整一个晚上还没跑完。后来改成UNWIND批量写入,几分钟就完成了。速度差距如此大,主要原因是每执行一条Cypher语句,Neo4j都需要做一次事务提交和网络往返,批量写入则把几千条数据放到一个事务里,开销被平均摊薄了。
还有一个优化点是索引。在建完节点后,立刻在user_id字段上建立唯一约束或索引。否则后续每次MERGE节点时都要做全库扫描,数据量一大就寸步难行。建立索引的Cypher语句非常简单:
CREATE CONSTRAINT IF NOT EXISTS ON (u:User) ASSERT u.user_id IS UNIQUE;4.3 PageRank结果异常的排查实录
项目第一次跑PageRank的时候,我拿到结果后傻眼了:排名第一的竟然是一个粉丝数只有几百的小号。当时第一反应是算法出了问题,但仔细排查之后发现,问题出在图的构建阶段。
这个小号有个特殊行为:它在短时间内关注了大量用户,而这些用户又都回关或者转发过它的内容,导致它的入边权重特别高。再加上PageRank计算时边的权重对结果影响很大,所以它就“意外”地成了高影响力节点。
这个现象其实反映了PageRank的一个重要特性:它衡量的是节点在给定网络结构中的相对重要性,而不是绝对知名度。解决方式也很简单,只需要在构建图时过滤掉“过度关注型”节点,或者对权重做一次归一化处理,问题就消失了。这个经验让我意识到,在用算法做判断前,一定要先对图数据的特性有基本认知,否则再好的算法也容易被异常数据带偏。
4.4 社区发现算法结果与预期不符
还有一次让我印象很深的异常:使用Louvain算法跑社区划分时,某个明显不同的两个用户群被分到了同一个社区里。我起初怀疑是算法参数没调好,于是尝试调整resolution参数,但效果不明显。
后来仔细查看了这两个用户群的原始边数据,才发现他们之间存在大量低权重的关注边。也就是说,这两个用户群体之间并不是没有联系,而是有很多的“弱连接”。Louvain算法在识别社区时,对这些弱连接比较敏感,所以把它们归到了一个更大的社区里。
这提醒我:社区发现的结果本质上是一个“视角”问题。算法的目标是把图切成模块度最高的若干块,但不一定符合人类的认知标签。拿到结果后,需要结合业务视角做后处理,必要时可以通过调整resolution来达到更细粒度的划分。这项操作需要反复试验,我最终的参数调整范围大约在0.7到1.2之间,多次对比后选择了一组最贴近业务场景的结果。
4.5 实操心得与避坑清单
前面说了很多问题和排查过程,这里整理一份我的独家避坑清单,都是常规文档里不会写的:
第一,爬虫代码必须带断点续跑功能。无论你的采集任务有多短,都要假设它会中断,而且要保证中断后能快速恢复。我当时就是靠这个功能,避免了多次因网络抖动导致的全量重爬。
第二,构建图之前,先做一层“边数过滤”。如果你采集的数据中存在大量用户互相关注的情况,图中会形成非常密集的全连接小团体,这会严重拖慢后续算法的运行速度,也会干扰中心性指标的计算结果。我的做法是删除边数超过阈值(比如300条)的用户节点,因为这类节点往往是机构号或营销号,对分析真实社交关系没有太大贡献。
第三,NetworkX的内存问题不可忽视。几万个节点加几十万条边,NetworkX还能扛得住,但如果到几百万条边,内存就会明显吃紧。届时建议换用图数据库配合Cypher查询做分析,或者使用Spark GraphX这类分布式图计算框架。
第四,算法参数不是一成不变的。PageRank的alpha、社区发现的resolution、介数中心性的k采样比例,都需要根据你的图规模和业务场景反复调整。先跑一版默认参数看结果,再根据结果微调,是最高效的做法。
第五,做社交网络分析必须有伦理意识。采集公开数据是合法的,但是数据使用边界一定要清楚。不要去挖掘用户的隐私信息,也不要把分析结果用于骚扰、歧视或操纵他人。能在技术文章里把这种意识写明白的博主不多,但我认为这是行业长期健康发展的前提。
5. 从数据到洞察:图挖掘结果的业务应用与扩展方向
图数据和算法只是过程,真正有价值的是最后的洞察。我以模拟项目X为例,把分析结果应用到几个具体场景中,这块内容偏分享性质,供你后续参考。
第一个应用方向是“关键节点发现”。通过PageRank和介数中心性的排序,我找出了在一个特定话题领域中真正有话语权的30个用户。他们中有些是粉丝过百万的知名博主,但也有一些是粉丝数不大、却频繁被核心用户转发的中转节点。如果要做内容推广,这些人比单纯看粉丝数选人要精准得多。
第二个应用方向是“传播路径还原”。我在图上选取了一个种子节点,用NetworkX的最短路径算法回溯了一条热门话题的传播链路,发现话题最初在两个小社区里萌芽,然后经过几个中转节点,才被大V转发扩散到全网。这条链路光看数据表是看不出来的,但画在图上一目了然。
第三个应用方向是“社区重叠分析”。很多用户同时属于多个社区,他们就像一个桥墩,把不同圈子的信息连接起来。通过提取节点所属的多个社区标签,我做了一个简单的多维分类,将用户划分为“圈内活跃型”“跨圈桥梁型”“边缘观望型”,这在用户画像构建和内容推荐里都有实用价值。
后续如果你想把这个项目扩展成更完整的产品,有几个方向值得考虑:一是接入实时流数据,让图动态更新,分析热点事件的演化过程;二是结合自然语言处理技术,把用户的文本内容向量化后作为节点属性,做“内容+结构”的联合分析;三是引入时序图模型,分析关系强度的变化趋势。这些方向每一块都能撑起一个独立项目,但地基还是这篇文章里讲的爬虫、建图和图挖掘这套完整流程。
根据我个人经验,做这类项目的最大收益不只是产出了一份分析报告,而是养成了“用结构视角看问题”的习惯。关系网络分析并不是什么高不可攀的前沿技术,它本质上就是一套方法论的组合:用爬虫拿数据、用图建模、用算法找模式、用可视化讲故事。你只要把这四步走通了,任何带有关系属性的数据集,都能拿过来做一次有意思的探索。
如果你正准备上手,我建议从一个小目标开始:先抓1000个用户的关系数据,跑一遍中心性指标和社区发现,再试着把结论用图展示出来。等你完整跑通了这条链路,再考虑扩大数据量和引入更复杂的算法也不迟。希望这篇复盘对你有点帮助。