Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

linkedin-candidate-search

v1.0.0

自动使用 Chrome 调试在 LinkedIn 搜索指定职位和地点的候选人,提取并基于云经验等硬性条件智能筛选,避免重复扫描。

0· 34· 1 versions· 0 current· 0 all-time· Updated 7h ago· MIT-0

/linkedin-search — LinkedIn 候选人搜索

根据用户的搜索要求,使用 Chrome DevTools MCP 自动搜索 LinkedIn 候选人,保存档案到 ./linkedin-save/{role}/ 目录,防止重复扫描。


参数分层

用户的自然语言查询条件必须明确分为两层,在 PRE-CHECK 通过后、开始搜索前完成拆解

第一层:搜索参数(硬嵌入搜索 query)

这两类条件直接决定 Google/LinkedIn 的搜索字符串,不满足则无法检索

参数说明示例
职位关键词必须出现在 query 中presales solution architect
地点城市或国家,必须出现在 query 中hanoi / vietnam

构建搜索 query 时直接拼入:

site:linkedin.com/in/ ("presales" OR "pre-sales") "cloud" "hanoi"

第二层:LLM 分析参数(获取 profile 内容后由 LLM 推断)

这类条件无法在搜索阶段过滤,必须等拿到 profile 文本后,由 LLM 阅读并推断:

参数硬/软推断方式
云经验年限(≥5年)硬性累加各段 cloud 相关工作经历时长
总工作经验(≤15年)硬性推算首份工作起始年份 → 至今年数
售前经历明确硬性职位名含 presales/pre-sales/solution consultant 或 bio 有描述
公有云经验软性工作经历提及 AWS/Azure/GCP/OCI/VNG/Viettel Cloud 等
私有云经验软性工作经历提及 OpenStack/VMware/私有化部署/on-premise cloud

LLM 推断规则:

  • 时长不足以判断时,标注 unknown,match_level 降为 B 或 C
  • 所有硬性条件均满足 → A;部分满足或有一项 unknown → B;仅 URL,内容极少 → C

运行时环境要求

执行本 skill 前,必须满足以下所有条件。在正式开始搜索前,依次完成 PRE-CHECK 检查;任一条件不满足时,立即输出修复提示并停止执行。

条件一:Chrome 调试会话已启动

Chrome 必须以远程调试模式运行在 端口 9222,且使用带有 LinkedIn 登录态的用户配置文件。

验证方法: 调用 mcp__chrome-devtools__list_pages,若返回页面列表则通过。

不满足时的提示:

❌ Chrome 调试会话未就绪

请在终端执行以下步骤:

1. 关闭当前所有 Chrome 窗口(或强制退出):
   pkill -9 -f "Google Chrome"

2. 复制登录态配置文件(首次使用时执行):
   mkdir -p /tmp/chrome_debug_profile
   cp -r ~/Library/Application\ Support/Google/Chrome/Default/{Cookies,"Login Data","Web Data",Preferences,"Local State"} \
     /tmp/chrome_debug_profile/ 2>/dev/null

3. 以调试模式启动 Chrome:
   nohup "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
     --remote-debugging-port=9222 \
     --user-data-dir="/tmp/chrome_debug_profile" \
     --no-first-run --disable-sync \
     > /tmp/chrome_debug.log 2>&1 &

4. 等待 3 秒后重新执行 /linkedin-search

条件二:LinkedIn 已登录

浏览器中必须有已登录 LinkedIn 的会话(用于访问个人档案和内部搜索)。

验证方法: 导航至 https://www.linkedin.com/feed/,执行脚本检查:

() => document.querySelector('.global-nav__me-photo') !== null ||
      document.querySelector('[data-control-name="nav.homepage"]') !== null ||
      !document.URL.includes('/login')

返回 true 则通过。

不满足时的提示:

❌ LinkedIn 未登录

请在 Chrome 浏览器中手动登录 LinkedIn:
1. 在已打开的 Chrome 窗口中访问 https://www.linkedin.com
2. 完成登录(账号/密码或验证码)
3. 确认进入 LinkedIn 主页 feed 后,重新执行 /linkedin-search

条件三:linkedin-save 目录存在

./linkedin-save/ 目录必须存在于当前工作目录下。

验证方法:

ls ./linkedin-save/ 2>/dev/null || echo "MISSING"

不满足时的提示:

❌ linkedin-save 目录不存在

正在自动创建...(执行 mkdir -p ./linkedin-save)

此条件由 skill 自动修复,无需用户干预。

条件四:当前工作目录正确

