前阵子帮一个初创团队收拾爬虫摊子,发现他们十几台服务器上躺着上百个无人认领的 Python 脚本,全是crontab手动挂的,有的跑一半就死了,有的重复抓了三遍,还有的连数据落在哪儿都不知道。排查了半天之后,我给他们搭了一套基于 OpenClaw 的私有爬虫管理平台,用 Docker 一把梭部署完事,从此任务编排、去重、调度、结果回传都统一走平台,再也没出现"半夜三点某台机器脚本崩了但没人发现"的尴尬。
如果你也遇到类似情况——爬虫脚本散落、依赖环境各搞一套、定时任务全靠手写、根本没有任务面板和状态追踪,那这篇文章就是给你写的。我会把 OpenClaw 用 Docker 从零部署这套私有爬虫管理平台的完整过程拆开讲:包含环境准备、Compose 编排、跑通第一个爬虫任务、踩坑实录、以及进阶玩法。不整花活,全程实操视角。
1. 部署前先想明白:OpenClaw 解决的是什么问题
1.1 爬虫项目失控的典型症状
先对号入座。爬虫项目刚开始总是很爽:写一个脚本,requests.get()拿数据,pandas洗一洗,存个 CSV。但到第二个、第三个、第十个爬虫上线之后,痛点会集中爆发。
最典型的问题有几个。一是任务没有统一调度,crontab写得到处都是,改一次服务器就要重新理一遍;二是去重靠脚本里自己维护一个 set 或者查数据库,任务一多就乱,我见过一个团队因为忘记去重,写了 800 万条重复数据进去,把数据库直接搞死;三是没有失败重试和告警,爬虫半夜崩了,第二天早上业务方才发现数据没更新;四是任务状态不透明,老板问"现在有几个爬虫在跑、跑到哪一步了",没人能立刻答上来。
这些问题单独看都不大,但合在一起就是灾难。你需要的不是再写一个爬虫,而是一个能管所有爬虫的平台:能注册任务、排优先级、派发到 worker、自动重试、记录结果、提供 Web 控制台。
1.2 OpenClaw 的核心模块拆解
我理解的 OpenClaw,是一个面向私有部署的爬虫任务管理平台。它把爬虫这件事拆成了几层,每一层都有明确职责。
- Web 控制台:用来创建任务、查看运行状态、浏览抓取结果。不需要每次操作都碰命令行。
- 调度器:把
cron表达式或手动触发的任务转换成可执行的消息,推送到队列。相当于整个平台的"大脑",只负责派单,不负责干活。 - 消息队列:任务的等待区。OpenClaw 依赖 Redis 做队列存储,这样任务高峰时不会直接把数据库打死。
- Worker 节点:真正执行爬虫脚本的部分。它可以和调度器在同一个容器里,也可以独立部署多副本。一个 worker 消费一个任务消息,拿到要抓的 URL 和解析规则,跑完把结构化结果回传给平台。
- 持久化存储:任务元数据、抓取结果、去重记录,放在 PostgreSQL/MySQL 这类关系型数据库里。文件类的结果(图片、PDF)可以落到对象存储或本地卷。
所以你在部署的时候,并不是只跑一个 OpenClaw 容器就行,而是一整套依赖:应用本身 + Redis + 数据库。这也是为什么要用 Docker Compose 而不是docker run逐个启动。
1.3 为什么选 Docker 部署而不是裸机
很多人觉得裸机跑更简单,但私有部署的本质是"可复制、可迁移、可回滚"。我实际对比过十二次以上:裸机部署虽然省了镜像构建这一步,但后续升级、换机器、加 worker 节点、统一依赖版本,每一步都是坑。Docker 把运行时依赖(Node.js 版本、Python 环境、系统库、时区配置)全部打进镜像,换一台机器docker compose up -d就恢复,不用再对着文档装环境。
另外,爬虫平台往往要跑大量自定义脚本。你用裸机跑,脚本的依赖装在系统里,过一阵子系统升级可能就挂了;用 Docker,每个 worker 的依赖都是独立的,互相不污染。这也是我在这类场景里坚持容器化的核心理由。
1.4 先明确边界:不是让你抛弃商用方案
这里我先把丑话说在前面:OpenClaw 这类私有平台,和八爪鱼、后羿采集器、云采集 API 这类商用方案,定位完全不同。商用方案胜在开箱即用,零代码采集常见网站,但灵活性有限,数据要先落到对方平台,涉及敏感数据时很难过合规审计。OpenClaw 则强调可控和可编程:规则自己写,存储在自己服务器上,爬虫行为全掌握。
所以选择的逻辑很清楚:如果抓取规模大、规则多变、数据要入库做自家业务,或者对数据主权有要求,私有部署更合适。如果只是临时抓几个公开资讯页,商用工具就够了,没必要自己折腾一套平台。别为了技术上的爽感过度自建。
2. 环境准备:Windows 翻车现场与 Linux/ARM 设备的正确打开方式
2.1 Windows 本地跑 Docker:先过 WSL2 这一关
很多人在自己电脑上先试跑,Windows 10/11 上装 Docker Desktop 是必经之路。我遇到过的问题中,最高频的是这个:Docker Desktop 启动时报错 "virtualization support not detected",或者弹窗提示 WSL 存在问题。
这个报错的实际含义是,Docker Desktop 依赖的虚拟化层没起来。排查链路不复杂,按顺序来。
第一步,确认 Windows 的虚拟化功能有没有开启。打开 PowerShell(管理员权限),运行:
systeminfo | findstr /i "Hyper-V"如果输出里显示Virtualization Enabled In Firmware: Yes,说明主板和系统层面的虚拟化没问题;如果显示 No,需要去 BIOS/UEFI 里把 Intel VT-x 或 AMD-V 打开。惠普、戴尔、联想的 BIOS 入口不一样,但一般都在Advanced或Security菜单下,开启后重启。
第二步,确认 WSL 功能正常。热词里提到的那句"请在 PowerShell 中运行 wsl -- status"其实是个笔误,真正的命令是wsl --status。运行之后看输出里 WSL 版本是不是 2,默认版本对不对。我见过一种情况:WSL 内核太旧,Docker Desktop 不认,解决办法是更新 WSL 内核:
wsl --update wsl --set-default-version 2第三步,检查 Hyper-V 和"虚拟机平台"两个 Windows 功能是否启用。在控制面板的"启用或关闭 Windows 功能"里,找到 Hyper-V 和虚拟机平台(Windows Hypervisor Platform)勾上,重启。Docker Desktop 新版还要求开启"内核隔离"(如果系统支持的话),但多数情况下开启 Hyper-V 就够了。
这些做完,Docker Desktop 一般就能正常启动。如果还不行,打开任务管理器 -> 性能 -> CPU,看左下角虚拟化状态是否为"已启用"。如果这里显示已禁用,那问题基本锁死在 BIOS 层面,Windows 里怎么折腾都没用。
2.2 Linux 服务器部署:Ubuntu / CentOS 的关键差异
生产环境我强烈建议直接用 Linux 服务器,别在 Windows 上长时间挂机跑爬虫。一是资源占用更少,二是少一层 Docker Desktop 的虚拟化开销,三是重启自愈更容易配置。至于选 Ubuntu 还是 CentOS,个人经验是Ubuntu 优先。原因很简单:官方 Docker 仓库对 Ubuntu 的适配最激进,内核新,装 Docker 几乎不踩坑,遇到问题搜到的文档也最多。
Ubuntu 上安装 Docker 的完整链路:
# 1. 卸载可能残留的旧版本 sudo apt-get remove docker docker-engine docker.io containerd runc # 2. 安装依赖和 GPG 密钥 sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 3. 添加官方软件源并安装 echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin # 4. 启动并设置开机自启 sudo systemctl start docker sudo systemctl enable docker注意一点,Debian 系和 RPM 系的软件源格式完全不同,别把 Ubuntu 的命令直接搬到 CentOS 上跑。CentOS 如果非用不可,用yum install -y yum-utils,然后yum-config-manager --add-repo加 Docker 官方源,再yum install docker-ce。CentOS 7 上遇到过 Docker 依赖的iptables版本过旧导致 NAT 失效的问题,升级内核或调整防火墙策略都是常见解法,但这很折腾,所以我的结论还是优先 Ubuntu。
2.3 ARM 设备:RK3588 跑 OpenClaw 可行,但要注意镜像架构
为什么提 ARM 设备?因为我自己真的在一台 RK3588 的开发板上跑过这套平台,当低功耗采集节点用。很多人的需求是在 NAS、开发板、ARM 云主机上部署,成本和功耗都比 x86 云服务器低得多。
RK3588 属于 ARM64 架构,部署的核心注意点只有一个:镜像必须用 arm64 版本。好在主流镜像都支持多架构,拉取时会自动选择对应平台。如果你自己写 Dockerfile,注意基础镜像要选node:20-alpine这种带 arm64 的 tag,别用node:20里某些特殊平台版本。
在 ARM 设备上还要留意内存。RK3588 开发板常配 4GB 或 8GB 内存,OpenClaw 全家桶(应用 + Redis + PostgreSQL)跑起来大概占 1.5-2GB,再叠加爬虫 worker 的 Python 进程,8GB 板子比较从容,4GB 会紧张。建议在 Compose 里给每个服务设置mem_limit,防止某个服务吃满内存拖垮整个系统。我自己的做法是这样的:
services: openclaw-server: mem_limit: 768m openclaw-worker: mem_limit: 1g redis: mem_limit: 256m postgres: mem_limit: 512m这个配置在 RK3588 上跑起来很稳,8GB 板子剩下的内存足够跑两三个轻量采集任务。
2.4 环境自检清单
部署前花三分钟做个自检,能省后面一大半排错时间。我每次给团队搭环境都会发这样一张表:
| 检查项 | 命令 | 预期结果 |
|---|---|---|
| Docker 版本 | docker --version | v20.10 以上 |
| Compose 插件 | docker compose version | v2.x 以上 |
| 当前架构 | uname -m | x86_64 或 aarch64 |
| 可用内存 | free -h | 总计 2GB 以上 |
| 磁盘剩余 | df -h / | 剩余 10GB 以上 |
| 虚拟化状态 | systeminfo或 BIOS | 已启用 |
| 服务自启 | systemctl is-enabled docker | enabled |
每条都过一遍,再开始部署。有经验的人都知道,环境问题占部署问题的一半以上,前期多花三分钟,后期少踩无数坑。
3. 用 Docker Compose 把 OpenClaw 全家桶跑起来:目录规划与容器编排
3.1 先想好目录结构,再动手写文件
部署这类多服务项目,最忌讳的是把文件乱扔。我推荐的目录结构是这样:
/opt/openclaw/ ├── .env # 环境变量,密钥集中存放 ├── docker-compose.yml # 容器编排入口 ├── data/ │ ├── postgres/ # 数据库数据卷挂载点 │ ├── redis/ # Redis 持久化目录 │ └── results/ # 爬虫结果落盘目录 ├── logs/ # 容器日志的宿主机映射 └── scripts/ ├── backup.sh # 数据库备份脚本 └── restore.sh # 恢复脚本把数据文件、日志、脚本和编排文件分开,不只是为了整洁——数据目录独立挂载后,容器随便删、镜像随便更新,数据不丢。我见过有人图省事把数据直接写在容器里,结果docker rm一下,几个月采集的数据全没了,心态直接崩。
3.2 Compose 文件逐段解析
下面是核心的docker-compose.yml。这个文件覆盖了 OpenClaw 服务端、Worker、Redis、PostgreSQL 四个服务的完整编排。我加注释,方便你理解每个字段为什么存在。
version: "3.8" services: # OpenClaw 主服务:Web 控制台 + 调度器 openclaw-server: image: openclaw/server:latest container_name: openclaw-server restart: unless-stopped env_file: .env environment: - TZ=Asia/Shanghai - DATABASE_URL=postgresql://openclaw:${POSTGRES_PASSWORD}@postgres:5432/openclaw - REDIS_URL=redis://redis:6379/0 ports: - "8080:8080" depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "node", "-e", "fetch('http://localhost:8080/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"] interval: 30s timeout: 5s retries: 3 start_period: 20s volumes: - ./data/results:/app/data/results - ./logs:/app/logs networks: - openclaw-net mem_limit: 768m # Worker 节点:执行具体爬虫任务的容器 openclaw-worker: image: openclaw/worker:latest container_name: openclaw-worker restart: unless-stopped env_file: .env environment: - REDIS_URL=redis://redis:6379/1 - DATABASE_URL=postgresql://openclaw:${POSTGRES_PASSWORD}@postgres:5432/openclaw depends_on: - openclaw-server volumes: - ./data/results:/app/data/results - ./scripts:/app/scripts:ro networks: - openclaw-net mem_limit: 1g # 消息队列:Redis redis: image: redis:7-alpine container_name: openclaw-redis restart: unless-stopped command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"] volumes: - ./data/redis:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 networks: - openclaw-net mem_limit: 256m # 元数据与结果存储:PostgreSQL postgres: image: postgres:16-alpine container_name: openclaw-postgres restart: unless-stopped environment: - POSTGRES_USER=openclaw - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=openclaw - TZ=Asia/Shanghai volumes: - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U openclaw"] interval: 10s timeout: 3s retries: 5 networks: - openclaw-net mem_limit: 512m networks: openclaw-net: driver: bridge几个字段的用意展开说。
restart: unless-stopped是我在所有服务上的默认值。它保证宿主机重启后容器能自动拉起,而且手动docker stop过的容器不会被强制拉起,很贴合运维直觉。
depends_on配合condition: service_healthy控制启动顺序。OpenClaw 服务端依赖数据库和 Redis 就绪再启动,避免应用启动后连不上数据库疯狂打日志。Redis 和 PostgreSQL 的healthcheck分别用redis-cli ping和pg_isready,都是官方推荐的方式,准确且开销极小。
env_file: .env让所有服务共享一套环境变量,密钥不会硬编码进 compose 文件。这是安全底线,后面细说。
volumes把容器内数据目录映射到宿主机。结果是./data/results,数据库是./data/postgres,Redis 是./data/redis。这样即使整个 Compose 项目删掉重建,数据依然还在。
mem_limit是给每容器设的内存上限。之前提过,尤其 ARM 板子内存紧张,这个必须有。宁可让某个容器触发 OOM 重启,也不能让它拖垮宿主机导致所有服务一起死。
3.3 .env 文件:密钥管理别犯低级错误
.env文件长这样:
# ===== OpenClaw 部署环境变量 ===== POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD REDIS_PASSWORD=CHANGE_ME_ANOTHER_STRONG_PASSWORD OPENCLAW_SECRET_KEY=CHANGE_ME_SESSION_SECRET ADMIN_USERNAME=admin ADMIN_PASSWORD=CHANGE_ME_ADMIN_PASSWORD三个关键原则我必须反复强调:
- 环境变量不要写死在 compose 文件里。明文密钥一旦进了 git 历史就很难彻底清除,你只能改密钥,不能改历史。正确做法是
.env文件加入.gitignore,只在服务器上维护一份。 - 用强随机密钥。
openssl rand -hex 32生成,别自己编一个"password123"。平台部署后要对外提供服务,弱密钥分分钟被爆破。 - 容器间通信走内网。compose 文件里服务之间通过服务名(
postgres:5432、redis:6379)访问,不需要把数据库端口暴露到宿主机。我刻意没有在 Redis 和 PostgreSQL 服务上写ports,就是不让外部访问。真要远程管理数据库,用 SSH 隧道或别的安全途径,别偷懒裸开 5432 端口。
3.4 首次启动:从拉镜像到登录控制台
文件准备好之后,首次启动按这个顺序来:
cd /opt/openclaw docker compose config # 先校验配置,有语法错误会直接报出来 docker compose pull # 拉取所有镜像 docker compose up -d # 后台启动全部服务 docker compose ps # 查看状态,确认没有 restart 循环 docker compose logs -f openclaw-server # 跟踪主服务日志等openclaw-server日志出现类似Server started on port 8080的输出后,浏览器打开http://服务器IP:8080,用.env里配置的ADMIN_USERNAME/ADMIN_PASSWORD登录。
第一次登录后建议立刻做两件事:改一遍管理员密码,然后在设置页里把控制台从 HTTP 切到 HTTPS(或者在前面挂一层 Nginx/Caddy 做 TLS 终结)。爬虫管理平台控制台里全是任务配置和数据结果,裸跑 HTTP 等于把训练数据送给走廊里的抓包者。
3.5 镜像 tag 策略:升级不翻车
Compose 文件里我写的是latesttag,方便演示,但实际长期跑不建议直接追 latest。镜像更新是不可控的,今天docker compose pull拉的新版本可能改了配置项格式,明天平台就起不来了。我的习惯是:
- 第一次部署时固定一个明确版本 tag,例如
openclaw/server:0.5.1,然后手动确认版本号。 - 升级时改 tag,先
docker compose pull,再docker compose up -d。如果起不来,改回旧 tag 重拉即可。 - 升级前一定先把数据库备份做掉。数据真的比代码金贵。
另外,别忘了docker compose pull之后旧的悬空镜像会堆积,定期清理一下:
docker image prune -f省磁盘的事不要等它自己发生。
4. 跑通第一个爬虫任务:任务定义、调度去重与结果回传
4.1 先分清控制台操作和 API 接入
OpenClaw 支持两种方式管理任务。一是直接在 Web 控制台里手动创建任务,适合临时抓取、调试规则;二是通过 API 接入,把任务注册集成到你的发布流程里,适合批量、程序化操作。
我实际使用时,日常调试走控制台,正式运行的任务全部走 API。原因很简单:可审计。API 接入意味着每个任务的创建人、创建时间、参数都有记录,出问题能回溯。
任务的核心数据结构大概长这样:
{ "task_id": "task_20240621_001", "name": "每日新闻头条采集", "url": "https://example-news.com/top", "schedule": "0 7 * * *", "priority": 5, "dedup_key": "news_top_20240621", "parser": "news_parser", "max_retries": 3, "timeout": 30, "callback": "http://openclaw-worker:8080/tasks/task_20240621_001/result" }字段含义不复杂:schedule是 cron 表达式,dedup_key是去重键,parser指定用哪个解析器,max_retries控制失败重试次数。
4.2 调度和执行机制
任务进入平台后,调度器会做三件事。先把 cron 表达式注册进调度循环,到点了检查是否有未执行的任务;然后把任务消息推进 Redis 队列,队列命名按优先级分,比如queue:high、queue:normal;最后 worker 从队列消费消息,执行爬虫脚本。
这里要理解一个关键点:OpenClaw 的 worker 本身不内置爬虫逻辑,它执行的是你注册进去的自定义脚本。也就是说,平台管"什么时候跑、跑哪个任务、结果怎么存",你的 Python 脚本管"怎么抓、怎么解析"。
去重机制依赖dedup_key。同一个 dedup_key 在 Redis 里有 SETNX 的原子操作保证只会被消费一次。比如每天抓新闻头条,dedup_key 带上日期,当天的任务重复提交也不会重复执行。
4.3 一个实际的 Python 爬虫任务示例
下面是一个把公开新闻页面标题列表抓下来并回传结果的完整示例。代码本身用的是最常见的requests+BeautifulSoup,不涉及任何绕过访问控制的手段,只抓公开可访问的页面。
# crawler_jobs/news_parser.py import json import requests from bs4 import BeautifulSoup def parse_news(task_config): # 任务参数从 task_config 里取 url = task_config["url"] timeout = task_config.get("timeout", 30) headers = { "User-Agent": "Mozilla/5.0 (compatible; OpenClawBot/1.0; +https://example.com/bot)" } # 抓取公开页面 resp = requests.get(url, headers=headers, timeout=timeout) resp.raise_for_status() # 解析页面结构 soup = BeautifulSoup(resp.text, "html.parser") items = [] for li in soup.select("ul.news-list li"): a = li.find("a") if a and a.get("href"): items.append({ "title": a.get_text(strip=True), "link": a["href"] }) if len(items) >= 20: break # 把结构化结果回传给平台 return { "code": 0, "data": items, "source_url": url, "fetched_at": int(time.time()) }Worker 的加载方式是把脚本放进./scripts挂载目录里,任务配置里的parser字段指定文件名和函数名,例如news_parser:parse_news。脚本只要实现"接收 task_config、返回可序列化结果"的约定,平台就自动完成剩下的流程——执行、超时控制、失败重试、结果入库。
我在真实项目里用这个套路跑了两年,踩过的最大坑是:Worker 容器里别装一堆爬虫专用库。以前试过在 worker 镜像里预装 50 多个 Python 包,结果升级一个包导致另一个脚本崩了。最佳实践是给高频任务准备两三个固定镜像(一个偏 requests 系,一个偏 Scrapy 系),按任务类型指定用哪个镜像跑,互不干扰。
4.4 合规意识必须放在第一位
这一步我想多说一点。爬虫管理平台越强大,你越要有数据合规意识。我在给团队做内部培训时反复强调几条红线:
- 只抓你有权访问的数据。登录后才能看到的内容,用你自己的账号去抓,很可能违反网站服务条款。
- 尊重
robots.txt。虽然技术上它拦不住你,但遵守它是行业的专业底线。OpenClaw 的 worker 配置里可以加一个 robots 检查的中间件,我认为自带判断比事后辩解强。 - 抓取频率要像人。就算目标站点没反爬,你每秒请求 100 次也是不礼貌的。平台里给任务加个固定间隔参数,例如
request_interval: 1(秒),压力小一半,封禁风险低一半。 - 个人信息和大规模数据抓取,务必先做法律评估。这不是吓唬人,是真实发生过很多案例了。
一句话总结:能力越大,越要克制。平台解决了"能不能跑"的问题,但"该不该跑"永远是人决定的。
5. 让服务稳定过夜:健康检查、日志、备份与安全加固
5.1 健康检查与自动重启
部署完只是开始,真正的考验是让它稳定跑一个月不崩。Compose 文件里的healthcheck和restart: unless-stopped组合是第一步,但还不够。我建议在宿主机再加一层兜底:写一个看门狗脚本,定期检查容器健康状态,发现异常就执行docker compose restart。
看门狗脚本思路很简单:
#!/usr/bin/env bash # scripts/healthcheck.sh unhealthy=$(docker inspect --format='{{.State.Health.Status}}' openclaw-server 2>/dev/null) if [ "$unhealthy" != "healthy" ]; then echo "$(date): openclaw-server unhealthy, restarting..." >> /var/log/openclaw-watchdog.log cd /opt/openclaw && docker compose restart openclaw-server fi配合 cron 每两分钟执行一次。这比单纯靠 Docker 自带的 restart 策略更主动——restart 策略是容器退出才生效,但健康检查能发现"容器还活着但服务已不可用"的假死状态。
5.2 日志:别等出了事故再看
日志管理被九成小团队忽略。默认情况下,Docker 的 JSON File 日志驱动会把日志写到宿主机,但不做轮转的话,单个容器日志能涨到几个 GB。必须在 Compose 里显式配置日志轮转:
logging: driver: "json-file" options: max-size: "50m" max-file: "5"这样每个容器日志最多占 250MB,超过自动滚动。再加上第 3 章里挂载的./logs目录输出业务日志,排查问题时grep 关键字 /opt/openclaw/logs/就能定位,不用进容器翻。
关于日志,我的经验是:业务日志要结构化和带 trace_id。每次任务执行都输出一行 JSON 日志,包含 task_id、状态、耗时、错误码。这样从控制台看到"某任务失败",就能直接在日志里grep task_xxx看到完整链路。纯文本日志在任务量上来之后,你会后悔的。
5.3 数据备份:数据库和文件卷一个都不能漏
爬虫管理平台里,最值钱的是任务配置和抓取结果。备份方案我用的是三层:
- PostgreSQL 定期逻辑备份:每天凌晨
pg_dump,保存最近 7 天。 - Redis AOF 持久化:RDB 加 AOF 双保险,防止内存数据丢失。
- 结果文件卷快照:
data/results目录每日增量同步到异地存储。
最简单的备份脚本长这样:
#!/usr/bin/env bash # scripts/backup.sh BACKUP_DIR=/opt/openclaw/backups/$(date +%Y%m%d) mkdir -p "$BACKUP_DIR" docker exec openclaw-postgres pg_dump -U openclaw openclaw | gzip > "$BACKUP_DIR/openclaw.sql.gz" docker exec openclaw-redis redis-cli -a "$REDIS_PASSWORD" BGSAVE tar czf "$BACKUP_DIR/results.tar.gz" -C /opt/openclaw/data results find /opt/openclaw/backups -type d -mtime +7 -exec rm -rf {} \;然后用 cron 每天早上 3 点跑一次。这个备份策略成本很低,但能让你在灾难发生时只丢最多一天的数据。不要觉得"平台才跑了两周不用备份",数据做好准备是从第一天就要开始的。
5.4 安全加固:开放端口前先做这几件事
任何面向网络的平台,安全加固都要在开放端口前做完。我的最低安全清单:
- 控制台必须走 HTTPS。最简单的方式是前面挂 Caddy,自动申请证书,Caddyfile 三行配置搞定,比手工维护 Nginx 配置省心不是一点半点。
- 容器内禁止 root 运行。前后端进程在 Dockerfile 里通过
USER node或单独创建低权限用户执行。PostgreSQL 和 Redis 官方镜像本身就做了非 root 降权,OpenClaw 自定义镜像要自己注意。 - 依赖漏洞扫描。定期跑
docker scout cves openclaw/server:latest看镜像有没有高危 CVE。爬虫平台要处理大量不可信网页内容,解析库的漏洞可能被恶意页面利用,不能忽视。 - 防火墙只放行必要端口。宿主机上用 ufw 或 firewalld,只开 80(HTTPS 走 Caddy)、22(SSH),其他一律拒绝。数据库和 Redis 端口绝不对公网开放。
- 不要用默认管理员密码。这个我说了两遍,因为它太常见了,见过太多人部署完不改密码。
这些做完,一般的小团队平台就足够安全了。再往上要接 LDAP、双因素、审计日志,那属于企业版玩法,后面再说。
6. 我踩过的坑:网络不通、OOM 和 Docker 启动失败的完整排查链路
6.1 Docker Desktop 启动失败:从报错到解决的全过程
这个坑我帮人排了很多次。具体场景:新买的 Windows 笔记本,装好 Docker Desktop,启动后弹窗报错 "Docker Desktop failed to start because virtualization support wasn't detected",一晃而过根本来不及截图。
第一步别慌,先在 PowerShell 里跑:
wsl --status正常输出会显示 "默认版本:2"。如果提示"正在安装",先跑一遍wsl --update和wsl --shutdown。如果提示找不到 WSL,则需要安装 WSL 功能:
wsl --install装完重启,再启动 Docker Desktop。
如果 WSL 正常但 Docker 仍报错,检查 Windows 功能。PowerShell(管理员)跑:
Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform两个状态如果不是 Enabled,用下面的命令开启:
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All -All Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -All然后重启。这一套在我接触过的绝大多数机器上都解决了。
最后还有一个容易忽略的点:老旧电脑的 BIOS 虚拟化可能被厂商标记成禁用。开机进 BIOS,找到 "Intel Virtualization Technology" 或 "SVM Mode"(AMD 平台),确认是 Enabled。这一步在系统里无法修改,必须进固件设置。
6.2 容器网络不通:一次 Node 进程连不上 Redis 的排障
容器都起来了,但 OpenClaw 服务端日志疯狂报Redis connection refused。这时按顺序查:
第一步,确认容器状态和网络。执行:
docker compose ps docker network ls第二步,手动测试容器间连通性。进入 server 容器,ping Redis 容器的 IP 或服务名:
docker exec -it openclaw-server bash ping redis如果 ping 不通,检查两个容器是否在同一网络。Compose 默认会为项目创建独立网络,但如果你手动docker run过其他容器,可能不在同一网络里。解决办法是自定义 network,显式把服务加进去,就像我在 Compose 文件里写的networks: openclaw-net。
另一种情况是宿主机防火墙拦截。Linux 上 Docker 使用 iptables 做 NAT,如果iptables -F清过规则,容器网络会神秘失效。判断方法很简单:宿主机上iptables -L -n | grep DOCKER,看有没有 DOCKER 链。没有的话,重启 Docker 服务通常能重建规则。
systemctl restart docker注意:重启 Docker 会重启所有容器,尽量选业务低峰期操作。
6.3 OOM:内存一满,容器全躺
跑采集任务时最怕内存打满。现象是某个容器突然消失,docker ps -a里状态显示Exited (137)。137 退出码的含义就是被 OOM Killer 杀掉了。
排查方式有条不紊:
# 查看容器被杀时的系统日志 journalctl -k | grep -i oom # 查看容器内存使用趋势 docker stats --no-stream # 查看 Compose 里 mem_limit 配置是否合理解决办法分两层。首先,给任务并发数设置上限。OpenClaw worker 的配置里通常有max_concurrent_tasks,设为 2 或 3,别默认全开。其次,宿主机加一点 swap,它的作用是缓冲突发内存压力,避免 OOM Killer 立刻动手。Ubuntu 上临时加 4GB swap:
sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile # 永久生效需在 /etc/fstab 添加一行我的最终建议是:爬虫项目,内存配置宁多勿少。Python 解析大 HTML 页面时内存峰值很高,而容器 OOM 重启之后任务重跑又是一轮消耗,得不偿失。
6.4 时区与中文数据编码
这个坑很小但极其常见。容器默认时区是 UTC,如果你在任务里打时间戳、按天做 dedup_key,结果全是 UTC 时间,和业务时间对不上。解决方式是 Compose 里统一设置TZ=Asia/Shanghai,我在第 3 章的配置里已经加上了。
另外一个坑是中文乱码。worker 容器如果没装中文字体或者系统 locale 不对,页面抓下来中文文本进入 PostgreSQL 可能变成????。排查方法:进入容器执行locale,看有没有 UTF-8。最好在基础镜像里提前配置好:
ENV LANG=C.UTF-8 ENV LC_ALL=C.UTF-8数据库连接串里也显式加?charset=utf8mb4(MySQL)或确认 PostgreSQL 数据库编码是 UTF8。中文数据出问题基本逃不出这两处。
7. 从单机到多节点:接入 Teams / Obsidian / 本地大模型的进阶玩法
7.1 横向扩容:一个 Compose 项目跑多个 Worker
单机跑 OpenClaw 能应对小规模采集需求,但任务量上来后,一个 worker 容器消费消息的速度会成为瓶颈。横向扩容的姿势很简单:在 Compose 里把 worker 服务用deploy.replicas指定多副本。
services: openclaw-worker: image: openclaw/worker:latest deploy: replicas: 3docker compose up -d就会拉起三个 worker 容器,它们共享同一个 Redis 队列,互不抢任务,天然实现负载均衡。这是 OpenClaw 这类队列架构最大的优势:加节点就能线性提升吞吐,不需要改业务代码。
多节点部署时注意:结果写库没有冲突,因为 PostgreSQL 支持并发写;但结果文件落到本地卷会有并发问题。更好的做法是像前面说的,把文件结果映射到独立的共享存储,或者干脆只存元数据在平台里,文件放到外部对象存储。
7.2 接入 Microsoft Teams 当告警通道
爬虫平台不能只靠人工盯控制台。我的做法是把告警接入 Microsoft Teams,任务失败、调度器异常、worker 掉线,各发一条卡片消息到团队群。
Teams 接入的核心是 Webhook URL。在 Teams 频道里添加 "Incoming Webhook" 连接器,拿到一条https://your-org.webhook.office.com/webhookb2/...的 URL。然后在 OpenClaw 的告警配置里加上这个地址,平台触发告警时 POST 一个 JSON 消息过来。
我在脚本里验证过的最简卡片消息:
{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "summary": "OpenClaw 任务失败告警", "themeColor": "FF0000", "sections": [ { "activityTitle": "任务执行失败", "facts": [ { "name": "任务ID", "value": "task_20240621_001" }, { "name": "失败原因", "value": "Timeout after 30s" }, { "name": "重试次数", "value": "3/3" } ] } ] }接入之后,团队的响应时间从"第二天早上发现"缩短到"几分钟内处理",这是非常值得的一步。
7.3 在 Obsidian 里查任务结果
这是个小而实用的玩法:OpenClaw 社区有不少人写了 Obsidian 插件,直接在笔记里查平台上的任务状态和结果摘要。我自己的用法是,每天早上起来打开 Obsidian,跑一条命令就能看到"昨晚 7 个任务全部成功,3 个任务抓了 5214 条数据"。
原理就是调 OpenClaw 的 API。插件本质上是个 API 客户端,配置好服务器地址和 token 就能用。这个玩法适合重度 Obsidian 用户,把监控嵌入日常笔记流,比打开单独的控制台更符合习惯。
开放 API 的好处就在这里:不只能和 Teams、Obsidian 对接,理论上也能接企业内部的工作流系统。
7.4 本地大模型辅助解析:Qwen 和 DeepSeek 的正确用法
最近一年,本地大模型和爬虫平台结合是热度很高的方向。热搜词里大量的 DeepSeek 本地部署、Qwen2.5-3B 关联到 OpenClaw,也印证了这一点。这个方向的实际价值在于:让大模型帮我们做页面结构理解和解析规则生成,而不是人工写死 CSS 选择器。
页面改版是爬虫维护最大的痛点。以前是页面一改版,你的解析规则全废,得人工重新看 HTML 写选择器。现在可以让本地大模型干这件事——把页面 HTML 片段和需要提取的字段给模型,让它输出对应的解析规则或者直接输出结构化 JSON。
具体架构是:
爬虫抓取 HTML -> 本地大模型服务(Ollama 跑 Qwen2.5-3B 等) -> 结构化 JSON -> 回传 OpenClaw我测试过的链路很稳定。Ollama 部署非常简单,一条命令拉起服务,OpenClaw 的 worker 里只需要加一个请求,把 HTML 和字段描述发给localhost:11434的接口。选模型的时候我的建议是:优先 3B-7B 参数量的模型,比如 Qwen2.5-3B。生成解析规则这种任务接近 pattern 提取,不需要 70B 那种大体量,小模型的响应速度在几十毫秒到百毫秒量级,够用且省资源。
这一步把"页面改版导致长期维护工作量"的问题彻底降下来了。我自己项目的实际体感是:以前每次目标站点改版,人工维护解析规则要两个小时,现在交给模型,十分钟内搞定,而且准确率高得多。
需要留意的是,本地大模型对服务器内存和显卡有要求。没有 GPU 的机器,用 CPU 跑 3B 模型也能勉强运行,但延迟会高一些。如果服务器配置紧张,更合理的方案是把解析请求转发到一个独立的推理节点,不要和 OpenClaw 主服务抢资源。
我在实际项目中把 OpenClaw 和 Ollama 分开部署,Ollama 专门跑在一台带 GPU 的机器上,OpenClaw 集群通过内网地址调用它。这样两边互不干扰,升级、重启也互不影响。
最后说几句实在话
这套 OpenClaw Docker 部署方案,目前已经在我手上跑过好几类环境:两核 4G 的小云主机、RK3588 开发板、还有十几台节点的采集集群,都没有出过结构性问题。每次帮团队搭完,我都会留一句话:部署只是开始,运维和规则维护才是长期工作。
你想真正用好爬虫管理平台,最值得投入的不是部署脚本,而是把任务分类、去重策略、失败告警、备份恢复这四条基础能力用起来。先把一两个任务跑稳,再逐步扩大,比一次性把平台搭得花里胡哨但没人用要实在得多。
我这篇里的 Compose 文件和配置,你直接拿走用,环境变量和密码记得换掉。有不清楚的命令,照着敲一遍基本都能跑通。真要遇到怪问题,记住我第 6 章的排查思路:先看日志,先看健康状态,先确认环境,再去动配置。爬虫平台说到底也是个 Web 应用,没有什么神秘问题,只是链路长了一点,排查要有耐心。