Files
offerpai_backend/.kiro/steering/数据清洗方案.md
T
2026-03-18 21:33:07 +08:00

12 KiB
Raw Blame History

inclusion
inclusion
manual

数据清洗方案

总体架构

爬虫(公司网络) → app_job_data(原始数据)
                      ↓
              Java定时任务读取(多线程)
                      ↓
            调用AI API清洗/结构化
                      ↓
         写入业务表(bg_company + bg_job + 关联表)
                      ↓
            公司信息不完整的 → 调工商API补充

所有清洗逻辑放在 manager 模块,通过 @Scheduled 定时任务触发。

讨论分区

整体方案分为四部分逐步讨论:

  1. 岗位清洗触发逻辑
  2. 岗位清洗逻辑
  3. 公司数据触发逻辑
  4. 公司数据补充逻辑(待定API后补充)

一、岗位清洗触发逻辑

1.1 表结构变更

app_job_data 新增字段:

ALTER TABLE app_job_data ADD COLUMN clean_status TINYINT(1) DEFAULT 0 NOT NULL COMMENT '清洗状态 0=待清洗 1=清洗中 2=已入库 3=已丢弃';
CREATE INDEX idx_clean_status ON app_job_data (clean_status);

状态说明:

  • 0=待清洗:新爬到的数据,默认值,不影响爬虫原有插入逻辑
  • 1=清洗中:定时任务已锁定,正在处理
  • 2=已入库:清洗成功,已写入 bg_job
  • 3=已丢弃:AI判定为无效数据,不入库

时间记录:不加额外时间字段,利用已有的 updated_atON UPDATE CURRENT_TIMESTAMP),状态变更时自动更新。

1.2 两个定时任务

任务A:岗位清洗任务(高频,每5分钟)

  1. 批量锁定:UPDATE app_job_data SET clean_status=1 WHERE clean_status=0 AND is_valid=1 LIMIT N(原子操作,防止多线程重复捞取)
  2. 将锁定的数据丢入线程池,多线程并发调用 AI API 清洗
  3. 每条处理完毕后,单独更新 clean_status 为 2(已入库)或 3(已丢弃)
  4. 单条写入事务:bg_job 入库 + clean_status 更新放在同一个短事务中,保证一致性

任务B:僵尸恢复任务(低频,每30分钟)

处理因发布重启导致卡在"清洗中"的僵尸数据:

UPDATE app_job_data SET clean_status=0 WHERE clean_status=1 AND updated_at < NOW() - INTERVAL 10 MINUTE

一条SQL搞定,将超时10分钟仍在"清洗中"的数据重置为待清洗,下次任务A会重新捞取处理。

1.3 去重保障

即使同一条数据被重复清洗(僵尸恢复后重新处理),写入 bg_job 时通过 source_id 判断是否已存在,存在则跳过,不会产生重复数据。

1.4 设计决策记录

决策点 结论 原因
清洗状态放哪 app_job_data 加字段 同库,简单直接
是否加"清洗中"状态 多线程并发需要锁定机制
长事务 vs 短事务 短事务(单条) AI调用耗时长,不能hold连接
僵尸恢复方式 独立低频定时任务 避免每次清洗任务都多一次查询,节省性能
是否加 clean_time 字段 updated_at 自动更新,够用
失败重试 僵尸恢复任务自动处理 clean_status=1 超时后重置为0,自动重试

二、岗位清洗逻辑

2.1 前置校验(Java侧,不调AI)

  • description 为空或长度 < 20 → 直接标记 clean_status=3(丢弃),跳过,节省AI调用成本

2.2 参考数据准备

应用启动时加载并缓存(定期刷新):

  • bg_job_category 全量:拼成 id:name 文本列表
  • bg_industry 全量:拼成 id:name 文本列表

这两份列表作为 prompt 的一部分传给AI,ID由人工维护为短数字,不使用雪花ID。

