Pinterest 粉丝与关注列表 API:userid 该传用户名还是用户 ID?
userid,就这一个参数名,在 Pinterest 粉丝与关注接口里却不是同一种输入。把它封装成“通用用户 ID”,后面就很难分清是标识传错,还是没有可用数据。先别写导出循环,用同一个公开账号 marthastewart 把两条请求对齐。
同名 userid 参数:粉丝接口传数字 ID,关注接口传用户名
粉丝接口的 /followers 对应操作 list-user-followers,首次请求传 userid=32088353503445352;关注接口的 /followings 对应 list-user-following,传的却是 userid=marthastewart。前者是“谁关注这个账号”,后者是“这个账号关注谁”,不能交换方向,也不能互换参数值。
即使参数声明为可选,也应明确指定目标,别靠示例默认值定位账号。请求地址按 operationId 拼接,例如资料查询是:
https://api.opendata-api.com/api/project/pinterest-scraper/get-user-details?key=YOUR_API_KEY&username=marthastewart
key 是你获授权的平台密钥,不是 Pinterest 密码或会话令牌。从控制台取得后放在服务端环境变量中,日志需隐藏查询串中的密钥;不要写进前端代码或分享完整请求链接。阅读和复制示例不会发出数据请求。
从 marthastewart 查到 32088353503445352,先防止长 ID 精度丢失
用户资料查询接收 username。已观察到该账号的 data.user.id 为 32088353503445352,这是后续粉丝请求的数字 ID,不是显示名 Martha Stewart。若手里只有显示名,先用用户搜索找候选,再核对用户名和资料,不能直接认定第一个结果就是目标。
保存的成功响应中,data.user.id 是带引号的十进制字符串 "32088353503445352",不是 JSON 整数;直接保留原字符串即可,不要转成数值。它的数值大小超过 JavaScript 的安全整数范围。只有在其他响应将 ID 编码为无引号 JSON 整数时,才需从原始响应文本无损解析数字再转为字符串;先用普通 JSON.parse 或 response.json() 解析这种数值,之后再执行 String(id) 或 BigInt 都无法恢复已舍入的位数。带引号的字符串 ID 不受这一数值解析问题影响,后端、数据库和表格仍应按文本保存标识。
排查“同一个用户到了前端就变号”时,依次比对响应原文、服务端保存值和页面显示值。若原文正确、页面末尾数字却不同,先修复解析和序列化链路,而不是拿变号后的 ID 反复请求粉丝接口。若要保存诊断材料,只截取必要字段,不附带授权密钥。
下面用 Python 标准 JSON 解析保留字符串 ID;若响应包含整数标识,也不会先转换成浮点数。确认网关、业务状态及账号身份后,才把核对过的标识交给关系接口。运行环境需要 requests,并设置自己的 OPENDATA_API_KEY;示例不包含真实密钥,也不保证目标始终可查。
import json
import os
import requests
BASE = "https://api.opendata-api.com/api/project/pinterest-scraper"
KEY = os.environ["OPENDATA_API_KEY"]
def read(operation, **query):
response = requests.get(
f"{BASE}/{operation}",
params={"key": KEY, **query},
timeout=30,
)
response.raise_for_status()
envelope = json.loads(response.text)
if not isinstance(envelope, dict) or str(envelope.get("code")) != "200":
raise RuntimeError("Gateway business failure; inspect its redacted message")
payload = envelope.get("data")
if not isinstance(payload, dict) or payload.get("status") == "error":
raise RuntimeError("Operation returned an error or unusable payload")
operation_data = payload.get("data")
if isinstance(operation_data, dict) and operation_data.get("status") == "error":
raise RuntimeError("Operation returned a nested business error")
return payload
username = "marthastewart"
profile = read("get-user-details", username=username)
profile_data = profile.get("data")
user = profile_data.get("user") if isinstance(profile_data, dict) else None
if (profile.get("status") != "ok" or not isinstance(user, dict)
or user.get("username") != username):
raise RuntimeError("Profile missing or identity not confirmed")
raw_id = user.get("id")
if isinstance(raw_id, bool) or not isinstance(raw_id, (str, int)):
raise RuntimeError("Expected an exact string or integer account ID")
user_id = str(raw_id)
if not user_id.isascii() or not user_id.isdigit():
raise RuntimeError("Expected a numeric account ID")
followers = read("list-user-followers", userid=user_id)
following = read("list-user-following", userid=username)
分别展开 data.followers 与 data.followings 中的 node 资料
两份名单不是顶层用户数组。read 已校验并移除网关的 code、message、data 外层,返回内部业务对象;其中的粉丝记录在 data.followers[],关注记录在 data.followings[],每项再包一层 node。接着上面的变量读取,缺失集合或结构错误应报错,而不是当作空名单:
def nodes(reply, collection):
data = reply.get("data")
rows = data.get(collection) if isinstance(data, dict) else None
if not isinstance(rows, list):
raise RuntimeError(f"Missing {collection} collection; not an empty result")
if any(not isinstance(row, dict) or not isinstance(row.get("node"), dict)
for row in rows):
raise RuntimeError("Unexpected relationship record shape")
return [row["node"] for row in rows]
follower_nodes = nodes(followers, "followers")
following_nodes = nodes(following, "followings")
已观察到粉丝节点含 username、fullName、entityId、id,关注节点含 username 和 id;不要把资料接口的 full_name 原样套在关系节点上,也不要假设两类节点总有同样多的字段。导出时保留关系方向、目标账号、原始节点标识和采集时间,缺失属性留空,不能补成零。CSV 中的 ID 列也应按文本导入。
Instagram、TikTok 等同类应用的 API 可通过联系页面提出需求,也提供私有源码部署。
粉丝 cursor 为 null 就停;关注 bookmark 尚未验证下一页
该账号的粉丝响应返回了 10 条记录,每条 cursor 都是 null,没有观察到可用续页组合。这时保存已有记录并停止,不能自己生成游标,也不能把某位粉丝的 id 当成 node_id。
粉丝接口的续页约定是使用响应实际提供的 node_id 与 cursor,并移除 userid,包括客户端自动带上的默认值,否则 userid 会优先生效。但当前证据并未证明这组输入可用;不能仅因参数存在就声称能遍历全量粉丝。
关注响应则返回了 50 条记录及非空的集合级 data.bookmark。如需按约定续传,保持 userid=marthastewart,把收到的值原样放进 bookmark;首次请求不传它。节点上同样为 null 的 cursor 不是这里的续页入口。下一页尚未实际验证,遇到书签缺失、重复或重复节点应停止、去重并记录进度,不能把“收到了书签”写成“已完成翻页”。
HTTP 200 加 status=error 不能当成零粉丝账号
一次不存在用户名的资料查询返回 HTTP 200,但正文是 status: "error",data.user 为 {},消息说明未找到数据。这是“资料不可用”,不是“找到了一个粉丝数为零的账号”。HTTP 成功之后还要判断业务状态和非空资料,上面的代码因此会中止,而不会继续拿空 ID 查名单。
成功资料中的 follower_count=0、字段缺失,以及资料未命中,应在报表中保留为不同状态。关系响应也不能一律照搬资料格式:粉丝成功样本没有顶层 status,关注的成功标记位于 data.status。字段路径缺失或结构不符时应报错待查,不要用空数组兜底后悄悄标注“无粉丝”。
拿到 10 条粉丝记录,不等于导出了 1,867,912 位粉丝
这次资料快照显示 follower_count=1867912、following_count=321,而两份已返回名单分别只有 10 条和 50 条。总数是资料属性,行数是本次采集结果,两者不能互相替代。Pinterest 粉丝列表导出 CSV 时,应注明账号、采集时间、返回行数和续页是否验证,不能把有限记录包装成完整受众,也不能据此推断粉丝地域、真实触达或购买意愿。
公开资料与可见关系适合给跨境品牌做红人初筛、给竞品研究提供核对线索,不提供私有曝光、点击或人群画像。Pin 接口虽曾返回 25 条内容,但没有分页参数;不能因为资料里的 Pin 总数更大,就自行拼接页码宣称采全。
也不要把这里的公开用户名查询等同于 Pinterest 官方 API:官方账号接口围绕授权账号工作,不能据此假定可传任意竞品用户名。最终交付的应是一份标注覆盖范围的公开数据快照,而不是“完整粉丝库”或“全站历史内容”。