Skill 在 /Users/junye/project/test-case/linkedin-zp 下执行,档案相对路径才正确。

验证方法:

pwd

不满足时的提示:

❌ 当前工作目录不匹配

当前目录:{实际pwd结果}
期望目录:/Users/junye/project/test-case/linkedin-zp

请在正确目录下重新打开会话,或确认 linkedin-save 目录的实际位置。

PRE-CHECK 执行顺序

[1] 检查 Chrome 调试会话     → 失败则输出提示,停止
[2] 检查 LinkedIn 登录态     → 失败则输出提示,停止
[3] 检查/创建 linkedin-save  → 自动修复
[4] 检查当前工作目录         → 不匹配则警告(不强制停止)
[5] 拆解用户查询 → 分层输出第一层/第二层参数
[6] 读取已扫描 URL 集合      → 正常继续
✅ 全部通过 → 开始搜索

[5] 拆解输出示例:

🔍 搜索参数拆解结果:

【第一层 — 搜索 Query(硬嵌入)】
  职位关键词:presales solution architect / pre-sales / solution consultant
  地点:hanoi, vietnam

【第二层 — LLM 分析条件(profile 获取后推断)】
  硬性:云经验 ≥ 5年 / 总经验 ≤ 15年 / 售前明确
  软性:公有云经验优先 / 私有云经验加分

目标候选人数:20人
保存目录:./linkedin-save/presales-architect/

执行步骤

执行前必须完成上方 PRE-CHECK(含参数分层),全部通过后再执行以下步骤。

STEP 0 — 确定保存目录,读取已扫描 URL(防重复)

根据职位关键词确定子目录:

职位类型目录
售前架构师 / Presales Architect./linkedin-save/presales-architect/
解决方案架构师 / Solution Architect./linkedin-save/solution-architect/
云销售 / Cloud Sales./linkedin-save/cloud-sales/
其他./linkedin-save/general/

读取已扫描 URL(用于后续去重跳过):

