☰
Python爬虫实战:从零爬取QQ关系网并可视化
2026/10/14 9:58:53 网站建设 项目流程

做爬虫这些年,踩过不少坑,也积累了不少实战经验。今天抽空整理一个很多人问过的项目:用 Python 爬取 QQ 关系网。这个项目看起来只是“爬个好友列表”,但真正做起来会发现,它涉及到登录态管理、接口参数签名、广度优先遍历、图数据存储与可视化等一系列问题,整个过程相当考验基本功。

需要先说明的是,这个项目的核心是“技术研究”——爬取你自己账号能看到的好友关系数据,用来学习和分析数据爬取的全流程。无论做什么爬虫项目,都必须遵守平台规则和相关法律规定,不碰他人隐私数据。在这个前提下,我们可以把这个项目拆得非常细,从环境准备到最终可视化,完整走一遍。

1. 项目整体设计与核心思路

1.1 这个项目到底在爬什么:关系网的数据从哪里来

先搞清楚一个问题:QQ 关系网在哪里能看到?很多人第一反应是去抓 QQ 客户端的聊天列表,但这个思路并不好落地,因为 PC 端和移动端的聊天协议都是加密的,抓包成本很高。

实际项目中,最可行的数据入口是 QQ 空间。QQ 空间本身是 Web 端产品,有成熟的 HTTP 接口体系,其中就包括好友列表接口。只要你登录了 QQ 空间,就能通过这个接口拿到自己可见的好友关系数据。而这个关系数据,就是构建“关系网”最核心的原料。

那“关系网”到底是什么?如果你只爬自己的好友列表,得到的是一个星型结构——你自己是中心,好友是外围节点。这没什么好看的。真正有价值的关系网,是往下多走几层:拿到好友的好友列表(在权限允许的范围内),把每一对“好友关系”都记录下来,最终形成一张包含多个节点和大量边的社交网络图。这张图才是关系网。

要注意的是,QQ 空间对好友的好友列表是有权限限制的。某些用户设置了隐私保护,你无法通过接口获取对方的好友列表。所以在设计爬虫时,一定要把“部分节点可能拿不到子节点”这种情况考虑进去,否则程序很容易在遍历时崩溃。

1.2 技术栈选型与架构设计

这个项目用到的技术栈并不复杂,但组合起来非常经典。我的选择是这样的:

  • requests:发送 HTTP 请求,维持登录会话
  • json:解析接口返回的数据
  • SQLite 或 JSON 文件:存储关系对
  • networkx:构建图数据结构
  • matplotlib 或 pyecharts:关系图可视化

有朋友会问:为什么不用 Scrapy?原因有两点。第一,这个项目的爬取逻辑不是“下载大量 URL”,而是“递归遍历好友关系”,更像是一个图遍历算法驱动的任务,用 Scrapy 反而要绕一层。第二,Scrapy 的并发调度在这种需要严格控制请求频率的场景下并不占优势。requests 加循环加延时,逻辑最直白,出了问题也最好排查。

整体架构可以拆成四个模块:登录态获取模块、接口请求模块、关系数据存储模块、可视化分析模块。最核心的接口请求模块需要处理参数签名、请求头伪装、Cookie 自动更新等问题。这个架构在任意一个需要登录态的爬虫项目里都能复用,写一次受益很久。

2. 核心细节拆解:登录态、接口与反爬

2.1 登录态获取的三种方案对比

QQ 空间的所有接口都需要登录态。没有登录态,接口会直接返回错误。获取登录态的方式有三种,我逐一分析利弊。

第一种是手动复制 Cookie。用浏览器登录 QQ 空间,按 F12 打开开发者工具,在网络面板里复制请求头中的 Cookie,粘贴到代码里直接用。这种方式的优点是零风险、不会触发登录风控,缺点是 Cookie 有效期有限,通常只能维持几个小时到一天,过期后要重新复制。

第二种是使用 Selenium 自动化扫码登录。通过 Selenium 打开登录页面,人工扫码后程序自动获取登录后的 Cookie。这种方式比手动复制要“优雅”一些,但 Selenium 启动浏览器比较占用系统资源,而且 QQ 空间的登录页面偶尔会有滑块验证,处理起来稍显繁琐。

第三种是直接在代码里模拟账号密码登录。这种方式看起来最“自动化”,但风险也最高。频繁的密码登录很容易触发 QQ 的风控机制,轻则验证码,重则账号受限。我强烈不建议在真实的个人账号上使用这种方式。

