前言
招聘旺季,HR 每天要在微信上处理数以百计的候选人联系请求——手动搜索微信号、发送好友申请、回复初筛问题,这些重复操作占据了大量精力,而真正有价值的面试沟通和人才评估反而被压缩。
本文从工程角度出发,介绍如何利用微信 HTTP 接口构建一套自动化的候选人添加与初筛机器人流程:HR 从 ATS(招聘管理系统)或 Excel 名单中导出候选人微信号,系统自动按频率安全地发送好友申请;候选人通过验证后,机器人立即推送标准化初筛问卷,并根据关键词回答完成第一轮意向识别,最终把合格候选人标记为"待电话确认"状态推给 HR。整套流程把 HR 从重复的微信操作里解放出来,让人专注于判断而非执行。
一、整体架构与流程设计
1.1 核心流程
候选人名单(Excel/ATS导出)
↓
[名单解析模块]
读取微信号、职位、来源
↓
[好友申请队列]
按安全频率逐批发申请
↓
[回调监听服务]
监听好友通过事件
↓
[初筛问卷推送]
发送标准化问题集
↓
[回答解析模块]
关键词匹配 + 意向评分
↓
[结果写入 ATS/钉钉/飞书]
合格候选人推送给 HR
1.2 技术选型
| 模块 | 技术 |
|---|---|
| 语言 | Python 3.10+ |
| 任务队列 | Celery + Redis |
| 数据库 | SQLite(小规模)/ PostgreSQL(规模化) |
| 回调服务 | FastAPI |
| 微信接口 | HTTP REST API |
| 部署 | 云服务器(需公网 IP 接收回调) |
二、环境准备与接口初始化
2.1 基础配置
pythonimport requests
import time
import random
BASE = "https://你的接口域名" # 注册后在官方文档获取
TOKEN = "你的Token"
APPID = "你的appId"
HEADERS = {"token": TOKEN} # 鉴权字段名以官方文档为准
# 代码为示例,具体接口/字段以官方文档为准
2.2 设置消息回调地址
系统收到候选人回复时,微信接口会把消息 POST 到我们预先设置的公网地址。回调设置只需调用一次:
pythondef set_callback(callback_url: str):
payload = {
"appId": APPID,
"callbackUrl": callback_url
}
resp = requests.post(
f"{BASE}/setCallback",
json=payload,
headers=HEADERS
)
result = resp.json()
if result.get("ret") == 200:
print(f"回调设置成功: {callback_url}")
else:
print(f"设置失败: {result}")
return result
# 部署后调用一次
set_callback("https://你的服务器域名/wechat/callback")
三、候选人好友申请模块
3.1 防封频率策略
招聘场景中批量添加好友是高风险操作,需要严格遵守频率限制,否则账号会被微信限制。以下是实践中总结的安全策略:
| 时间维度 | 推荐上限 | 说明 |
|---|---|---|
| 每天总量 | 5~15 个 | 新号从 5 个起步,稳定账号≤15 |
| 每 2 小时 | ≤5 个 | 不要集中在某一时段 |
| 单次间隔 | 随机 3~8 分钟 | 避免机械规律 |
| 新账号 | 先在线 3 天 | 新微信号激活后至少在线 3 天再批量操作 |
这些限制不是为了规避检测而刻意设置的"伪随机",而是模拟真实 HR 的工作节奏——真人每天也不可能加几十个陌生人,合理的频率才是长期可用的保障。
为什么要严格控制每日上限?
微信对账号的行为模式有持续监测,添加陌生好友的频率、验证语的文本相似度、账号的"年龄"(注册时间与活跃历史)都是触发风控的因素。新注册或刚换设备的账号尤其脆弱,初期应以手动操作为主,让账号积累正常的社交行为记录,再逐步引入自动化。实操建议是:同一批候选人申请应分散在多个工作日发送,验证语每次适度变化,避免完全一致的模板文本触发机器行为识别。
此外,HR 账号还应保持正常的日常使用行为,例如与同事、朋友正常聊天,参与公司内部群讨论等,这些真实的社交行为有助于维持账号健康评分。
3.2 好友申请实现
pythonimport time
import random
from typing import Optional
def add_candidate(wxid: str, remark: str, job_title: str) -> dict:
"""
向候选人发送好友申请
wxid: 候选人微信号
remark: 验证语,建议包含职位和来源
job_title: 职位名称,用于本地记录
"""
payload = {
"appId": APPID,
"wxId": wxid,
"remark": remark, # 好友申请验证语
"addType": 1 # 字段含义以官方文档为准
}
resp = requests.post(
f"{BASE}/addContacts",
json=payload,
headers=HEADERS
)
result = resp.json()
print(f"[添加] {wxid} | {job_title} | ret={result.get('ret')}")
return result
def batch_add_candidates(candidates: list, daily_limit: int = 10):
"""
批量添加候选人,带频率控制
candidates: [{"wxid": "...", "job": "...", "source": "..."}]
"""
today_count = 0
for item in candidates:
if today_count >= daily_limit:
print(f"已达今日上限 {daily_limit},停止")
break
wxid = item["wxid"]
job = item.get("job", "")
source = item.get("source", "招聘平台")
remark = f"您好,我是XX公司 HR,在{source}看到您的简历,诚邀了解{job}职位,期待认识!"
add_candidate(wxid, remark, job)
today_count += 1
# 随机等待 3~8 分钟
wait_sec = random.randint(180, 480)
print(f" 等待 {wait_sec}s 后继续...")
time.sleep(wait_sec)
print(f"本次共发送申请: {today_count} 个")
3.3 从 Excel 读取候选人名单
pythonimport pandas as pd
def load_candidates_from_excel(file_path: str) -> list:
"""
读取候选人名单
Excel 列:微信号、姓名、职位、来源渠道、是否已发申请
"""
df = pd.read_excel(file_path)
# 过滤已处理的行
pending = df[df["是否已发申请"].isna() | (df["是否已发申请"] == "")]
candidates = []
for _, row in pending.iterrows():
candidates.append({
"wxid": str(row["微信号"]).strip(),
"name": str(row["姓名"]),
"job": str(row["职位"]),
"source": str(row.get("来源渠道", "招聘平台"))
})
return candidates
Excel 名单维护建议
Excel 作为候选人名单的管理载体,需要注意几个细节:微信号字段在 Excel 中有时会被自动识别为数字格式,导致开头的零或特殊字符丢失,读取时应统一转为字符串并去除首尾空格(代码中已处理)。"是否已发申请"列建议在发送成功后立即写回 Excel,防止程序中断重跑时重复发送同一候选人。如果名单来自 ATS 系统导出,最好在导出环节就加入去重逻辑,避免同一候选人因为投递多个职位而被重复添加。
四、好友通过事件监听与初筛触发
4.1 回调服务(FastAPI)
当候选人通过好友申请,微信接口会把事件 POST 到我们设置的回调地址。以下是监听服务的核心逻辑:
pythonfrom fastapi import FastAPI, Request
import json
app = FastAPI()
# 简单的上下文存储(生产环境建议用 Redis)
pending_survey = {} # {wxid: {"job": ..., "step": 0, "answers": []}}
SURVEY_QUESTIONS = [
"您好!感谢通过好友验证 😊 请问您目前在职还是离职状态?",
"您期望的到岗时间大约是什么时候?",
"您的期望薪资范围是多少(月薪)?",
"请问您目前在哪个城市?是否接受异地?"
]
@app.post("/wechat/callback")
async def wechat_callback(request: Request):
body = await request.json()
# 字段名以官方文档为准,下面仅作示例
event_type = body.get("type")
from_wxid = body.get("fromWxid")
content = body.get("content", "")
app_id = body.get("appId")
# 好友通过事件(event_type 值以官方文档为准)
if event_type in ("friend_accept", 10000):
handle_friend_accept(from_wxid)
# 普通文本消息
elif event_type in ("text", 1):
handle_text_message(from_wxid, content)
return {"code": 200} # 必须返回 200,否则平台会重试
def handle_friend_accept(wxid: str):
"""好友通过后,立即发送第一个问题"""
pending_survey[wxid] = {"step": 0, "answers": []}
send_message(wxid, SURVEY_QUESTIONS[0])
def handle_text_message(wxid: str, content: str):
"""接收候选人回答,推进问卷流程"""
if wxid not in pending_survey:
return # 不在初筛流程中,忽略
state = pending_survey[wxid]
step = state["step"]
state["answers"].append(content)
next_step = step + 1
if next_step < len(SURVEY_QUESTIONS):
# 还有问题,继续问
state["step"] = next_step
time.sleep(random.randint(3, 8)) # 模拟真人思考
send_message(wxid, SURVEY_QUESTIONS[next_step])
else:
# 问卷完成,评分并通知 HR
finalize_survey(wxid, state["answers"])
del pending_survey[wxid]
4.2 发送消息
pythondef send_message(to_wxid: str, content: str) -> dict:
payload = {
"appId": APPID,
"toWxid": to_wxid,
"content": content
}
resp = requests.post(
f"{BASE}/message/postText",
json=payload,
headers=HEADERS
)
return resp.json()
# 代码为示例,具体接口/字段以官方文档为准
问卷设计注意事项
初筛问卷的问题数量建议控制在 4~6 题,问题之间逻辑递进,不要一次抛出所有问题。每道题发送前加入短暂的随机延迟(3~8 秒),模拟真人打字的节奏,避免候选人感觉在和机器人对话而产生反感。问题措辞应当友好、简洁,避免过于正式或让人觉得在填表。薪资、到岗时间等敏感信息放在后几题,先用轻松的在职状态问题暖场,有助于提升候选人的回复率。
对于长时间没有回复的候选人,建议在 24 小时后发送一条温和的提醒消息,超过 48 小时无回复则标记为"待人工跟进",由 HR 决定是否主动电话联系。
五、初筛评分与结果推送
5.1 意向评分逻辑
初筛机器人收集到候选人的回答后,需要做简单的意向判断,避免把明显不符合的候选人推给 HR:
pythondef score_candidate(answers: list) -> dict:
"""
简单评分逻辑,实际项目可接入 LLM 做语义理解
answers: [在职状态回答, 到岗时间回答, 薪资回答, 城市回答]
"""
score = 0
flags = []
if len(answers) < 4:
return {"score": 0, "flags": ["问卷未完成"], "pass": False}
# 在职/离职状态
status_ans = answers[0]
if "离职" in status_ans or "裸辞" in status_ans:
score += 30 # 离职状态到岗更快
flags.append("离职-到岗快")
elif "在职" in status_ans:
score += 20
flags.append("在职")
# 到岗时间
arrive_ans = answers[1]
if any(k in arrive_ans for k in ["一周", "两周", "马上", "随时", "1周", "2周"]):
score += 30
flags.append("到岗快")
elif any(k in arrive_ans for k in ["一个月", "1个月", "月内"]):
score += 20
flags.append("一个月内")
elif "三个月" in arrive_ans or "3个月" in arrive_ans:
score += 5
flags.append("到岗慢")
# 薪资(简单判断是否提了明确数字)
salary_ans = answers[2]
if any(c.isdigit() for c in salary_ans):
score += 20
flags.append("薪资明确")
else:
flags.append("薪资模糊")
# 城市意愿
city_ans = answers[3]
if "接受" in city_ans or "可以" in city_ans or "没问题" in city_ans:
score += 20
flags.append("异地接受")
return {
"score": score,
"flags": flags,
"pass": score >= 60,
"raw_answers": answers
}
5.2 结果通知 HR
pythondef finalize_survey(wxid: str, answers: list):
result = score_candidate(answers)
# 告知候选人问卷已完成
if result["pass"]:
send_message(wxid, "感谢您的回答!我们的 HR 将在 1 个工作日内与您进一步沟通,请保持微信畅通 😊")
else:
send_message(wxid, "感谢您的参与!目前的岗位与您的情况可能还需进一步评估,我们会在有合适机会时第一时间联系您。")
# 推送给 HR(示例:发消息到 HR 微信或企业内部群)
hr_wxid = "HR的微信号"
flag_str = "、".join(result["flags"])
hr_msg = (
f"【初筛完成】候选人 {wxid}\n"
f"评分:{result['score']} | 标签:{flag_str}\n"
f"结论:{'✅ 建议跟进' if result['pass'] else '⚠️ 暂缓'}\n"
f"-----\n"
f"回答摘要:\n"
+ "\n".join([f"Q{i+1}: {a}" for i, a in enumerate(answers)])
)
send_message(hr_wxid, hr_msg)
print(f"[初筛完成] {wxid} | 得分={result['score']} | pass={result['pass']}")
评分策略的扩展方向
上述评分逻辑是基于关键词的规则匹配,适合快速验证方案可行性。实际招聘场景中,候选人的回答往往表达方式多样,例如"下周就能来"和"随时都行"表达相同的意思,但规则匹配未必能都覆盖。可以在此基础上引入大语言模型做语义理解,将候选人的自由文本回答转换为结构化字段(在职状态、到岗周期、薪资区间、城市),再套用评分规则,准确率会显著提升。此外,评分阈值(当前设为 60 分)应根据实际招聘岗位的供需情况调整,供不应求的岗位可适当降低门槛,避免过滤掉潜在合适的候选人。
六、关于托管 HTTP 接口的选型说明
上述所有示例调用的是微信 HTTP REST 接口。这类接口通常由第三方平台提供——用手机或云端设备扫码登录微信后,通过接口代理实现好友添加、消息收发、群管理等操作,开发者只需发 HTTP 请求,不需要处理微信私有协议的逆向工程。
WechatApi 提供扫码登录、消息收发、好友与群管理等 REST 接口,HTTP 调用即可,适合作为本文方案的接口层选项。
接入这类平台前,建议关注以下几点:
- 合规用途:接口仅用于本人账号的合法业务,不得用于群发广告、骚扰用户或违反微信用户协议的场景。
- 账号隔离:招聘账号专号专用,不要和个人微信混用。
- 数据安全:候选人信息属于个人数据,存储和传输需符合相关法规要求。
- 频率合规:严格遵守上文提到的加好友频率限制,避免账号被封。
七、部署与运维要点
7.1 服务器配置
回调服务需要部署在有公网 IP 且 80/443 端口可访问的服务器上。国内服务器还需要域名备案。推荐使用 HTTPS,部分接口平台强制要求回调地址必须是 HTTPS。
bash# 使用 uvicorn 启动 FastAPI 回调服务
uvicorn callback_server:app --host 0.0.0.0 --port 8080
# 生产环境建议用 nginx 反代 + SSL
服务稳定性保障
回调服务是整个自动化流程的枢纽,一旦中断,候选人的通过事件和消息回复都会丢失,无法重新触发。因此需要配置进程守护(如 supervisor 或 systemd),在服务异常退出时自动重启。同时建议在服务启动时检查微信账号的在线状态,若账号掉线则立即告警通知 HR,避免在无感知的情况下漏接候选人消息。
对于 pending_survey 状态存储,小规模场景下用内存字典即可,但进程重启后状态会丢失——正在进行中的问卷会中断。生产环境建议迁移到 Redis,设置合理的过期时间(如 48 小时),并在服务启动时从 Redis 恢复状态。
7.2 异常处理
pythondef safe_send(wxid: str, content: str, retry: int = 3) -> bool:
"""带重试的消息发送"""
for i in range(retry):
try:
result = send_message(wxid, content)
if result.get("ret") == 200:
return True
print(f"发送失败 ret={result.get('ret')},第{i+1}次重试")
time.sleep(5)
except Exception as e:
print(f"异常: {e},第{i+1}次重试")
time.sleep(10)
return False
7.3 常见问题排查
| 问题 | 可能原因 | 处理方式 |
|---|---|---|
| 好友申请发出但对方收不到 | 账号被限频 | 检查今日添加数量,降低频率 |
| 回调收不到消息 | 服务器公网不可达 | 检查防火墙/安全组,确认端口开放 |
| 消息发送接口报错 | 微信账号离线 | 检查扫码登录状态 |
| 初筛问题没有回复 | 候选人未看消息 | 设置 24h 超时逻辑,标记为"待人工跟进" |
总结
通过将微信 HTTP 接口与任务队列、回调事件驱动相结合,可以构建出一套相对完整的招聘候选人自动化触达流程——从批量好友申请到初筛问卷推送,再到评分结果通知 HR,整条链路在保持合理频率的前提下,能够显著降低 HR 在重复微信操作上花费的时间,让招聘工作聚焦在真正需要人判断的环节。
自动化不是替代 HR 的判断力,而是把 HR 从"发消息、等回复、记录数据"这类机械操作中解放出来。初筛机器人过滤的是明显不符合的候选人,对于评分接近边界的情况,建议仍由 HR 人工核查,避免因规则过死而错失合适人才。整套方案的核心价值在于把 HR 的精力集中到面试沟通和最终决策上,而不是让机器完全替代人的判断。
