news 2026/10/15 15:30:40

Python爬虫之获取QQ关系网:合规工程实现与可视化分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫之获取QQ关系网:合规工程实现与可视化分析

先说一个可能让很多人失望的结论:如果你期望像某些教程里写的那样,直接爬到某个QQ账号的私密好友列表、完整关系链,那这条路不仅走不通,也不应该走。平台侧对这类接口的访问控制非常严格,未经授权抓取他人私密关系数据,不仅违背用户协议,更会踩到个人信息保护的红线。但“python爬虫之获取QQ关系网”这个项目本身非常有价值,只是需要换一个合规的姿势去做。今天我分享的是一套我自己搭过的工程实现方案,核心目标不是“拿到某人的真实好友列表”,而是在可控环境与模拟数据下,把登录态获取、请求管理、接口解析、关系网建模、关系可视化这条链路完完整整跑通。把这套链路跑明白之后,你能掌握的能力完全可以从关系网泛化到任何带交互结构的公开数据场景。

这个项目适合有一定Python基础、想进阶爬虫工程能力的开发者。如果你已经会写最简单的requests请求,但不知道如何处理登录态、频率控制、字段清洗、关系图展示,这篇内容可以给你一条完整的参考路径。我尽量把每一步的技术细节和踩坑记录写清楚,也把边界在哪、哪些事不能碰写明白。

1. 项目整体设计与合规边界

1.1 项目目标:你到底要“爬”什么

先拆解标题。很多人看到“关系网”三个字,第一反应就是好友列表。但真实项目里,QQ关系网数据至少有四层形态:好友关系、群成员关系、公开互动关系(说说/评论/点赞)、关注关系。其中好友关系和群成员关系属于敏感度最高的私密信息,无论如何都不能在没有授权的情况下抓取;公开互动关系在理论上属于公开可见的信息,但也不是说可以无限制采集,平台协议依然对自动化访问有明确限制。

所以我做这个项目时的定位是:在本地搭建一个模拟的数据源,或者使用我自己拥有权限的测试账号,在完全合规的环境下演示“从请求到关系图”的整套流程。换句话说,关系网的“数据层”用模拟数据代替,但“工程层”所有代码和思路都是真实的。这样既能练技术,又不用碰红线。

这个思路很关键。我见过不少新手一上来就想直接抓真实数据,结果第一步就被验证码和风控拦住了,然后开始研究各种“绕过机制”,最后不仅没学到东西,还把自己的账号搭进去。合规版本的最大好处是:你能把精力放在真正有复利的技术细节上,比如请求设计、数据建模、性能优化,而不是和风控系统玩猫鼠游戏。

1.2 技术选型:为什么是 requests + NetworkX

这个项目的技术栈我做过好几轮选型对比,最终确定的是 requests + BeautifulSoup(或json解析) + pandas + NetworkX + matplotlib/pyecharts。每个组件都有替代方案,但组合起来是最轻量、最容易排查问题的搭配。

requests负责HTTP请求和会话保持。相比于selenium,requests直接走HTTP协议,速度快、内存占用小、更可控。很多人一碰到“要登录”的项目就下意识上selenium,但真实生产环境里,能走API就一定不要走浏览器渲染,因为浏览器渲染既容易被检测,又慢得让人崩溃。只有在目标站点没有开放接口、必须模拟点击的情况下,才值得用selenium。

响应解析我建议优先用json模块而不是BeautifulSoup。现在的服务端接口绝大多数返回结构化JSON,直接resp.json()就行。如果你面对的页面只有HTML,那就用BeautifulSoup解析。我的习惯是:先打开开发者工具看Network面板,如果能找到接口URL就直接请求;如果找不到,再去解析HTML。

关系图建模选的是NetworkX。它是Python社区里最成熟的关系网络分析库,建图、算中心性、做聚类都一条龙。可视化部分我用了两种方式:静态图用matplotlib,交互图用pyecharts。静态图适合快速看结构,交互图适合做成果展示。Graphviz我也试过,做层次型关系比较强,但中文标签渲染需要额外配置,所以不作为首选。

1.3 整体流程与项目架构

