ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

OpenCLI LinkedIn Adapter 实战指南:用已登录浏览器把 LinkedIn 变成可编程 CLI

OpenCLI LinkedIn Adapter 实战指南:用已登录浏览器把 LinkedIn 变成可编程 CLI OpenCLI LinkedIn Adapter 实战指南用已登录浏览器把 LinkedIn 变成可编程 CLI【免费下载链接】OpenCLIMake Any Website into CLI Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLILinkedIn 是全站强登录、强反爬的封闭站点OpenCLI 的linkedin适配器通过复用你已登录的 Chrome 浏览器会话把个人资料读取、职位搜索、收件箱、Sales Navigator 乃至发连接请求/发私信这类写操作全部封装成可编排、可验证、可脚本化的 CLI 命令。读完本文你将掌握该适配器 20 条命令的完整用法、输出字段语义、fail-closed失败即关闭安全模型以及它在源码层面如何绕过虚拟化 DOM、重放页面自身的 API 请求来稳定取数。前置条件浏览器已登录 Browser Bridgelinkedin适配器属于Browser 模式文档头部标注 Browser · Domain: linkedin.com它不维护独立的账号体系而是直接操作你本机的浏览器会话。因此使用前必须满足两条前提见 linkedin.md 末尾Chrome 正在运行且已登录 linkedin.com——会话状态以 cookie 形式存在命令运行期间浏览器不能关闭已安装 Browser Bridge 扩展——它是 OpenCLI 与浏览器页面之间进行导航、求值、输入注入的桥梁安装与使用方式见 browser-bridge 指南。从源码看适配器对登录态的依赖是显式且强制的clis/linkedin/shared.js中的requireLinkedInCookie()会通过 CDP 读取https://www.linkedin.com域名下的JSESSIONIDcookie读取不到就直接抛出AuthRequiredErrorassertLinkedInAuthenticated()则会在页面内注入一段探针脚本shared.js通过正则匹配 URL、标题和正文中的login|checkpoint|authwall|sign in|join linkedin|captcha|verification required以及中文的请登录/登录领英/安全验证等特征来判断是否撞上登录墙。# 快速验证适配器可用性需要有登录态浏览器 opencli linkedin search --limit 5命令全景21 条命令的职责划分适配器在 linkedin.md 中登记了 21 条命令按能力可划分为五大类类别命令一句话职责职位检索search通过 Voyager API 搜索职位支持--details详情增强job-detail读取单个职位页描述、申请链接、办公形式、申请人数、公司元数据jobs-preferences只读当前职位偏好与提醒设置人脉与资料people-search标准 LinkedIn 人脉搜索SSR DOM 抓取消耗 CUL 配额profile-read读取资料各区块头条、About、经历、教育、服务、精选profile-analytics读取资料后台计数浏览量、动态曝光、搜索出现次数profile-experience读取经历条目职位、日期、地点、技能、媒体profile-projects读取项目条目services-read读取 Services 服务页posts导出资料动态页的可见帖子及互动指标post-analytics汇总原始互动计数器timeline读取首页信息流帖子消息与安全写操作inbox列出私信会话及未读状态thread-snapshot加载线程并返回可用上下文快照safe-send校验线程/收件人/最新消息后安全发送私信connect校验资料后 fail-closed 发送连接请求sent-invitations列出已发送待处理邀请CRM 对账Sales Navigatorsalesnav-search按关键词搜索销售线索salesnav-inbox列出 Sales Navigator 会话API 分页salesnav-thread返回线程历史salesnav-message校验后发送 InMail其中最值得注意的架构特征是读命令统一走Strategy.COOKIE策略、写命令统一走Strategy.UI策略且默认 dry-run--send才真正点击发送写操作默认不落盘是全适配器的安全基线。职位搜索searchVoyager API 直连 详情增强search是使用频率最高的命令它不抓取搜索结果 DOM而是直接请求 LinkedIn 的 Voyager 内部 API。实现位于 search.js。参数与筛选# 基础搜索默认 limit10 opencli linkedin search site reliability engineer # 组合筛选地点 远程 opencli linkedin search data scientist --location San Francisco Bay Area --remote remote # 公司名/公司 ID 过滤支持逗号分隔 opencli linkedin search backend --company Google,Amazon # 经验等级、职位类型、发布时间筛选 opencli linkedin search product manager --experience-level mid-senior --job-type full-time --date-posted week命令在 search.js 注册了以下参数参数类型默认值说明querystring必填位置参数职位关键词locationstring无地点文本如San Francisco Bay Arealimitint10返回职位数范围 1–100startint0分页偏移非负整数detailsboolfalse是否附带完整描述与申请链接更慢companystring无逗号分隔的公司名或 LinkedIn 公司 IDexperience-levelstring无internship/entry/associate/mid-senior/director/executivejob-typestring无full-time/part-time/contract/temporary/volunteer/internship/otherdate-postedstring无any/month/week/24hremotestring无on-site/hybrid/remote从源码可以确认几处实现细节筛选值会做别名归一EXPERIENCE_LEVELS映射中entry与entry-level都对应2mid/senior/mid-senior均对应4JOB_TYPES里full-time/fulltime/full都是FREMOTE_TYPES中onsite与on-site同为1、hybrid为3、remote为2search.js。映射不存在的值会直接抛出ArgumentError不会静默吞掉。公司名通过 DOM 交互解析成 ID如果--company传入的是公司名而非数字 ID命令会先在职位搜索页打开 All filters 面板在 Add a company 输入框逐个输入公司名、等待联想结果、读取input[namecompany-filter-value]的 value 作为公司 ID解析失败的公司名会以ArgumentError报错search.js。Voyager URL 构造请求路径为/voyager/api/voyagerJobsDashJobCardsquery中keywords、locationUnion、selectedFilterscompany/experience/jobType/timePostedRange/workplaceType的List(...)组合均由buildVoyagerSearchQuery()拼装并携带csrf-token取自 JSESSIONID与x-restli-protocol-version: 2.0.0请求头search.js。API 返回 401/403 时抛出AuthRequiredError其他非 2xx 抛出CommandExecutionError。输出字段与--details的失败可见性基础模式每行固定返回rank · title · company · location · listed · salary · urllisted取自卡片footerItems中type LISTED_DATE的timeAt格式化为YYYY-MM-DDurl从jobPostingUrn/entityUrn中提取数字 ID 拼成/jobs/view/id。开启--details后每个职位额外返回三列这是文档明确强调的一个设计改进列类型语义descriptionstring | nullAbout the job 全文上游无内容或抓取失败时为nullapply_urlstring | null页面上第一个apply标签链接无或失败时为nulldetail_errorstring | null成功为null失败为no url该行无 jobId、fetch failed: message导航/解析抛错或missing description页面加载成功但正文为空这一设计的背景写在 search.js 的注释中旧实现里没有 URL和静默捕获两条路径都返回description: , apply_url: 调用方无法区分上游本来就没内容与抓取失败。现在用null 类型化原因把失败显性化同时单行失败不会中断整批结果——--details逐行导航详情页部分失败是可预期状态每个失败都会以[opencli:linkedin]前缀打到 stderr 便于排查。# 带详情增强每行多一次页面导航速度更慢 opencli linkedin search data scientist --limit 3 --details详情页抓取逻辑enrichJobDetails()对每一行执行page.goto(job.url)→ 登录态探针 → 等待 About the job 文本8 秒超时→ 若存在 Show more 按钮先点击展开 → 用最短含标题容器策略提取正文因为最短容器往往是最具体、不含外部包裹文本的节点见 search.js→ 通过decodeLinkedinRedirect()解开/redir/redirect/跳转还原真实申请链接。这里有一个关键边界--limit必须介于 1–100、--start必须为非负整数登录/认证墙以AuthRequiredError直接中止而不会折进detail_error。职位偏好与详情jobs-preferences与job-detail# 读取职位偏好与提醒JSON 输出 opencli linkedin jobs-preferences -f json # 读取单个职位详情 opencli linkedin job-detail https://www.linkedin.com/jobs/view/4412279099 -f jsonjobs-preferences打开 LinkedIn Jobs 页面返回open_to_work、job_titles、locations、job_alerts、preferences_url、alerts_url、raw_preferences。它只读取可见设置绝不修改任何偏好或提醒——从命令声明access: read即可印证。job-detail接受https://www.linkedin.com/jobs/view/id形式的 URL返回title、company、location、workplace_type、job_type、applicants、listed、apply_url、company_url、url、description。它会将 URL 归一化为 LinkedIn 登录后的职位详情形态并把内联元数据与渲染后的描述文本合并输出。人脉搜索people-searchSSR DOM 切片与 CUL 配额people-search是适配器中技术最刁钻的一条命令源码头注释直接点明了原因Voyager people-search REST 在 web 上下文会返回 HTTP 500LinkedIn 现在改为服务端渲染结果每次导航消耗一次 CUL 查询people-search.js。# 搜索人脉消耗 1 次 CUL 搜索配额 opencli linkedin people-search site reliability engineer berlin --limit 5返回字段rank、name、headline、location、profile_url。其中profile_url是行身份标识必须归一到稳定的/in/handle/形式——提取出的载荷若不含可解析的行身份缺 name 或缺稳定 profile URL命令会以CommandExecutionError类型化失败而不是返回空结果掩盖问题。--limit必须介于 1–10登录/认证墙 →AuthRequiredErrorCommercial Use Limit商业使用限额重定向→CommandExecutionError因为此时页面已不再包含可信结果列表。源码中当页面被重定向离开/search/results/people/时会提示Likely Commercial Use Limit reached - the limit resets on the 1st of next monthpeople-search.js并在命令描述中提醒使用者自行节流。为什么不用类名选择器extractionScript()的注释揭示了两个反直觉的事实LinkedIn 每次部署都会轮换 hashed 类名写死的 class 选择器必死同时display:contents会把 DOM 树拍平卡片级容器根本不存在。因此实现改为先收集main下所有/in/handle/锚点作为候选人再对main.innerText按行切分用姓名行定位 相邻行切片推断 headline 与 location并辅以collapseRepeatedName()去除 LinkedIn 渲染时重复拼接的姓名people-search.js。如果页面存在候选人却解析不出稳定行同样抛类型化错误而非假装空结果。资料读取五件套profile-read/profile-analytics/profile-experience/profile-projects/services-read这组命令默认打开/in/me/即自己的资料也可用--profile-url https://www.linkedin.com/in/handle/指向他人资料。opencli linkedin profile-read -f json opencli linkedin profile-analytics -f json opencli linkedin services-read -f json opencli linkedin profile-experience -f json opencli linkedin profile-projects -f json命令关键输出字段备注profile-readprofile_url, name, headline, location, about, about_character_count, about_skills, experience, education, services, featured读取可见区块认证墙类型化失败。about_character_count如123/2,600字符计数与about_skills仅在默认自我资料流程中通过打开 About 编辑表单获得读取他人资料时不会填充profile-analyticsprofile_views, post_impressions, search_appearances, followers, connections, raw_analytics资料后台仪表板可见计数器profile-experiencerank, total_count, title, employment_type, company, date_range, start_date, end_date, location, location_type, description, skills, media, urls, skill_url, media_url, profile_url, raw_text解析到 Experience 详情页若本人资料没有可见 Experience 区块返回EmptyResultError而非占位行profile-projectsrank, title, date_range, associated_with, description, skills, media, urls, profile_url, raw_text同样在无可见 Projects 区块时返回EmptyResultErrorservices-readservice_url, page_title, overview, availability, work_locations, pricing, services_provided, services_count, media_count, media, messages, reviews_visibility接受--services-url或带 Services 链接的资料 URLowner-only 的编辑/媒体字段仅在自我资料流程填充从 profile-read.js 可以看到资料提取的核心手段按section h2/h3标题定位区块readSection(/^About$/i)、/^Experience$/i等取heading.closest(section)的 innerText再剥离标题、Show all、See more 等噪声文本姓名优先取main h1/h2并用compact()去除 LinkedIn 常出现的文本重复拼接现象compactRepeatedText同样实现在 shared.js当文本前后两半完全相同时只保留前半。动态导出与汇总posts/post-analytics/timeline# 导出自己资料动态页的可见帖子默认打开 /in/me/recent-activity/all/ opencli linkedin posts --limit 5 -f json # 汇总原始可见计数器 opencli linkedin post-analytics --limit 5 -f json # 读取首页信息流 opencli linkedin timeline --limit 5posts返回rank, author, posted_at, body, reactions, comments, reposts, impressions, media, media_urls, url, raw_text。media_urls只包含 LinkedIn 在渲染卡片中暴露的非装饰性媒体或外链 URL——头像、点赞图标等装饰元素会被过滤。--limit范围 1–100登录墙抛AuthRequiredError。可用--profile-url https://www.linkedin.com/in/handle/指向他人动态页源码中activityUrl()会把/in/handle/拼成/recent-activity/all/见 posts-core.js。指标解析的巧妙之处在 posts-core.js 的parseMetric()它把1.2k、3.4K、1.5M这类缩写统一换算成数字k×1000、m×1000000并兼容1,234 reactions、X and N others等多种文案形态。post-analytics只做原始汇总不做任何自定义评分、话题标签、格式标签或推荐——这保证了指标的口径中立可审计。返回posts_analyzed, total_reactions, total_comments, total_reposts, total_impressions, posts_with_media, posts_with_urls, latest_posted_at, latest_reactions, latest_comments, latest_reposts, latest_impressions, latest_url--limit同为 1–100。消息体系inbox重放页面自身 API 请求inbox是另一个体现适配器设计哲学的命令——不抓取虚拟化 DOM而是重放页面自己发出的网络请求。源码注释inbox.js说明LinkedIn 消息 UI 是实时、虚拟化的 SPA直接刮取会话列表非常脆弱懒渲染、列表虚拟化、标记频繁变更。于是命令让页面像真人一样加载/messaging/页面自然触发自己的messengerConversationsGraphQL 请求命令再通过Performance API从已加载资源中捞出这个请求的 URLperformance.getEntriesByType(resource)这样旋转的queryId永远是当前值用会话的 csrf token 原样重放然后解析 LinkedIn 归一化 JSON。# 列出最近会话默认 40 条范围 1–100 opencli linkedin inbox --limit 20 -f json # 只看未读 opencli linkedin inbox --unread-only -f json返回字段rank, thread_url, thread_id, person_name, last_message_preview, unread, timestamp实际还附带了counterparty_type与category列见 inbox.js。解析层parseConversations()处理的是 LinkedIn 归一化 JSON 的included扁平实体数组通过 URN 建立索引识别com.linkedin.messenger.Conversation类型实体解析参与者成员/组织/Agent 三类participantType剔除邮箱拥有者hostIdentityUrn mailboxUrn按lastActivityAt倒序排列inbox.js。消息预览截断到 300 字符登录墙、畸形载荷均类型化失败空收件箱则返回空结果。# 先对线程做快照供调用方做收件人安全校验 opencli linkedin thread-snapshot --thread-url https://www.linkedin.com/messaging/thread/abc/ -f jsonthread-snapshot打开精确线程 URL导航前先校验--max-scrolls滚动加载可用历史返回适合调用方做收件人安全检查的 JSON 快照。写操作的安全模型connect与safe-send的 fail-closed 设计这两条命令是适配器中最值得学习的工程实践默认 dry-run只有显式传入--send才会真正点击写操作且点击前要经过多道校验任何一项不匹配都以类型化错误中止fail-closed宁可失败也不发错。connect先验证资料再发连接请求# 仅验证dry-run确认落点资料与可见姓名匹配 opencli linkedin connect https://www.linkedin.com/in/example/ --expected-name Jane Doe --note quick note # 验证通过后真正发送 opencli linkedin connect https://www.linkedin.com/in/example/ --expected-name Jane Doe --note quick note --send要求提供精确的https://www.linkedin.com/in/profile/URL 与--expected-name。发送前connect.js 的assessProfileSafety()依次校验登录墙 →auth_required抛AuthRequiredError页面未解析到可见姓名 →profile_name_not_found归一化姓名与--expected-name不一致 →profile_name_mismatch落点 URL 与期望 URL 不一致 →profile_url_mismatch已是一度连接 →already_connected已发过请求待处理 →connection_pending顶栏存在 Connect 按钮 →connectable若 Connect 藏在 More 菜单里moreAvailable→verified_via_more都找不到 →connect_button_not_found。姓名匹配使用归一化比较 token 集合匹配normalizeName()会剥离学位后缀• 1st degree connection、P.Eng.、CPA、MBA、Ph.D.与标点要求期望 token 命中 ≥2 个且命中率 ≥80%connect.js。--note限制300 字符以内超出抛ArgumentError。发送路径有两条若 Connect 是a锚点指向/preload/custom-invite/?vanityName...直接导航到邀请路由打开对话框若 Connect 是button则注入buildOpenConnectDialogScript()在页面内点击——源码注释特别强调了一个陷阱资料页侧栏People also viewed也渲染其他人的 Connect 按钮绝不能点错因此每次点击都通过姓名 token 限定在资料 owner 的操作区内connect.js。发完之后还有投递验证等待数秒后访问/mynetwork/invitation-manager/sent/用buildSentInvitationsProbeScript()按姓名或 URL 在已发送列表中找到这条邀请返回status: sent_verified重试 3 次仍找不到则返回send_unverified含delivery_verified: false与匹配到的姓名/URL 字段绝不假装成功。safe-send先验证线程再填消息发送# 快照线程后仅当精确收件人/线程上下文仍匹配时才发送 opencli linkedin thread-snapshot --thread-url https://www.linkedin.com/messaging/thread/abc/ -f json opencli linkedin safe-send --thread-url https://www.linkedin.com/messaging/thread/abc/ --expected-name Jane Doe --message thanks --sendsafe-send参数--thread-url精确线程 URL、--expected-name期望收件人、--message消息正文可选--expected-last-text期望的当前可见最新消息子串与--expected-last-hash最新消息文本的 SHA-256 哈希。线程 URL 必须匹配/messaging/thread/id/形态且只能指向 linkedin.com 主机canonicalizeLinkedInThreadUrlshared.js。assessThreadSafety()safe-send.js的校验链包括认证墙、搜索失败可见文本、线程 URL 匹配、收件人头部名称匹配、composer 输入框是否存在、以及可选的expectedLastHash/expectedLastText最新消息守卫。dry-run 模式默认返回status: verified_dry_run。真正发送时聚焦 composer 并清空buildFocusComposerScript通过InputEvent(input, ...)触发 React 的受控组件更新page.insertText(message)注入文本后回读 composer 内容做精确比对不一致立即中止composer_text_mismatch填完后再次重跑线程安全校验afterFillSafety防止填写期间上下文漂移找到可见的 Send 按钮点击send_button_not_found/send_button_disabled时中止可选--screenshot在验证阶段截图留证。sent-invitationsCRM 对账# 对账已发送的待处理邀请 opencli linkedin sent-invitations -f json读取待处理邀请页面返回rank, name, profile_url, invited_date_text供外部 CRM 系统做邀请对账。Sales Navigator 系列搜索、收件箱、线程与 InMailSales Navigator 命令同样复用重放页面自身 API的思路但请求的是sales-api/salesApiLeadSearch等 Sales 端点。# 搜索销售线索默认 25 条范围 1–500 opencli linkedin salesnav-search quality manager food manufacturing --limit 10 -f json # 列出 Sales Navigator 会话 opencli linkedin salesnav-inbox --limit 20 -f json # 查看某线索的线程历史 opencli linkedin salesnav-thread https://www.linkedin.com/sales/inbox/2-thread -f json # 先 dry-run 校验 InMail校验通过后再加 --send opencli linkedin salesnav-message urn:li:fs_salesProfile:(PROFILE,NAME_SEARCH,TOKEN) --subject Quick question --body Hellosalesnav-search返回rank, name, title, company, location, degree, profile_url, lead_url, recipient_urn。recipient_urn是后续发 InMail 的寻址凭据缺失线索身份或载荷畸变时类型化失败绝不输出不可寻址的行。API URL 使用版本化装饰com.linkedin.sales.deco.desktop.searchv2.LeadSearchResult-14源码注释提醒LinkedIn 重新部署时可能变更响应结构需要从真实的/sales/search/people请求刷新salesnav-search.js。salesnav-inbox/salesnav-thread使用 Sales Navigator 消息 API 而非虚拟化 DOM 行。要求已登录且具备 Sales Navigator 权限的会话认证/API 失败与畸形线程载荷类型化失败合法的空收件箱/空线程则如实返回空结果。salesnav-message是写命令但默认 dry-run先解析收件人到 Sales Navigator 线索身份检查资料/信用状态只有--send才发送发送后会打开线索页验证已发送活动记录。工程要点小结这套适配器值得借鉴的设计读走 cookie 策略、写走 UI 策略且默认 dry-run——最大程度降低误操作风险写命令把验证和执行显式拆成两个阶段--send参数。能重放 API 就不刮 DOM——inbox用 Performance API 捞起页面自己的messengerConversations请求并原样重放天然免疫 queryId 旋转与虚拟化列表salesnav-*同理。只有无 API 可用的people-search才退回 SSR DOM 文本切片并用候选人锚点 行定位替代脆弱的 class 选择器。失败必须类型化且可区分——detail_error区分no url/fetch failed/missing description行级失败不中断整批登录墙用AuthRequiredError与业务错误严格分层。URL 一律白名单归一——canonicalizeLinkedInProfileUrl/canonicalizeLinkedInThreadUrl/assertSafeLinkedinUrl强制 https、无凭据/端口、仅限linkedin.com域含国家子域归一为www.从源头杜绝跳转注入。每处写操作后都有投递验证——connect回访 sent 列表核对邀请salesnav-message回访线索页核对活动记录safe-send发送前回读 composer 精确比对。输出与排查约定所有命令均支持-f json输出结构化结果便于 Agent 或脚本直接消费登录墙统一表现为AuthRequiredError错误信息形如... requires an active signed-in LinkedIn browser session.此时应在浏览器中重新登录 LinkedIn 后重试详情抓取等逐行失败会以[opencli:linkedin]前缀写入 stderr排查时关注该前缀日志即可people-search遇到 CUL 重定向会明确提示配额用尽及重置时间次月 1 日使用时需自行节流。整套命令的权威参数与输出定义以 linkedin.md 为准全部实现与单元测试位于 clis/linkedin 目录每个命令均配有同名.test.js如 search.test.js、connect.test.js、inbox.test.js可从中看到各解析函数的边界用例。如需把 LinkedIn 的读写能力编排进更复杂的自动化流程可进一步参考 docs/guide 下的浏览器桥接与扩展指南。【免费下载链接】OpenCLIMake Any Website into CLI Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表