跨来源求职检索
用一套结构化条件同时检索多个招聘平台与企业官网招聘页,去重后输出统一的岗位列表。
不绑定任何单一招聘软件:新增来源只需写一段配置,不改代码。
一、触发场景
出现以下意图时使用本技能:
- "帮我找杭州的 Java 开发岗位,本科,35 岁以下"
- "搜一下上海 3-5 年经验、月薪 3 万以上的产品经理"
- "把几个招聘平台的岗位汇总成一张表,去掉重复的"
- "看看这些公司官网有没有在招 xx 岗"
- "不只用某个 App,多找几个渠道的岗位"
- 补充已有检索结果、按相同条件换城市重跑、把人工查到的岗位并入统一列表
二、硬性约束(必须遵守,不可协商)
- 只采集公开可见信息。 需要登录、需要提交手机号、需企业账号才能看到的岗位,一律不作自动来源。
- 不绕过任何验证。 遇到验证码、滑块、人机校验、JS 反爬挑战页,放弃该来源并在结果中注明原因。
禁止引入打码服务、伪造 cookie、复用他人会话。 - 遵守 robots.txt。 目标路径被 Disallow 就不抓。默认开启检查,不要用
--no-robots规避。 - 限速。
min_interval不低于 2 秒,小型企业官网建议 3-5 秒;--max-pages不要开大。 - 降级而非中断。 任一来源失败、无结果或被拦截,都只记录原因,其余来源继续,最终在
meta.degraded中如实说明。
三、环境准备
依赖(首次使用前安装一次):
pip install requests beautifulsoup4 pyyaml
无需凭据、无需账号。脚本位于本技能 scripts/ 目录下,全部为纯 Python,无外部服务。
首次使用先跑自检,确认环境与逻辑正常(离线,不联网):
python scripts/selftest.py
输出 全部通过 ✅ 即环境可用。
四、工作流程
步骤 1:把用户需求翻译成结构化条件
从用户的话里抽取条件,抽不出的就是不限,不要臆造。抽出后向用户复述一遍确认,
特别是学历下限、年龄、薪资这类会大幅改变结果量的条件。
条件字段全集见 references/criteria-and-output.md。常用字段:
| 字段 | 含义 |
|---|---|
keywords |
职位关键词 |
exclude_keywords |
排除词(外包、销售、驻场等) |
city / district |
目标城市、区县 |
education_min |
学历下限(岗位要求高于此值即剔除) |
age |
求职者本人年龄(用于剔除年龄不符合的岗位) |
age_min / age_max |
直接限定岗位要求的年龄区间 |
salary_min / salary_max |
期望月薪区间(元/月) |
experience_min / experience_max |
经验年限区间 |
published_within_days |
只看 N 天内发布 |
步骤 2:确认启用了哪些来源
查看 assets/sources.yaml。该文件默认不存在,需从模板创建:
cp assets/sources.example.yaml assets/sources.yaml
模板里的示例来源默认 enabled: false。要真正抓到东西,必须先把目标来源的
enabled 改为 true 并按实际接口填好。
references/source-onboarding.md 给出了接入新来源的完整方法(如何从浏览器 DevTools
找到公开数据出口、如何写配置、如何单测与核验)。
按用户需求选择来源组合:
- 关键词明确的通用岗位 → 招聘平台为主
- 用户点名了公司 → 优先该公司官网招聘页(
tier: official) - 平台有强反爬 → 标为
type: manual,走步骤 4
步骤 3:执行检索
python scripts/job_search.py \
--criteria '{"keywords":["Java开发"],"city":["杭州"],"education_min":"本科","age":29,"salary_min":25000}' \
--sort relevance --format both --outdir ./job-out --stdout summary
常用开关:
| 开关 | 说明 |
|---|---|
--config |
数据源配置路径,默认 assets/sources.yaml |
--sources |
临时只用某几个来源(逗号分隔 id) |
--sort |
relevance(默认) / time / salary |
--format |
json / csv / md / both(默认) |
--outdir |
输出目录;留空只打印到终端 |
--max-pages |
每来源最多翻页数,默认 2 |
--strict |
字段未知的岗位一律剔除(默认放行) |
--dedupe-report |
输出中被合并的重复岗位 |
--extra-jobs |
并入人工/浏览器检索结果 |
--stdout |
summary(默认) / full / none |
-v |
调试日志 |
脚本运行时会向 stderr 逐条打印来源状态(成功条数、耗时、降级原因),
stdout 只输出结果本身,因此可以直接管到文件。
步骤 4:处理降级来源
读取 meta.degraded。对其中 status 非 ok 的来源:
blocked/captcha:改用浏览器能力访问该平台,按同样的条件人工检索,
把结果整理成 JSON,用--extra-jobs并入同一次检索,source_tier标为manual。
不要尝试绕过验证。empty:换更宽的关键词重试一次;仍为空则如实告知该来源无相关岗位。error(超时/连接失败):可调低--workers、提高--timeout后重试一次。
步骤 5:交付结果
用 present_files 展示输出文件,并在回复中给出文字摘要,至少包含:
- 命中 / 去重后条数,以及来源分布(平台 / 官网 / 人工)
- 降级来源清单与原因(不能省略,用户需要知道哪些渠道没覆盖到)
- 排名前若干条的岗位要点
- 若筛除数量很大,说明是否是条件过严,并指出哪一条条件卡得最狠
(_rejected[].reasons里有逐条原因,可直接统计) - 一句范围说明:仅采集公开信息,未绕过登录与验证码
五、输出格式
每次运行产出(--format both):
jobs-<时间戳>.json 完整结果:meta / jobs / duplicates / _rejected / _source_reports
jobs-<时间戳>.compact.json 精简版:meta.counts + jobs
jobs-<时间戳>.csv UTF-8 BOM,Excel 可直接打开
jobs-<时间戳>.md Markdown 汇总表 + 降级说明 + 被筛除清单
jobs 每条字段:
| 字段 | 说明 |
|---|---|
company |
公司名称 |
job_title |
职位名称 |
location |
工作地点 |
education |
学历要求(原文) |
age |
年龄要求(原文) |
salary |
薪资(原文) |
published_at |
发布时间(YYYY-MM-DD) |
url |
来源链接(已剥离跟踪参数) |
source / source_tier |
来源 id / 层级(platform·official·manual) |
experience |
经验要求 |
salary_range / age_range |
解析后的数值区间,供程序使用 |
missing |
该条缺失的字段名列表 |
relevant_score |
相关度分 |
字段取不到时填 未注明(薪资为 面议),同时登记进 missing。
完整说明、去重规则与排序细则见 references/criteria-and-output.md。
输出示例
{
"company": "星辰科技有限公司",
"job_title": "Java开发工程师",
"location": "杭州·西湖区",
"education": "本科",
"age": "25-35岁",
"salary": "25-40K·14薪",
"published_at": "2026-09-27",
"url": "https://example.com/job/1",
"source": "platform-alpha",
"source_tier": "platform",
"experience": "3-5年",
"salary_range": [29167, 46667],
"age_range": [25, 35],
"missing": [],
"relevant_score": 23.3
}
六、文件结构
job-search-multi-source/
├── SKILL.md
├── scripts/
│ ├── job_search.py 主入口:并行检索 → 归一化 → 筛选 → 去重 → 排序 → 输出
│ ├── criteria.py 条件定义、学历/年龄/薪资/时间/经验解析、逐条匹配判定
│ ├── normalize.py 岗位归一化、相关度打分、跨来源去重、排序
│ ├── common.py 请求器(限速·robots·重试·拦截识别)、路径取值、模板填充
│ ├── selftest.py 离线自检,33 项断言
│ └── adapters/
│ ├── __init__.py
│ └── base_adapter.py http_json / html / manual 三类适配器
├── references/
│ ├── criteria-and-output.md 条件字段全集、语义细节、输出格式、去重规则
│ └── source-onboarding.md 接入新来源的方法与排错
└── assets/
└── sources.example.yaml 来源配置模板(复制为 sources.yaml 后启用)
七、快速排错
| 现象 | 原因与处理 |
|---|---|
没有可用的数据源 |
assets/sources.yaml 不存在,或其中所有来源都是 enabled: false |
某来源 raw_count: 0,notes 提示未提取到岗位 |
parse.items_path 或 field_map 写错,见 references/source-onboarding.md |
某来源 被拦截: HTTP 403 |
触发了登录墙/反爬,改用 type: manual,不要尝试规避 |
某来源 疑似人机验证页 |
同上,标记为 manual |
| 命中数量远少于预期 | 用 _rejected[].reasons 统计卡得最多的条件;薪资区间需有交集,学历下限过严会大量剔除 |
| 学历/年龄筛选不准 | 该站字段写法特殊,在 criteria.py 的对应解析函数里补正则 |
| 同一岗位重复出现 | 该公司名写法差异过大,在 normalize.py 的 COMPANY_SUFFIX 补后缀词 |
| 全平台都失败 | 检查网络/代理;ProxyError 说明本机代理有干扰 |