grep -h "^url:" ./linkedin-save/{role_dir}/*.md 2>/dev/null | awk '{print $2}'

STEP 1 — 搜索候选人 URL(Google → LinkedIn 内部)

使用第一层参数(职位关键词 + 地点)直接构建搜索 query。

优先:Google site 搜索

搜索策略依次尝试(将 {职位} 和 {地点} 替换为实际第一层参数):

# A: 精确职位
site:linkedin.com/in/ ("presales" OR "pre-sales") "cloud" "{地点}"

# B: 扩展职位变体
site:linkedin.com/in/ "solution architect" "cloud" "presales" "{地点}"

# C: 按目标公司
site:linkedin.com/in/ ("presales" OR "solution architect") "cloud" ("FPT" OR "Viettel" OR "VNPT" OR "CMC" OR "AWS" OR "Oracle") "{地点}"

翻页:URL 加 &start=10&start=20

提取 Google 结果 JS:

() => {
  const out = [];
  document.querySelectorAll('h3').forEach(h3 => {
    const block = h3.closest('.g') || h3.closest('[data-hveid]') || h3.closest('[jscontroller]');
    const a = block && block.querySelector('a[href*="linkedin.com/in/"]');
    if (a) out.push({
      url: a.href.split('?')[0].replace(/^https?:\/\/[a-z]+\.linkedin\.com/, 'https://www.linkedin.com'),
      title: h3.innerText,
      snippet: block ? block.innerText.slice(0, 300) : ''
    });
  });
  return out;
}

备用:LinkedIn 内部搜索(当 Google 结果不足时)

将第一层参数拼入 URL({keywords} = 职位关键词,{geoUrn} = 地点对应 ID):

https://www.linkedin.com/search/results/people/?keywords={keywords}&geoUrn=%5B%22{geoUrn}%22%5D&origin=FACETED_SEARCH&page=N

常用 geoUrn:

  • 越南 Vietnam:104195383
  • 河内 Hanoi:104195383(越南范围,无单城市 ID)
  • 胡志明市 HCM:104195383

提取 LinkedIn 搜索结果 JS:

async () => {
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  for (let y = 0; y <= 5000; y += 300) { window.scrollTo(0, y); await sleep(150); }
  await sleep(2000);
  return Array.from(document.querySelectorAll('a[href*="/in/"]'))
    .map(a => ({ href: a.href.split('?')[0], text: a.innerText.trim().slice(0, 200) }))
    .filter(l => /linkedin\.com\/in\/[^/]+\/?$/.test(l.href) && l.text.length > 3)
    .filter((v, i, a) => a.findIndex(x => x.href === v.href) === i);
}

STEP 2 — 逐一访问档案(跳过已扫描),LLM 分析第二层条件

对每个新 URL:

  1. navigate_page(允许超时,继续执行)
  2. 执行滚动+提取脚本获取原始文本:
async () => {
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  for (let y = 0; y <= 8000; y += 300) { window.scrollTo(0, y); await sleep(200); }
  await sleep(2000);
  const t = document.body.innerText;
  const ei = t.indexOf('工作经历');
  return {
    header: t.slice(0, 2500),
    exp: ei > -1 ? t.slice(ei, Math.min(ei + 4000, t.length)) : '',
    url: location.href.split('?')[0]
  };
}
  1. LLM 分析第二层条件(根据 header + exp 文本推断):
分析目标(逐项推断,无法判断则填 unknown):
- cloud_years:累加含 AWS/Azure/GCP/OCI/云 字样的工作段时长
- total_years:推算最早工作年份 → 至今
- presales:职位名或 bio 是否含 presales/pre-sales/solution consultant/售前
- public_cloud:是否有 AWS/Azure/GCP/OCI/公有云 经历
- private_cloud:是否有 OpenStack/VMware/私有化/on-premise 经历
- match_level:
    A = 地点✅ + cloud_years≥目标 + total_years≤上限 + presales✅
    B = 上述条件部分可验证 / 有一项 unknown
    C = 仅 URL 或内容极少,需人工核实
  1. 立即保存档案 → STEP 3

STEP 3 — 保存档案(URL 必存,内容尽力)

文件路径:./linkedin-save/{role_dir}/{linkedin_id}.md
其中 linkedin_id = URL 最后一段,例如 nguyen-the-hung-70725b1b

档案格式(最小化版,仅 URL 时):

---
name: unknown
url: {完整LinkedIn URL}
location: unknown
current_role: unknown
cloud_years: unknown
total_years: unknown
public_cloud: unknown
private_cloud: unknown
presales: unknown
match_level: C
scanned_date: {YYYY-MM-DD}
---

档案格式(有内容时,LLM 推断填充):

---
name: Nguyen The Hung
url: https://www.linkedin.com/in/nguyen-the-hung-70725b1b/
location: Hanoi, Vietnam
current_role: Customer Solution Consultant – GCP @ Google
cloud_years: 8
total_years: 15
public_cloud: true
private_cloud: true
presales: true
match_level: A
scanned_date: 2026-04-28
---

## 摘要
{LLM 对第二层条件的2-3句评估说明}

## 工作经历
{从页面提取的工作经历文本}

STEP 4 — 循环直到达标

统计 match_level 为 A 或 B 的档案数量:

grep -l "match_level: [AB]" ./linkedin-save/{role_dir}/*.md 2>/dev/null | wc -l

未达目标则切换搜索词继续(仍只改第一层参数变体,第二层条件不变):

  • 职位变体:"technical presales" / "pre-sales engineer" / "solution consultant"
  • 地点不变,但可加公司名替换地点做补充:FPT / Viettel / CMC / VNPT / Noventiq

输出汇总

完成后输出:

## 候选人汇总 — {职位},{地点}

【搜索条件】职位: presales solution architect | 地点: hanoi
【分析条件】云经验≥5年 | 总经验≤15年 | 售前明确 | 公有云优先

| # | 姓名 | 职位@公司 | 云年限 | 总年限 | 公有云 | 私有云 | 售前 | 级别 | 链接 |
|---|------|-----------|--------|--------|--------|--------|------|------|------|
| 1 | Nguyen The Hung | GCP Consultant @ Google | 8yr | 15yr | ✅ | ✅ | ✅ | A | [🔗](url) |
...

A级: N人 | B级: N人 | C级: N人(待核实)
已保存至 ./linkedin-save/presales-architect/(共 N 个档案)

注意事项

  • URL 必存:哪怕页面完全空白,也要保存含 URL 的最小档案
  • 3度墙:3度好友工作经历节不可见,LLM 仅凭 header/bio 推断,标记 C 级,exp 字段留空
  • 导航超时:超时后继续执行滚动脚本(页面可能已部分加载)
  • 重复检测:每次保存前 grep 检查 URL 是否已存在,跳过已扫描
  • 速率:每个档案访问后自然等待(滚动脚本本身约需4秒)
  • 第一层不可协商:地点和职位关键词必须进 query,不能只靠 LLM 事后过滤

Version tags

latestvk97f03b97pzrk9wrg7yy816vb985rp96