整个项目可以拆成六个阶段:环境准备、登录态获取、请求伪装与限速、响应解析、数据建模、可视化。关系图里,node是用户标识,edge是用户之间的关联。每个阶段都不复杂,但串起来之后很容易在边界环节出问题,比如Cookie过期、字段缺失、图中出现重复节点。我建议一开始就用模块化组织代码,把“请求”“解析”“清洗”“可视化”分成不同文件,不要全堆在一个脚本里。这不是小题大做,一旦数据量涨上去,一个函数干所有事的代码会让你连排查问题都无从下手。

为了更好理解,我做了一个简单的项目结构:

qq_relation_crawler/ ├── config.py # 配置:请求头、频率、Mock地址 ├── session.py # 登录态与Cookie管理 ├── parser.py # 接口响应解析与字段提取 ├── cleaner.py # 数据清洗与去重 ├── graph_builder.py # NetworkX建模 ├── visualize.py # 可视化输出 └── main.py # 调度入口

这样拆分的好处是:哪一环出问题,直接打开对应文件排查,不需要把整个流程重新读一遍。

2. 数据获取层:Cookie、请求伪装与接口解析

2.1 登录态与 Cookie 管理

凡是要登录的业务系统,第一步都是处理登录态。模拟环境里,我通常会起一个本地Mock服务,模拟返回登录Cookie;真实项目中,正确做法是在自己拥有权限的账号上获取登录态,而不是尝试破解别人的账号体系。

用requests管理登录态的核心思路是复用Session对象。Session会把第一次请求拿到的Cookie自动保存下来,后续请求自动携带。这一点很重要,因为很多接口依赖Cookie来识别身份,如果你每次请求都重新构造一个requests.get,登录态就丢失了。

import requests session = requests.Session() # 模拟登录:在合规测试环境中获取Cookie login_resp = session.post( "https://mock.example/qqzone/login", data={"account": "你的测试账号", "password": "你的密码"}, timeout=10, ) # 后续请求自动携带Cookie resp = session.get( "https://mock.example/qqzone/friends", timeout=10, ) print(resp.status_code)

这段代码虽然简单,但有几个细节值得展开。第一,密码参数千万不要写死在代码里,而是从环境变量或配置文件中读取;第二,登录接口可能带加密参数,需要先用开发者工具确认参数名,不要凭猜测去构造;第三,Session对象不是永久的,Cookie会过期,过期的表现通常是接口返回需要重新登录的状态码。

我在测试环境里遇到过一类很隐蔽的问题:登录接口返回了200,但Cookie里只带了sessionid,关键的身份票据字段没有落上,导致后续接口一直返回未授权。排查办法是把登录后的session.cookies打印出来,和浏览器里的Cookie逐一对比,看少了哪个字段。Cookie字段名每个平台都不同,但在项目里把这一步做成了自动化检查,能省下大量排错时间。

2.2 请求头伪装与频率控制策略

Cookie解决的是“我是谁”,请求头解决的是“我像不像一个正常用户”。很多接口会校验User-Agent、Referer、Accept-Language等字段,如果请求头里缺了这些,或者UA是不常见的文本,很容易被识别为爬虫。

一个相对完整的请求头配置长这样:

HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Referer": "https://mock.example/qqzone/", "Origin": "https://mock.example", }

这里我故意用了一个比较通用的Chrome UA,而不是某些教程里那种明显过时的UA。记住一个原则:不要伪造不属于你的浏览器特征,用最常规、最常见的配置就行。

频率控制是很多人会忽略但极其重要的部分。对关系网这类接口来说,请求频率如果太高,轻则触发验证码,重则账号进入风控状态。我的经验是:每次请求之间至少随机延时1到2秒,不要写死sleep(1),因为固定间隔反而更容易被识别。

import random import time def safe_request(session, url, max_retries=3): for attempt in range(max_retries): try: resp = session.get(url, headers=HEADERS, timeout=10) if resp.status_code == 200: return resp elif resp.status_code in (403, 429): wait = 2 ** attempt + random.uniform(0.5, 1.5) time.sleep(wait) continue except requests.RequestException: time.sleep(random.uniform(1, 2)) return None

重试机制采用指数退避:第一次失败等2秒,第二次等4秒,每次加上随机抖动。这种方式在接口偶尔抖动的场景下非常管用,也能避免在风控触发后继续频繁撞击接口。