三种方案的最终产物其实都一样:一份包含关键字段的 Cookie。这份 Cookie 里最重要的字段是skey和p_skey——它们不仅是登录凭证,还是后面计算接口签名参数的基础。我在实际项目中通常采用第一种方案,简单、可靠、不折腾。

2.2 关键接口与参数分析:g_tk 的算法

拿到 Cookie 只是第一步,QQ 空间的好友接口还要求一个动态签名参数 g_tk。这个参数每次请求都要带上,它的值是根据 Cookie 里的 skey 字段算出来的。算法是固定的,网上也有各种版本的实现,但核心思路都一样:通过一个固定的哈希算法把 skey 转换成一个整数。

下面是 g_tk 计算函数的 Python 实现,我实测过很多次,计算结果稳定:

def get_g_tk(skey): hash_val = 5381 for ch in skey: hash_val += (hash_val << 5) + ord(ch) return str(hash_val & 0x7fffffff)

这个函数的逻辑很简单,先给 hash_val 一个初始值 5381,然后遍历 skey 字符串的每个字符,每轮把当前 hash 值左移 5 位再加回自身,然后加上字符的 ASCII 码。最后与 0x7fffffff 做按位与运算,保证结果是一个非负整数。说白了就是一个传统字符串哈希算法的变体。

在发起请求时,g_tk 要作为 query 参数放在 URL 里,和 Cookie 一起提交。只带 Cookie 不带 g_tk,接口同样会报错。所以写代码时,最好把 get_g_tk 函数单独提出来,每次请求前动态计算,这样即使 Cookie 更新了,代码也不用改。

2.3 反爬策略与风控规避:怎么爬得又稳又慢

QQ 空间的接口反爬不算特别强,但也不是随便就能硬刚的。我见过不少人一上来就并发请求,结果十分钟内账号就被限制了。这个项目里最有效的反爬手段恰恰是“慢”。

我建议把请求频率控制在每秒 1 到 2 个请求之间。具体做法是在每次请求之间加time.sleep(0.5)到time.sleep(1)。遍历好友的好友时,请求量会指数级增长,更要把延时调大。有人觉得慢,但关系网这种数据本身就不是时效性数据,晚几个小时拿到完全没影响,账号安全永远是第一位的。

请求头也需要伪装。爬虫默认的 User-Agent 很容易被识别,建议准备一个 User-Agent 池,每次请求随机取一个浏览器 UA。Referer 字段也要注意补上,QQ 空间对 Referer 校验比较严格,最好设置成https://user.qzone.qq.com/。

关于并发:我不建议对这个项目做并发优化。原因很简单,好友关系遍历天然是一个图遍历算法,节点之间存在大量依赖关系,并发做不好很容易造成重复请求和频率失控。单线程加合理延时,最安全。

3. 实操过程:从登录到可视化全流程

3.1 环境准备与依赖安装

开始写代码之前,先准备好环境。建议使用 Python 3.8 以上版本,然后安装以下几个依赖库:

pip install requests networkx matplotlib

这里多说一句,networkx 是 Python 里最常用的图分析库,它本身不负责绘图,绘图依赖 matplotlib。所以两个库都要装。如果想做交互式的 Web 端可视化,后期可以换成 pyecharts,这个看个人需求。

我的项目目录结构大致是下面这样的,养成分层习惯,后续维护会轻松很多。目录分为三个文件:登录态配置、核心爬虫逻辑、可视化和测试逻辑。

qq_relationship/ ├── config.py # 存放 Cookie 和请求头 ├── crawler.py # 核心爬虫逻辑 └── visualize.py # 关系图可视化

3.2 第一步:获取登录 Cookie 并验证有效性

按照前面说的第一种方案,先手动获取 Cookie。打开浏览器访问 QQ 空间并登录,F12 打开开发者工具,随便点击一个请求,找到 Cookie 请求头,全部复制出来。然后粘贴到 config.py 中:

COOKIE = "这里粘贴你的完整Cookie字符串" 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", "Referer": "https://user.qzone.qq.com/", "Cookie": COOKIE }

拿到 Cookie 后,先不要急着写完整爬虫,先验证 Cookie 是否有效。验证方式很简单:请求一次好友列表接口,看看返回值是正常数据还是登录失效提示。

