译:我是如何实现求职自动化的(第一部分)

原文:https://blog.daviddodda.com/how-i-automated-my-job-application-process-part-1
作者:David Dodda
译者:Claude 3.5 Sonnet

编者注:作者通过编写 Python 脚本来自动化求职流程。1) 手动获取职位列表,2) 清理原始 HTML 数据,3) 获取完整职位详情,4) 将 HTML 转换为结构化数据,5) 使用 LLM 生成个性化求职信,6) 自动发送邮件。整个系统最终能在 20 分钟内发出 250 份求职申请。

说实话,找工作真的很糟糕。

这是一个令人身心俱疲的循环:反复复制粘贴相同的信息,第 100 次修改简历,写求职信时既要表现出渴望又不能显得太过急切。

但问题在于:重复性任务 + 结构化流程 = 完美的自动化场景。

所以我做了任何一个理性的开发者都会做的事 —— 构建一个系统来自动化整个过程。最终,我在 20 分钟内发出了 250 份求职申请。(讽刺的是?我在完成这个系统之前就收到了工作 offer。这个后面再说。)

让我来告诉你我是怎么做到的。

求职流程已经崩坏

想想看 —— 每个求职申请都遵循相同的基本模式:

  1. 找到职位发布
  2. 检查你是否符合资格
  3. 研究公司(说实话,大多数人都会跳过这步)
  4. 提交简历 + 求职信
  5. 等待…再等待…继续等待…

这就像一个非常无聊的电子游戏,你不断重复同样的任务,期待不同的结果。

构建概念验证

我首先编写了一些快速的 Python 脚本来测试这个疯狂的想法是否可行。以下是我的分解步骤:

第 1 步:获取职位列表(手动部分)

第一个挑战:大规模获取职位列表。我尝试了网页爬虫,但很快意识到:求职网站就像雪花 —— 每个都有其独特的爬取难度。

我测试了将整个网页输入到 LLM 中来清理数据,但是:

  • 成本高得吓人
  • 我不想让 AI 产生虚假的职位要求(想象一下在面试中解释这个)

所以我选择了最原始的方式 —— 手动复制 HTML。是的,这很原始。是的,这很有效。有时最简单的解决方案就是最好的解决方案

第 2 步:清理原始 HTML

原始 HTML 一团糟,但我需要像这样的结构化数据:

{
    "job_link": "https://example.com/job/12345",
    "job_id": "12345",
    "job_role": "software developer",
    "employer": "Tech Corp Inc",
    "location": "San Francisco, CA",
    "work_arrangement": "Remote",
    "salary": "$150,000"
}

专业提示:你只需要向 ChatGPT 展示 HTML 样本和你想要的输出格式,它就会为你编写解析脚本。要聪明工作,而不是拼命工作

第 3 步:获取完整的职位详情

这部分很直接但需要一些技巧。对于每个职位列表,我发送一个 GET 请求来获取完整描述。每个请求返回的原始 HTML 仍然包含所有网站框架 —— 导航栏、弹窗、页脚垃圾信息等。

我编写了一个简单的 HTML 解析器来剔除除了实际职位描述之外的所有内容。有时你会遇到额外的障碍 —— 比如必须点击按钮才能显示招聘人员的邮箱或公司详情。好消息是?由于你一次只处理一个求职网站,你只需要找出这些模式一次。

专业提示:始终在请求之间添加延迟。我将延迟设置为 2-3 秒。确实,这会使过程变慢,但总比被封 IP 好。不要成为那个对求职网站发动 DDOS 攻击的人 —— 我添加延迟是因为我不是恶魔。

第 4 步:将原始 HTML 转换为结构化数据

这里开始变得有趣了。职位发布就像人一样 —— 都有相同的基本部分,但组织方式却是一团混乱。有些在顶部列出技能,有些则将其埋在企业话语的段落中。

这是拯救我理智的 LLM 提示:

const prompt = `Please analyze these HTML contents from a job posting and extract information into a structured JSON format.

[... HTML content ...]

Format the response as valid JSON object with these exact keys:
- contact_email
- application_instructions
- job_posting_text (in markdown)
- job_posting_link
- additional_info (salary, location, etc.)
- job_title
- job_company
- job_department
- job_location
- job_skills
- job_instructions (how to apply)

optional keys

- hiring_manager_name
- job_portal
`

第 5 步:生成不烂的求职信