2.3 响应数据的字段抽取与格式化

拿到接口响应后,最关键的是把“关系”结构从JSON里安全地抽取出来。模拟接口的返回结构通常长这样:

{ "code": 0, "data": { "list": [ { "uin": "10001", "nickname": "用户甲", "remark": "同事", "group": "工作" }, { "uin": "10002", "nickname": "用户乙", "remark": "", "group": "好友" } ] } }

解析时我建议用三重防御:先判断code,再取list,最后逐字段清洗。很多人习惯直接resp.json()["data"]["list"],一旦接口返回异常或者调整字段名,脚本就崩了。正确的做法是给每个字段设置默认值,并且用strip清理空格。

payload = resp.json() if payload.get("code") != 0: raise RuntimeError(f"接口返回异常: {payload}") friends = [] for item in payload.get("data", {}).get("list", []): friend = { "uin": str(item.get("uin", "")), "nickname": item.get("nickname", "").strip() or "未知用户", "remark": item.get("remark", "").strip(), "group": item.get("group", "未分组"), } friends.append(friend)

这里我把uin强制转成字符串,是因为QQ号这类ID看起来是数字,但在网络传输中很容易被JSON解析成int,后续去重、作为节点ID时可能出现类型不一致的问题。这种看似无关紧要的细节,在真实项目里能让你少踩一堆坑。

3. 关系网构建与可视化

3.1 数据清洗与去重

关系网数据最怕两件事:重复节点和脏数据。重复节点会让图里出现同一个用户被拆成两个圆圈;脏数据会让边连到不存在的用户上。

清洗的第一步是去重。同一用户在接口里可能出现多次,比如既出现在好友列表又出现在群成员列表。去重时要用唯一的用户ID,也就是uin,而不是昵称或备注。昵称和备注谁都有可能重复,uin才是真正的身份证。

def clean_friends(friend_list): seen = set() result = [] for friend in friend_list: if friend["uin"] in seen: continue seen.add(friend["uin"]) result.append(friend) return result

第二步是处理空值和异常值。比如有一类用户没有设置头像或昵称完全没填,展示时就显示为“未知”。这种节点虽然不美观,但不应该直接删除,因为它可能是关系网中的一个连接点,删掉会让整个图的连通性发生变化。

我还建议对分组做一次归一化。同一含义的组名可能叫“同学”“大学同学”“高中同学”,如果不做归一化,可视化时会出现很多颜色相近的小色块,整体观感很乱。简单做法是维护一个映射表,把相近的分组映射到统一标签。

3.2 构建关系图模型

关系网本质上是一张无向图,因为双方只要建立了好友关系,这条边就是双向的。用NetworkX构建模型时,节点是用户标识,边表示两个用户之间的关系。如果需要区分关系类型,可以在边上增加group属性。

import networkx as nx G = nx.Graph() # 添加节点 for friend in clean_friends: G.add_node(friend["uin"], nickname=friend["nickname"], group=friend["group"]) # 添加关系边 for relation in relation_list: G.add_edge(relation["source"], relation["target"], relation_type="friend")

这里有个容易忽略的点:在真实场景中,关系数据是通过“获取某个人的好友列表”拿到的。假如拿到A的好友列表,发现里面有B,那就能画一条A-B边;如果也拿到了B的好友列表,发现里面没有A,这条边就变成了单向。所以图模型到底用有向图还是无向图,取决于数据源的关系方向。建议一开始先用无向图观察整体结构,后续再根据实际需求改成有向图。

构建好图之后,可以顺手算几个指标。度中心性告诉你谁是关系网络里最活跃的节点,介数中心性能反映出谁是连接不同小团体的“桥梁”。这些指标在社交分析场景里非常实用,也是关系网项目区别于普通爬虫项目的亮点所在。

degree_centrality = nx.degree_centrality(G) top_nodes = sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True)[:5] print("关系网中最核心的5个节点:", top_nodes)

3.3 可视化:把关系网画出来

可视化的核心原则是:先跑通,再优化。第一版直接用matplotlib画静态图,代码最简单,只要能看到结构就算成功。