我个人的习惯是写一个专门的小函数来做验证,如果返回结果里code字段不是 0,就抛出异常提醒 Cookie 失效了。这样后续程序跑起来如果 Cookie 过期,可以第一时间定位到问题,而不是看到一大堆杂乱无章的报错。

3.3 第二步:编写核心爬虫,获取第一层好友列表

验证完 Cookie,下一步就是正式请求好友列表接口。QQ 空间的接口地址和参数格式并不是公开文档化的,我在首次调试时是通过浏览器开发者工具的“网络”面板一步一步抓包拿到的。

好友列表接口的核心逻辑是:在 URL 中带上 uin(当前账号 QQ 号),然后加上刚才计算的 g_tk 参数。请求方式为 GET,入参中还需要begin和num两个参数来控制分页。每次最多获取 100 个好友,如果好友数量超过 100,需要多次请求,用 begin 偏移量翻页拉取。

获取好友列表的核心函数我写成了下面这种形式:

def get_friends(uin, g_tk, begin=0, num=100): url = "https://user.qzone.qq.com/proxy/domain/roaming.qzone.qq.com" params = { "host": "user.qzone.qq.com", "path": "/cgi-bin/friendship/cgi_friends_list", "uin": uin, "g_tk": g_tk, "begin": begin, "num": num } resp = requests.get(url, params=params, headers=HEADERS, timeout=10) data = resp.json() if data.get("code") != 0: raise Exception("接口返回异常") friends = data["data"].get("friends", []) has_more = data["data"].get("hasMore", 0) return friends, has_more

这个函数返回两个值:当前页的好友数据列表,以及是否还有下一页。之后用 while 循环不断累加 begin 值,直到 has_more 为 0,就说明这一层的好友全部拉完了。

写到这里,“第一层好友”的概念就已经跑通了。打个比方,你现在的社交圈是“第一层关系”,你的好友列表就是你在这个圈子里的直接连接。要想“关系网”有看头,还得继续往深处走。

3.4 第三步:广度优先遍历,构建关系矩阵

核心算法来了。在爬虫项目中引入图的遍历算法,用“广度优先搜索”逐层解析好友关系,从你自己出发,往外扩散。

算法的思路是这样:先把你自己的 QQ 号加入队列,然后逐个处理队列里的每个账号,拉取这些账号的好友列表。拉取出来的 QQ 号,如果之前还没有访问过,就加入队列,继续往外扩散。在遍历过程中,每发现一对好友关系(你和好友、好友和好友),就把这一对关系保存到边列表里。

用 Python 实现出来是这样的:

from collections import deque def build_relationship(start_uin, g_tk, max_depth=2): queue = deque() queue.append((start_uin, 0)) visited = set() visited.add(start_uin) nodes = set() edges = [] while queue: uin, depth = queue.popleft() begin = 0 while True: friends, has_more = get_friends(uin, g_tk, begin=begin) for friend in friends: friend_uin = friend["uin"] edges.append((uin, friend_uin)) nodes.add(friend_uin) if friend_uin not in visited and depth + 1 < max_depth: visited.add(friend_uin) queue.append((friend_uin, depth + 1)) if not has_more: break begin += 100 time.sleep(0.5) time.sleep(0.8) return nodes, edges

两个关键参数要说清楚。第一个是 max_depth,也就是遍历深度。我建议设置成 2,不要超过 3。原因很直白,社交网络的数据量是呈指数级增长的。假设你 500 个好友,每个人平均 200 个好友,深度 2 就有 10 万个节点、几十万条边。深度 3 直接上千万,这已经超出了个人项目能优雅处理的量级。

第二个是 visited 集合,它的作用是防止重复访问。社交网络里 A 是 B 的好友、B 也是 A 的好友这种双向关系非常普遍,如果没有 visited 集合,程序会陷入死循环,无限套娃。这是遍历类爬虫最容易犯的错误。

3.5 第四步:数据存储与网络可视化

遍历完所有数据,爬虫的工作其实已经完成了。后面这一步解决的是“拿到数据之后怎么展示”的问题。

拿到 edges 列表之后,先别着急跑可视化,我建议先把原始数据落盘。格式直接选 JSON,简单方便,以后想重新生成图片就不用重新爬一遍。保存格式做成下面这样,每条记录就是一条关系对:

[ {"source": "10001", "target": "10002"}, {"source": "10001", "target": "10003"} ]

接下来用 networkx 把 edges 变成一张图,然后交给 matplotlib 做渲染。示例代码不长,但效果很直观:

