Files
tg_crawl/README.md

292 lines
8.2 KiB
Markdown
Raw Normal View History

2026-02-26 20:00:06 +08:00
# jobs_robots
本项目用于采集 Telegram 招聘数据并进行结构化清洗,当前统一使用 MySQL。
## 1. 当前流程
1. `main.py`
-`config.json` 读取数据源、时间窗口、限频、MySQL 配置。
- 爬取 Telegram 消息,写入 MySQL `messages`
- 维护每个来源的增量游标到 `sync_state`
2. `clean_to_structured.py`
- 从 MySQL `messages` 增量读取新增消息(按 `messages.id` + `clean_state` 检查点)。
- 按来源规则清洗(`@DeJob_official` 有专用规则,其他走通用规则)。
- 仅保留招聘类数据,写入 MySQL `structured_jobs`
3. `run_daily_incremental.sh`
- 每日调度入口。
- 运行前自动更新 `config.json` 时间窗口(滚动窗口)。
- 依次执行 `main.py``clean_to_structured.py`
## 2. 数据库表
### 2.1 原始层
- `messages`
- 原始消息存储(`source + message_id` 唯一)
- `sync_state`
- Telegram 增量抓取游标
### 2.2 清洗层
- `structured_jobs`
- 结构化岗位数据(`source + message_id` 唯一)
- `clean_state`
- 清洗增量检查点(`pipeline_name -> last_message_row_id`
## 2.3 字段级数据字典(详细)
### messagesTelegram 原始消息)
- `id`BIGINT, PK, 自增)
含义MySQL 行主键,清洗增量检查点使用这个字段。
示例:`530812`
- `source`VARCHAR
含义:消息来源标识(频道/群组),通常是 `@xxx`
示例:`@DeJob_official`
- `chat_id`BIGINT, 可空)
含义Telegram 实体 ID。
示例:`-1001234567890`
- `message_id`BIGINT
含义:该 source 内部的消息 ID。
约束:与 `source` 组成唯一键。
- `content`LONGTEXT, 可空)
含义:抓取到的消息正文(含非文本补充段,如 `MEDIA_JSON`)。
示例:招聘 markdown 文本 + `[MEDIA_TYPE] ...`
- `date`DATETIME
含义消息时间UTC
示例:`2026-02-26 09:31:10`
- `created_at`DATETIME
含义:该条记录写入数据库时间。
### sync_state抓取增量状态
- `source`VARCHAR, PK
含义:来源标识,与 `messages.source` 对应。
- `last_message_id`BIGINT
含义:该来源已抓取到的最大 message_id。
用途:下次抓取时只拉 `message_id > last_message_id`
- `updated_at`DATETIME
含义:该来源游标最近更新时间。
### structured_jobs清洗后结构化岗位
- `id`BIGINT, PK, 自增)
含义:结构化表主键。
- `source`VARCHAR
含义:来源标识。
示例:`@DeJob_official`
- `source_channel`VARCHAR, 可空)
含义:来源品牌/渠道归类。
示例:`DeJob`
- `parser_name`VARCHAR
含义:使用的解析器名称。
示例:`dejob_official` / `generic`
- `parser_version`VARCHAR
含义:解析器版本号,用于规则演进追踪。
示例:`v1`
- `chat_id`BIGINT, 可空)
含义:原始 Telegram chat_id。
- `message_id`BIGINT
含义:原始消息 IDsource 内)。
约束:与 `source` 组成唯一键。
- `message_date`DATETIME
含义原始消息时间UTC
- `job_type`VARCHAR, 可空)
含义:岗位类型标记。当前仅保留 `招聘`
示例:`招聘`
- `company_name`VARCHAR, 可空)
含义:公司/项目方名称。
示例:`88EX`
- `industry_tags_json`JSON
含义:行业/赛道标签数组。
示例:`[\"CEX\",\"Infra\"]`
- `company_intro`LONGTEXT, 可空)
含义:公司简介文本。
- `company_url`TEXT, 可空)
含义:公司官网/介绍页链接。
- `work_mode`VARCHAR
含义:办公模式。
枚举:`remote | onsite | hybrid | unknown`
- `job_nature`VARCHAR
含义:用工性质。
枚举:`full_time | part_time | contract | intern | freelance | unknown`
- `job_location_text`VARCHAR, 可空)
含义:主地点文本(首个地点)。
- `job_location_tags_json`JSON, 可空)
含义:地点标签数组。无地点时为 `NULL`(不是空数组)。
- `employment_type_raw`TEXT, 可空)
含义:原始“合作方式”文本,便于回溯规则。
示例:`🛵 合作方式:#全职 #远程 #吉隆坡`
- `position_name`VARCHAR, 可空)
含义:岗位主名称。
示例:`社区运营`
- `position_tags_json`JSON
含义:岗位标签数组。
示例:`[\"社区运营\",\"运营\"]`
- `salary_raw`TEXT, 可空)
含义:薪资原始字符串。
示例:`$1000 - $3000 / month`
- `salary_currency`VARCHAR, 可空)
含义:薪资币种(已识别)。
示例:`USD`
- `salary_min`BIGINT, 可空)
含义:薪资下限数值。
- `salary_max`BIGINT, 可空)
含义:薪资上限数值。
- `salary_period`VARCHAR, 可空)
含义:薪资周期。
枚举:`month | year | day | NULL`
- `responsibilities_json`JSON
含义:岗位职责数组(按条目拆分)。
- `requirements_json`JSON
含义:岗位要求数组(按条目拆分)。
- `apply_email`VARCHAR, 可空)
含义:投递邮箱。
- `apply_telegram`VARCHAR, 可空)
含义:投递 Telegram 用户名。
示例:`@lulu_lucky1`
- `job_source_url`TEXT, 可空)
含义:岗位来源原文链接(如 DeJob 详情页)。
- `body_text`LONGTEXT
含义:清洗后的主体文本(去除部分技术元段)。
- `raw_content`LONGTEXT
含义:原始消息内容快照(用于审计/回刷)。
- `cleaned_at`DATETIME
含义:最近清洗/更新该条结构化记录的时间。
### clean_state清洗增量状态
- `pipeline_name`VARCHAR, PK
含义:清洗流程标识。
示例:`structured_cleaner_v1`
- `last_message_row_id`BIGINT
含义:已处理到的 `messages.id` 最大值。
用途:下次清洗只处理更大的 `messages.id`
- `updated_at`DATETIME
含义:检查点更新时间。
## 3. 配置文件说明config.json
关键字段:
- `sources`: Telegram 来源列表
- `time_window.enabled`: 是否启用时间窗口
- `time_window.start` / `time_window.end`: 抓取窗口(脚本会每日自动刷新)
- `daily_window_days`: 滚动窗口天数(当前默认 `2`
- `throttle`: 限频配置
- `enabled`
- `per_message_delay_sec`
- `between_sources_delay_sec`
- `mysql`: MySQL 连接配置
- `host`, `port`, `user`, `password`, `database`, `charset`
## 4. 运行方式
### 4.1 手动运行
```bash
uv run main.py
uv run clean_to_structured.py
```
### 4.2 每日定时(推荐)
脚本:`run_daily_incremental.sh`
示例 crontab每天 01:10
```cron
10 1 * * * /home/liam/code/python/jobs_robots/run_daily_incremental.sh
```
日志文件:
- `logs/app.log`
- `logs/clean_to_structured.log`
- `logs/daily_job.log`
## 5. 增量策略
### Telegram 抓取增量
- 依据 `sync_state.last_message_id`
- 每个来源独立增量
### 清洗增量
- 依据 `clean_state.last_message_row_id`
- 每次只处理 `messages.id > checkpoint`
- 成功后更新 checkpoint
## 6. 字段约定(结构化就业类型)
`structured_jobs` 使用拆分字段,不再依赖 `employment_type_json`
- `work_mode`: `remote | onsite | hybrid | unknown`
- `job_nature`: `full_time | part_time | contract | intern | freelance | unknown`
- `job_location_text`: 主地点文本
- `job_location_tags_json`: 地点数组(无地点为 `NULL`
- `employment_type_raw`: 原始“合作方式”行
## 7. 常见问题
1. 为什么当天凌晨跑出来窗口看着不对?
- 当前滚动窗口按 UTC 日期更新。如果需要按本地时区(如 Asia/Shanghai可再改。
2. 为什么清洗没有新增?
-`clean_state` 检查点是否已经到最新。
-`messages` 是否有新数据。
3. 为什么 MySQL 报字段超长/类型错误?
- 优先看对应脚本日志,字段已做多数保护;若仍报错,保留错误堆栈并反馈。
## 8. 协作建议
- 改规则时优先只改对应来源 parser避免影响全局。
- 改字段前先确认 `structured_jobs` 兼容性与迁移策略。
- 所有定时行为以 `run_daily_incremental.sh` 为统一入口,避免多处调度冲突。