Python 进阶爬虫:解析知识星球 API

一、知识星球 API 核心原理与接口分析

知识星球的前端页面采用动态加载技术(JavaScript 渲染),所有内容数据均通过后端 API 接口以 JSON 格式返回,前端再将数据渲染为可视化页面。因此,API 爬虫的核心逻辑是模拟前端请求,直接调用 API 接口获取原始 JSON 数据,而非解析 HTML 页面。

1.1 API 接口基础架构

知识星球的 API 接口遵循 RESTful 设计规范,核心请求域名为<font>https://api.zsxq.com</font>,所有接口均通过 HTTPS 协议传输,确保数据安全性。接口主要分为三大类:

  • 认证类接口:用于获取登录凭证(Cookie/Token),是后续数据请求的基础;
  • 内容类接口:获取星球列表、主题列表、主题详情、评论、点赞等核心内容数据;
  • 用户类接口:获取用户信息、粉丝列表、关注列表等辅助数据。

所有接口的请求方式以<font>GET</font><font>POST</font>为主,请求参数包含公共参数(如时间戳、签名、设备信息)与业务参数(如星球 ID、主题 ID、分页参数),其中签名验证是知识星球反爬机制的核心,也是 API 爬虫的关键难点。

1.2 关键接口梳理(核心业务接口)

在实际爬虫开发中,我们重点关注以下高频核心接口,覆盖数据抓取的全流程:

接口功能请求 URL请求方式核心参数响应数据
获取我的星球列表<font>/v1/groups</font>GET<font>count</font>
(每页数量)、<font>end_time</font>
(分页时间戳)
星球 ID、星球名称、星球描述、成员数等
获取星球主题列表<font>/v1/groups/{group_id}/topics</font>GET<font>group_id</font>
(星球 ID)、<font>count</font>
<font>end_time</font>
主题 ID、标题、内容摘要、发布时间、作者信息等
获取主题详情<font>/v1/topics/{topic_id}</font>GET<font>topic_id</font>
(主题 ID)
完整主题内容、图片链接、附件信息、评论数等
获取主题评论<font>/v1/topics/{topic_id}/comments</font>GET<font>topic_id</font>
<font>count</font>
<font>end_time</font>
评论内容、评论者、评论时间等
点赞主题<font>/v1/topics/{topic_id}/likes</font>POST<font>topic_id</font>点赞状态、点赞数更新

1.3 反爬机制与签名验证解析

知识星球的 API 接口通过签名(signature) 机制防止非法请求,所有非公开接口的请求头或请求参数中必须包含合法的签名,否则会返回<font>401 Unauthorized</font>(未授权)或<font>403 Forbidden</font>(禁止访问)错误。签名的生成逻辑是知识星球 API 爬虫的核心,其生成规则如下:

  1. 基础参数准备:签名生成需要以下固定参数与动态参数:
    • 固定参数:<font>app_version</font>(APP 版本,如<font>3.11.0</font>)、<font>platform</font>(平台,如<font>ios</font>/<font>android</font>)、<font>timestamp</font>(当前时间戳,精确到毫秒);
    • 动态参数:请求的<font>path</font>(接口路径,如<font>/v1/groups</font>)、请求参数(如<font>group_id</font><font>count</font>);
    • 密钥:知识星球客户端内置的密钥<font>secret</font>(通过反编译客户端或抓包分析可获取,核心密钥为<font>zsxqapi2020</font>)。
  2. 签名生成步骤
    • 步骤 1:将所有请求参数(包括公共参数与业务参数)按照键名升序排列,拼接为<font>key1=value1&key2=value2</font>的字符串;
    • 步骤 2:将接口路径<font>path</font>与拼接后的参数字符串用<font>&</font>连接,形成待签名字符串;
    • 步骤 3:将待签名字符串与内置密钥<font>secret</font>拼接,使用<font>MD5</font>算法加密,生成 32 位小写的签名值;
    • 步骤 4:将签名值放入请求头的<font>X-Signature</font>字段,随请求一起发送。

示例:若请求接口为<font>/v1/groups</font>,参数为<font>count=20&end_time=1735872000000</font>,公共参数为<font>app_version=3.11.0&platform=ios&timestamp=1735872100000</font>,则待签名字符串为<font>/v1/groups&app_version=3.11.0&count=20&end_time=1735872000000&platform=ios&timestamp=1735872100000</font>,拼接密钥后 MD5 加密即为签名。