好求职信的秘诀?上下文。我将我的简历和职位详情输入到 LLM 中。这样,AI 就能将我的经验与他们的要求匹配起来。突然间,那些"我对这个机会感到兴奋"的信件真的有了实质内容。

这是实现这一点的提示:

const prompt = `Please help me write a professional job application email based on the following information:

=== MY RESUME ===
${resumeMarkdown}

=== JOB DETAILS ===
Job Title: ${job_title}
Company: ${job_company}
Department: ${job_department || ''}
Location: ${job_location || ''}
Job Description: ${job_posting_text }
Required Skills: ${job_skills?.join(', ') || ''}
Application Instructions: ${job_instructions || ''}

Additional Context:
- Hiring Manager Name: ${hiring_manager_name || ''}
- Referral Source: ${referral_source || 'Job board'}
- Application Portal: ${job_portal || ''}

Instructions:
1. Create an email that is ready to send without any placeholders or edits needed
2. If any critical information is missing (like company name or job title), respond with an error message instead of generating incomplete content
3. Skip any optional fields if they're empty rather than including placeholder text
4. Use natural sentence structure instead of obvious template language
5. Include specific details from both the resume and job description to show genuine interest and fit
6. Any links or contact information should be properly formatted and ready to use

Format the response as a JSON object with these keys:
{
  "status": "success" or "error",
  "error_message": "Only present if status is error, explaining what critical information is missing",
  "email": {
    "subject": "The email subject line",
    "body_html": "The email body in HTML format with proper formatting",
    "body_text": "The plain text version of the email",
    "metadata": {
      "key_points_addressed": ["list of main points addressed"],
      "skills_highlighted": ["list of skills mentioned"],
      "resume_matches": ["specific experiences/skills from resume that match job requirements"],
      "missing_recommended_info": ["optional fields that were missing but would strengthen the application if available"],
      "tone_analysis": "brief analysis of the email's tone"
    }
  }
}

Critical required fields (will return error if missing):
- Job title
- Company name
- Job description
- Resume content

Recommended but optional fields:
- Hiring manager name
- Department
- Location
- Application instructions
- Referral source
- Required skills list

Please ensure all HTML in body_html is properly escaped for JSON and uses only basic formatting tags (p, br, b, i, ul, li) to ensure maximum email client compatibility.
`

这个提示做了一些巧妙的事情:

  1. 强制结构化输出 —— 没有模棱两可的回应
  2. 跟踪你的哪些技能与职位要求匹配
  3. 识别任何可能加强申请的缺失信息
  4. 同时生成 HTML 和纯文本版本(因为某些求职门户讨厌格式化)

最关键的是 —— 如果缺少关键信息,它会快速失败。不再有泛泛而谈的"我看到了你们的职位发布"邮件。要么求职信有实质内容,要么就不发送。就这么简单。

(我所有的提示都以"please"开头,这样当 AI 最终接管时,他们会认为我是友好的 😁)

第 6 步:发送邮件(关键时刻)

最后一步 —— 实际发送这些精心制作的申请。听起来很简单,对吧?只需连接一个邮件服务然后轰炸出去?

没那么快。我需要一种方式来:

  • 发送看起来专业的邮件
  • 跟踪实际发送的内容
  • 监控回复(不能对招聘人员置之不理)
  • 不被标记为垃圾邮件(至关重要!)

为了测试,我首先将所有邮件发送到一个测试账户。专业提示:当你真正发送给招聘人员时,记得给自己发 BCC。没有什么比怀疑"那封邮件到底发出去了没有?"更糟糕的了

在这个概念验证阶段,我只使用了像 Mailgun 这样的简单邮件提供商。快速、粗糙,但有效。别担心 —— 在第二部分中,我会告诉你我在尝试构建完整的邮件管理系统时遇到的种种问题。(剧透:涉及被 AWS 拒绝的申请和运行自己的邮件服务器的失败尝试。有趣的时光。)

结果

概念验证的效果比预期的要好。我可以获取求职网站的列表,提取职位,解析它们,并生成个性化申请 —— 所有这些都只需要几个 Python 脚本。

但这仅仅是开始。真正的挑战?将这些脚本转变为一个正式的应用程序,它可以:

  • 处理多个求职网站
  • 跟踪申请
  • 管理邮件回复
  • 不会让我被所有 HR 系统拉黑

在第二部分中,我会向你展示我是如何构建实际应用程序的,包括所有技术决策、权衡和"我当时在想什么"的时刻。

敬请期待 —— 会更精彩。