LinkedIn 页面在浏览器里看起来相似,但不同实体的数据模型完全不同:个人资料有经历和教育,公司有行业和地点,帖子有持续变化的互动指标,职位则有雇佣类型和申请信息。
本教程使用四个 SocQ LinkedIn API 搭建可维护的公开数据流程,覆盖个人资料、公司、帖子和职位,并说明 URL 预处理、异步任务、标准化记录、存储、校验、刷新频率与合规边界。
**快速答案:**把公开 LinkedIn URL 发送到匹配的
POST /v1/linkedin/{resource}端点,保存返回的任务 ID,轮询至完成,再校验并 upsert 标准化记录。不要把个人资料 URL 发到职位端点,也不要把不可见字段当成空值或零。
官方 LinkedIn API 还是公开数据 API?
成员授权应用、已获得合作伙伴产品权限,或需要发布等许可型操作时,应使用 LinkedIn 官方 API。官方接入使用 OAuth,许多权限和合作伙伴计划需要明确审批;Profile 结果还受成员隐私设置与合同存储规则约束。
本文使用的 SocQ 端点接受已知公开 URL,并执行只读采集。它们不会以 LinkedIn 成员身份登录、不会搜索整个 LinkedIn、不能发布内容,也不会自动授予返回数据的使用权。应先决定访问模式:授权账号操作选择官方 API,范围明确的公开读取再评估公开数据 API。
选择正确的 LinkedIn 端点
SocQ 当前提供四个基于 URL 的读取端点:
| 资源 | 端点 | 接受的公开 URL | 常见用途 |
|---|---|---|---|
| 个人资料 | /v1/linkedin/profiles | /in/{slug} | 身份、职位、经历、教育 |
| 公司 | /v1/linkedin/companies | /company/{slug} | 公司信息、网站、地点、可见受众 |
| 帖子 | /v1/linkedin/posts | /posts/、/pulse/、支持的 /feed/update/ | 内容与互动快照 |
| 职位 | /v1/linkedin/jobs | /jobs/view/{slug} | 职位、地点、雇佣与申请字段 |
这些都是公开读取操作。需要成员授权、发布、广告或其他许可型工作流时,应使用 LinkedIn 官方产品。
规范化并路由输入 URL
保留原始提交 URL 作为数据来源,同时对副本做规范化:
- 只接受 HTTPS 和 LinkedIn 域名。
- 删除跟踪查询参数。
- 在安全的前提下统一地区和移动端 host。
- 按路径识别实体类型。
- 在批次内按规范 URL 去重。
- 请求前拒绝不支持的路径。
简单路由器可将 /in/ 分给个人资料、/company/ 分给公司、已知帖子格式分给帖子、/jobs/view/ 分给职位。无法确定的 URL 应进入人工检查队列,而不是猜测。
提交个人资料采集任务
curl -X POST "https://api.socq.ai/v1/linkedin/profiles" \
-H "Authorization: Bearer $SOCQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"urls":["https://www.linkedin.com/in/satyanadella"]}'
其他资源使用相同请求结构:
curl -X POST "https://api.socq.ai/v1/linkedin/companies" \
-H "Authorization: Bearer $SOCQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"urls":["https://www.linkedin.com/company/microsoft/"]}'
curl -X POST "https://api.socq.ai/v1/linkedin/jobs" \
-H "Authorization: Bearer $SOCQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"urls":["https://www.linkedin.com/jobs/view/software-engineer-at-microsoft-4429527118"]}'
响应会创建异步任务。把任务 ID 与内部批次 ID、输入 URL 一起持久化。
轮询任务且避免重复提交
curl "https://api.socq.ai/v1/tasks/$TASK_ID?limit=100" \
-H "Authorization: Bearer $SOCQ_API_KEY"
queued 和 running 都是正常状态。使用指数退避并设置最大间隔。任务成功后处理 data.results.items;若 has_more 为 true,使用 next_cursor 继续分页。
任务表至少保存:
- 内部批次 ID 与 SocQ 任务 ID;
- 资源类型和规范化输入 URL;
- 提交、最后轮询和完成时间;
- 终态和标准化错误类别;
- 最后已提交的结果 cursor。
Worker 重启不代表需要重新提交任务,应优先从持久化状态恢复。
理解四种数据结构
所有记录共享 id、platform、resource、type、url、created_at、collected_at 和 extra 等标准字段,同时保留资源专用字段。
个人资料包括 name、description、avatar_url、可见粉丝和连接数,以及 extra 中可能存在的职位、公司、地点、经历、教育和技能。
公司记录增加 website、logo、可见粉丝和员工数,以及行业、规模、总部、成立年份、地点与专长。
帖子包括 text、标准化 author、可见点赞/评论/转发指标、媒体、发布时间,以及帖子类型和标签等扩展字段。
职位使用 name 表示职位名称、text 表示描述、author 表示招聘公司,extra 包含地点、级别、雇佣类型、行业、申请 URL、Easy Apply 和公开薪资。
缺失数据必须保留为 null 或不存在。不可见粉丝数不是零,未知薪资不代表无薪,缺少发布时间也不能用采集时间代替。
混合 URL 的 Python Worker
import time
import requests
BASE = "https://api.socq.ai/v1"
HEADERS = {
"Authorization": f"Bearer {SOCQ_API_KEY}",
"Content-Type": "application/json",
}
def collect(resource, urls):
created = requests.post(
f"{BASE}/linkedin/{resource}",
headers=HEADERS,
json={"urls": urls},
timeout=30,
)
created.raise_for_status()
task_id = created.json()["data"]["task_id"]
delay = 2
while True:
response = requests.get(
f"{BASE}/tasks/{task_id}",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
task = response.json()["data"]
if task["status"] == "succeeded":
return task["results"]["items"]
if task["status"] in {"failed", "cancelled"}:
raise RuntimeError(task)
time.sleep(delay)
delay = min(delay * 1.5, 15)
profiles = collect("profiles", [
"https://www.linkedin.com/in/satyanadella"
])
生产环境还需只对瞬时错误重试、轮询前持久化任务,并完整处理分页。
分离实体与快照
个人和公司变化较慢,帖子指标和职位可用状态变化更快。建议分成两层:
linkedin_entity
platform_id
resource
canonical_url
stable_public_fields
first_seen_at
last_seen_at
linkedin_snapshot
platform_id
collected_at
visible_metrics
mutable_public_fields
source_task_id
存在 ID 时使用 resource + id upsert;没有 ID 时暂用规范 URL,并为以后发现 ID 保留合并路径。只有采集成功且通过 schema 校验后才追加快照。
职位应保存 first_seen_at、last_seen_at 和本地推断的可用状态。一次刷新失败不能证明职位已经关闭。
入库前校验
至少检查:
platform必须为linkedin;resource必须与调用端点一致;- 返回 URL 符合预期的公开 LinkedIn 路径;
- 不兼容的资源类型之间不能发生 ID/URL 冲突;
- 可见计数存在时必须非负;
created_at与collected_at能解析为时间;- 数组和嵌套对象类型正确;
- 每条记录都能追溯到输入和任务。
错误记录应隔离,而不是静默强制转换。保留原始响应、校验错误、供应商版本和采集时间,才能有效诊断 schema 漂移。
按实体设置刷新频率
不要对所有实体使用同一频率:
| 实体 | 合理起点 | 原因 |
|---|---|---|
| 个人资料 | 每周或每月 | 公开职业字段变化较慢 |
| 公司 | 每周或每月 | 公司信息通常不是实时数据 |
| 帖子 | 活跃期每小时至每天 | 发布后互动快速变化 |
| 职位 | 开放期每天 | 可用性和申请信息会变化 |
结合业务必要性、同意、合同限制与数据最小化原则,减少不必要采集。
负责任地使用 LinkedIn 数据
只采集合法目的所需的公开字段。不要绕过登录、私密资料、访问控制、速率限制或技术限制,也不要在缺少适当保障时根据敏感推断对个人作出重大自动化决定。
LinkedIn 数据可能属于个人信息。应在需要时建立合法依据,制定保留和删除流程,保护 API key 与存储记录,响应适用的数据权利,并由合格法律顾问审查 LinkedIn 当前条款和当地法律。
常见故障
端点没有返回记录
确认 URL 公开、规范、仍然可用且分配到了正确端点。保留输入级错误,不要生成空记录。
某个字段消失了
公开可见性和页面渲染会改变。保持字段 nullable,并先比较采集时间再判断解析器故障。
同一个人出现两次
统一 URL 并优先使用稳定公开 ID。维护 alias 表,不要在没有证据时直接删除一条记录。
指标与浏览器不一致
计数是采集时快照,可能被取整、隐藏、缓存或在请求间更新。比较采集时间和字段定义。
常见问题
抓取 LinkedIn 合法吗?
没有适用于所有场景的答案,取决于司法辖区、数据、访问方式、目的、合同和保障措施。本文不构成法律意见。
API 可以搜索全部 LinkedIn 个人资料或职位吗?
当前四个 SocQ 端点基于已知 URL 采集,并不承诺穷举 LinkedIn 搜索。
可以抓取私密资料吗?
不可以,也不应绕过隐私和访问控制。
应该保存原始响应吗?
应该,但需配合安全与保留策略。原始响应有助于审计转换和诊断 schema 变化。
去重应该使用什么 key?
优先使用 resource + id;没有公开 ID 时回退到规范化 URL。