绿联NAS n8n + Browserless 自动化爬虫部署方案
一、绿联 NAS 前置准备
1.1 安装 Docker
打开 应用中心 → 搜索 Docker → 点击安装。安装完成后,绿联会自动在文件管理器中创建 /volume1/docker 共享文件夹。
1.2 确认路径
打开 文件管理器 → 右键 共享文件夹/docker → 属性 → 查看"存储位置":
- 显示"存储空间1" → 路径为
/volume1/docker - 显示"存储空间2" → 路径为
/volume2/docker
下文统一以 /volume1/docker 为例,如果你的在存储空间2,请自行替换。
1.3 创建文件夹
在 /volume1/docker 下新建文件夹:
|
1 2 3 |
/volume1/docker/n8n-scraper/ ├── data/ # n8n 数据持久化 └── output/ # 爬虫结果输出 |
二、Docker Compose
2.1 为什么需要 Browserless?
n8n 官方镜像不含 Chromium,无法直接执行浏览器自动化。绿联 NAS 的 Docker 管理器不支持直接构建 Dockerfile,因此我们将浏览器服务独立为 browserless 容器,n8n 通过内部 HTTP 调用它。
2.2 编写 docker-compose.yml
在 /volume1/docker/n8n-scraper/ 下创建 docker-compose.yml:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# Docker Compose 版本声明,3.8 兼容绝大多数 NAS 系统 version: "3.8" # 定义两个服务:n8n(工作流引擎)和 browserless(无头浏览器服务) services: # ==================== 服务1:n8n 工作流引擎 ==================== n8n: # 使用 n8n 官方最新镜像,包含完整的工作流编排能力 image: n8nio/n8n:latest # 容器名称,方便在 Docker 管理器中识别 container_name: n8n # 端口映射:将容器内的 5678 端口映射到宿主机的 5678 端口 # 访问方式:http://你的NASIP:5678 ports: - "5678:5678" # 环境变量配置 environment: # 启用基础身份验证(登录弹窗),防止未授权访问 - N8N_BASIC_AUTH_ACTIVE=true # 登录用户名 - N8N_BASIC_AUTH_USER=admin # 登录密码(建议修改为复杂密码) - N8N_BASIC_AUTH_PASSWORD=你的强密码 # ⚠️ 务必修改 # 限制 Node.js 最大内存使用为 1GB,防止 NAS 内存耗尽 - NODE_OPTIONS=--max-old-space-size=1024 # 系统时区设置,影响定时任务的执行时间 - TZ=Asia/Shanghai # n8n 内部时区,与 TZ 保持一致 - GENERIC_TIMEZONE=Asia/Shanghai # Webhook 外部访问地址,用于触发工作流时的回调链接 # 必须替换为你的 NAS 实际内网 IP - WEBHOOK_URL=http://localhost:5678/ # 关闭安全 Cookie 限制,允许通过 HTTP(非 HTTPS)访问 # 解决 "Access Denied / secure cookie" 报错 - N8N_SECURE_COOKIE=false # 设置默认界面语言为中文 - N8N_DEFAULT_LOCALE=zh-CN # 数据持久化卷映射 volumes: # 将 n8n 的所有数据(工作流、凭证、执行记录)保存到 NAS 本地 # 即使容器删除重建,数据不会丢失 - /volume1/docker/n8n-scraper/data:/home/node/.n8n # 额外映射一个输出目录,方便爬虫结果直接存到 NAS 共享文件夹 - /volume1/docker/n8n-scraper/output:/output # 重启策略:除非手动停止,否则容器异常退出后自动重启 restart: unless-stopped # ==================== 服务2:Browserless 无头浏览器 ==================== browserless: # 使用 browserless 官方镜像,内置 Chromium,专门用于远程浏览器自动化 image: browserless/chrome:latest # 容器名称 container_name: browserless # 端口映射:将容器内的 3000 端口映射到宿主机 # n8n 通过 http://localhost:3000 内部调用,也可外部访问 ports: - "3000:3000" # 环境变量配置 environment: # 最大同时运行的浏览器会话数,限制为 2 防止 NAS 内存爆掉 - MAX_CONCURRENT_SESSIONS=2 # 单个连接超时时间:300000 毫秒 = 5 分钟 # 适合加载较慢或需要等待 JS 渲染的页面 - CONNECTION_TIMEOUT=300000 # 请求队列最大长度,超过的请求会被拒绝 - MAX_QUEUE_LENGTH=5 # 预启动 Chrome 实例,减少首次请求的等待时间 - PREBOOT_CHROME=true # 时区同步 - TZ=Asia/Shanghai # Chromium 启动参数,核心反检测配置: # --no-sandbox: 禁用沙箱(Docker 环境下必需) # --disable-setuid-sandbox: 配合 no-sandbox 使用 # --disable-dev-shm-usage: 禁用 /dev/shm 共享内存,防止容器内崩溃 # --disable-blink-features=AutomationControlled: 隐藏 "自动化控制" 标记 # --disable-automation: 禁用自动化提示条 # --disable-infobars: 禁用信息栏 # --window-size=1920,1080: 设置窗口分辨率,模拟真实桌面 # --user-agent=...: 伪装成 Windows Chrome 浏览器 # --lang=zh-CN: 浏览器语言设为中文 # --disable-features=IsolateOrigins,site-per-process: 禁用站点隔离,提升兼容性 - DEFAULT_ARGS=--no-sandbox,--disable-setuid-sandbox,--disable-dev-shm-usage,--disable-blink-features=AutomationControlled,--disable-automation,--disable-infobars,--window-size=1920,1080,--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36,--lang=zh-CN,--disable-features=IsolateOrigins,site-per-process # 保持浏览器实例存活,提升后续请求响应速度 - KEEP_ALIVE=true # 共享内存大小:1GB,Chromium 渲染需要大量共享内存 # 不设置或设置太小会导致浏览器崩溃(尤其是截图、PDF 生成时) shm_size: 1gb # 自动重启 restart: unless-stopped |
2.3 创建项目
在绿联 Docker 管理器中:
- 点击 创建项目
- 选择
docker-compose.yml所在路径/volume1/docker/n8n-scraper/ - 点击 部署
等待镜像拉取完成,两个容器状态变为"运行中"即可。
三、部署验证
3.1 访问 n8n 面板
浏览器打开 http://你的NASIP:5678,使用 admin / 你设置的密码登录。
3.2 测试 Browserless 连通性
在 n8n 中创建一个测试工作流:
- 添加 Manual Trigger 节点
- 添加 HTTP Request 节点,配置如下:
- Method: POST
- URL:
http://browserless:3000/content - Body:
{"url":"https://www.baidu.com"}
点击执行,若返回百度首页 HTML,说明浏览器服务正常。
四 创建自动化流程
以下以一个"批量抓取页面文档编号"的实战场景为例,演示完整流程。
4.1 输入 URL 列表
URL 列表是手动输入的,在 Manual Trigger 后接一个 Set 节点:
| 字段名 | 值(换行 URL 列表) |
| data | URL 列表(每行一个) |
值内容:每行一个 URL,例如:
|
1 2 3 |
https://example.com/doc/1 https://example.com/doc/2 https://example.com/doc/3 |
然后 Set 节点 → Code 节点 → Loop Over Items。
4.2 数据分割
添加 Code 节点,选择 JavaScript,将换行文本转为标准 item 数组:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 获取上游输入 const input = $input.first().json.data; const urls = input .split("\n") // 按换行分割 .map(line => line.trim()) // 去首尾空格 .filter(line => line.length > 0); // 去掉空行 // 转成 n8n 需要的 item 数组 const items = urls.map(url => ({ json: { url: url } })); return items; |
这样生成的数组就是url的json文本
|
1 2 3 4 |
[ { "url": "https://example.com/doc/1" }, { "url": "https://example.com/doc/2" } ] |
4.3 循环执行
搜索添加 Loop Over Items(旧版叫 Split in Batches),配置如下:
| 参数 | 值 | 说明 |
| Batch Size | 1 | 每次只取 1 条 URL,逐条处理 |
| Reset | 关闭 | 保持默认即可 |
该节点有两个输出:
- loop:每次循环执行的逻辑
- done:所有循环完成后执行的逻辑
4.4 数据处理
前面是把url整理出来了,现在我们需要处理成HTTP Request能够执行的代码。在 loop 分支中添加 Code 节点,选择Javascript脚本。生成发送给 Browserless 的 Puppeteer 脚本:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
// ========== 输入参数 ========== const targetUrl = $input.first().json.url; // ========== Browserless 执行代码(在远程 Chrome 中运行)========== const code = `module.exports = async ({ page, context }) => { // 1. 从 context 获取传入参数 const targetUrl = context.url; // 2. 初始化结果变量 let span4Value = null; // 文档编号 let lastError = null; // 错误记录 // ========== 反检测配置(必须在页面加载前设置)========== await page.evaluateOnNewDocument(() => { // 抹除自动化标记 Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); window.chrome = { runtime: {} }; Object.defineProperty(navigator, 'plugins', { get: () => [{}] }); }); // ========== 请求拦截:屏蔽Cookie同意弹窗(可选)========== await page.setRequestInterception(true); page.on('request', (req) => { if (req.url().includes('第三方CMP域名')) { return req.abort(); // 阻止弹窗脚本加载 } req.continue(); }); // ========== 浏览器指纹伪装 ========== await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ... Edg/137.0.0.0'); await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1, isMobile: false }); await page.setExtraHTTPHeaders({ 'Accept-Language': 'zh-CN,zh;q=0.9', 'sec-ch-ua': '"Microsoft Edge";v="137", "Chromium";v="137", "Not/A)Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', }); // ========== 重试循环(最多3次)========== for (let attempt = 1; attempt <= 3; attempt++) { console.log('=== 第' + attempt + '次尝试 ==='); try { // 访问目标页面 const response = await page.goto(targetUrl, { waitUntil: 'load', timeout: 60000 }); console.log('【响应状态】', response?.status()); // 等待JS渲染(动态加载页面需要较长时间) await page.waitForTimeout(15000); // ========== 操作1:获取文档编号 ========== await page.waitForXPath('//hgroup/span[4]', { timeout: 15000 }); const span4Handles = await page.$x('//hgroup/span[4]'); if (span4Handles.length > 0) { span4Value = await span4Handles[0].evaluate(el => el.textContent?.trim() || null); console.log('span[4] 获取成功:', span4Value); lastError = null; break; // 成功,跳出重试 } } catch (mainError) { console.log('页面访问失败:', mainError.message); lastError = mainError.message; if (attempt < 3) await page.waitForTimeout(3000); } } // ========== 组装返回结果 ========== const result = { data: { span4Value: span4Value, pageTitle: await page.title().catch(() => 'unknown'), retryError: lastError } }; console.log('返回数据:', JSON.stringify(result)); return result; };`; // ========== 组装HTTP请求体(发送给Browserless)========== const bodyObj = { code: code, // 要在远程Chrome中执行的代码 context: { // 传入远程代码的参数 url: targetUrl } }; // ========== 输出给n8n HTTP Request节点 ========== return [{ json: { body: bodyObj // n8n HTTP Request节点会序列化为JSON发送 } }]; |
需要注意,组装返回结果时,一定要包裹到data里面,才能在后续的HTTP Request中拿到JSON返回值。
4.5 执行HTTP请求
在 Code 节点后添加 HTTP Request 节点,配置如下图。发送的body信息是上一个节点的body输出。
- Method: POST
- URL:
http://Localhost:3000/function - Body: 选择 JSON,内容引用上一个节点的
{{ $json.body }} - Options → Timeout:
60000(毫秒)
4.6 其他
可以制作时间计算,用于判断当前执行到哪一步,用时多少,剩余时间多少。
数据持久化,每一次循环执行完都把数据存储到数据库内,或者输出数据到excel里面。
延时等待
五、完整工作流节点顺序参考
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
Manual Trigger ↓ Set (URL 列表输入) ↓ Code (文本转数组) ↓ Loop Over Items ├──→ Code (组装 Browserless 请求体) │ ↓ │ HTTP Request (调用 browserless:3000/function) │ ↓ │ Set (解析响应字段) │ ↓ │ MySQL (插入数据库) │ ↓ │ Wait (随机延时 2-5s) │ ↓ └──→ (loop 回到 Loop Over Items) ↓ done → Code (生成 Excel 文件名) ↓ Spreadsheet File (写入 /output/result.xlsx) ↓ Telegram (发送完成通知,可选) |
以上就是完整的绿联 NAS 部署 n8n + Browserless 自动化爬虫方案。