5.9 KiB
5.9 KiB
inclusion
| inclusion |
|---|
| manual |
数据清洗方案
总体架构
爬虫(公司网络) → app_job_data(原始数据)
↓
Java定时任务读取(多线程)
↓
三次AI调用:结构化 → 专业匹配 → 技能提取
↓
写入业务表(bg_company + bg_job + 关联表 + bg_skill_tag)
↓
公司信息不完整的 → 调AI补充
所有清洗逻辑放在 manager 模块,通过 @Scheduled 定时任务触发。
一、岗位清洗触发逻辑
1.1 状态管理
app_job_data.clean_status:
- 0=待清洗:新爬到的数据,默认值
- 1=清洗中:定时任务已锁定,正在处理
- 2=已入库:清洗成功,已写入 bg_job
- 3=已丢弃:AI判定为无效数据
1.2 两个定时任务
任务A:岗位清洗(每5分钟)
- 事务内
SELECT FOR UPDATE+UPDATE clean_status=1批量锁定 - 丢入线程池,多线程并发处理
- 每条独立更新状态
任务B:僵尸恢复(每30分钟)
将超时10分钟仍在清洗中的数据重置为待清洗。
二、岗位清洗逻辑(三次AI调用)
2.1 前置校验
description为空或长度 < 20 → 标记丢弃,跳过
2.2 参考数据缓存(DictCacheService)
启动时加载:
bg_job_category叶子节点(level=3),格式id:name(一级/二级)bg_industry叶子节点(level=2),格式id:name(一级)bg_major_category叶子节点(level=3),格式id:name(一级/二级)bg_china_regions_code省市级,供城市名匹配
2.3 第一次AI:提取岗位结构化信息
输入
- 原始字段:job_title、salary、location、company、experience、education、description
- 参考列表:岗位分类、行业
返回JSON
{
"valid": true,
"title": "Java高级开发工程师",
"salary": "15-25K",
"education": 2,
"minExperience": 3,
"employmentType": 0,
"categoryId": 12,
"requiredIndustryId": 5,
"description": "岗位职责...",
"requirement": "任职要求...",
"bonus": "加分项...",
"tags": ["数据分析", "产品策略"],
"skillTags": ["Java", "Spring Boot"],
"companyShortName": "字节跳动",
"cities": ["北京", "上海"]
}
Java处理流程
- valid=false → 丢弃
- source_id 去重检查
- 公司处理(查或创建 bg_company)
- 地区匹配(cities → region_code)
- 写入 bg_job + bg_job_region_relation + 更新 clean_status=2
2.4 第二次AI:专业匹配
岗位入库后,单独调用AI匹配专业要求。
输入
- 岗位标题、职责、要求
- 三级专业分类列表(845条,格式
id:name(一级/二级))
返回JSON
{
"requiredMajorIds": [101, 203],
"majorSensitivity": 1
}
规则
- requiredMajorIds:从专业列表中选最相关的,最多3个,无明确要求则空数组
- majorSensitivity:0=不限 1=优先 2=强制
- majorSensitivity=0 时,requiredMajorIds 应为空数组
- 结果更新到 bg_job 的 required_major_ids 和 major_sensitivity 字段
2.5 第三次AI:技能提取
自由提取岗位核心技能,不再依赖预定义标签库。
输入
- 岗位标题、职责、要求
返回JSON
["java", "spring boot", "mysql", "redis"]
prompt 规则
- 统一小写字母
- 尽量简短,使用业界通用缩写
- 提取范围:技术栈、专业领域知识、行业工具、专业资质能力
- 不提取纯软技能(沟通、协作、学习能力等)
- 无专业能力要求的岗位(销售、行政等)返回空数组
- 最多15个
技能入库流程
- 遍历AI返回的技能名,统一转小写
INSERT IGNORE INTO bg_skill_tag(依靠 name 唯一索引去重,ID 由 IdWorker 生成)SELECT id FROM bg_skill_tag WHERE name=?拿到ID- 写入 bg_job_skill_tag_relation
并发安全
依靠数据库唯一索引保证,不加应用层锁。多线程同时插入相同技能名时,INSERT IGNORE 自动忽略重复。
2.6 容错设计
- 第二次、第三次AI调用失败不影响岗位入库
- 每次调用独立 try-catch,仅日志记录
三、公司数据触发逻辑
3.1 状态
bg_company.status:0=待完善 1=已完善 2=禁用 3=补充中 4=补充失败
3.2 定时任务
- 任务C:公司补充(每小时),SELECT FOR UPDATE 锁定 status=0 的数据
- 任务D:僵尸恢复(每小时,与C错开),重置超时10分钟的 status=3 数据
四、公司数据补充逻辑(AI补充)
4.1 流程
- 拿 short_name 调AI
- prompt 附带行业列表,AI直接返回 industryId
- valid=false → status=4(补充失败)
- regionCode:AI返回城市名,Java侧匹配
- 回填 bg_company 各字段,status=1
4.2 AI返回JSON
{
"valid": true,
"name": "北京字节跳动科技有限公司",
"city": "北京",
"companyType": "独角兽",
"industryId": 5,
"tags": ["短视频", "人工智能"],
"summary": "全球领先的内容平台和科技公司",
"description": "字节跳动成立于2012年...",
"foundedYear": "2012",
"address": "北京市海淀区...",
"scale": "10000人以上",
"website": "https://www.bytedance.com",
"financingStage": "已上市",
"latestValuation": "2200亿美元",
"news": ["新闻1", "新闻2", "新闻3"]
}
五、关键设计决策
| 决策点 | 结论 | 原因 |
|---|---|---|
| 技能标签来源 | AI自由提取,自动入库 bg_skill_tag | 去掉预定义标签限制,覆盖面更广 |
| 技能并发去重 | INSERT IGNORE + 唯一索引 | 不加应用层锁,性能好 |
| 专业匹配 | 单独一次AI调用 | 专业列表845条,和第一次prompt合并会超token |
| AI调用次数 | 三次(结构化 + 专业 + 技能) | 各维度独立,容错互不影响 |
| 公司数据来源 | AI补充,不用工商API | 公司简称查API不精确,AI覆盖率高 |