地区数据(bg_china_regions_code)不传给AI,由Java侧根据AI返回的城市名自行匹配。

2.3 AI 调用(单次调用,返回结构化JSON)

输入

  • 原始字段:job_title、salary、location、company、experience、education、description
  • 参考列表:岗位分类(id:name)、行业(id:name

AI 返回 JSON 结构

{
  "valid": true,
  "title": "Java高级开发工程师",
  "salary": "15-25K",
  "education": 2,
  "minExperience": 3,
  "employmentType": 0,
  "categoryId": 12,
  "industryId": 5,
  "description": "1. 负责核心业务系统开发...",
  "requirement": "1. 本科及以上学历...",
  "bonus": "1. 有分布式系统经验优先...",
  "tags": ["数据分析", "产品策略", "团队协作"],
  "skillTags": ["Java", "Spring Boot", "MySQL"],
  "companyShortName": "字节跳动",
  "cities": ["北京", "上海"]
}

各字段清洗规则

字段 来源 规则
valid AI综合判断 数据是否有效,false则丢弃
title job_title 存在则保留;不存在则AI从description归纳生成
salary salary 有效则标准化(10-20K / 20K / 面议);无效或空则null
education education + description 映射为 0=不限 1=大专 2=本科 3=硕士 4=博士
minExperience experience + description 提取最低年限数值,不要求则为0
employmentType description 判断 0=全职 1=兼职,默认0
categoryId description + job_title 必选,从分类列表中选最接近的,不允许返回null
industryId description(任职要求部分) 仅当明确提到行业经验要求时设置;列表中无完全匹配则选最相似的;未提到则null
description description + experience + education 提取"岗位职责"部分,保持原文风格,格式化展示
requirement description + experience + education 提取"任职要求"部分,保持原文风格,格式化展示
bonus description + experience + education 提取"加分项"部分,无则空
tags description + job_title 核心职能标签(如数据分析、产品策略、团队协作),最多5个
skillTags description 技能关键词(如Java、Spring Boot、MySQL),最多8个
companyShortName company 提取简洁的公司简称,去掉地区后缀、招聘后缀、括号内容等,保持"中国平安""字节跳动"风格
cities location 提取城市名列表,精确到市级

2.4 AI 返回后的 Java 处理流程

  1. valid=false → 更新 clean_status=3,结束
  2. 公司处理:按AI清洗后的 companyShortNamebg_company.short_name,存在则拿 company_id;不存在则创建一条(short_name=companyShortName, status=0待完善),拿新ID
  3. 地区处理cities 列表逐个匹配 bg_china_regions_code(按name匹配到市级),匹配上的准备写入关联表
  4. 去重:用 source_idapp_job_data.id)查 bg_job,已存在则跳过,更新 clean_status=2
  5. 写入 bg_job:组装所有字段,source_id=app_job_data.idsource_url=detail_urlstatus=0(上架)
  6. 写入 bg_job_region_relation:岗位ID + 匹配到的region_code,一岗多地区
  7. 更新 app_job_data.clean_status=2

步骤 2-7 放在一个短事务中,保证数据一致性。

2.5 设计决策记录

决策点 结论 原因
AI调用次数 一次调用返回全部字段 减少API调用成本和延迟
分类/行业列表怎么给AI 直接传 id:name 文本 ID人工维护为短数字,token消耗可控
地区匹配方式 AI输出城市名,Java侧匹配 城市名无歧义,不需要传参考列表
categoryId 是否可空 不可空,必须选一个 岗位分类是核心维度
industryId 何时设置 仅描述中明确提到行业经验时 行业经验是任职要求,不是所有岗位都有
tags 定位 核心职能标签,最多5个 区别于福利标签,体现岗位核心能力要求
skillTags 数量 最多8个 控制数量,保持精炼
source_id 取值 app_job_data.id 简单直接,用于去重
公司不存在时 自动创建 status=0 待完善 后续由公司数据补充逻辑完善