import matplotlib.pyplot as plt plt.figure(figsize=(12, 12)) pos = nx.spring_layout(G, seed=42, k=0.8) nx.draw_networkx_nodes(G, pos, node_size=100) nx.draw_networkx_edges(G, pos, alpha=0.3) nx.draw_networkx_labels(G, pos, font_size=8) plt.axis("off") plt.show()

这段代码在节点少于300个时表现还行,超过300个就会很卡。原因是spring_layout要做大量迭代计算,一次性全量布局CPU扛不住。这时候有两个优化方向:一是先按分组过滤出子图,只画主要关系链;二是改用pyecharts生成交互式HTML图,渲染交给浏览器,Python端只负责输出配置。

pyecharts的Graph类型非常好用,支持拖拽、缩放、悬浮提示,用来做项目演示效果很好。基本用法是先把NetworkX的节点和边转换成pyecharts需要的格式,然后输出HTML。

from pyecharts import options as opts from pyecharts.charts import Graph nodes = [{"name": n, "symbolSize": 40} for n in G.nodes] links = [{"source": u, "target": v} for u, v in G.edges] graph = Graph() graph.add("", nodes, links) graph.set_global_opts(title_opts=opts.TitleOpts(title="关系网可视化")) graph.render("relation_graph.html")

我个人经验是:静态图和交互图各留一份。静态图放进文档或报告,交互图用于演示时给观众自己拖拽探索。如果不幸画出“一团毛线”,优先检查是不是清洗阶段没有去重,或者spacing参数调太大。

4. 常见问题与排查技巧实录

4.1 登录态失效的常见原因与处理

我在测试中遇到最多的问题就是请求明明带了Cookie,却还是被提示未登录。根据我的排查经验,原因通常有三种。

第一种是Cookie字段没传完整。requests.Session虽然会自动保存Cookie,但如果登录接口返回的Set-Cookie里包含了HttpOnly字段,requests默认也能处理,出现问题最多的是跨域场景下缺少某个关键字段。排查方法是用session.cookies输出所有Cookie,和浏览器开发者工具里的Cookie清单做diff。

第二种是Cookie过期。Session保存的Cookie不会自动刷新,一旦过期,接口就会返回登录态失效的状态码。解决办法是检测到失效后,重新执行登录流程,不要手动去改Cookie,这样既不安全也不可持续。

第三种是时间戳和签名参数没跟上。很多接口除了Cookie,还会校验请求体里的时间戳、签名甚至一次性token。这时候要回到开发者工具里看清楚这些参数是怎么生成的,再决定是复现算法还是在请求前先调用一个初始化接口获取参数。

排查这类问题有个统一的技巧:在接口返回异常时,把状态码、响应体前200个字符、当前Cookie名集合一起打日志。批量跑数据时,这个日志能帮你节省大量时间。

4.2 字段缺失与分页游标处理

接口返回的list并不是一次给全的,通常采用分页机制。在模拟接口里,我设置了每页50条,用游标(next_key)标记下一页位置。很多新手的错误是只取第一页就结束,然后绘出来的图只有一小部分。

处理分页的逻辑要写在解析函数里:

def fetch_all_pages(session, start_url): current_key = "" all_data = [] while True: url = f"{start_url}?cursor={current_key}" resp = safe_request(session, url) if resp is None: break payload = resp.json() data = payload.get("data", {}) all_data.extend(data.get("list", [])) current_key = data.get("next_key", "") if not current_key: break return all_data

字段缺失的情况我建议用一个统一的字段默认值函数去兜底。在模拟数据里我就故意留了一条没有备注的记录,用来演示空值处理逻辑。真实接口比这个情况复杂得多,最怕的是字段类型变化:昨天返回字符串,今天返回int,解析脚本直接崩。所以要在解析层做类型强制转换,就像前面把uin转成str一样。

4.3 节点过多导致图渲染卡顿的处理

当关系网数据量超过1000个节点时,不管用什么布局算法,一次性全画出来都会卡。解决思路不是优化绘图性能,而是降低单次渲染规模。

最实用的策略是把大图拆成“核心图+边缘图”。先计算连通分量,把最大的那个连通子图拎出来画主视图,剩下的节点按分组归类后,只显示统计数字,不逐个画点。这样既保留了关系网的骨架,又不会让图变成毛线球。

