Metadata-Version: 2.5
Name: crawlerkeeper-agent
Version: 1.3.0
Summary: CrawlerKeeper 节点代理程序：节点注册、心跳上报、任务拉取与并行执行、部署同步与日志指标上报
Author: CrawlerKeeper Team
License-Expression: MIT
License-File: LICENSE
Keywords: agent,crawler,crawlerkeeper,distributed,spider
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: System :: Distributed Computing
Requires-Python: >=3.10
Requires-Dist: prometheus-client>=0.17
Requires-Dist: psutil>=5.9.0
Requires-Dist: python-daemon>=3.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: requests>=2.28.0
Description-Content-Type: text/markdown

# CrawlerKeeper Agent

爬虫节点代理程序，负责与主控系统通信，执行爬虫任务。

> **部署与升级操作手册**：PyPI 发布、服务器部署、版本升级的完整 SOP 属团队内部文档，不随发布包公开。

## 功能特性

- 节点自动注册
- 定时心跳上报（CPU/内存/磁盘）
- 任务自动拉取和执行
- **并行任务执行**：支持跨-Spider 并行，不同 Spider 的任务可同时执行
- **同一 Spider 串行**：通过 Semaphore 机制确保同一 Spider 的任务串行执行，避免冲突
- **容器资源感知**：根据容器 CPU 限制动态计算并发数
- **优雅关闭**：支持 SIGTERM/SIGINT 信号处理，任务排空不丢失
- 支持 Scrapy、Playwright、Selenium 爬虫框架
- 任务结果和日志上报
- YAML 配置文件支持
- 环境变量覆盖

## 目录结构

```
crawlerkeeper-agent/
├── src/
│   ├── crawlerkeeper/
│   │   ├── __init__.py
│   │   ├── __main__.py        # 命令行入口（pip 安装后的 crawlerkeeper-agent 命令）
│   │   ├── agent.py           # 主程序组装根
│   │   ├── config.py          # 配置管理
│   │   ├── client.py          # HTTP 客户端
│   │   ├── context.py         # 参数对象（执行/部署/日志上下文）
│   │   ├── heartbeat.py       # 心跳管理
│   │   ├── task.py            # 任务管理（轮询/抢占/线程池/优雅关闭）
│   │   ├── executor.py        # 爬虫执行器
│   │   ├── reporter.py        # 任务状态/结果/日志上报
│   │   ├── log_monitor.py     # 爬虫日志文件监控
│   │   ├── deployment.py      # 部署拉取
│   │   ├── deps.py            # 依赖安装（uv/npm）
│   │   ├── spider_pkg.py      # 爬虫代码包下载/校验/解压
│   │   ├── nodejs.py          # nvm/node 版本管理
│   │   ├── runtime_scanner.py # 节点运行时版本扫描
│   │   ├── metrics.py         # Prometheus 指标
│   │   ├── code_package.py    # ZIP 魔数校验工具
│   │   └── logger.py          # 日志管理
│   └── main.py               # 本地源码运行入口
├── config/
│   └── config.yaml           # 配置文件
├── pyproject.toml            # 依赖声明 + PyPI 打包配置
├── publish.sh                # 构建 + 发布脚本
├── bump_version.sh           # 版本号管理
├── run.sh                    # 本地启动脚本
└── sdk/
    └── python/              # 爬虫 SDK（独立发包到 PyPI）
        ├── crawlerkeeper_sdk/
        ├── pyproject.toml
        ├── publish.sh        # 构建 + 发布脚本
        └── migrate_spiders.py  # 爬虫项目迁移脚本
```

## 快速开始

### 1. 安装

```bash
# 方式一：从 PyPI 安装（推荐，服务器部署用）
pip install crawlerkeeper-agent
# 或
uv tool install crawlerkeeper-agent

# 方式二：源码运行（本地开发用）
uv sync
```

### 2. 配置

编辑 `config/config.yaml` 文件，配置主控系统地址：

