求职这件事大部分人的路径差不多打开招聘网站输入岗位关键词一页一页翻看到差不多的职位点进去再和自己简历对比一下判断要不要投。这个过程天然有三个痛点关键词搜索太机械、职位信息太多看不完、JD和简历的匹配完全靠主观感觉。我写了一个叫ai-job-search的小工具把整个流程做成半自动自动拉取职位、自动解析JD、自动用大模型做语义匹配和排序最后输出一张带匹配分数的表格。原来需要一两个小时翻完的职位现在十分钟内就能完成初筛匹配的准确率也比单纯靠关键词过滤高不少。这篇文章不绕弯子直接把完整的思路、代码、参数选择逻辑和踩过的坑都放出来。无论你是准备直接用现成的ai-job-search项目还是想自己从零搭一套都能在这篇里找到可以抄作业的部分。1. 核心思路拆解为什么AI能帮我们筛职位1.1 传统关键词搜索的天然短板招聘网站自带的搜索框本质上是一个倒排索引系统你输入“Python 后端”它返回的是标题或者JD文本中同时包含这两个词的职位。问题在于关键词匹配完全不懂语义。举个很典型的例子你写的是“熟悉Django、Flask有RESTful API设计经验”职位描述里写的是“负责Web服务端接口开发使用Python Web框架”。关键词层面两边只命中了“Python”但语义层面这其实是一个匹配度非常高的职位。反过来一个JD里堆了一堆“Python”关键词但实际岗位是数据分析和你的后端方向完全不沾边关键词搜索也会把它排到很前面。这种语义gap带来的直接后果就是筛出来的职位大量无用真正合适的职位被淹没在列表深处。你得点进去一个个看看完还得拿JD和自己的简历逐条对比非常消耗精力。我认为这里的本质问题是职位匹配是一个语义比较问题不是一个词频统计问题。所以解决思路也应该从“字面匹配”转向“语义匹配”而大语言模型恰好擅长干这个。1.2 语义匹配是如何解决这个问题的ai-job-search的核心逻辑很简单把职位描述和你的简历摘要都交给大语言模型让它站在招聘方的角度给匹配度打分输出分数和理由。这样做的优势在于模型理解的是含义不是字面。JD里写“负责服务端接口开发”它能知道这就是后端开发和你简历里的“Python Web开发经验”对应上。模型能识别技能权重。JD里反复强调“高并发”“性能优化”说明这是核心要求模型会提高这两项的权重如果只是顺带提一句“熟悉Redis加分”权重就低。模型能判断硬性条件和软性条件。比如“5年以上经验”是硬门槛你只有2年它会明确告诉你这里失分但“有跨团队协作经验”可能只是偏好项不会一票否决。这不是玄学。我对比过纯关键词匹配、TF-IDF相似度和GPT类模型打分三种方案在实际职位数据上的表现关键词匹配的精确率大约只有三成左右TF-IDF稍好一些但也会被同义词和行业黑话干扰而语义匹配的准确率明显高出一个档次尤其在JD比较长、岗位描述比较专业的场景下。1.3 整体架构与工具选型我搭的这套ai-job-search整体分四层模块作用选型数据采集拉取职位列表和JD详情requests 招聘网站RSS/开放接口数据清洗提取职位标题、公司、地点、薪资、JD文本BeautifulSoup 正则语义匹配对JD和简历做匹配度打分大模型API结果输出汇总排序、生成投递清单pandas CSV/Excel为什么选Python因为生态齐全从HTTP请求到数据处理再到调用大模型API一条链路下来不用切换语言。为什么不直接做成一个完整的Web服务因为我个人觉得求职工具最核心的价值在于“定制化”每个人的简历、目标岗位、求职偏好都不同做成脚本反而容易改、容易跑想换Prompt也就是改一处字符串的事。这套方案不需要GPU不需要本地部署大模型只依赖云端API普通笔记本就能跑。入门成本很低。2. 十分钟上手的完整实操流程2.1 环境准备别急先装好这些第一步先把Python环境搞定。我推荐3.10以上版本主要是新语法支持和类型注解体验更好。没有Python的话直接用官方安装包或者包管理器装一个就行这里不展开。然后建一个项目目录装依赖mkdir ai-job-search cd ai-job-search python -m venv venv source venv/bin/activate # Windows下用 venv\Scripts\activate pip install requests beautifulsoup4 lxml pandas openai python-dotenv这几个库的功能分别说一下避免有的朋友不清楚该装什么requests发HTTP请求用拉职位列表和JD详情都靠它。beautifulsoup4lxml解析HTML用的速度比自带的html.parser快。pandas最后整理输出结果用排序、筛选、导出Excel都方便。openai大模型API的官方Python SDK用来调用语义匹配接口。python-dotenv管理API Key之类的环境变量不把密钥写死在代码里。2.2 配置大模型API这一步是让ai-job-search有“AI能力”的关键。去大模型服务商的平台注册账号创建API Key注意Key只显示一次保存好。在项目根目录建一个.env文件OPENAI_API_KEY你的密钥 OPENAI_BASE_URLhttps://api.你的服务商.com/v1 OPENAI_MODELgpt-4o-mini为什么要用.env而不是直接写在代码里两个原因一是代码如果传到Git仓库会把密钥泄露出去这个教训非常惨痛二是求职工具免不了反复调整Prompt和模型参数把这些配置集中在一个文件里改起来方便也不会污染代码逻辑。加载环境变量的代码import os from dotenv import load_dotenv load_dotenv() MODEL os.getenv(OPENAI_MODEL, gpt-4o-mini)需要注意一点不同服务商的OPENAI_BASE_URL可能不一样如果你是用的兼容OpenAI接口的服务一定要设置成对应的地址否则调用会报404或者认证失败。2.3 拉取职位数据写一个能跑的采集脚本职位数据从哪来常规方案有两种调招聘网站的开放接口或者抓RSS源。很多招聘网站不开放完整API但提供了按关键词订阅的RSS结构清晰、请求量小最适合个人脚本用。这里以RSS方案为例。搜索“Python开发”职位的订阅地址通常是这样的结构https://xxx.com/jobs/rss?keywordspythonlocation全国写个简单的采集函数import requests import feedparser def fetch_jobs(keyword, location全国, max_count50): url https://xxx.com/jobs/rss params { keywords: keyword, location: location, max: max_count, } resp requests.get(url, paramsparams, timeout15) resp.raise_for_status() feed feedparser.parse(resp.content) jobs [] for entry in feed.entries: jobs.append({ title: entry.get(title, ), company: entry.get(author, ), link: entry.get(link, ), summary: entry.get(summary, ), published: entry.get(published, ), }) return jobs这个函数做了几件事构造带关键词的请求URL、发起请求、解析RSS、把每条职位信息整理成字典。feedparser会自动处理XML的编码问题比用正则硬抠省事很多。如果目标网站没有RSS也可以直接抓搜索页面的HTML核心逻辑是一样的只不过解析从feedparser换成了BeautifulSoupfrom bs4 import BeautifulSoup def fetch_jobs_html(keyword): url https://xxx.com/search?q keyword resp requests.get(url, headers{User-Agent: Mozilla/5.0}, timeout15) soup BeautifulSoup(resp.text, lxml) for item in soup.select(.job-item): title item.select_one(.job-title).text.strip() link item.select_one(a)[href] print(title, link)两种方式对比一下RSS源的数据更规范不容易被反爬拦截HTML解析则可以拿到更完整的页面字段但需要根据实际页面结构调整选择器。建议优先考虑RSS方案。2.4 写匹配脚本让AI给职位打分职位数据拿到手重头戏来了。这一步要让大模型对每一条职位JD和你的简历摘要做匹配评分。先准备一份简历摘要。这里注意不是让你把整个简历扔进去而是提取出“技能栈、年限、项目经历、求职方向”这些关键信息整理成一段结构化文本。比如求职方向Python后端开发 工作年限3年 核心技术栈Python、Django、Flask、FastAPI、MySQL、Redis、Docker 项目经验负责过电商平台的结算系统日订单量10万主导接口性能优化QPS提升3倍 证书/学历计算机本科然后是匹配函数from openai import OpenAI client OpenAI() SYSTEM_PROMPT 你是一位资深招聘顾问请根据求职者的简历摘要和职位描述进行岗位匹配度评分。 评分标准 1. 年龄限制需要给出0-100的整数分数 2. 考虑维度技能匹配度(40%)、经验匹配度(30%)、行业相关性(20%)、其他加分项(10%) 3. 硬性条件不满足时需要明确扣分 4. 输出格式为JSON{score: 整数, reason: 匹配理由, missing: [缺失技能/条件]} 注意只输出JSON不要输出其他内容。 def score_job(resume_summary, job_title, job_summary): user_prompt f简历摘要 {resume_summary} 职位名称{job_title} 职位描述{job_summary} try: resp client.chat.completions.create( modelMODEL, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_prompt}, ], temperature0.2, response_format{type: json_object}, ) text resp.choices[0].message.content import json result json.loads(text) return result.get(score, 0), result.get(reason, ), result.get(missing, []) except Exception as e: print(f匹配出错: {e}) return 0, 打分失败, []这里几个参数值得展开说明temperature0.2降低生成随机性让分数更稳定。求职匹配不是创意写作不需要模型自由发挥所以温度要低。我实测过temperature0.7时同一个职位两次打分能差10分以上调到0.2之后基本稳定在±3分以内。response_format{type: json_object}强制模型输出JSON格式方便程序解析。如果不加这个参数模型偶尔会在JSON前后加解释文字解析时就得多做容错。SYSTEM_PROMPT里给权重不是让模型“凭感觉打分”而是明确技能匹配占40%、经验占30%、行业相关性占20%、其他占10%。这样做分数有可解释性也更贴近真实的招聘筛选逻辑。2.5 批量处理与结果排序单条打分没问题接下来就是把拉到的全部职位批量跑一遍。这里要注意循环调用API是有并发限制的简单场景下直接串行循环就行。import pandas as pd def batch_score(jobs, resume_summary): results [] for job in jobs: score, reason, missing score_job( resume_summary, job[title], job[summary] ) job[match_score] score job[match_reason] reason job[missing] 、.join(missing) results.append(job) return pd.DataFrame(results) df batch_score(jobs, resume_summary) df df.sort_values(match_score, ascendingFalse) df.to_csv(matched_jobs.csv, indexFalse, encodingutf-8-sig)为什么导出用utf-8-sig而不是utf-8因为Excel打开UTF-8编码的CSV时会把中文显示成乱码加上BOM头utf-8-sig就能直接双击打开看到正常中文。输出结果大概长这样职位标题公司匹配分数缺失技能匹配理由Python后端开发工程师某电商平台92无技能栈高度重合有电商结算经验QPS优化经验加分高级后端工程师Go某云计算公司45Go、K8s技术栈不匹配后端基础可迁移数据分析师某咨询公司30数理统计、SQL岗位偏业务分析与后端开发方向偏离到这里十分钟上手的主流程已经完整了采集职位、AI打分、排序输出。剩下的就是打开CSV从高分开始投递。3. 核心细节解析与调优3.1 Prompt设计好的Prompt是匹配质量的半条命很多朋友跑通流程之后发现效果一般分数分布不合理或者理由写得像套话。我复盘下来绝大多数问题出在Prompt上。先说一个我踩过的坑最初我在SYSTEM_PROMPT里只写了“请对职位和简历进行匹配度评分”没有任何约束。结果模型把所有职位都打到了80分以上因为每个职位描述里多少能找到几个匹配点它倾向于给“看起来还行”的职位打高分。这完全没用因为你要的是“优劣拉开差距”的排序。后来我改成现在的版本加了三个关键约束一是权重分配。明确写技能40%、经验30%、行业20%、其他10%模型就有了一张评分表不会走偏。二是要求输出“缺失技能”。这个约束非常有效因为模型必须找出简历里没有、JD里又明确要求的技能点这个动作迫使它真的去逐条对比而不是扫一眼给个大概感觉。三是硬性条件扣分规则。比如JD写“5年以上经验”简历只有3年必须扣分禁止在理由里含糊带过。这一步让分数分布更合理高分就是真的匹配低分也明确知道差在哪。另外简历摘要里的“求职方向”要写在最前面。大模型处理长文本时前面的信息对后续判断的影响通常更大把求职方向放在开头能让模型在一个正确的语义框架里去比对后续细节。3.2 打分结果的校验与阈值设定拿到分数之后不要盲目相信先做一轮抽查。我通常会从高分档、中分档、低分档各抽一两个职位点进去看原始JD核对模型的匹配理由是否成立。这个校验非常必要。我自己遇到过模型把“C开发工程师”岗位打高分的情况原因是模型把“熟悉Linux环境”当成了强匹配项忽略了岗位语言栈和我的简历完全不搭。虽然语义模型比关键词聪明但它毕竟不是招聘方该抽查还得抽查。阈值怎么定以我用的100分制为例我一般把85分以上划为“强烈推荐投递”70-85分划为“可以投递”70分以下直接不看。这个阈值不是固定的如果你投递方向比较宽泛可以降到60分如果方向特别聚焦干脆只看90分以上的。还有一个小技巧在排序表里加一列“JD核心要求关键词”让模型在处理时顺带提取JD里的核心要求。这样即使你对某个职位犹豫不决也能不用点开JD、只看表格就快速判断效率高很多。3.3 成本控制与批量策略调用大模型API是要花钱的。一开始我特别担心成本因为每天刷出来的新职位可能有几百条一条条都调用API钱包扛不住。实测下来按我的用量每天跑50个职位、每个职位一次API调用一个月的花费大概在一两杯咖啡的价格级别并不高。但如果你一天要跑几千条那就有优化的必要了。我常用的省钱策略有三个先用硬性关键词预筛一遍。比如只要求Python后端方向就先过滤掉标题明显不是这个方向的职位减少无效API调用。分批处理。把职位分成每20条一批先跑一个便宜的快速模型做粗筛分数高的再调用更强模型细评。两级筛选可以省掉不少费用。控制每个职位的输入长度。JD描述被RSS截断的情况下只取前500字左右参与打分基本不影响结果。JD越长输入的token越多费用越高而招聘方真正想表达的要素通常集中在前半段。3.4 数据采集的合规与反爬注意事项这里要提醒一句任何抓取行为都要遵守目标网站的robots.txt和服务条款。个人求职场景下的低频抓取问题不大但不代表可以去踩网站的反爬红线。我的合规底线是这样只抓公开可见的职位列表和JD页面不碰用户数据控制请求频率每次请求间隔至少3-5秒设置合理的User-Agent标识自己是个人的爬虫用途被抓到就停手绝不硬刚。实际上低频率、带礼貌的请求加上优先选择RSS源基本不会触发反爬。如果遇到验证码或者403最简单的方式是换一个数据源而不是写复杂的绕过逻辑。个人工具不值得为这点事冒合规风险。4. 常见问题与排查实录这部分直接把我在使用ai-job-search过程中遇到的高频问题列成清单每条都附上了排查思路。问题现象可能原因解决方案拉取职位返回空列表RSS地址参数不对或目标网站更换了接口先用浏览器手动访问RSS地址确认返回XML内容后再改代码参数请求被403拦截User-Agent被识别或请求频率过高换用更完整的User-Agent把请求间隔从1秒提高到5秒以上API返回401认证失败BaseURL配置错误或API Key失效检查.env里的OPENAI_BASE_URL和OPENAI_API_KEY确认服务商支持OpenAI兼容接口同一职位两次打分差异大temperature过高把temperature降到0.2以下必要时设为0所有职位分数都偏高Prompt缺少约束和区分指令按3.1节的方式加上权重分配、缺失技能输出和硬性条件扣分规则输出的CSV用Excel打开乱码编码用了utf-8而非utf-8-sig写入CSV时改成encodingutf-8-sig模型输出的JSON解析失败模型偶尔会在JSON前后夹带其他文本开启response_formatjson_object解析失败时设置重试逻辑再讲一个排查思路上的经验出了问题先定位是哪一层。ai-job-search这条链路是“采集-清洗-打分工-输出”任何一环出错都会影响最终结果。我的排查顺序是先看数据层有没有抓到完整JD确认原始数据没问题后再看API调用是否成功最后才怀疑打分逻辑本身。很多朋友遇到结果不对第一反应是调Prompt其实问题多半出在采集阶段JD就已经缺字段了。另一个容易被忽视的问题是职位数据的时效性。RSS里的职位可能已经下线点进链接发现招聘方已经停止接收简历。所以在我的脚本里会把published字段保留下来投递时优先处理三天内发布的新职位。这不算技术问题但直接影响投递成功率。还有一点大模型的打分是基于你提供的简历摘要的。如果你在简历摘要里漏写了某个关键技能模型就认为你没有这个技能这不公平也会拉低匹配分数。所以每当目标岗位方向变化时记得同步更新简历摘要这不是一次配置一劳永逸的工具。5. 从工具到流程ai-job-search还能怎么扩展跑通基础流程之后我陆续给它加了一些周边功能每一个都在实际求职中派上了用场。第一个是自动生成定制化求职信。如果匹配分数高直接把职位描述和简历摘要扔给模型让它生成一封三百字以内的求职信开头。因为模型已经理解了JD里最看重的技能点生成的求职信针对性很强不是那种千篇一律的“本人有丰富经验”。第二个是定时监控新职位。把采集脚本挂到系统定时任务里每天固定时间跑一次新的高匹配职位自动推送。这个功能的好处是抢新职位发布的先机招聘高峰期有些岗位早上发布下午就关闭早几个小时看到差别非常大。第三个是对历史投递结果的复盘。把“投了哪些职位、拿到了哪些面试”记录下来跑完一轮后让模型分析什么样的JD和我聊天最深入、什么样的公司方向最容易拿到反馈。这种复盘虽然主观但至少给了我一个量化的视角去看自己的求职策略。最后再分享一个小技巧不要只跑一个关键词。可以把搜索职位方向写成多个并列的关键词组合比如“Python后端”“Django开发”“FastAPI工程师”分开各跑一遍然后合并去重。一个关键词可能漏掉一些用词不同但实际匹配的职位多个关键词组合能明显提高覆盖率。这套工具我用了将近一个月最大的感受是它没有帮我做决定但它帮我把决定成本从体力活变成了脑力活。投什么、不投什么依然是人在判断但AI帮我排除了大量明显不合适的选项让我能把精力花在高价值职位的准备上。