二、环境准备与依赖安装

在实现 API 爬虫前,需准备 Python 开发环境并安装必要的依赖库,核心依赖包括:

  • <font>requests</font>:用于发送 HTTP 请求,处理 API 接口调用;
  • <font>pycryptodome</font>:用于 MD5 签名生成(Python 内置 hashlib 也可实现,pycryptodome 兼容性更强);
  • <font>json</font>:用于解析 API 返回的 JSON 数据(Python 内置,无需安装);
  • <font>time</font>/<font>datetime</font>:用于时间戳生成与时间格式转换(Python 内置)。

2.1 环境要求

  • Python 版本:3.8 及以上(建议 3.10+,兼容性更好);
  • 操作系统:Windows/MacOS/Linux 均可;
  • 网络环境:可正常访问知识星球(需科学上网,若国内访问失败)。

三、Python 实现知识星球 API 爬虫

本节将分模块实现知识星球 API 爬虫,包括签名生成工具、登录凭证获取、核心接口请求、数据解析与存储,最终实现从星球列表到主题详情的全量数据抓取。

3.1 核心工具类:签名生成与请求封装

首先实现签名生成工具,封装请求头、参数处理与签名逻辑,确保所有 API 请求符合知识星球的验证规则。该工具类是整个爬虫的基础,需保证签名生成的准确性。

python

运行

import requests import hashlib import time import json from urllib.parse import urlencode from datetime import datetime class ZsxqApiSpider: def __init__(self, cookie=None): """ 初始化知识星球API爬虫 :param cookie: 登录后的Cookie(若未提供,需手动登录获取) """ # 基础配置 self.base_url = "https://api.zsxq.com" self.app_version = "3.11.0" # 客户端版本,固定值 self.platform = "ios" # 平台类型,固定值 self.secret = "zsxqapi2020" # 知识星球内置密钥,核心参数 self.cookie = cookie # 登录凭证,必须提供 # 基础请求头(公共请求头,部分字段可固定) self.headers = { "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", "Accept": "application/json, text/plain, */*", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", "Cookie": self.cookie, "Origin": "https://wx.zsxq.com", "Referer": "https://wx.zsxq.com/" } def generate_signature(self, path, params=None): """ 生成知识星球API签名(核心方法) :param path: 接口路径(如/v1/groups) :param params: 请求参数字典(GET参数) :return: 签名字符串(32位小写MD5) """ # 1. 初始化公共参数 common_params = { "app_version": self.app_version, "platform": self.platform, "timestamp": str(int(time.time() * 1000)) # 毫秒级时间戳 } # 2. 合并公共参数与业务参数,并按键名升序排列 all_params = common_params.copy() if params and isinstance(params, dict): all_params.update(params) # 按键名升序排序 sorted_params = sorted(all_params.items(), key=lambda x: x[0]) # 拼接为key=value格式 params_str = urlencode(sorted_params) # 3. 拼接待签名字符串:path + & + params_str + & + secret sign_str = f"{path}&{params_str}&{self.secret}" # 4. MD5加密生成签名 md5 = hashlib.md5() md5.update(sign_str.encode("utf-8")) signature = md5.hexdigest() return signature, common_params["timestamp"] def send_get_request(self, path, params=None): """ 发送GET请求(封装签名与请求逻辑) :param path: 接口路径 :param params: 请求参数 :return: 响应数据(字典格式) """ # 生成签名与时间戳 signature, timestamp = self.generate_signature(path, params) # 更新请求头,添加签名与时间戳 self.headers["X-Signature"] = signature self.headers["X-Timestamp"] = timestamp # 拼接完整请求URL url = f"{self.base_url}{path}" try: # 发送GET请求 response = requests.get(url, headers=self.headers, params=params, timeout=10) response.raise_for_status() # 抛出HTTP错误(如404、500) return response.json() # 返回JSON格式数据 except requests.exceptions.RequestException as e: print(f"GET请求失败:{str(e)}") return None def send_post_request(self, path, data=None): """ 发送POST请求(如点赞、评论) :param path: 接口路径 :param data: POST请求体数据 :return: 响应数据 """ signature, timestamp = self.generate_signature(path) self.headers["X-Signature"] = signature self.headers["X-Timestamp"] = timestamp self.headers["Content-Type"] = "application/json;charset=UTF-8" url = f"{self.base_url}{path}" try: response = requests.post(url, headers=self.headers, json=data, timeout=10) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: print(f"POST请求失败:{str(e)}") return None 