```yaml
server:
  url: "http://localhost:8080"  # 主控系统地址

agent:
  name: "node-001"              # 节点名称
  ip: ""                        # 留空自动获取
  port: 9000                    # Agent 监听端口
  registration_token: ""        # 节点注册 Token（必填，从管理后台获取）

heartbeat:
  interval: 30                  # 心跳间隔（秒）

task:
  poll_interval: 10             # 任务拉取间隔（秒）
  work_dir: "./data/spiders"     # 工作目录
  # 最大并行任务数无需配置：默认按容器 CPU 自动计算，可用环境变量 AGENT_MAX_WORKERS 覆盖

logging:
  level: "INFO"
  file: "/var/log/agent.log"
```

### 3. 启动

```bash
# PyPI 安装方式（命令行入口）
crawlerkeeper-agent -c config/config.yaml

# 或模块方式
python -m crawlerkeeper -c config/config.yaml

# 源码运行（本地开发）
python src/main.py -c config/config.yaml

# 或使用启动脚本
./run.sh
```

### 4. 环境变量

也可以使用环境变量覆盖配置文件：

| 变量名 | 说明 |
|--------|------|
| AGENT_SERVER_URL | 主控系统地址 |
| AGENT_API_KEY | API 密钥 |
| AGENT_NAME | 节点名称 |
| AGENT_IP | 节点 IP |
| AGENT_PORT | Agent 监听端口 |
| AGENT_REGISTRATION_TOKEN | 节点注册 Token（必填） |
| AGENT_HEARTBEAT_INTERVAL | 心跳间隔（秒） |
| AGENT_TASK_POLL_INTERVAL | 任务拉取间隔（秒） |
| AGENT_TASK_EXECUTE_TIMEOUT | 单个任务执行超时（秒），默认 3600；设为 0 表示不限时 |
| AGENT_WORK_DIR | 工作目录 |
| AGENT_MAX_WORKERS | 最大并行任务数（覆盖自动计算，范围 2-8，优先级最高） |
| CONTAINER_CPU_LIMIT | 容器 CPU 限制（如 "2000m" 或 "4"），自动计算 max_workers |
| AGENT_LOG_LEVEL | 日志级别 |
| AGENT_LOG_FILE | 日志文件 |
| AGENT_UV_INDEX_URL | 爬虫依赖安装使用的 PyPI 索引地址；未设置时自动探测（pypi.org 可达走官方源，不可达回退清华镜像） |
| AGENT_UV_CACHE_DIR | uv 全局缓存目录；容器部署建议指向持久化路径，让重复部署命中缓存 |
| AGENT_UV_SYNC_TIMEOUT | uv sync 安装依赖超时（秒），默认 180 |

## 部署到服务器（免手动上传）

服务器上只需要 `run.sh` + `config/config.yaml` 两个东西，无需再手动上传安装包：

```
/opt/crawlerkeeper-agent/
├── run.sh
└── config/
    └── config.yaml
```

启动命令与源码模式完全一致：

```bash
./run.sh --cleanup_temp_files=on --daemon=on start
```

`run.sh` 会自动判断运行模式：

| 模式 | 判定条件 | 行为 |
|------|----------|------|
| 源码模式 | 脚本同目录存在 `src/main.py` | `uv sync` 后跑源码（本地开发） |
| 安装模式 | 脚本同目录没有源码 | 自动从 PyPI 安装 `crawlerkeeper-agent` 后运行（服务器部署） |

安装模式下的安装策略：已安装则直接复用；未安装则依次尝试 `uv tool install` → `pip install --user` → `pip install`，
装到 `~/.local/bin`（或 `uv tool` 的 bin 目录），失败时给出明确的手动安装命令。

相关环境变量：

| 变量名 | 说明 |
|--------|------|
| AGENT_PACKAGE | 安装的包名/版本，默认 `crawlerkeeper-agent`；可写 `crawlerkeeper-agent==1.0.1` 锁定版本 |
| AGENT_AUTO_INSTALL | 设为 `off` 关闭自动安装（未安装时直接报错提示） |
| AGENT_PYPI_INDEX_URL | 安装/升级时的 PyPI 索引地址；未设置时自动探测（官方源可达走 pypi.org，不可达回退清华镜像） |

