# TG Lead Scraper 系统指南 > 最后更新: 2026-04-08 > 代码位置: `23.95.10.148:/opt/tg-lead-scraper/` > 前端位置: `/Users/admin/claude/tg-lead-scraper-frontend/` --- ## 一、系统概览 **一句话**: 从 Telegram 频道/网页/搜索结果/GitHub 自动挖掘商户联系方式,清洗去重验证,最后按质量打分。 **核心能力**: 输入是"种子频道"和"关键词",输出是**可直接外呼的商户清单**(带 TG 用户名、网站、邮箱、电话、行业标签、质量分)。 --- ## 二、数据模型(输入 → 输出) ``` 输入源 ├── managed_seeds TG 频道起点(当前: 1 个 bbs3000) ├── managed_keywords 搜索关键词(当前: 108 个) └── industry_rules.yaml 行业分类规则 ↓ 7 阶段 Pipeline 处理 ↓ 中间表 ├── channels 发现到的 TG 频道(当前: 1907+ 条) ├── nav_sites 候选导航网页(当前: 646 条) └── merchants_raw 待清洗商户(当前: 1946 条) ↓ cleaner 三关过滤 ↓ 最终输出 └── merchants_clean 可用商户(valid/invalid/bot/duplicate/group) ``` --- ## 三、7 阶段 Pipeline 完整流程 ### 全景图 ``` Phase 1 discover → Phase 2 search → Phase 3 github ↓ ↓ ↓ (snowball 裂变) (关键词搜索) (GitHub README 挖) ↓ ↓ ↓ channels 表 nav_sites + channels channels 表 ↓ ↓ ↓ ┌─────────────┴──────────────┐ ↓ ↓ Phase 4 scrape Phase 5 crawl (TG 消息抓取) (网页爬取) ↓ ↓ merchants_raw merchants_raw ↓ ↓ └──────────┬─────────────────┘ ↓ Phase 6 clean (清洗三关) ↓ merchants_clean ↓ Phase 7 score (打分) ↓ merchants_clean.quality_score ``` ### Phase 1: discover (频道发现) - **文件**: `core/snowball.py` - **做什么**: 从种子频道出发,裂变发现更多相关频道 **流程**: 1. 从 `managed_seeds` 拿起点(比如 `@bbs3000`) 2. 用 TG 账号进入每个种子,读最近 100 条消息 3. 提取消息里的 forward_from 和 TG 推荐频道 4. 把新发现的频道当第二层种子继续裂变 5. 最多 3 层(max_depth),每层 200 个上限,全局 500 个上限(防指数爆炸) 6. 每个频道间 sleep 5 秒 **输入**: `managed_seeds` + TG 账号 **输出**: 写入 `channels` 表,source='seed' 或 'snowball' **瓶颈**: 单账号 FloodWait(历史上裂变 200 个就被限速) ### Phase 2: search (搜索引擎) - **文件**: `core/search_engine.py` - **做什么**: 用关键词去 Google 搜索,把结果里的 TG 频道和导航站分拣入库 **流程**: 1. 从 `managed_keywords` 拿 108 个关键词(如"机场推荐"、"发卡网") 2. 对每个关键词调 Serper API(Google Search) 3. 翻页多次,每页 10 条结果 4. 识别结果 URL: - 是 `t.me/xxx` → 写 `channels` 表 - 是导航站(domain 含 nav/list/catalog)→ 写 `nav_sites` 表 - 是博客/产品官网/社交媒体 → 丢弃 5. 关键词间 sleep 若干秒避免限速 6. Serper 失败可 fallback 到 DuckDuckGo **外部依赖**: Serper API(**当前 key 已失效**,400 Bad Request) **输出**: `channels` + `nav_sites` ### Phase 3: github (GitHub 采集) - **文件**: `core/github_crawler.py` - **做什么**: 搜 GitHub 仓库的 README 里的 TG 链接 **流程**: 1. 用 query 搜 GitHub repo(按 star 排序) 2. 下载每个 repo 的 README.md 3. 要求 README 前 5000 字含中文,过滤英文项目 4. 正则匹配 `t.me/xxx` 链接 5. 链接前后 200 字必须含中文才算有效(过滤通用 bot 和 proxy 频道) 6. repo 间 sleep 2s,query 间 sleep 5s **外部依赖**: GitHub Search API(**无 token**,rate limit 10 req/min) **输出**: `channels` 表,source='github' ### Phase 4: scrape (TG 消息采集) ← 最慢最贵的阶段 - **文件**: `core/scraper.py` - **做什么**: 真正进入 TG 频道,读历史消息,提取商户 **流程**: 1. 从 `channels` 表拿 status='pending' 的频道 2. 对每个频道: - `get_entity(channel_username)` 解析频道 - **GLM 相关性评估**:频道名+简介+成员数传给 GLM 判"是不是商户相关",不相关直接 skip - 读频道简介(about) - 读置顶消息(limit=20) - 遍历历史消息(limit=500,断点续传用 `last_message_id`) 3. 每条消息: - `MessageService` 系统消息跳过 - 非中文跳过 - 先用**正则** `extract_contacts_enhanced` 快速判是否含联系方式 - 有联系方式 → 调 **GLM 精准解析** 提取 merchant(failover 到正则) - 写入 `merchants_raw` 4. 消息间 sleep `delay_message`,频道间 sleep `delay_channel` **输入**: `channels` 表 + `managed_settings.tg_scraper.*` **输出**: `merchants_raw` **瓶颈**: 单账号每 200-500 次 `get_entity` 触发 FloodWait 10-24 小时 ### Phase 5: crawl (网页爬取) - **文件**: `core/web_crawler.py` + `core/nav_filter.py` - **做什么**: 爬导航站,从网页 HTML 提取商户 **流程**: 1. 从 `nav_sites` 表拿 status='pending' 的网页 2. **预过滤** `rule_filter(url)`: - 黑名单域名(t.me/twitter/google 等 80 个)→ filtered - 黑名单扩展名(.apk/.zip/.pdf 等 40 种)→ filtered - 黑名单路径(`/api/`、`/login/`、`?ref=` 等)→ filtered - 正向信号(含 nav/directory/catalog)→ valid - 都不确定 → 弱候选,进 GLM 二次过滤 3. **GLM 二次过滤**:问 GLM "这个 URL 是不是导航站",置信度 ≥0.6 才放行 4. 对通过的网页: - 用 requests → cloudscraper → playwright **三层 fallback** 抓 HTML - HTML 前 5000 字非中文直接跳过 - BeautifulSoup 解析出商户链接(CSS 选择器可配置) 5. 对每个商户链接: - 如果直接带 `@tg_username` → 走 TG 入库路径 - **t.me 死号预检**(新加的):抓 t.me/{username} 网页,没头像就 drop - 活号 → 写 `merchants_raw` - 如果只有网站链接 → 爬商户首页 + `/contact`、`/about` 等子页,用 extractor 提取联系方式 **输入**: `nav_sites` 表 **输出**: `merchants_raw` ### Phase 6: clean (数据清洗) ← 三关过滤 - **文件**: `core/cleaner.py` - **做什么**: 对 raw 商户做黑名单过滤、去重、真实性验证 **流程**: #### 第一关: `_filter_blacklist`(本地,秒级) - 黑名单 username(26 个系统 bot + `xxxbot` 后缀)→ 标 bot - 邀请链接哈希(16-24 位 base64 + 高熵)→ 标 invalid - `original_message` 非空且不含中文 → 标 invalid #### 第二关: `_deduplicate`(本地,秒级) - 同 username 多条记录,按信息丰富度打分(有 website/email/phone 加分) - 保留最丰富的一条,副本迁 clean 桶标 duplicate - 合并所有 source 链接到 keeper #### 第三关: `_verify_merchant`(最贵,TG API) - 调 Telethon `client.get_entity(username)` 去 TG 服务器验证 - 返回 User 且非 bot → **valid**(顺手拿 first_name/last_name/is_premium/last_online/active_level) - 返回 Bot → bot - 返回 Channel/Chat → group - UsernameNotOccupied → invalid - FloodWait ≤60s → 重试 - FloodWait >60s → 切账号 - **FloodWait >300s → 直接 break 整轮**(修复后) **输入**: `merchants_raw` status='raw' **输出**: `merchants_clean` (valid/invalid/bot/duplicate/group) **瓶颈**: 同 Phase 4,TG API 节流严 ### Phase 7: score (商户评分) - **文件**: `core/scorer.py` - **做什么**: 对 clean 桶的商户按 6 维度加权打分 **6 个维度**(总权重 1.0): | 维度 | 权重 | 规则 | |---|---|---| | `member_count` | 0.25 | <100→10 / <1k→30 / <1w→50 / <10w→80 / ≥10w→100 | | `premium` | 0.15 | 是 TG Premium→100,不是→0 | | `activity` | 0.25 | active→100 / moderate→50 / inactive→20 | | `multi_source` | 0.20 | 被多个来源发现→100 / 3+→70 / 2→40 / 1→10 | | `has_website` | 0.10 | 有→100,没有→0 | | `has_email` | 0.05 | 有→100,没有→0 | **可选第 7 维**: GLM 内容质量打分(默认关闭,因为 GLM API 会 hang) **输入**: `merchants_raw` + `merchants_clean` 两表 **输出**: 写 `merchant.quality_score`(0-100) --- ## 四、核心模块对照表 | 模块 | 文件 | 主要职责 | |---|---|---| | snowball | `core/snowball.py` | Phase 1 频道裂变 | | search_engine | `core/search_engine.py` | Phase 2 Serper/DuckDuckGo 搜索 | | github_crawler | `core/github_crawler.py` | Phase 3 GitHub README 挖 TG 链接 | | scraper | `core/scraper.py` | Phase 4 TG 消息采集 | | web_crawler | `core/web_crawler.py` | Phase 5 导航站爬取 | | nav_filter | `core/nav_filter.py` | 导航站识别过滤器 | | cleaner | `core/cleaner.py` | Phase 6 清洗三关 | | scorer | `core/scorer.py` | Phase 7 评分 | | extractor | `core/extractor.py` | 联系方式提取(正则+GLM) | | classifier | `core/classifier.py` | 行业分类(关键词+GLM) | | account_manager | `core/account_manager.py` | 多 TG 账号轮换 + FloodWait 管理 | | pipeline | `core/pipeline.py` | Pipeline 状态管理 | | task_manager | `core/task_manager.py` | 任务调度 + 看门狗 + 断点续传 | | config_service | `core/config_service.py` | 配置服务 (managed_settings) | | database | `core/database.py` | ORM + promote_merchant helper | | tme_validator | `core/tme_validator.py` | t.me 网页死号预检(新加) | --- ## 五、具体能实现的功能清单 ### 📥 数据采集能力 - ✅ 从种子 TG 频道裂变发现新频道(snowball,最多 3 层,500 个上限) - ✅ 用关键词从 Google 搜索引擎发现 TG 频道和导航网页 - ✅ 从 GitHub 仓库 README 挖 TG 链接 - ✅ 抓取 TG 频道历史消息(支持断点续传,单频道最多 500 条) - ✅ 抓取 TG 频道简介 + 置顶消息 - ✅ 爬取导航网站 HTML(三层 fallback: requests / cloudscraper / playwright) - ✅ 爬取商户官网 + 常见子页(`/contact`, `/about`, `/关于我们`) ### 🧠 智能识别能力 - ✅ GLM 频道相关性评估(过滤掉不是商户的频道) - ✅ GLM 消息商户解析(提取非标准格式如"加V:xxx"、"t点me/xxx") - ✅ GLM 行业分类(机场/发卡/成人等,可配置) - ✅ GLM 导航站识别(弱候选 URL 交 GLM 判断) - ✅ 正则提取联系方式(TG 用户名 / t.me 链接 / 邮箱 / 电话 / 网址) - ✅ 中文检测(非中文消息/商户名直接跳过) - ✅ 联系意图识别(文本里含客服/购买/咨询等关键词标记) ### 🧹 数据清洗能力 - ✅ 26 条系统 bot 黑名单过滤 - ✅ 邀请链接哈希识别(base64 + 高熵检测) - ✅ 80+ 域名黑名单(社交媒体/大站/政府站) - ✅ 40+ 扩展名黑名单(apk/zip/pdf 等非网页资源) - ✅ 非中文内容过滤 - ✅ 同 username 去重 + 信息合并 - ✅ **t.me 网页死号预检**(新加,16% 死号率,100% 准确) - ✅ Telethon 真实性验证(拿头像/显示名/premium/最后在线时间) - ✅ FloodWait 智能处理(短等待重试,长等待切账号,超 300s 跳过) ### 📊 评分与分类 - ✅ 6 维度加权打分(成员数/Premium/活跃度/多来源/网站/邮箱) - ✅ 行业标签(关键词匹配 + GLM) - ✅ 活跃度分级(active <3 天 / moderate / inactive >30 天) - ✅ Premium 用户识别 ### 🎛️ 运维能力 - ✅ 多 TG 账号轮换(account_manager) - ✅ FloodWait 自动切换账号 - ✅ 代理池(proxy_mgr,给 GitHub/requests 用) - ✅ 断点续传(`channels.last_message_id`) - ✅ 任务状态机(`pipeline_state.json` 单一权威) - ✅ 任务 stop / force_stop 接口 - ✅ 每阶段独立运行(任意阶段都能单独跑) - ✅ 全链路 pipeline(7 阶段自动调度) - ✅ skip_phases 参数(跳过指定阶段) - ✅ 测试模式(item_limit / message_limit 限流) - ✅ 实时日志 + 进度回调 - ✅ 任务并发保护(同类型不能叠跑) ### 🖥️ 前端能力 - ✅ 任务启动/停止/预览 - ✅ 数据总览仪表盘 - ✅ 采集数据表格(默认按 created_at desc) - ✅ 种子频道管理(增删改查) - ✅ 候选网页管理 - ✅ 系统配置(种子 / 关键词 / 流水线阶段 tab) - ✅ 系统状态(运行日志 / 监控) - ✅ 任务历史(7 个阶段独立按钮) ### 🗄️ 数据管理 - ✅ merchants_raw / merchants_clean 桶分离 - ✅ Merchant 视图兼容(只读 UNION) - ✅ promote_merchant helper(原子跨桶迁移,保留 id) - ✅ managed_settings 11 条运行参数(hot-reload 和 new_task 两种生效级别) - ✅ config_revisions 审计日志 - ✅ 自动备份(`backup/` 目录 14+ 份历史快照) --- ## 六、可用任务类型(API `/api/tasks/start`) | task_type | 用途 | 独占 | |---|---|---| | `full` | 跑完整 pipeline(7 阶段) | 是(全局空闲才能跑) | | `discover` | Phase 1 单跑 | 否 | | `search` | Phase 2 单跑 | 否 | | `github` | Phase 3 单跑 | 否 | | `scrape` | Phase 4 单跑 | 否 | | `crawl` | Phase 5 单跑 | 否 | | `clean` | Phase 6 单跑 | 否 | | `score` | Phase 7 单跑 | 否 | **参数**: - `target`: 可选目标(频道名 / 关键词) - `test_run`: 测试模式 - `item_limit`: 每轮处理条数上限 - `message_limit`: 每频道消息上限 - `skip_phases`: 每任务覆盖全局默认的 skip_phases --- ## 七、外部依赖 | 依赖 | 用途 | 状态 | |---|---|---| | Telethon + TG API | Phase 4 / Phase 6 | account_01/02 FloodWait 10-20h | | Serper API (Google Search) | Phase 2 | **key 失效,400 错误** | | GitHub Search API | Phase 3 | 无 token,10 req/min | | GLM API | 频道评估 / 消息解析 / 行业分类 / 导航站判断 | 正常 | | t.me 网页 | 死号预检 | 正常,无限速 | | requests / cloudscraper / playwright | Phase 5 | 正常 | --- ## 八、当前 managed_settings(11 条) | key | value | type | effect_level | |---|---|---|---| | `pipeline.skip_phases` | `["scrape"]` | json | new_task | | `pipeline.checkpoint_interval` | 30 | int | runtime | | `tg_scraper.message_limit_per_channel` | 500 | int | runtime | | `tg_scraper.delay_per_verify` | 3.0 | float | runtime | | `clean.timeout_seconds` | 3600 | int | runtime | | `search.timeout_seconds` | 3600 | int | runtime | | `snowball.max_channels_per_layer` | 200 | int | runtime | | `snowball.max_channels_total` | 500 | int | runtime | | `tme_validator.enabled` | true | bool | runtime | | `tme_validator.rate_per_min` | 60 | int | runtime | | `tme_validator.concurrency` | 10 | int | runtime | **effect_level 说明**: - `runtime`: 改了立即生效 - `new_task`: 只对下一个新任务生效 --- ## 九、当前数据基线 (2026-04-08) | 表 | 记录数 | |---|---| | managed_seeds | 1(`bbs3000`) | | managed_keywords | 108 | | managed_settings | 11 | | channels | 1907+ | | nav_sites | 646(pending=0, scraped=156, filtered=490) | | **merchants_raw** | **1946**(raw=1571, group=308, glm_parsed=67) | | **merchants_clean** | **125**(valid=62, invalid=34, duplicate=27, bot=2) | | scored 商户 | 448 | --- ## 十、已知限制 1. **单账号/双账号 TG API 限速严重** — Phase 4/6 容易卡 13-20 小时 2. **Serper API key 失效** — Phase 2 完全失效 3. **GitHub 无 token** — Phase 3 rate limit 10 req/min 4. **cleaner `_verify_merchant` 没有独立节流** — 是触发 FloodWait 的根因 5. **raw 桶新数据(web 来源)merchant_name 污染** — site_scraper 把 HTML 标签写入 merchant_name --- ## 十一、系统能力边界(不能做什么) - ❌ 不能绕过 TG 限速(必须等 FloodWait 或加账号) - ❌ 不能解析 TG 加密群组内容(只能公开频道) - ❌ 不能抓 TG Premium 专享内容 - ❌ 不能识别图片/视频里的联系方式(只处理文本) - ❌ 不能通过 TG 建立会话发消息(纯读) - ❌ 不能处理 JS 渲染的单页应用(除非 Playwright fallback) - ❌ 不能自动注册 TG 账号(需要手机号+验证码) --- ## 十二、2026-04-07/08 修复的 9 个 bug | # | 修复 | commit | |---|---|---| | 1 | crawl GLM 超时 + 进度日志 | `6380f43` | | 2 | cleaner FloodWait 无限 sleep | `6380f43` | | 3 | score use_glm 强制 True hang | `6380f43` | | 4 | merchants 默认排序按 created_at desc | `04f2d7e` | | 5 | cleaner 非中文规则误杀 web 数据 | `625b0e3` | | 6 | config.py bootstrap GET 回填 | `21e6e29` | | 7 | snowball 加每层 200 + 总数 500 上限 | `50b580d` | | 8 | t.me 死号预检模块 | `027895a` | | 9 | tme_validator lint 修复 | `f0456c7` | --- ## 十三、运维与回滚 ### 服务器信息 - **IP**: 23.95.10.148 (RackNerd VPS) - **SSH**: `root / 4e8F2McWxRC7iEa0b4` - **代码**: `/opt/tg-lead-scraper/` - **虚拟环境**: `venv/` - **服务**: `tg-lead-scraper-api` (systemd) - **对外端口**: 8134 (Nginx 反代 → 127.0.0.1:8900) - **API 认证**: `admin / admin` (Basic Auth) ### 日志位置 - **Pipeline 执行日志**: `/opt/tg-lead-scraper/logs/scraper_YYYY-MM-DD.log` - **HTTP 日志**: `journalctl -u tg-lead-scraper-api` - **Pipeline 状态**: `/opt/tg-lead-scraper/data/pipeline_state.json` ### DB 备份 位于 `/opt/tg-lead-scraper/backup/`: - `leads_pre_refactor_20260407_151503.db` — 7 批重构前 - `leads_pre_batch5_20260407_162334.db` — 表拆分前 - `leads_pre_rollback_20260408_041132.db` — cleaner 规则回滚前 - `leads_pre_dead_purge_20260408_094432.db` — 死号清理前 - `seeds_backup_20260407_142211.json/.sql` — 原 17 个种子专项备份 - `seeds_full_snapshot_20260407_142211.db` — 种子专项全库快照 ### 前端部署 ```bash cd /Users/admin/claude/tg-lead-scraper-frontend npm run build rsync -az --delete dist/ root@23.95.10.148:/var/www/tg-lead-scraper/ ``` ### Git tag - `v7-crawl-clean-score-fixes` — 2026-04-08 凌晨修复基线 ### 紧急回滚 ```bash # 后端代码回滚到 tag cd /opt/tg-lead-scraper && git reset --hard v7-crawl-clean-score-fixes systemctl restart tg-lead-scraper-api # DB 回滚 cp backup/leads_pre_refactor_20260407_151503.db data/leads.db # 前端回滚 cd /Users/admin/claude/tg-lead-scraper-frontend git reset --hard npm run build rsync -az --delete dist/ root@23.95.10.148:/var/www/tg-lead-scraper/ ``` --- ## 十四、开发规范(重要) ### 写入 merchant 的规则 - **新增 merchant** → 只能用 `MerchantRaw(...)`,不能用 `Merchant(...)` - **修改 merchant status 跨桶**(raw→valid)→ 用 `promote_merchant()` helper - **修改 merchant status 桶内**(raw→glm_parsed)→ 直接改对应桶 ORM 对象 - **按 id 查询未知桶** → 用 `merchant_by_id(session, id)` - **只读聚合查询** → `session.query(Merchant)`(指向视图,零改动) ### Commit 规范 ```bash git -c user.email=refactor@local -c user.name=refactor commit -m "..." ``` ### 配置权威 - **种子**: `managed_seeds` 表(不是 config.yaml) - **关键词**: `managed_keywords` 表 - **运行参数**: `managed_settings` 表(通过 `/api/config/settings` 改) - config.yaml 里的 `seed_channels`/`keywords` 已废弃,启动时打警告