import networkx as nx import matplotlib.pyplot as plt G = nx.Graph() G.add_edges_from(edges) plt.figure(figsize=(16, 12)) nx.draw_networkx( G, node_size=50, node_color="#66c2a5", with_labels=False, edge_color="#999999", alpha=0.8 ) plt.axis("off") plt.savefig("qq_relationship.png", dpi=300)

如果你是第一次做关系图,大概率会遇到一个问题:节点一多,图直接变成“一团毛线”,密密麻麻看不清。这很正常,说明数据量超出了 matplotlib 的舒适区。两个讨巧的解决办法,一是只保留度(也就是好友数)大于某个阈值的节点,把“边缘人”过滤掉;二是换用像 Gephi 这种专业可视化工具,导入 JSON 后它能自动做社区发现和聚类布局,出图效果比 matplotlib 好很多。

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

4.1 登录态失效与 Cookie 过期

最常见的问题就是程序跑着跑着突然全部报错,提示接口返回登录异常。这种情况绝大多数是 Cookie 过期了。手动复制的 Cookie 就是有这个毛病,有效期不可控,短则一小时,长则一天。

排查方法是:单独跑一次 get_friends 函数,把返回的原始 JSON 打印出来。如果 code 异常,直接换 Cookie。我在实际开发中会把 Cookie 写进一个独立配置文件,每次启动时优先检查配置文件的修改时间,超过一定时长就提示用户去重新复制 Cookie。

4.2 接口返回错误或参数签名错误

如果你确定 Cookie 没问题,但接口始终报错,最常见的原因是 g_tk 计算错误。这里有一个很多人会踩的坑:Cookie 里的skey字段之间有空格或者被折行复制,导致计算出来的 g_tk 值不对。

解决办法很简单:复制 Cookie 后先打印出来,确认 skey 的值完整无空格。还有一种情况是用了p_skey而不是skey去算 g_tk,在 QQ 空间的部分接口里,这两个字段都能用,但签名计算的入口不一样,具体用哪个要看抓包结果。

4.3 分页数据丢失

好友列表接口单次最多返回 100 条数据。有人只拉了一次接口就停手,代码里也没有处理 has_more 字段,结果数据只有一页,关系图看起来稀稀拉拉。

正确的逻辑是在 while 循环中持续累加 begin 参数,直到 has_more 为 0。同时,每次分页请求之间加短延时,避免短时间请求次数过多。这个技巧对任何分页接口都通用。

4.4 可视化效果差:节点太多看不清

这是最后一个环节最容易出的问题。网上很多教程到 networkx 画图就结束了,但实际画出来的效果根本不能看。我建议在作图前先统计一下每个节点的度数(关联边的数量),然后做一次“剪枝”操作,只保留度数超过 5 的节点。这样过滤掉那些只出现一次两次的弱关联节点,关系网络的主干结构会清晰很多。

如果过滤完还是不够清晰,就换成社区发现算法,把图划分成几个簇,再给每个簇配一个颜色,读图体验会大幅提升。说到底,爬虫只是数据获取手段,分析才是这个项目真正的价值所在。

4.5 风控触发与账号受限

这是最需要重视的问题。我见过有人开着 10 个线程去爬好友列表,结果一小时不到,账号登录直接被限制。QQ 空间的风控逻辑没有公开文档,但从实际反馈来看,短时间请求频率过高一定会触发限制。

我的操作纪律是:单线程运行、每次请求后延时 0.8 到 1.5 秒、每天控制总量在几千个请求以内。做到这三条,风控基本不会找你麻烦。另外,来回切 IP 反而容易被判定异常,保持一个固定的家庭网络地址更安全。

再分享一个排查技巧:如果你的代码之前跑得好好的,突然某个请求开始返回滑块验证,不要继续跑下去了。立刻停止程序,过几个小时再继续。强行突破滑块验证在技术上固然可行,但风险极高,为了一个学习项目去赌账号安全完全不值得。


做这个项目给我最大的体会是:爬虫项目是否成功,不只是看能不能拿到数据,更要看整个流程中的数据规模控制和合规边界。关系网这个题目,设计两到三层的遍历深度,做出来一张能看清主结构的关系图,就已经达到了学习和练手的目的。这个项目后续还可以加上社区发现分析、关键人物识别、中心度计算等图算法应用,把“爬”和“分析”两条腿都走起来,你会收获更多。

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

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

立即咨询