### 部署目录文件落位

所有运行时文件都在 **run.sh 所在目录**（不会写进 site-packages 或其他位置）：

| 路径 | 说明 | 源码模式 | 安装模式 |
|------|------|----------|----------|
| `config/config.yaml` | 主控地址、节点 Token 等配置（需自行提供） | 手动放置 | 手动放置 |
| `logs/agent.log` | 运行日志 | 自动创建 | 自动创建 |
| `agent.pid` | 进程 PID（供 stop/restart 使用） | 自动创建 | 自动创建 |
| `.last_daemon_args` | 后台启动参数记忆 | 自动创建 | 自动创建 |
| `data/` | 爬虫代码与任务工作目录 | 自动创建 | 自动创建 |
| `.venv/` | 源码模式的 Python 虚拟环境 | 自动创建（`uv sync`） | 不创建 |

安装模式下 Agent 程序本体装在用户目录（`~/.local/bin` 或 `~/.local/share/uv/tools/`），不占部署目录。

### 版本升级

> 完整的发布 SOP、首次部署步骤、常见问题属团队内部文档，不随发布包公开。

发布新版本到 PyPI 后，服务器上一条命令完成升级（查 PyPI 最新版 → 对比本地版本 → 升级 → 自动重启）：

```bash
./run.sh upgrade            # 升级到 PyPI 最新版
./run.sh upgrade 1.0.2      # 升级到指定版本
```

行为说明：

- 已是最新版本时不做任何操作，也不会重启 Agent（避免服务中断）
- 本地版本高于目标版本时跳过，不做降级
- 升级成功后自动重启（仅当 Agent 正在运行）
- `AGENT_UPGRADE_DRY_RUN=on` 可预演：打印将要执行的安装命令但不实际安装、不重启

## 任务执行流程

1. **注册节点** - 启动时向主控注册，获取节点 ID 和 API Key
2. **心跳上报** - 定时上报节点资源状态
3. **拉取任务** - 定时从主控获取待执行任务
4. **同步代码** - 从主控下载爬虫文件
5. **安装依赖** - 自动安装爬虫所需依赖（Python 爬虫使用 `uv sync`，超时默认 180 秒，可通过 `AGENT_UV_SYNC_TIMEOUT` 调整；安装过程日志实时输出，便于定位慢在哪个包）
6. **执行爬虫** - 在本地执行爬虫脚本（超过 `AGENT_TASK_EXECUTE_TIMEOUT` 秒未结束的进程会被终止：先 SIGTERM 宽限 10 秒，仍存活则 SIGKILL，任务按 FAILED/「执行超时」上报）
7. **上报结果** - 上报任务执行结果和日志

> **存量长任务迁移提示**：默认超时 3600 秒（1 小时）。如有运行超过 1 小时的存量爬虫任务，
> 升级后会被超时终止，可在对应节点设置 `AGENT_TASK_EXECUTE_TIMEOUT=0` 关闭超时。

## 并行任务执行

Agent 支持跨-Spider 并行执行任务，提高多爬虫场景下的资源利用率。

### 三重去重机制

为防止同一 Spider 任务被重复执行，Agent 实现了三重保险：

1. **第一道防线（后端）**：后端根据 `excludeSpiderIds` 参数过滤，不返回已在执行的 Spider 的任务
2. **第二道防线（Agent `_running_spiders`）**：Agent 侧维护运行中 Spider 列表，防止重复提交
3. **第三道防线（Semaphore）**：每个 Spider 有独立的 Semaphore(1)，确保同一 Spider 任务串行执行

### 并发数计算

Agent 根据以下顺序确定最大并发数：

1. **环境变量 `AGENT_MAX_WORKERS`**（优先级最高）：直接指定，范围 [2, 8]
2. **环境变量 `CONTAINER_CPU_LIMIT`**：K8s 注入的容器 CPU 限制
   - 格式 `2000m` 表示 2 CPU
   - 格式 `4` 表示 4 CPU
3. **CPU 核心数**：`min(os.cpu_count(), 8)`，最小为 2

