ServiziDigitali24SRL/lead-scraper-agent
GitHub: ServiziDigitali24SRL/lead-scraper-agent
一款开源的 B2B 潜在客户挖掘 Agent,通过 15 阶段的严格流水线实现从客户画像访谈、公开数据源发现到多渠道信息富化与导出的全自动化流程。
Stars: 0 | Forks: 0
# 🕵️ Lead Scraper Agent
**许可证:** MIT · **要求:** Python 3.10+ · **付费设置:** 无
## ⚡ 快速开始
```
git clone https://github.com//lead-scraper-agent.git
cd lead-scraper-agent
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
# (可编辑)使用 CLI 命令 `lead-scraper` 安装
pip install -e . # runtime; per i test: pip install -e ".[dev]"
# 启动 agent:开始交互式问答,然后是 15 个阶段
lead-scraper # equivalente a: python -m agent
```
无需修改任何配置文件:开箱即用。
`.env` 是**可选的**,仅供想要扩大数据量的用户使用(参见 § 扩展)。
### 想在不接触网络的情况下查看 pipeline 吗?
```
python -m examples.offline_demo
```
它使用本地 fixture(一个目录列表 + 三个
虚假主页)和 mock 的 DNS resolver 执行**全部 15 个阶段**:
**零网络请求**。这有助于理解 discovery → qualification → enrichment → export 的完整流程。
## 🧭 工作原理(15 阶段 pipeline)
pipeline 被划分为 **15 个原子阶段**。每个阶段都有明确的**输入要求**,
在数据库/磁盘上产生**已验证的 artifact** 和一个 **gate**:如果前一阶段未通过其自身的检查,当前阶段就无法开始。
这可以防止“压缩”或跳过步骤(即使是修改该工具的 LLM 也无法绕过)。
| # | 阶段 | 已验证的 artifact | 退出 Gate |
|---|---|---|---|
| 0 | **PREFLIGHT** | `run_id`, 文件夹, DB init | DB 可写,存在 deps |
| 1 | **INTERVISTA** | `icp_profile.json` | 必填字段已填写 |
| 2 | **VALIDAZIONE ICP** | 通过 schema 验证的配置文件 | **显式**选择 `limit` |
| 3 | **QUERY MAPPING** | `queries.json` | ≥1 个 OSM tag + ≥1 个 keyword + seed directory |
| 4 | **PIANO DISCOVERY** | 来源计划 + 配额 | 配额与 `limit` 一致 |
| 5 | **DISCOVERY** | `raw_domains` (DB) | ≥1 个域名,状态已保存 |
| 6 | **DEDUP + CANONICAL** | `domains` (根域名) | 剩余 0 个重复项 |
| 7 | **FILTRO ESCLUSIONI** | 已移除社交/聚合平台 | exclude_list 中无任何域名 |
| 8 | **QUALIFICA (ICP)** | `qualified` (score ≥ min) | 子集已保存,count > 0 |
| 9 | **CODA ENRICHMENT** | `pending` 队列 | 队列 = 合格数量(仅限合格项!) |
| 10 | **FETCH (礼貌)** | 缓存中的 HTML | 已读取 robots,rate-limit 已激活 |
| 11 | **ENRICHMENT 25 METODI** | `candidates` (+ `source`) | 每个 candidate 都有一个 `source` |
| 12 | **VALIDAZIONE** | 已验证 MX + E.164 | `verified` 已填充 |
| 13 | **SCORING** | `role_score` | 每个 contact 都有一个 score |
| 14 | **COMPLIANCE** | 抑制列表 + 来源 | 排除 opt-out |
| 15 | **EXPORT** | `CSV` + `JSON` 位于 `./output/` | 文件已写入,行数 = 预期 |
每个阶段都会写入 **SQLite**,并为每条记录保存状态(`pending/done/failed/blocked`):
如果运行中断,会从特定阶段和未完成的记录处**恢复**(`--resume`)。
## 🚧 严格的 Guardrail(防捷径,以代码形式实现)
1. **每个阶段在磁盘/数据库上生成一个 artifact**,并带有明确的 `phase_status`。绝对没有仅在内存中进行的数据传递。
2. **入场 Gate**:每个阶段都会调用 `require_phase(N-1, "done")`。如果上游的 artifact 缺失或状态不是 `done` → 触发 `RuntimeError`,停止运行。
3. **禁止合并**:enrichment(阶段 11)**仅**从合格的队列中读取数据。无法对未通过 qualification 的域名进行 enrich(`queue_only_contains_qualified()` 会验证这一点)。
4. **顺序不可变**:`PHASES = [0..15]`。`--from N` / `--only N` 虽然可用于调试,但**仍会**检查上游的 artifact。
5. **由用户决定 limit**:如果 `target.limit` 不明确(来自访谈或 `--limit`),阶段 2 将失败。没有隐藏的默认值。
6. **幂等性**:基于 key 进行 upsert;重新运行不会导致数据重复。
7. 在每个阶段之后进行 **Checkpoint**(在继续之前进行 commit)。
8. **顺序测试**:`tests/test_pipeline_order.py` 证明跳过阶段会引发错误。否则工具无法通过测试(不是 "verde")。
## ⭐ Discovery — 搜索犬(优先处理站点目录)
最丰富的数据载体不是单个网站:而是**列出了大量站点的页面**(行业协会、商会、B2B 门户网站、专业名录、展会参展商名单、联盟成员名单)。
`discovery/directories.py` 是一个专门的 crawler,它会:
1. 根据您的 ICP **寻找 seed directory**(`query_mapper` 生成类似 `"elenco "`、`"associazione "`、`"albo "`、`"registro imprese "`、`" aziende site:."` 的查询)。
2. **跟踪目录列表**:在每个 seed 上,提取所有指向企业域名的外部链接(排除内部/导航链接)。
3. **受控的递归**,深度可达 `--directory-depth`(默认为 2),并使用已访问 URL 的集合来避免循环。
4. **分页扩展**:处理 `?page=2`、"下一页"、load-more、A-Z 字母索引。
5. **目录识别**:如果一个页面链接到许多不同的外部域名,则认为它是一个列表 → 宝藏矿脉,提取所有内容。
其他来源将补充该数据级联,每个来源都有自己的配额(阶段 4):
| 来源 | 优势 | 需要的 Key |
|---|---|---|
| **directories.py** ⭐ | 网站列表,针对特定行业极其密集 | 否 |
| **osm.py** (Overpass) | 本地企业,通常已内置 `phone` | 否 |
| **wikidata.py** (SPARQL) | 按行业 + 国家/地区结构化的企业 | 否 |
| **commoncrawl.py** | 针对高数量的原始数据量 | 否 |
| **ddg.py** (DuckDuckGo HTML) | 针对 keyword 的精细化结果 | 否 |
所有数据都被标准化为**根域名**(`tldextract`),并进行全局去重;社交/市场/聚合平台(facebook、instagram、tripadvisor、thefork、justeat、deliveroo、amazon…)均被**剔除**。
## 🎤 阶段 1 — 访谈
启动时,agent 会通过示例逐一询问:
1. **你是谁?** 你卖什么。
2. **Buyer persona?** 决策者角色 + 企业规模。
3. **行业/类别?**
4. **国家?**(必填,ISO 格式)。
5. **城市/地区?**(可选)。
6. **需要多少个 lead?** — **由你决定,没有隐藏的默认值:**
```
[1] ~500 veloce, minuti (prova/test)
[2] ~5.000 ore, gratis e gentile
[3] ~50.000 mezza giornata / giorno
[4] 100k-300k più giorni gratis, o ore con proxy/SERP (.env)
[5] personalizzato -> inserisci il numero
```
如果你不选择,agent 会**要求**你提供该值。输出 → `icp_profile.json`。
你也可以跳过访谈:`python -m agent --profile icp_profile.json`。
## 🔎 阶段 11 — 25 种 enrichment 方法
全部免费,按优先级级联执行;每个 contact 上都会保留 `source` 追踪。
**A · 在已下载的 HTML 中**
`/contatti /impressum /about` · `mailto:` · JSON-LD · Cloudflare cfemail (XOR 解码) ·
去混淆 `[at]/[dot]` + 实体 + unicode · `tel:` · Microdata/RDFa · hCard ·
OpenGraph · 注释/`data-*` · 图片 alt/title
**B · 同一站点的其他页面**
深层页面 `/team /staff /legal` · sitemap.xml + robots.txt(递归 sitemapindex) · security.txt/humans.txt · privacy/DPO · careers · 链接的 PDF/DOCX(提取文本) · JS/CSS bundle · vCard/RSS
**C · 外部公开来源(无需 key)**
Wayback CDX · crt.sh (subdomains) · Overpass(通过 `website` tag 获取电话) ·
Common Crawl index · GitHub(公开 commit 中的 email)
**D · 推断 + 验证**
根据页面上找到的姓名进行 pattern-guess `nome.cognome@`,并进行 **MX** 验证(在 `--enable-guess` 下,**绝不**发送 SMTP)。
## ✅ 验证、scoring、输出
- **Email:** 验证域名的 **MX** 记录(无 SMTP 探测 → 避免黑名单)。
- **电话:** 使用 `phonenumbers` 规范化为 **E.164** 格式。
- **Contact score:** 一个 `mario.rossi@`(决策者)的权重高于通用的 `info@`。
位于 `./output/` 的最终 CSV:
```
domain,company_name,source_discovery,kind,value,source_methods,confidence,verified,
status,exportable,guessed,inferred,role_score,source_url,page_url,found_at,last_seen
```
- **`status`**:`valid` / `role` / `no_mx` / `catch_all` / `invalid` / `disposable`。
- **`exportable`**:仅对已验证且可操作的 lead 为 `true` → 在此字段上进行筛选以用于 CRM 导入。
- **`inferred`**:如果地址是推断出来的(pattern-guess)而不是直接找到的,则为 `true`。
- **`source_url` + `last_seen`**:每个数据的来源均可审计。
找到但无联系信息的企业仍会被记录(`value` 为空的行) → 这样你就可以衡量按来源/行业划分的**真实覆盖率**。也支持导出 JSON。
可选的 CRM 插件(`export/plugins/`)默认为 **OFF**。
## ⚙️ 主要 Flag
| Flag | 默认值 | 功能 |
|---|---|---|
| `--profile FILE` | — | 使用现有的 `icp_profile.json`(跳过访谈) |
| `--limit N` | — | 限定要 enrich 的合格域名数量(必须显式指定!) |
| `--resume` | off | 从 SQLite 状态恢复中断的运行 |
| `--concurrency N` | 5 | 全局并发请求数 |
| `--rate SEC` | 1.0 | 向同一 host 发送请求的最小间隔秒数 |
| `--directory-depth N` | 2 | directory 的递归深度 |
| `--enable-guess` | off | 启用第 25 种方法(带 pattern 推断 + MX 的 pattern-guess) |
| `--smtp` | off | 启用 SMTP 验证(RCPT + catch-all)。⚠️ 端口 25 经常被屏蔽 → `unknown`;有被列入黑名单的风险,建议在代理后使用 |
| `--gdpr` | off | GDPR 模式:强制执行 `robots.txt`,记录合法依据,保证来源可追溯 |
| `--skip GROUP` | — | 跳过一组 enrichment 方法 (A/B/C/D),可重复使用 |
| `--skip-source SRC` | — | 跳过 discovery 来源,可重复使用 |
| `--from N` / `--only N` | — | 调试:从阶段 N 开始 / 仅执行阶段 N(验证上游 artifact + 配置 hash) |
| `--ignore-robots` | off | (不推荐)忽略 robots.txt(在 `--gdpr` 下被忽略) |
| `--out DIR` | ./output | 输出文件夹 |
### 反馈循环(退信账本)
记录营销活动的真实结果:该工具会学习哪些方法有效,并且不会重新导出已发生退信的地址。
```
lead-scraper-ledger record --db output/state.sqlite \
--value mario.rossi@acme.it --outcome bounce --method D:pattern-guess
lead-scraper-ledger stats --db output/state.sqlite # hit-rate per metodo
```
## 🚀 扩展(可选,`.env`)
默认情况下,agent 是**缓慢且温和的**(尊重目标 host,支持断点续传)。为了扩大数据量,请将 `.env.example` 复制到 `.env`:
```
PROXY_POOL= # aggira i rate-limit degli host
SERP_API_KEY= # discovery più rapida (rimpiazza Bing, ritirata ago-2025)
LLM_API_KEY= # query_mapper adattivo per settori non pre-mappati
CRM_PLUGIN= # es. "example" per attivare uno stub di export
```
这些都不是必需的:如果缺失 = 100% 免费模式。
## ⚖️ 合规性(在欧盟地区上线前请先阅读)
此工具收集的是**公开的联系数据**。在欧盟,将其用于 B2B 的主动营销需要**合法依据(合法利益)**和 **opt-out** 机制。agent 会为你提供帮助:
- 每条记录均保存**来源**(discovery 来源 + 提取方法)。
- **抑制列表**(`compliance/suppression.txt`):始终排除。
- 默认遵守 `robots.txt`;具有可识别且实的 UA(从不伪造 Googlebot);每个域名最多抓取 40 页;验证 MX,**不进行 SMTP RCPT**。
⚠️ 使用的法律责任仍由运营者承担。本文不构成法律建议。
## 🧱 仓库结构
```
lead-scraper-agent/
agent/
__main__.py # entrypoint CLI (python -m agent)
interview.py # Fase 1 -> icp_profile.json
query_mapper.py # ICP -> tag OSM + keyword + query + seed directory
core/
orchestrator.py # esegue PHASES[0..15] in ordine, applica i gate
phases.py # enum fasi + require_phase() (GUARDRAIL)
db.py # SQLite: stato per record/fase, resume, upsert idempotente
fetcher.py # httpx async: robots-aware, rate-limit, retry backoff, UA onesto
config.py # .env opzionale (proxy/SERP/LLM -> spenti di default)
discovery/
directories.py ⭐ # crawler di ELENCHI DI SITI
osm.py wikidata.py commoncrawl.py ddg.py base.py
qualify/
scorer.py # dominio vs ICP -> tiene solo i match
enrich/
extractors/ # i 25 metodi: group_a/b/c/d.py + common.py
validate.py # MX + phonenumbers E.164
score.py # decisore vs info@
export/
to_csv.py to_json.py plugins/ # plugin CRM OFF di default
compliance/
suppression.py suppression.txt
examples/
offline_demo.py # run completo su fixture, zero rete
tests/ # test_pipeline_order.py (gate) + cfemail/deobf/sitemap/phones/dedup/e2e
.env.example requirements.txt LICENSE (MIT) README.md
```
## 🤝 贡献
最有用的扩展点是 `agent/query_mapper.py` 中的 **行业 → OSM tag / keyword / seed directory 映射**:映射的垂直领域越多,你挖出的企业就越多。欢迎提交 PR。
标签:B2B销售, Python, Terrascan, URL抓取, 数据增强, 数据抓取, 无后门, 潜在客户挖掘, 自动化代理, 逆向工具