LinkedIn 登录态通道(Playwright)
通道 linkedin-session 用你自己的 LinkedIn 登录态(一个 Playwright 浏览器)读 LinkedIn 的职位搜索页和岗位详情页,是 LinkedIn 级联的第 3 位(Bright Data → Apify → session → guest)。另有旁路 linkedin-session:recommended:每天读一次“为你推荐”,不参与级联。
它只读:打开页面、读页面,不点赞、不申请、不发消息、不关注、不填任何表单。遇到登录页、验证页(checkpoint / CAPTCHA / 安全验证),立即停止、熔断、发 Telegram,绝不尝试自动通过——验证由你自己在普通浏览器里完成。
状态:代码和测试完成,但还没有用真实登录态做冒烟——需要你先运行
pnpm auth:linkedin登录一次(见“真实冒烟”)。页面选择器按 LinkedIn 登录后页面的公开结构写成,用保存的 HTML fixture 测过,还没有对着真实登录态验证过。
- 用登录态自动读 LinkedIn 违反它的用户协议;它可能限制或封停你的账号。所以默认关闭、请求量很小(见下)、只读、遇到验证立即停。要不要开由你决定;级联里其他通道(Bright Data / Apify / guest)不依赖它。
- 登录文件
.auth/linkedin.json里是会话 cookie,等同于登录状态:不提交(已 gitignore)、权限 600、不发给任何人、不放进镜像。泄露了就在 LinkedIn 的“设置 → 登录和安全 → 在哪里登录”里退出所有设备。
- 在宿主机(不是容器)的项目目录打开终端:
终端窗口 pnpm exec playwright install chromium # 只需第一次pnpm auth:linkedin - 弹出一个有界面的 Chromium 窗口,停在 LinkedIn 登录页。你自己输入账号密码、完成两步验证。看到自己的信息流后关闭这个窗口。
- 脚本在你关闭窗口时把登录态(cookie)存到
.auth/linkedin.json(目录 700、文件 600)。它不读、不存、不输入密码;窗口里输入的任何内容它都看不到,只在登录成功后保存浏览器的 cookie。 - 如果数据库可达(
DATABASE_URL),脚本还会把登录态标为“有效”,并关掉因登录失效 / 验证而打开的熔断,不用等冷却。 - 先试一轮,再打开通道:
干跑会真的打开 LinkedIn 页面(有请求),但不写数据库、不计入当天用量。输出里看
终端窗口 # data/config/sources.yaml 里先把 linkedin.session.enabled 改成 true,然后:pnpm cycle:run --channel linkedin-session --dry-runfetched / parsed。 - 没问题就留着
enabled: true。runner 在 Docker 里时,./.auth以只读方式挂进容器(compose.yaml已写好),需要docker compose up -d --build一次让镜像带上 playwright-core。
pnpm auth:linkedin --status 只看状态,不打开浏览器:文件在不在、cookie 到期时间、通道最近知道的状态。
配置(data/config/sources.yaml → linkedin.session)
Section titled “配置(data/config/sources.yaml → linkedin.session)”| 键 | 含义 |
|---|---|
enabled |
开关(默认关)。没有登录文件时通道显示“未登录”,级联直接跳过它,不算失败 |
monthly_budget_usd |
保持 0(没有付费用量) |
throttle |
两次页面加载之间随机等 3~8 秒 |
max_results |
每个查询最多读多少条(再受 search.yaml: max_per_group_per_location 限制) |
daily_request_limit |
每个自然日(schedule.yaml 时区)最多加载多少个 LinkedIn 页面(搜索页、详情页、推荐页合计,两个 session 通道共用)。默认 200。用完后通道被跳过,级联换下一个 |
max_pages |
每个查询最多翻几页搜索结果(每页约 25 条) |
fetch_details |
是否为每条新岗位打开详情页(JD、薪资、办公方式、申请人数)。库里已有的岗位不再打开:原样重新交付(保持“最近看到”时间),不产生请求 |
recommended_daily / recommended_max_results |
是否读“为你推荐”、最多读几条 |
site.base_url / site.auth_file / site.country_name / site.remote_locations |
站点、登录文件路径(相对项目目录)、城市查询的国家、远程查询的地点 |
browser.* |
headless、页面超时、渲染等待、滚动次数和停顿(列表只渲染可见卡片)、语言、不加载的资源类型、User-Agent |
data/config/schedule.yaml 的 linkedin_recommended(enabled、cron,默认每天 07:00)决定“为你推荐”何时读。它不是单独的定时任务:处理轮里,第一轮发现“今天的 cron 时间点已过、还没读过”时顺带读一次;手动运行总会读。
- 搜索:每个查询 =
search.yaml的一个关键词组 × 一个城市(或远程)。URL:/jobs/search/?keywords="A" OR "B"&location=<城市, 国家>&f_TPR=r<回看秒数>&sortBy=DD,远程加f_WT=2。打开后滚动列表让卡片全部渲染,读出每张卡的岗位 id、标题、公司、地点、发布时间;然后对库里没有的岗位打开/jobs/view/<id>/,读 JSON-LDJobPosting和页面上的薪资、办公方式、雇佣类型、申请人数、JD。 - id 与去重:岗位 id 就是 LinkedIn 的数字 id,和 Bright Data / Apify / guest 通道一致,同一个岗位被不同通道取到会合并成一个。
- 原始数据:存的是页面上读到的字段,JD 以 HTML 保存,方便以后
pnpm reprocess重新解析。不存 cookie、请求头、整页 HTML。 - 登录态状态(
/sources):未登录(没有登录文件或没有会话 cookie)、有效、已过期(cookie 到期,或通道被带到登录页 / 验证页后你还没重新登录)。重新运行pnpm auth:linkedin后自动恢复。“上次成功”时间显示在通道卡片上。 - 缺登录文件时:
isConfigured为假,级联跳过 session,下一个通道(guest)接上;“为你推荐”不运行。 - 只一个浏览会话:两个 session 通道共用一个账号、一个计数器,进程内一次只开一个浏览器。
遇到验证 / 登录页时
Section titled “遇到验证 / 登录页时”通道在第一个这样的页面上立即停止(不再加载任何页面),然后:
- 把登录态标为“已过期”(
runtime_state,/sources可见), - 抛出可熔断的错误:登录页 / 授权墙 →
auth_expired,验证页 / checkpoint / CAPTCHA / HTTP 999 / 403 →blocked,一次就开熔断(circuit_breaker.cooldown_hours), - Telegram 告警:登录失效 → “LinkedIn 登录态失效,需要重新登录”;验证页 → “LinkedIn 通道 … 遇到验证页或被拦截”(提示自己去普通浏览器里完成验证)。同一告警
alerts.dedupe_hours内只发一次, - 同一个查询和本轮后面的查询交给级联里的下一个通道(guest)。
恢复:自己在普通浏览器里登录 LinkedIn、完成它要求的验证,然后运行 pnpm auth:linkedin。HTTP 429 只算一次可重试失败(不立即熔断,连续失败才熔断)。页面结构变了(既没有岗位卡片、也不是“没有结果”页)会报 parse 错误,不会悄悄返回空。
- runner 容器里 Playwright 基础镜像带浏览器,
playwright-core在镜像里单独安装(build:runner把它标为 external)。/dev/shm很小,已加--disable-dev-shm-usage。 .auth/以只读方式挂进 runner 和 web(web 只用它判断“未登录 / 有效”,不浏览)。macOS 的 Docker Desktop 上权限 700/600 没问题;Linux 宿主机上容器用户看不到 600 的文件时,把.auth/的属主改成容器用户的 uid,不要放宽权限。- 宿主机直接跑也行:
pnpm cycle:run --channel linkedin-session。
执行 pnpm auth:linkedin 之后:
pnpm auth:linkedin --status应显示“有效”。pnpm cycle:run --channel linkedin-session --dry-run:看fetched、parsed、parseFailed、示例标题;页面选择器如果对不上,会表现为parse错误或cardOnly(详情页读不出来、只保留了列表卡片)。pnpm cycle:run --channel linkedin-session:recommended --dry-run。- 对着真实页面对一遍:把页面 HTML 脱敏后替换
tests/fixtures/linkedin-session/里的 fixture,再跑pnpm test看解析器是否仍然通过。
本页同步自产品库 Joevonlong/job-radar@2947f47 的 docs/。Job Radar 与任何招聘网站均无关联。代码采用 AGPL-3.0-only 许可。