代码解析

  • <font>__init__</font>方法:初始化基础配置,包括请求域名、固定参数、登录 Cookie 与请求头,Cookie 是登录凭证,必须从浏览器中手动获取;
  • <font>generate_signature</font>方法:严格按照知识星球的签名规则,实现参数排序、字符串拼接与 MD5 加密,返回合法签名与时间戳;
  • <font>send_get_request</font>/<font>send_post_request</font>方法:封装请求逻辑,自动添加签名与时间戳,处理请求异常,返回结构化 JSON 数据。

3.2 登录凭证(Cookie)获取

知识星球的 API 接口需要登录后才能访问,因此必须先获取登录后的 Cookie。获取步骤如下:

  1. 打开浏览器,访问知识星球网页版(https://wx.zsxq.com/),完成扫码登录;
  2. 按 F12 打开开发者工具,切换到「Network」(网络)面板;
  3. 刷新页面,找到任意以<font>api.zsxq.com</font>为域名的请求(如<font>groups</font>);
  4. 在请求头中找到<font>Cookie</font>字段,复制完整的 Cookie 值(以<font>zsxq_access_token</font>开头的字符串)。

注意:Cookie 具有时效性,通常有效期为 1-3 个月,过期后需重新获取。

3.3 核心功能实现:星球与主题数据抓取

在工具类的基础上,实现具体的业务功能,包括获取星球列表、主题列表、主题详情,并将数据保存为 JSON 文件,方便后续分析。

python

运行

def get_my_groups(self, count=20): """ 获取我的知识星球列表 :param count: 每页获取的星球数量(最大50) :return: 星球列表数据 """ path = "/v1/groups" params = {"count": count} response = self.send_get_request(path, params) if response and response.get("succeeded"): groups = response.get("resp_data", {}).get("groups", []) print(f"成功获取{len(groups)}个星球") return groups else: print("获取星球列表失败:", response.get("resp_err", "未知错误")) return [] def get_group_topics(self, group_id, count=20, end_time=None): """ 获取指定星球的主题列表 :param group_id: 星球ID :param count: 每页主题数量 :param end_time: 分页时间戳(用于加载更多,首次为None) :return: 主题列表数据 """ path = f"/v1/groups/{group_id}/topics" params = {"count": count} if end_time: params["end_time"] = end_time response = self.send_get_request(path, params) if response and response.get("succeeded"): topics = response.get("resp_data", {}).get("topics", []) # 提取下一页的end_time(用于分页加载) next_end_time = response.get("resp_data", {}).get("end_time") return topics, next_end_time else: print("获取主题列表失败:", response.get("resp_err", "未知错误")) return [], None def get_topic_detail(self, topic_id): """ 获取主题详情(完整内容、图片、附件) :param topic_id: 主题ID :return: 主题详情数据 """ path = f"/v1/topics/{topic_id}" response = self.send_get_request(path) if response and response.get("succeeded"): topic_detail = response.get("resp_data", {}).get("topic", {}) return topic_detail else: print("获取主题详情失败:", response.get("resp_err", "未知错误")) return {} def save_data_to_json(self, data, filename): """ 将数据保存为JSON文件 :param data: 要保存的数据(字典/列表) :param filename: 文件名(如groups.json) """ try: with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) print(f"数据已保存至{filename}") except Exception as e: print(f"保存数据失败:{str(e)}") # 将方法绑定到类中 ZsxqApiSpider.get_my_groups = get_my_groups ZsxqApiSpider.get_group_topics = get_group_topics ZsxqApiSpider.get_topic_detail = get_topic_detail ZsxqApiSpider.save_data_to_json = save_data_to_json 

代码解析

  • <font>get_my_groups</font>:调用星球列表接口,返回当前账号加入的所有星球,包含星球 ID、名称等核心信息;
  • <font>get_group_topics</font>:根据星球 ID 获取主题列表,支持分页加载(通过<font>end_time</font>参数实现),返回主题 ID、标题、摘要等;
  • <font>get_topic_detail</font>:根据主题 ID 获取完整详情,包括富文本内容、图片直链、附件下载地址;
  • <font>save_data_to_json</font>:将结构化数据保存为 JSON 文件,保留原始数据结构,方便后续处理。

3.4 主程序:全流程数据抓取

编写主程序,实现从「获取星球列表→遍历星球→获取主题列表→获取主题详情→保存数据」的全流程,同时添加分页逻辑,确保抓取所有数据。

python

运行

if __name__ == "__main__": # 1. 配置登录Cookie(替换为你自己的Cookie) ZSXQ_COOKIE = "zsxq_access_token=XXX; zsxqsessionid=XXX; ..." # 替换为实际Cookie # 2. 初始化爬虫 spider = ZsxqApiSpider(cookie=ZSXQ_COOKIE) # 3. 获取我的星球列表并保存 print("===== 开始获取星球列表 =====") groups = spider.get_my_groups(count=50) if groups: spider.save_data_to_json(groups, "zsxq_groups.json") # 4. 遍历每个星球,抓取主题列表与详情 for group in groups: group_id = group.get("group_id") group_name = group.get("name", "未知星球") print(f"\n===== 开始抓取星球:{group_name}(ID:{group_id})=====") all_topics = [] end_time = None page = 1 # 分页抓取主题列表(直到无更多数据) while True: print(f"正在抓取第{page}页主题...") topics, next_end_time = spider.get_group_topics(group_id, count=50, end_time=end_time) if not topics: break # 遍历每个主题,获取详情 for topic in topics: topic_id = topic.get("topic_id") print(f"正在获取主题详情:{topic_id}") topic_detail = spider.get_topic_detail(topic_id) if topic_detail: all_topics.append(topic_detail) # 更新分页参数 end_time = next_end_time page += 1 time.sleep(1) # 延时1秒,避免请求过快触发反爬 # 保存当前星球的所有主题数据 if all_topics: filename = f"zsxq_topics_{group_id}.json" spider.save_data_to_json(all_topics, filename) print("\n===== 所有数据抓取完成 =====") 

代码解析

  • 主程序首先配置 Cookie,初始化爬虫;
  • 先获取星球列表并保存,再遍历每个星球,通过分页循环抓取所有主题;
  • 对每个主题调用详情接口,获取完整数据后统一保存,添加<font>time.sleep(1)</font>延时,降低请求频率,避免触发反爬;
  • 最终每个星球的主题数据单独保存为 JSON 文件,便于管理。

四、反爬规避与爬虫优化

知识星球的反爬机制除了签名验证,还包括请求频率限制、IP 封禁、Cookie 过期检测,为保证爬虫的稳定性,需进行以下优化:

4.1 请求频率控制

  • 避免短时间内大量请求,在接口调用之间添加延时(<font>time.sleep(1-3)</font>);
  • 控制单次请求的<font>count</font>参数(建议 20-50),避免单次请求数据量过大触发限流。

4.2 IP 代理使用

若频繁请求导致 IP 被封禁,可使用代理 IP 池,在<font>send_get_request</font>中添加代理配置:

python

运行

import requests # 代理配置信息 proxyHost = "www.16yun.cn" proxyPort = "5445" proxyUser = "16QMSOML" proxyPass = "280651" # 构建代理字典(包含认证信息) # 格式:http://用户名:密码@代理主机:端口 proxies = { "http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}", "https": f"https://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}" } # 请求头(根据实际需求补充) 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" } # 示例:发送带代理的请求 url = "https://www.example.com" # 替换为目标URL params = {} # 替换为实际请求参数 try: # 发送请求时添加proxies参数 response = requests.get(url, headers=headers, params=params, proxies=proxies, timeout=10) response.raise_for_status() # 抛出HTTP错误状态码异常 print("请求成功!状态码:", response.status_code) print("响应内容:", response.text[:500]) # 打印前500字符 except requests.exceptions.RequestException as e: print("请求失败:", str(e)) 

当 Cookie 过期时,爬虫会返回<font>401</font>错误,可通过定时任务(如<font>APScheduler</font>)定期重新获取 Cookie,或实现自动登录逻辑(需处理验证码,复杂度较高)。

五、总结与合规提醒

本文通过解析知识星球 API 的核心原理,实现了从签名生成、接口请求到数据存储的全流程 API 爬虫,相比传统网页爬虫,API 爬虫具有数据提取精准、效率高、稳定性强的优势,是进阶爬虫的核心技能。通过本文的代码,你可以快速实现知识星球数据的批量抓取,为内容分析、数据挖掘提供基础支撑。

核心知识点回顾

  1. API 爬虫核心逻辑:模拟前端请求,直接对接后端 API,获取结构化 JSON 数据,避开前端渲染;
  2. 签名验证破解:掌握知识星球签名的生成规则(参数排序 + MD5 加密),是突破反爬的关键;
  3. 爬虫封装与优化:通过工具类封装请求逻辑,添加延时、代理、重试等优化,提升稳定性;
  4. 数据处理:将抓取的结构化数据保存为 JSON 格式,便于后续分析与使用。

Read more

【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、带环链表 * 1.1题目 * 1.2 算法原理 * 1.3 代码 * 1.4 数学证明 * 1.4.1 为什么带环slow与fast必定能相遇? * 1.4.2 fast一定只能走2步吗?可以是2步甚至更多吗? * 1.4.2.1 以3步为例 * 1.4.3结论 * 二、环形链表(寻找相遇点) * 2.1 题目

By Ne0inhk

Python金融数据获取终极指南:告别繁琐,5分钟掌握专业级数据源

Python金融数据获取终极指南:告别繁琐,5分钟掌握专业级数据源 【免费下载链接】mootdx通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 还在为金融数据获取而苦恼吗?面对复杂的行情接口、繁琐的数据格式转换,很多数据分析师和量化交易爱好者都感到力不从心。今天,我将为你揭秘一个强大的Python工具——mootdx,它能让你轻松获取通达信金融数据,为你的投资分析提供坚实的数据支撑。 🎯 你的数据获取痛点,我们懂! 数据获取的三大难题: * 接口复杂难上手:传统行情接口学习成本高,文档晦涩 * 数据格式不统一:不同来源的数据格式各异,转换工作繁琐 * 更新维护成本高:数据源不稳定,需要频繁调整和维护 mootdx正是为解决这些痛点而生,它提供了: * 📊 一站式数据解决方案:从历史数据到实时行情,全面覆盖 * 🔄 智能连接优化:自动选择最佳服务器,确保数据稳定获取 * 💡 开发体验升级:简洁的API设计,让数据获取变得简单高效 🚀 快速上手:5分钟开启数据

By Ne0inhk
Python Anaconda 换源: 设置清华源

Python Anaconda 换源: 设置清华源

为 Anaconda 设置清华源可以极大地提升软件包下载和更新的速度。以下是详细的步骤,分为两个主要部分:为 conda 本身设置频道镜像和为 pip 设置索引镜像。 方法一:通过命令行快速设置(推荐) 这是最快捷的方法,通过执行几条命令即可完成。 1. 打开终端(Windows 用 Anaconda Prompt, Mac/Linux 用 Terminal)。 验证配置: 执行以下命令查看当前的配置,确认 channels 里已经都是清华源的地址。 conda config --show channels (可选但推荐)移除默认的官方频道: 为了避免 conda 在官方源和清华源之间来回切换,可以移除默认的 defaults 频道。 conda config --remove channels defaults 设置搜索时显示频道地址: conda config

By Ne0inhk
Python核心:Django鉴权方案全解析

Python核心:Django鉴权方案全解析

让我们一起走向未来 🎓作者简介:全栈领域优质创作者 🌐个人主页:百锦再@新空间代码工作室 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[[email protected]] 📱个人微信:15045666310 🌐网站:https://meihua150.cn/ 💡座右铭:坚持自己的坚持,不要迷失自己!要快乐 目录 * 让我们一起走向未来 * 1. Django认证系统概述 * 2. 用户身份验证(Authentication) * 用户模型 * 用户登录验证 * 用户注销 * 3. 用户权限控制(Authorization) * 权限 * 用户组 * 权限检查 * 装饰器:`@login_required` 和 `@permission_required` * 4. 自定义认证系统 * 自定义用户模型 * 基于Token的认证(例如JWT)

By Ne0inhk