三、公司数据触发逻辑

3.1 状态扩展

bg_company.status 扩展为4个值:

  • 0=待完善:岗位清洗时自动创建的公司,只有 short_name
  • 1=已完善:工商API补充完成
  • 2=禁用:人工标记禁用
  • 3=补充中:定时任务已锁定,正在调用工商API

3.2 两个定时任务(与岗位清洗同一套模式)

任务C:公司数据补充任务(低频,每小时)

  1. 批量锁定(原子操作):
UPDATE bg_company SET status=3, update_time=NOW() WHERE status=0 LIMIT N

⚠️ 锁定时必须同时更新 update_time,因为 bg_companyupdate_time 不像 app_job_data.updated_at 那样由数据库自动维护,需要 Java 侧手动设值。如果不更新,后续僵尸恢复任务无法正确判断超时。

  1. 将锁定的数据丢入线程池,多线程并发调用工商API
  2. 每条处理完毕后,回填公司信息,更新 status=1(已完善)
  3. 工商API查不到或返回异常 → 保持 status=3,由僵尸恢复任务重置

任务D:公司僵尸恢复任务(低频,每小时,与任务C错开)

处理因发布重启导致卡在"补充中"的僵尸数据:

UPDATE bg_company SET status=0 WHERE status=3 AND update_time < NOW() - INTERVAL 10 MINUTE

超时10分钟仍在"补充中"的数据重置为待完善,下次任务C会重新捞取处理。

3.3 与岗位清洗触发逻辑的对比

对比项 岗位清洗 公司补充
状态字段 app_job_data.clean_status bg_company.status
锁定值 1=清洗中 3=补充中
完成值 2=已入库 / 3=已丢弃 1=已完善
时间字段 updated_at(数据库自动) update_timeJava手动设值)
锁定时是否需手动更新时间 不需要 需要,否则僵尸恢复无法判断超时
触发频率 每5分钟 每小时
僵尸恢复频率 每30分钟 每小时(与任务C错开)

3.4 设计决策记录

决策点 结论 原因
是否与岗位清洗同步触发 否,独立定时任务 外部API不同,频率不同,失败场景不同
触发模式 复用岗位清洗的"原子锁定+僵尸恢复"模式 统一架构,代码可复用
锁定时是否更新时间 bg_company.update_time 非数据库自动维护,不更新则僵尸恢复失效
补充频率 每小时 公司数据量少,工商API可能有频率限制

四、公司数据补充逻辑(待定API后补充)

4.1 补充流程概要

  1. short_name(公司简称)调用工商API搜索
  2. API返回匹配的企业列表,取最匹配的一条
  3. 回填 bg_company 各字段,更新 status=1

4.2 需要回填的字段

bg_company 字段 来源 说明
name 工商API 公司全称
logoUrl 待定 工商API可能不提供,需另外来源
regionCode 工商API(注册地址) 匹配 bg_china_regions_code
companyType 工商API 上市企业、独角兽、国企等
industryId 工商API(行业分类) 匹配 bg_industry
tags 工商API / AI 公司标签,JSON数组
summary 工商API / AI 公司简要介绍
description 工商API 公司详细描述
foundedYear 工商API 成立时间
address 工商API 注册地址
scale 工商API 企业规模(人数)
website 工商API 官网地址
financingStage 工商API / 其他来源 融资状态
latestValuation 工商API / 其他来源 最新估值
news 新闻API(待定) 新闻动态,JSON数组

4.3 待定事项

  • 选定工商信息API(天眼查、企查查、爱企查等)
  • 确认API返回字段与 bg_company 的映射关系
  • 新闻动态数据来源(工商API是否包含,还是需要单独的新闻API)
  • logoUrl 来源(工商API是否提供)
  • 匹配到多条结果时的处理策略
  • 查不到结果时的处理策略(保持待完善 or 标记为其他状态)
  • API调用频率限制和成本评估