另外,给节点设置分组颜色也能显著提升可读性。用group字段映射颜色,同一分组的人聚在一起,一眼就能看出关系网里哪些人是同学、哪些人是同事、哪些人跨了多组。我建议每个分组的颜色使用色系的明度阶梯,不要用彩虹色,彩虹色会让图显得很廉价,而且分不清主次。

4.4 数据合规自查清单

这个项目虽然是模拟数据演示,但我在每次跑真实场景前都会过一遍合规自查,这里把清单整理给你,建议直接存成笔记。

检查项合规做法风险表现
数据来源仅采集自己账号可见的公开信息,或已获得明确授权的数据采集他人私密好友关系链
访问频率设置合理延时和重试上限高频访问触发风控
存储与展示数据脱敏,不使用真实昵称/ID展示明文展示可识别个人身份的信息
使用目的仅用于技术学习、个人研究或授权分析用于未经同意的商业用途
平台协议遵守目标平台的服务条款与接口约束逆向破解、绕过权限控制

如果自查时发现任何一个环节站不住脚,就停下来重新设计数据获取方式。爬虫只是工具,工具用得是否合理,取决于项目边界是否清晰。

我自己实际跑完这个项目后,最大的体会是:技术难点不在“爬”,而在“整理”——把半结构化数据抽成干净的关系模型,再把它变成别人一眼能看懂的图,这中间的每一环都有学问。最后分享一个小技巧:在main.py里加一个--mock参数,默认走模拟数据,加了这个参数才允许连真实接口。这个小设计能让你的项目在开发、演示、生产三种场景之间自由切换,也能避免手滑在测试环境里跑出脏数据。这个项目做完之后,你再去看任何带“关系链”的业务场景,思路都会比之前清晰得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 15:30:11

SAP寄售库存配置与结算全链路避坑指南

简介:本资源是一份面向SAP MM模块初学者与实施顾问的供应商寄售全流程实操指南,聚焦采购与库存管理中的寄售业务场景,解决企业如何在SAP系统中准确配置并执行寄售采购、收料、库存转移及发票结算等核心问题。文档以结构化步骤呈现&#xff0c…

作者头像 李华
网站建设 2026/10/15 15:29:52

ArcGIS符号库.rar安装与使用全攻略:从.style到.stylx

简介:这份ArcGIS符号库资源面向地图制图、地理数据分析及GIS教学人员,用于解决地图要素表达单一、符号不统一的问题。压缩包共142个文件,约64.75MB,以74个style样式文件、15个styles样式集、14个lyr图层文件为主,另含2…

作者头像 李华
网站建设 2026/10/15 15:29:47

书霸AI:期刊论文写作的下一步,从记录开始

未来写期刊论文,最值得关注的变化,可能发生在正文动笔之前:研究材料能否整理清楚,问题能否说得具体,证据能否与结论对应。随着AI进入写作流程,作者的工作也在调整——除了组织语言,还要学会提供…

作者头像 李华
网站建设 2026/10/15 15:29:44

如何安装 WeKit:LSPosed 与 NPatch 免 Root 两种 Xposed 完整教程

【免费下载链接】WeKit WeChat, now with superpowers 项目地址: https://gitcode.com/gh_mirrors/wek/WeKit 点击查看 免费下载 WeKit 是一款面向微信的开源增强模块,支持通过 Xposed、Zygisk 或免 Root 注入框架加载,为微信提供聊天增强、…

作者头像 李华
网站建设 2026/10/15 15:29:40

Jsoncpp动态库与静态库:编译链接、部署与避坑实践

简介:这是一份已编译好的 Jsoncpp 库,面向使用 Visual Studio 进行 Windows 桌面开发的 C 程序员,适合需要 JSON 解析、序列化与配置读取的中小型工具及客户端项目。库按 C 方式编译导出,未按 C 接口封装,能直接使用 J…

作者头像 李华
网站建设 2026/10/15 15:29:10

Qwen2-VL图像理解项目实战:LoRA微调与结构化推理全流程

简介:本资源是一套基于Python实现的Qwen2-VL多模态大模型图像识别工程实践代码,面向人工智能方向的中级开发者与视觉语言模型学习者,聚焦于使用千问Qwen2-VL在COCO-2014 Caption数据集上完成端到端训练与推理任务。资源共4个Python文件&#…

作者头像 李华