### 容器环境变量注入（Kubernetes）

在 K8s Pod 中通过 downward API 注入容器资源限制：

```yaml
env:
  - name: CONTAINER_CPU_LIMIT
    valueFrom:
      resourceFieldRef:
        resource: limits.cpu
```

## 优雅关闭

Agent 支持 SIGTERM 和 SIGINT 信号，实现优雅关闭。

### 信号处理

- **SIGTERM**：收到后进入 DRAINING 状态，停止接受新任务，等待运行中任务完成（最多 60 秒），然后退出
- **SIGINT (Ctrl+C)**：收到后立即进入 TERMINATING 状态，强制取消所有任务，然后退出

### 关闭状态机

```
RUNNING (正常)
    │
    │ stop(graceful=True) 或 SIGTERM
    ▼
DRAINING (停止接受新任务，等待运行中任务完成)
    │
    │ 超时或 stop(graceful=False) 或 SIGINT
    ▼
TERMINATING (强制取消未完成任务)
    │
    │ 所有任务完成
    ▼
STOPPED (完全停止)
```

## 依赖要求

- Python 3.10+
- requests
- PyYAML
- psutil
- prometheus_client

## 后端 API 对接

Agent 需要主控系统提供以下接口：

| 接口 | 方法 | 说明 |
|------|------|------|
| /api/nodes/register | POST | 节点注册 |
| /api/nodes/{id}/heartbeat | POST | 心跳上报 |
| /api/nodes/{id}/tasks/pending | GET | 拉取待执行任务 |
| /api/tasks/{id}/result | POST | 上报任务结果 |
| /api/tasks/{id}/logs | POST | 上报任务日志 |
| /api/spider-files/spider/{spiderId} | GET | 获取爬虫文件列表 |
| /api/spider-files/{fileId} | GET | 获取文件内容 |

## Python SDK 发布

爬虫业务代码使用的 SDK 位于 `sdk/python/`，以 `crawlerkeeper-sdk` 包名独立发布到公网 PyPI
（<https://pypi.org/project/crawlerkeeper-sdk/>），爬虫项目通过依赖声明引用，不再拷贝源码。

```bash
cd sdk/python

# 仅构建（产物在 dist/）
./publish.sh

# 发布预演（校验凭证与产物，不上传）
./publish.sh --dry-run

# 构建并发布
./publish.sh --publish
```

凭证来源（优先级从高到低）：

1. 环境变量 `PYPI_TOKEN`
2. token 文件 `~/.pypi_token`（可用 `PYPI_TOKEN_FILE` 覆盖路径）

token 申请: <https://pypi.org/manage/account/token/>

- 版本号单一来源：`crawlerkeeper_sdk/__init__.py` 的 `__version__`
- 发布脚本内置品牌词闸门：待发布内容出现公司品牌词时直接中止
- 爬虫项目迁移说明见 `sdk/python/MIGRATION.md`

## Agent 发布

Agent 本体以 `crawlerkeeper-agent` 包名发布到公网 PyPI
（<https://pypi.org/project/crawlerkeeper-agent/>），服务器直接 pip/uv 安装，
无需手动上传安装包。

```bash
# 仅构建（产物在 dist/）
./publish.sh

# 发布预演（校验凭证与产物，不上传）
./publish.sh --dry-run

# 构建并发布
./publish.sh --publish
```

凭证来源（优先级从高到低）：

1. 环境变量 `PYPI_TOKEN`
2. token 文件 `~/.pypi_token`（可用 `PYPI_TOKEN_FILE` 覆盖路径）

token 申请: <https://pypi.org/manage/account/token/>

- 版本号单一来源：`src/crawlerkeeper/__init__.py` 的 `__version__`（用 `bump_version.sh` 管理）
- sdist/wheel 走 include 白名单：只打包 `src/crawlerkeeper`、`tests`、`LICENSE`、`README.md`，
  内部资产（生产配置、Dockerfile、k8s 清单、demo、打包脚本）一律不进发布包
- 发布脚本内置品牌词闸门：待发布内容出现公司品牌词或内部域名时直接中止

## License

MIT
