产品集成资源文档定价
立即开始

© 2026 CapSolver. All rights reserved.

联系我们

Slack: lola@capsolver.com

产品

  • reCAPTCHA v2
  • reCAPTCHA v3
  • Cloudflare Turnstile
  • Cloudflare Challenge
  • AWS WAF
  • 浏览器插件
  • 更多验证码类型

集成

  • Selenium
  • Playwright
  • Puppeteer
  • n8n
  • 合作伙伴
  • 查看所有集成

资源

  • 推荐返佣系统
  • 官方文档
  • API 参考
  • 博客
  • 常见问题 (FAQ)
  • 术语表
  • 系统状态

法律声明

  • 服务条款
  • 隐私政策
  • 退款政策
  • 请勿出售我的信息
博客//如何在 n8n 中使用 CapSolver 解决 Cloudflare Challenge
Mar10, 2026

如何在 n8n 中使用 CapSolver 解决 Cloudflare Challenge

Ethan Collins

Ethan Collins

Pattern Recognition Specialist

Cloudflare 的机器人防护远远超出 CAPTCHA 小组件的范畴。Cloudflare Challenge — 即覆盖整个页面的“正在验证您的浏览器…”屏幕,会完全阻止访问网站 — 是网络上最激进的机器人防御之一。它在真实的浏览器环境中运行完整的 JavaScript 挑战,检查行为信号,并在放行之前对您的 TLS 连接进行指纹识别。

标准的自动化工具在这里会失败。原因不是它们无法解决挑战,而是 Cloudflare 对 TLS 握手本身进行指纹识别 — 任何非浏览器的 HTTP 客户端会在挑战页面出现之前被拦截,或者即使带有有效的 cf_clearance cookie,也会被立即重新挑战。

在本指南中,您将学习如何在 n8n 中构建一个真正可用的Cloudflare Challenge 爬虫 — 结合使用**CapSolver** 解决挑战,使用一个本地 Go TLS 服务器伪装成 Chrome 的 TLS 指纹,并通过 n8n 工作流将它们整合在一起。

您将构建的内容:

  • 一个双触发工作流(定时 + webhook),绕过 Cloudflare 机器人管理
  • 一个轻量级 Go 服务器,使用真正的 Chrome TLS 指纹执行 HTTP 请求
  • 一个可重用的爬虫模板,可以指向任何受 Cloudflare 保护的网站
  • 用例工作流,包括爬取、账户登录和独立的挑战解决器 API — 全部适用于 Cloudflare Challenge
  • Turnstile 工作流 — 一个解题 API、爬取模板和用于使用 Cloudflare Turnstile 的站点账户登录流程(无需代理或 TLS 服务器)

什么是 Cloudflare Challenge?

Cloudflare Challenge(也称为 JS 挑战或机器人管理挑战)是 Cloudflare 在访客访问受保护网站之前注入的全页面插页。您一定见过:一个黑色或白色的屏幕,上面有“正在验证您的浏览器…”或“请稍候…”的字样,以及加载条或 Cloudflare 标志。

不同于 Turnstile — 它是嵌入在页面内的小部件 — Cloudflare Challenge 会接管整个页面。在验证完成之前,您无法访问任何内容。

Cloudflare Challenge Cloudflare Turnstile
出现位置 全页面插页 — 完全阻止访问网站 嵌入页面内的小部件(例如登录表单)
界面样式 “正在验证您的浏览器…”的加载屏幕 表单中小勾选框或隐藏部件
由谁添加 Cloudflare 根据安全规则自动添加 网站所有者嵌入在 HTML 中
解决方式 AntiCloudflareTask — 需要代理 AntiTurnstileTaskProxyLess — 不需要代理
返回的 Cookie cf_clearance(特定域名,绑定 IP) Turnstile 令牌(短期有效,使用一次)
是否需要代理 是 — 解题和抓取必须使用同一 IP 否

如果您看到嵌入在表单内的复选框或小部件,那就是 Turnstile — 不是此挑战。如果不确定,请查阅 CapSolver 关于识别挑战类型的指南。


为什么标准 HTTP 客户端会失败

这里是大多数指南跳过的问题:Cloudflare 不仅检查您的 cookie,还检查您的 TLS 指纹。

当浏览器通过 HTTPS 连接网站时,会发送包含其支持的密码套件、扩展和设置细节的 TLS ClientHello。Cloudflare 会记录这个指纹(称为 JA3 或 JA4 指纹)并与已知的浏览器配置进行比对。

Go 的 net/http、Python 的 requests、curl 以及大多数 HTTP 库都有独特的 TLS 指纹。即使 CapSolver 成功解决挑战并返回了有效的 cf_clearance cookie,如果随后抓取时检测到非浏览器的 TLS 指纹,Cloudflare 仍会重新发起挑战或阻止请求。

解决方法:使用 Go 服务器结合 httpcloak,这是一款伪装成真实 Chrome TLS 堆栈的库,包含:

  • JA3 / JA4 指纹
  • HTTP/2 SETTINGS 帧
  • ALPN 协商
  • 请求头顺序和值

这使得抓取请求在网络层级级别完全模仿 Chrome 浏览器请求。


前提条件

要求 说明
自托管的 n8n 必需 — TLS 服务器必须与 n8n 运行在同一台机器上。n8n Cloud 不适用此用例。
CapSolver 账户 在这里注册 并获取 API 密钥
Go 1.21 及以上版本 必须安装在 n8n 服务器上。用 go version 检查。
住宅或移动代理 数据中心代理大多数 Cloudflare 保护站点会失败。请参阅下文 代理要求。

在 n8n 中设置 CapSolver

CapSolver 作为 n8n 的官方集成提供 — 无需安装社区节点。

步骤 1:打开凭据页面

进入您的 n8n 实例,导航至 Settings → Credentials。

显示 CapSolver 账户的 n8n 凭据页面

步骤 2:创建 CapSolver 凭据

  1. 点击右上角的 Create credential
  2. 搜索 “CapSolver” 并选择 CapSolver API
  3. 输入您的 API Key(来自 CapSolver 控制面板)
  4. 保持 Allowed HTTP Request Domains 设置为 All
  5. 点击 Save

您应该会看到绿色的 “Connection tested successfully” 提示条。

CapSolver 凭据配置及测试成功

重要提示: 工作流中每个 CapSolver 节点都会引用此凭据。只需创建一次,所有解题工作流共享同一个凭据。


步骤 1 — 构建 TLS 服务器

这个 Go 服务器接受来自 n8n 的抓取请求,使用 httpcloak 的 Chrome TLS 配置执行请求。它是一个小巧的独立二进制,您可以与 n8n 并行运行。

创建源码文件

新建一个目录并保存以下代码为 main.go:

bash Copy
mkdir -p ~/tls-server && cd ~/tls-server
go Copy
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/sardanioss/httpcloak/client"
)

type FetchRequest struct {
	URL     string            `json:"url"`
	Method  string            `json:"method"`
	Headers map[string]string `json:"headers"`
	Proxy   string            `json:"proxy"`
	Body    string            `json:"body"`
}

type FetchResponse struct {
	Status  int                 `json:"status"`
	Body    string              `json:"body"`
	Headers map[string][]string `json:"headers"`
}

type ErrorResponse struct {
	Error string `json:"error"`
}

func writeError(w http.ResponseWriter, status int, msg string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(ErrorResponse{Error: msg})
}

func fetchHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "only POST allowed")
		return
	}

	var req FetchRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid JSON: "+err.Error())
		return
	}

	if req.URL == "" {
		writeError(w, http.StatusBadRequest, "url is required")
		return
	}
	if req.Method == "" {
		req.Method = "GET"
	}

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	c := client.NewClient("chrome-145", client.WithTimeout(60*time.Second))
	defer c.Close()

	if req.Proxy != "" {
		c.SetProxy(req.Proxy)
	}

	// Extract user-agent separately so httpcloak uses it instead of its preset value.
	headers := make(map[string][]string, len(req.Headers))
	var userAgent string
	for k, v := range req.Headers {
		lower := strings.ToLower(k)
		if lower == "user-agent" {
			userAgent = v
		} else {
			headers[k] = []string{v}
		}
	}

	var bodyReader io.Reader
	if req.Body != "" {
		bodyReader = strings.NewReader(req.Body)
	}

	hcReq := &client.Request{
		Method:    strings.ToUpper(req.Method),
		URL:       req.URL,
		Headers:   headers,
		Body:      bodyReader,
		UserAgent: userAgent,
		FetchMode: client.FetchModeNavigate,
	}

	resp, err := c.Do(ctx, hcReq)
	if err != nil {
		writeError(w, http.StatusBadGateway, "fetch failed: "+err.Error())
		return
	}

	body, err := resp.Text()
	if err != nil {
		writeError(w, http.StatusInternalServerError, "read body failed: "+err.Error())
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(FetchResponse{
		Status:  resp.StatusCode,
		Body:    body,
		Headers: resp.Headers,
	})
}

func main() {
	const port = "7878"

	mux := http.NewServeMux()
	mux.HandleFunc("/fetch", fetchHandler)
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, `{"status":"ok"}`)
	})

	log.Printf("TLS server (httpcloak chrome-145) listening on :%s", port)
	log.Fatal(http.ListenAndServe(":"+port, mux))
}

初始化并构建

bash Copy
go mod init tls-server
go get github.com/sardanioss/httpcloak/client
go build -o main main.go

运行服务器

bash Copy
./main

验证服务运行(新开终端)

bash Copy
curl http://localhost:7878/health

期待输出:{"status":"ok"}

注意: TLS 服务器必须与您的 n8n 实例运行在同一台机器上。n8n 工作流通过 http://localhost:7878/fetch 调用它。如果您使用 n8n Cloud,则需自托管设置 — 这也是强烈推荐自托管 n8n 用于 Cloudflare Challenge 爬取的原因之一。


步骤 2 — 允许 n8n 调用 localhost

默认情况下,n8n 阻止 HTTP Request 节点调用 localhost 地址(防 SSRF 攻击)。您需要禁用此限制。

添加环境变量 N8N_BLOCK_ACCESS_TO_LOCALHOST=false 并重启您的 n8n 实例。具体操作取决于您的运行方式:

如果直接运行 n8n:

bash Copy
export N8N_BLOCK_ACCESS_TO_LOCALHOST=false
n8n start

如果使用 Docker:

在您的 docker run 命令中添加 -e N8N_BLOCK_ACCESS_TO_LOCALHOST=false,或在 docker-compose.yml 的 environment 部分添加该变量。


工作流:Cloudflare Challenge 解题 API

此工作流创建一个 POST 接口,接受受 Cloudflare 保护的 URL 和代理,借助 CapSolver 解决挑战,并返回原始的 cf_clearance cookie 和 userAgent。无需 TLS 服务器 — 由您的应用执行抓取。

工作原理

Copy
Webhook (POST /solver-cloudflare-challenge) → Cloudflare Challenge (CapSolver)
                                             → 格式化解题结果 → 返回给 Webhook

4 个节点,纯 webhook, 无定时路径,无 TLS 服务器依赖。

  1. Webhook — 接收带 websiteURL 和 proxy 的 POST 请求
  2. Cloudflare Challenge — 使用 AntiCloudflareTask 的 CapSolver 解决挑战
  3. 格式化解题结果 — 将 cookies 对象序列化为 cookie 字符串,通过 continueOnFail 处理错误
  4. 响应 Webhook — 返回 cf_clearance、序列化 cookie 字符串和 userAgent### 节点配置

1. Webhook 节点

设置 值
HTTP 方法 POST
路径 solver-cloudflare-challenge
响应 Response Node

这将在以下地址创建一个端点:https://your-n8n-instance.com/webhook/solver-cloudflare-challenge

2. CapSolver 节点(Cloudflare 挑战)

参数 值 说明
操作 Cloudflare Challenge 选择 AntiCloudflareTask
类型 AntiCloudflareTask 全页面 Cloudflare 挑战
网站 URL ={{ $json.body.websiteURL }} 受 Cloudflare 保护的 URL
代理 ={{ $json.body.proxy }} 住宅代理,格式为 host:port:user:pass
失败时继续 true 返回结构化错误而不是崩溃

CapSolver 通过你的代理启动真实浏览器,加载目标 URL 并解决 Cloudflare 挑战。成功时,它返回一个包含以下内容的 solution 对象:

  • cookies — 一个对象 { cf_clearance: "..." } ,包含清除令牌 Cookie
  • userAgent — 浏览器在解决过程中使用的精确 User-Agent 字符串

注意: 字段为 websiteURL(而非 targetURL),这与所有其他 Solver API 和 CapSolver 节点本身使用的字段名一致。

3. 格式化解决方案(代码节点)

此节点是必需的,因为 AntiCloudflareTask 返回的 cookies 是一个对象({ cf_clearance: "..." }),而不是像 reCAPTCHA 或 Turnstile 那样的简单令牌字符串。它将 Cookie 序列化,提取 cf_clearance,并在 CapSolver 失败时返回结构化错误。

javascript Copy
const input = $input.first().json;

if (input.error || !input.data || !input.data.solution) {
  const errorMsg = input.error
    ? (input.error.message || JSON.stringify(input.error))
    : 'No solution returned — site may not be showing a challenge';
  return [{ json: { success: false, error: errorMsg } }];
}

const solution = input.data.solution;
const cookies = solution.cookies;
const cfClearance = (cookies && typeof cookies === 'object')
  ? (cookies.cf_clearance || '') : '';
const cookieString = (cookies && typeof cookies === 'object')
  ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')
  : (cookies || '');

return [{ json: {
  success: true,
  cf_clearance: cfClearance,
  cookies: cookieString,
  userAgent: solution.userAgent || '',
  solvedAt: new Date().toISOString()
}}];

4. 响应 Webhook

设置 值
响应格式 JSON
响应内容 ={{ JSON.stringify($json) }}

测试示例

bash Copy
curl -X POST https://your-n8n-instance.com/webhook/solver-cloudflare-challenge \
  -H "Content-Type: application/json" \
  -d '{
    "websiteURL": "https://protected-site.com",
    "proxy": "host:port:user:pass"
  }'

成功响应示例:

json Copy
{
  "success": true,
  "cf_clearance": "abc123...",
  "cookies": "cf_clearance=abc123...",
  "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/145.0.0.0 ...",
  "solvedAt": "2026-03-11T12:00:00.000Z"
}

失败响应示例(未找到挑战,代理错误等):

json Copy
{
  "success": false,
  "error": "No solution returned — site may not be showing a challenge"
}

导入此工作流

复制以下 JSON 并通过 n8n 的 菜单 → 从 JSON 导入 功能导入。导入后,在 Cloudflare Challenge 节点选择你的 CapSolver 认证。

点击展开工作流 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Cloudflare Challenge \u2014 Solver API\n\n### How it works\n\n1. Receives a request via webhook to solve a Cloudflare challenge.\n2. Uses the Cloudflare Challenge node to attempt solving the challenge.\n3. Formats the solved challenge token via custom code.\n4. Responds back to the original request with the solved token.\n\n### Setup steps\n\n- [ ] Configure the Webhook node to receive requests.\n- [ ] Set up CapSolver credentials to enable solving Cloudflare challenges.\n- [ ] Ensure the Response node is properly configured to send back the solved token.\n\n### Customization\n\nYou can customize the code in the 'Format Solution' node to change the formatting of the solved token response.",
        "width": 480,
        "height": 672
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -816,
        -128
      ],
      "id": "254f3829-0e6e-4ae3-bf83-85851be9a7bc",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Receive and solve challenge\n\nTriggers on a new request and attempts to solve the Cloudflare challenge.",
        "width": 512,
        "height": 304,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -256,
        -128
      ],
      "id": "44e79738-a2f0-41ec-8ff0-514afbd6cc45",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## Format and return solution\n\nFormats the solution and responds back with the solved token.",
        "width": 496,
        "height": 304,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        352,
        -128
      ],
      "id": "fc40112d-389d-4d4e-8872-af2ae533c513",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "solver-cloudflare-challenge",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -208,
        0
      ],
      "id": "cf770001-7777-7777-7777-777777777701",
      "name": "Receive Solver Request",
      "webhookId": "cf770001-aaaa-bbbb-cccc-777777777701",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.body.websiteURL }}",
        "proxy": "={{ $json.body.proxy }}",
        "userAgent": "={{ $json.body.userAgent }}",
        "html": "={{ $json.body.html }}"
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        112,
        0
      ],
      "id": "cf770001-7777-7777-7777-777777777702",
      "name": "Cloudflare Challenge",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first().json;\n\nif (input.error || !input.data || !input.data.solution) {\n  const errorMsg = input.error\n    ? (input.error.message || JSON.stringify(input.error))\n    : 'No solution returned \u2014 site may not be showing a challenge';\n  return [{ json: { success: false, error: errorMsg } }];\n}\n\nconst solution = input.data.solution;\nconst cookies = solution.cookies;\nconst cfClearance = (cookies && typeof cookies === 'object') ? (cookies.cf_clearance || '') : '';\nconst cookieString = (cookies && typeof cookies === 'object')\n  ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n  : (cookies || '');\n\nreturn [{ json: {\n  success: true,\n  cf_clearance: cfClearance,\n  cookies: cookieString,\n  userAgent: solution.userAgent || '',\n  solvedAt: new Date().toISOString()\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        0
      ],
      "id": "cf770001-7777-7777-7777-777777777703",
      "name": "Format Solution"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        700,
        0
      ],
      "id": "cf770001-7777-7777-7777-777777777704",
      "name": "Return Solved Token"
    }
  ],
  "connections": {
    "Receive Solver Request": {
      "main": [
        [
          {
            "node": "Cloudflare Challenge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cloudflare Challenge": {
      "main": [
        [
          {
            "node": "Format Solution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Solution": {
      "main": [
        [
          {
            "node": "Return Solved Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}

工作流:使用 Cloudflare 挑战解决方案

到目前为止,上述 Solver API 展示了如何获取已解决的 cf_clearance Cookie 和 userAgent。但实际上你如何使用它们呢?

与需要在表单字段中提交令牌的 reCAPTCHA 或 Turnstile 不同,Cloudflare 挑战返回的是Cookie(cf_clearance),必须在后续每次请求中以请求头发送。Cookie 绑定于解题时使用的代理 IP 和 User-Agent,两者必须完全匹配。

一般流程如下:

  1. 解决 Cloudflare 挑战 → 从 CapSolver 获得 cf_clearance Cookie 和 userAgent
  2. 准备请求 → 构建包含 Cookie、匹配的 User-Agent 和 Chrome 风格的 sec-ch-ua 头的请求头
  3. 通过 TLS 服务器发起请求 → 通过 http://localhost:7878/fetch 发送请求,以匹配 Chrome 的 TLS 指纹
  4. 验证响应 → 检查返回的是否是有效内容(状态码 200 且包含 HTML),而非再次出现的挑战页面
  5. 处理结果 → 使用 HTML 节点、编辑字段或代码节点提取所需数据

关键点: 即使拥有有效的 cf_clearance Cookie,标准 HTTP 客户端也会失败 — 因为 Cloudflare 会指纹 TLS 握手本身。Go TLS 服务器(httpcloak)使抓取请求在网络层面完全模仿 Chrome。所有获取 Cloudflare 保护页面的工作流都必须通过 TLS 服务器。### 示例:Cloudflare 挑战抓取器

Cloudflare 挑战在 n8n 中的工作流程,包含定时和 webhook 路径

工作流程流程

定时路径:

Copy
每 6 小时 → 设置目标配置 [定时] → 解决 Cloudflare 挑战
         → 准备 TLS 请求 → 通过 TLS 服务器获取 → 提取结果

Webhook 路径:

Copy
Webhook 触发 → 设置目标配置 [Webhook] → 解决 Cloudflare 挑战
             → 准备 TLS 请求 → 通过 TLS 服务器获取 → 提取结果 → 响应 Webhook

工作原理

  1. 设置目标配置 — 存储 targetURL 和 proxy(格式为 host:port:user:pass)。定时路径使用硬编码值;Webhook 路径从 POST 请求体读取。
  2. 解决 Cloudflare 挑战 — CapSolver 节点,onError: "continueRegularOutput" —— 即使页面当前没有显示挑战,也继续执行。
  3. 准备 TLS 请求 — 代码节点,将 host:port:user:pass 格式的代理转换为 http://user:pass@host:port 的 URL 格式,对 solution.cookies 进行序列化为 cookie 头字符串,并使用 solve 得到的精确 userAgent 构建类似 Chrome 的请求头。
  4. 通过 TLS 服务器获取 — 使用 contentType: "raw"(不是 "json",因为 n8n 的 JSON 模式会损坏请求体)向 http://localhost:7878/fetch 发送 HTTP 请求。
  5. 提取结果 — 从 TLS 服务器响应中提取 status、body 和 fetchedAt。
  6. 响应 Webhook — 将结果作为 JSON 返回(仅限 webhook 路径)。

为什么用 contentType: "raw" 而不用 "json"? n8n 的 json 内容类型模式期望请求体参数为键值对。如果你传入 JSON.stringify($json) 作为字符串,n8n 会把整个字符串视为单个格式错误的参数,发送 {"": ""} 给服务器。使用 raw 模式则会按表达式计算的结果精确发送请求体。

点击展开工作流程 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Cloudflare Challenge \u2014 CapSolver + Schedule + Webhook\n\n### How it works\n\n1. Triggers schedule every 6 hours to solve Cloudflare challenges.\n2. Webhook triggers on request to solve Cloudflare challenges.\n3. Sets target and proxy configuration for requests.\n4. Solves the Cloudflare challenge using CapSolver.\n5. Sends results via TLS and processes the HTTP response.\n\n### Setup steps\n\n- Ensure CapSolver credentials are configured.\n- Confirm webhook URL is set and accessible.\n- Check schedule configuration for every 6 hours trigger.\n- Verify endpoint http://localhost:7878/fetch is available.\n\n### Customization\n\nConsider customizing the target URL or proxy settings within the set nodes.",
        "width": 480,
        "height": 688
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1008,
        -112
      ],
      "id": "3c1c3e96-631a-4cf6-bbff-85fd8b1a01e8",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Scheduled Cloudflare challenge\n\nTriggered every 6 hours to solve Cloudflare challenge.",
        "width": 1696,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -448,
        -112
      ],
      "id": "e9187404-7818-4233-af66-091c90a40476",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## Webhook Cloudflare challenge\n\nRespond to solver requests via webhook to solve Cloudflare challenge.",
        "width": 2000,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -448,
        304
      ],
      "id": "b5d5ba23-8ed2-4afb-8113-3fa8f2fba51e",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -400,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111101",
      "name": "Every 6 Hours"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-s-001",
              "name": "targetURL",
              "value": "https://www.listaspam.com/busca.php?Telefono=671484239",
              "type": "string"
            },
            {
              "id": "cfg-s-002",
              "name": "proxy",
              "value": "YOUR_PROXY_HOST:PORT:USER:PASS",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -96,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111102",
      "name": "Set Target Config [Schedule]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}"
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        208,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111103",
      "name": "Solve Cloudflare Challenge [Schedule]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Target Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'none',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.targetURL,\n  method: 'GET',\n  proxy: toProxyURL(config.proxy),\n  headers\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111104",
      "name": "Prepare TLS Request [Schedule]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        800,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111105",
      "name": "Fetch via TLS Server [Schedule]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "out-s-001",
              "name": "status",
              "value": "={{ $json.status }}",
              "type": "number"
            },
            {
              "id": "out-s-002",
              "name": "body",
              "value": "={{ $json.body }}",
              "type": "string"
            },
            {
              "id": "out-s-003",
              "name": "fetchedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1104,
        0
      ],
      "id": "cf111111-1111-1111-1111-111111111106",
      "name": "Extract Result [Schedule]"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "cloudflare-scraper",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -400,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111107",
      "name": "Receive Solver Request",
      "webhookId": "cf111111-aaaa-bbbb-cccc-111111111107",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-w-001",
              "name": "targetURL",
              "value": "={{ $json.body.targetURL }}",
              "type": "string"
            },
            {
              "id": "cfg-w-002",
              "name": "proxy",
              "value": "={{ $json.body.proxy }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -96,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111108",
      "name": "Set Target Config [Webhook]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}"
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        208,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111109",
      "name": "Solve Cloudflare Challenge [Webhook]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Target Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'none',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.targetURL,\n  method: 'GET',\n  proxy: toProxyURL(config.proxy),\n  headers\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        512,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111110",
      "name": "Prepare TLS Request [Webhook]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        800,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111111",
      "name": "Fetch via TLS Server [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "out-w-001",
              "name": "status",
              "value": "={{ $json.status }}",
              "type": "number"
            },
            {
              "id": "out-w-002",
              "name": "body",
              "value": "={{ $json.body }}",
              "type": "string"
            },
            {
              "id": "out-w-003",
              "name": "fetchedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1104,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111112",
      "name": "Extract Result [Webhook]"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1408,
        420
      ],
      "id": "cf111111-1111-1111-1111-111111111113",
      "name": "Return Solved Token"
    }
  ],
  "connections": {
    "Every 6 Hours": {
      "main": [
        [
          {
            "node": "Set Target Config [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Target Config [Schedule]": {
      "main": [
        [
          {
            "node": "Solve Cloudflare Challenge [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Cloudflare Challenge [Schedule]": {
      "main": [
        [
          {
            "node": "Prepare TLS Request [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare TLS Request [Schedule]": {
      "main": [
        [
          {
            "node": "Fetch via TLS Server [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch via TLS Server [Schedule]": {
      "main": [
        [
          {
            "node": "Extract Result [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Solver Request": {
      "main": [
        [
          {
            "node": "Set Target Config [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Target Config [Webhook]": {
      "main": [
        [
          {
            "node": "Solve Cloudflare Challenge [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Cloudflare Challenge [Webhook]": {
      "main": [
        [
          {
            "node": "Prepare TLS Request [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare TLS Request [Webhook]": {
      "main": [
        [
          {
            "node": "Fetch via TLS Server [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch via TLS Server [Webhook]": {
      "main": [
        [
          {
            "node": "Extract Result [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Result [Webhook]": {
      "main": [
        [
          {
            "node": "Return Solved Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}

---## 工作流程:用例示例

上面的 Solver API 和爬虫示例展示了核心模式:解决验证码挑战,然后使用解决方案通过 TLS 进行抓取。以下工作流程将此模式扩展到生产就绪的用例——每个用例都具有双重触发器(定时 + webhook)、持久状态跟踪和结构化输出。每个用例都需要相同的先决条件:自托管的 n8n 实例、运行在 7878 端口的 TLS 服务器、住宅代理和 CapSolver 凭证。

工作流程 目的
Cloudflare Challenge Scraping — 价格与产品详情 — CapSolver + 定时 + Webhook 每 6 小时从 CF 保护页面爬取价格和产品名称,与之前的值比较,发生变化时发送警报
Cloudflare Challenge 账户登录 — CapSolver + 定时 + Webhook 先解决挑战验证码,再通过 TLS 服务器 POST 凭证,以登录你自己在 CF 保护网站上的账户
Turnstile — Solver API 提供一个 webhook 来解决 Turnstile 并返回令牌——无需代理或 TLS 服务器
Turnstile 爬虫 — 价格与产品监控 解决 Turnstile,使用令牌抓取产品页面,提取价格和名称,发生变化时发送警报
Turnstile 账户登录 先解决挑战验证码,然后使用令牌 POST 凭证登录你在 Turnstile 保护网站上的账户——无需代理或 TLS 服务器

此工作流每 6 小时(计划任务)或按需(Webhook)抓取一次产品页面,使用 HTML 节点提取价格,并与之前存储的值进行比较。

计划路径:

Copy
每 6 小时 → 设置目标配置 → 解决 Cloudflare 挑战 → 准备 TLS 请求
             → 通过 TLS 服务器抓取 → 提取数据 → 比较数据
             → 数据是否变更? → 构建警报 / 无变化

错误处理: 如果 CapSolver 失败,工作流继续运行但不使用 cookies(通过 continueOnFail)。如果页面当前没有显示挑战,TLS 服务器抓取仍可能成功。

关键行为:

  • 使用 dataPropertyName: "body"(而非 "data"),因为 TLS 服务器返回 { status, body, headers }
  • HTML 节点通过 CSS 选择器(.product-price,h1)提取价格和产品名称
  • $workflow.staticData.lastPrice 用于在执行间持久保存上次价格
  • 价格比较检测价格下降(严重级别:deal)和上涨(严重级别:info)
  • 代理格式自动转换:host:port:user:pass → http://user:pass@host:port,通过 toProxyURL() 辅助函数
点击展开完整工作流 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Cloudflare 挑战抓取 — 价格与产品详情 — CapSolver + 计划任务 + Webhook\n\n### 工作原理\n\n1. 触发器被设置为定期检查目标网站,或响应外部 webhook。\n2. 应用目标 URL 和代理配置。\n3. 解决 Cloudflare 挑战以绕过网站防护。\n4. 发起安全请求抓取目标服务器数据。\n5. 提取的数据进行比较以检测变化。\n6. 根据检测结果构建并发送警报或返回数据。\n\n### 设置步骤\n\n- [ ] 在 '设置目标配置' 节点中配置 URL 和代理设置。\n- [ ] 连接 CapSolver 凭据,用于解决 Cloudflare 挑战。\n- [ ] 确保 webhook URL 配置正确以接收外部请求。\n\n### 自定义\n\n调整监控频率,修改 '每 6 小时' 节点的时间间隔或 webhook 触发器设置。",
        "width": 480,
        "height": 896
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1104,
        -192
      ],
      "id": "85c55c3d-335a-47e5-8721-82fa4d633033",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## 计划触发器设置\n\n每 6 小时初始化调度以开始数据抓取流程。",
        "width": 1392,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -544,
        -112
      ],
      "id": "6ac3bd35-66ef-43ed-a8b5-5d5f6d367fba",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## 计划数据处理\n\n在计划触发后处理数据提取、比较和警报构建。",
        "width": 1088,
        "height": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        960,
        -192
      ],
      "id": "a1c60513-ed8b-43f2-bff3-a7b19436337f",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Webhook 触发器设置\n\n通过 webhook 监控外部请求以开始抓取流程。",
        "width": 1392,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -544,
        384
      ],
      "id": "ee4d9a0d-3233-4b70-828b-078c9eee0086",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Webhook 数据处理与响应\n\n从 webhook 触发器提取并比较数据;根据变化返回响应。",
        "width": 1392,
        "height": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        960,
        336
      ],
      "id": "4ad55fbf-2882-4c63-9e09-37e674b00145",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -500,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333301",
      "name": "每 6 小时"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-s-001",
              "name": "targetURL",
              "value": "https://YOUR_CF_PROTECTED_SITE.com/product-page",
              "type": "string"
            },
            {
              "id": "cfg-s-002",
              "name": "proxy",
              "value": "YOUR_PROXY_HOST:PORT:USER:PASS",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -200,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333302",
      "name": "设置目标配置 [计划]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}"
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        100,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333303",
      "name": "解决 Cloudflare 挑战 [计划]",
      "onError": "continueRegularOutput",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Target Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'none',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.targetURL,\n  method: 'GET',\n  proxy: toProxyURL(config.proxy),\n  headers\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333304",
      "name": "准备 TLS 请求 [计划]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        700,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333305",
      "name": "通过 TLS 服务器抓取 [计划]"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "price",
              "cssSelector": ".product-price, [data-price], .price",
              "returnValue": "text",
              "returnArray": false
            },
            {
              "key": "productName",
              "cssSelector": "h1, .product-title",
              "returnValue": "text",
              "returnArray": false
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        1000,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333306",
      "name": "提取数据"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || '产品';\n\nconst parsePrice = (str) => {\n  if (!str) return null;\n  const match = str.match(/[\\d]+\\.?\\d*/);\n  return match ? parseFloat(match[0].replace(',', '')) : null;\n};\n\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\n\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\n\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? '下降' : '上涨') : '无变化';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\n\nreturn [{\n  json: {\n    productName,\n    currentPrice,\n    previousPrice: previousPrice || '首次检查',\n    changed,\n    direction,\n    diff: changed ? `¥${diff}` : null,\n    checkedAt: new Date().toISOString()\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333307",
      "name": "比较数据"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "price-if-001",
              "leftValue": "={{ $json.changed }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1600,
        0
      ],
      "id": "cf333333-3333-3333-3333-333333333308",
      "name": "数据是否变更?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "alert-001",
              "name": "alert",
              "value": "=价格{{ $json.direction }}:{{ $json.productName }},从 {{ $json.previousPrice }} 变为 {{ $json.currentPrice }} ({{ $json.direction === '下降' ? '-' : '+' }}{{ $json.diff }})",
              "type": "string"
            },
            {
              "id": "alert-002",
              "name": "severity",
              "value": "={{ $json.direction === '下降' ? 'deal' : 'info' }}",
              "type": "string"
            },
            {
              "id": "alert-003",
              "name": "checkedAt",
              "value": "={{ $json.checkedAt }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1900,
        -80
      ],
      "id": "cf333333-3333-3333-3333-333333333309",
      "name": "构建警报"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "nc-001",
              "name": "status",
              "value": "no_change",
              "type": "string"
            },
            {
              "id": "nc-002",
              "name": "currentPrice",
              "value": "={{ $json.currentPrice }}",
              "type": "string"
            },
            {
              "id": "nc-003",
              "name": "checkedAt",
              "value": "={{ $json.checkedAt }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1900,
        128
      ],
      "id": "cf333333-3333-3333-3333-333333333310",
      "name": "无变化"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "cloudflare-price-monitor",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -500,
        500
      ],
      "id": "cf333333-3333-3333-3333-333333333311",
      "name": "接收监控请求",
      "webhookId": "cf333333-aaaa-bbbb-cccc-333333333311",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-w-001",
              "name": "targetURL",
              "value": "={{ $json.body.targetURL }}",
              "type": "string"
            },
            {
              "id": "cfg-w-002",
              "name": "proxy",
              "value": "={{ $json.body.proxy }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -200,
        500
      ],
      "id": "cf333333-3333-3333-3333-333333333312",
      "name": "设置目标配置 [Webhook]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}"
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        100,
        500
      ],
      "id": "cf333333-3333-3333-3333-333333333313",
      "name": "解决 Cloudflare 挑战 [Webhook]",
      "onError": "continueRegularOutput",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Target Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'none',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.targetURL,\n  method: 'GET',\n  proxy: toProxyURL(config.proxy),\n  headers\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        500
      ],
      "id": "cf333333-3333-3333-3333-333333333314",
      "name": "准备 TLS 请求 [Webhook]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        700,
        500
      ],
      "id": "cf333333-3333-3333-3333-333333333315",
      "name": "通过 TLS 服务器抓取 [Webhook]"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "price",
              "cssSelector": ".product-price, [data-price], .price",
              "returnValue": "text",
              "returnArray": false
            },
            {
              "key": "productName",
              "cssSelector": "h1, .product-title",
              "returnValue": "text",
              "returnArray": false
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        1008,
        528
      ],
      "id": "cf333333-3333-3333-3333-333333333316",
      "name": "提取数据 [Webhook]"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || '产品';\n\nconst parsePrice = (str) => {\n  if (!str) return null;\n  const match = str.match(/[\\d]+\\.?\\d*/);\n  return match ? parseFloat(match[0].replace(',', '')) : null;\n};\n\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\n\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\n\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? '下降' : '上涨') : '无变化';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\n\nreturn [{\n  json: {\n    productName,\n    currentPrice,\n    previousPrice: previousPrice || '首次检查',\n    changed,\n    direction,\n    diff: changed ? `¥${diff}` : null,\n    checkedAt: new Date().toISOString()\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1296,
        528
      ],
      "id": "cf333333-3333-3333-3333-333333333317",
      "name": "比较数据 [Webhook]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "price-if-002",
              "leftValue": "={{ $json.changed }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1600,
        528
      ],
      "id": "cf333333-3333-3333-3333-333333333318",
      "name": "数据是否变更? [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "alert-004",
              "name": "alert",
              "value": "=价格{{ $json.direction }}:{{ $json.productName }},从 {{ $json.previousPrice }} 变为 {{ $json.currentPrice }} ({{ $json.direction === '下降' ? '-' : '+' }}{{ $json.diff }})",
              "type": "string"
            },
            {
              "id": "alert-005",
              "name": "severity",
              "value": "={{ $json.direction === '下降' ? 'deal' : 'info' }}",
              "type": "string"
            },
            {
              "id": "alert-006",
              "name": "checkedAt",
              "value": "={{ $json.checkedAt }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1904,
        448
      ],
      "id": "cf333333-3333-3333-3333-333333333319",
      "name": "构建警报 [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "nc-004",
              "name": "status",
              "value": "no_change",
              "type": "string"
            },
            {
              "id": "nc-005",
              "name": "currentPrice",
              "value": "={{ $json.currentPrice }}",
              "type": "string"
            },
            {
              "id": "nc-006",
              "name": "checkedAt",
              "value": "={{ $json.checkedAt }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1904,
        656
      ],
      "id": "cf333333-3333-3333-3333-333333333320",
      "name": "无变化 [Webhook]"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        2208,
        528
      ],
      "id": "cf333333-3333-3333-3333-333333333321",
      "name": "返回抓取数据"
    }
  ],
  "connections": {
    "每 6 小时": {
      "main": [
        [
          {
            "node": "设置目标配置 [计划]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "设置目标配置 [计划]": {
      "main": [
        [
          {
            "node": "解决 Cloudflare 挑战 [计划]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "解决 Cloudflare 挑战 [计划]": {
      "main": [
        [
          {
            "node": "准备 TLS 请求 [计划]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "准备 TLS 请求 [计划]": {
      "main": [
        [
          {
            "node": "通过 TLS 服务器抓取 [计划]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "通过 TLS 服务器抓取 [计划]": {
      "main": [
        [
          {
            "node": "提取数据",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "提取数据": {
      "main": [
        [
          {
            "node": "比较数据",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "比较数据": {
      "main": [
        [
          {
            "node": "数据是否变更?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "数据是否变更?": {
      "main": [
        [
          {
            "node": "构建警报",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "无变化",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "接收监控请求": {
      "main": [
        [
          {
            "node": "设置目标配置 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "设置目标配置 [Webhook]": {
      "main": [
        [
          {
            "node": "解决 Cloudflare 挑战 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "解决 Cloudflare 挑战 [Webhook]": {
      "main": [
        [
          {
            "node": "准备 TLS 请求 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "准备 TLS 请求 [Webhook]": {
      "main": [
        [
          {
            "node": "通过 TLS 服务器抓取 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "通过 TLS 服务器抓取 [Webhook]": {
      "main": [
        [
          {
            "node": "提取数据 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "提取数据 [Webhook]": {
      "main": [
        [
          {
            "node": "比较数据 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "比较数据 [Webhook]": {
      "main": [
        [
          {
            "node": "数据是否变更? [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "数据是否变更? [Webhook]": {
      "main": [
        [
          {
            "node": "构建警报 [Webhook]",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "无变化 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "构建警报 [Webhook]": {
      "main": [
        [
          {
            "node": "返回抓取数据",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "无变化 [Webhook]": {
      "main": [
        [
          {
            "node": "返回抓取数据",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}
### 示例 2:账户登录

此工作流自动化登录到受 Cloudflare 保护的网站。Set Login Config 节点集中管理所有参数 — [Schedule] 用于定时路径,[Webhook] 用于按需 webhook 路径。

定时路径:

Copy
每 24 小时 → Set Login Config → 解决 CF 挑战
            → 准备 TLS 登录请求 → 通过 TLS 服务器提交登录
            → 登录成功? → 标记成功 / 标记失败

错误处理: 如果 CapSolver 失败,工作流会通过 continueOnFail 继续执行但不带 cookies。登录请求很可能失败,登录成功?节点会捕获此情况。

关键行为:

  • 使用 cf_clearance cookie + userAgent 作为 HTTP 头部(不会在表单体中提交 token — 不同于提交 g-recaptcha-response 的 reCAPTCHA 登录)
  • 表单字段通过 URLSearchParams 设置 — 在 Set Login Config 中编辑字段名(usernameField、passwordField)以匹配你的站点
  • 登录成功? 判断 status < 400 且响应体中包含 successMarker
  • Webhook 路径通过 Respond to Webhook 返回 JSON 格式结果
点击展开完整工作流 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Cloudflare 挑战账户登录 \u2014 CapSolver + 定时 + Webhook\n\n### 工作原理\n\n1. 每 24 小时调度一次登录流程并解决 Cloudflare 挑战。\n2. 当由定时触发时,准备并提交 TLS 登录请求。\n3. 处理定时登录的成功或失败并记录结果。\n4. 通过 webhook 接收登录请求并解决 Cloudflare 挑战。\n5. 当由 webhook 触发时,准备并提交 TLS 登录请求。\n6. 处理 webhook 登录的成功或失败并返回结果。\n\n### 设置步骤\n\n- [ ] 确保配置了 Cloudflare CapSolver API 凭据。\n- [ ] 搭建本地运行的 TLS 服务器,地址 http://localhost:7878。\n- [ ] 配置 webhook URL 以接收登录请求。\n\n### 自定义\n\n调整定时间隔以满足具体频率需求。",
        "width": 480,
        "height": 896
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1248,
        -320
      ],
      "id": "ba1d6098-8cd2-40f1-b9ae-b945303e5d12",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## 定时登录开始\n\n每 24 小时开始登录流程作为后续操作的触发点。",
        "width": 240,
        "height": 336,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -688,
        -256
      ],
      "id": "acc8f2a2-298f-4d1b-9db7-8a98fe626abb",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## 定时登录工作流\n\n处理 Cloudflare 挑战,准备并提交登录请求,每 24 小时检查登录是否成功。",
        "width": 1328,
        "height": 480,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -416,
        -320
      ],
      "id": "1259d9f4-0b54-4693-89af-193f3ccda6a0",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Webhook 登录开始\n\n通过 webhook 接收登录请求以启动后续流程。",
        "width": 240,
        "height": 320,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -688,
        272
      ],
      "id": "e95374d2-4029-469d-8e8f-a9afb66ae2ed",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Webhook 登录工作流\n\n处理接收到的 webhook 登录请求,解决 Cloudflare 挑战,准备并提交登录请求,检查是否成功,记录结果。",
        "width": 1216,
        "height": 528,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -416,
        192
      ],
      "id": "da56a87e-6675-439a-8c65-7da631a86df1",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "## 返回 webhook 结果\n\n以登录尝试结果响应最初的 webhook 请求。",
        "width": 240,
        "height": 320,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        832,
        256
      ],
      "id": "7a3f246c-ebd5-419f-a955-b01669743b31",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -640,
        -80
      ],
      "id": "cf666666-6666-6666-6666-666666666601",
      "name": "每 24 小时"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-001",
              "name": "targetURL",
              "value": "https://YOUR_CF_PROTECTED_SITE.com/login",
              "type": "string"
            },
            {
              "id": "login-002",
              "name": "loginActionURL",
              "value": "https://YOUR_CF_PROTECTED_SITE.com/login",
              "type": "string"
            },
            {
              "id": "login-003",
              "name": "proxy",
              "value": "YOUR_PROXY_HOST:PORT:USER:PASS",
              "type": "string"
            },
            {
              "id": "login-004",
              "name": "usernameField",
              "value": "email",
              "type": "string"
            },
            {
              "id": "login-005",
              "name": "passwordField",
              "value": "password",
              "type": "string"
            },
            {
              "id": "login-006",
              "name": "usernameValue",
              "value": "your-email@example.com",
              "type": "string"
            },
            {
              "id": "login-007",
              "name": "passwordValue",
              "value": "YOUR_ACCOUNT_PASSWORD",
              "type": "string"
            },
            {
              "id": "login-008",
              "name": "successMarker",
              "value": "account-dashboard",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -368,
        -80
      ],
      "id": "cf666666-6666-6666-6666-666666666602",
      "name": "设置登录配置 [定时]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        -144,
        -64
      ],
      "id": "cf666666-6666-6666-6666-666666666603",
      "name": "解决 Cloudflare 挑战 [定时]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Login Config [Schedule]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.usernameField, config.usernameValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'content-type': 'application/x-www-form-urlencoded',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'same-origin',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.loginActionURL,\n  method: 'POST',\n  proxy: toProxyURL(config.proxy),\n  headers,\n  body: params.toString()\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48,
        -64
      ],
      "id": "cf666666-6666-6666-6666-666666666604",
      "name": "准备 TLS 登录请求 [定时]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        240,
        -64
      ],
      "id": "cf666666-6666-6666-6666-666666666605",
      "name": "通过 TLS 服务器提交登录 [定时]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "login-if-001",
              "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Login Config [Schedule]').item.json.successMarker) }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        432,
        -64
      ],
      "id": "cf666666-6666-6666-6666-666666666606",
      "name": "登录成功? [定时]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-010",
              "name": "action",
              "value": "account_login",
              "type": "string"
            },
            {
              "id": "login-011",
              "name": "status",
              "value": "success",
              "type": "string"
            },
            {
              "id": "login-012",
              "name": "message",
              "value": "配置的账户登录流程成功(通过 Cloudflare Challenge 绕过)",
              "type": "string"
            },
            {
              "id": "login-013",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        768,
        -208
      ],
      "id": "cf666666-6666-6666-6666-666666666607",
      "name": "标记登录成功 [定时]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-014",
              "name": "action",
              "value": "account_login",
              "type": "string"
            },
            {
              "id": "login-015",
              "name": "status",
              "value": "failed",
              "type": "string"
            },
            {
              "id": "login-016",
              "name": "statusCode",
              "value": "={{ $json.status }}",
              "type": "number"
            },
            {
              "id": "login-017",
              "name": "message",
              "value": "登录响应未匹配配置的成功标志",
              "type": "string"
            },
            {
              "id": "login-018",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        768,
        0
      ],
      "id": "cf666666-6666-6666-6666-666666666608",
      "name": "标记登录失败 [定时]"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "cloudflare-account-login",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -640,
        432
      ],
      "id": "cf666666-6666-6666-6666-666666666609",
      "name": "接收登录请求",
      "webhookId": "cf666666-aaaa-bbbb-cccc-666666666609"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-019",
              "name": "targetURL",
              "value": "={{ $json.body.targetURL }}",
              "type": "string"
            },
            {
              "id": "login-020",
              "name": "loginActionURL",
              "value": "={{ $json.body.loginActionURL }}",
              "type": "string"
            },
            {
              "id": "login-021",
              "name": "proxy",
              "value": "={{ $json.body.proxy }}",
              "type": "string"
            },
            {
              "id": "login-022",
              "name": "usernameField",
              "value": "={{ $json.body.usernameField }}",
              "type": "string"
            },
            {
              "id": "login-023",
              "name": "passwordField",
              "value": "={{ $json.body.passwordField }}",
              "type": "string"
            },
            {
              "id": "login-024",
              "name": "usernameValue",
              "value": "={{ $json.body.usernameValue }}",
              "type": "string"
            },
            {
              "id": "login-025",
              "name": "passwordValue",
              "value": "={{ $json.body.passwordValue }}",
              "type": "string"
            },
            {
              "id": "login-026",
              "name": "successMarker",
              "value": "={{ $json.body.successMarker }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -368,
        480
      ],
      "id": "cf666666-6666-6666-6666-666666666610",
      "name": "设置登录配置 [Webhook]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Challenge",
        "websiteURL": "={{ $json.targetURL }}",
        "proxy": "={{ $json.proxy }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        -144,
        480
      ],
      "id": "cf666666-6666-6666-6666-666666666611",
      "name": "解决 Cloudflare 挑战 [Webhook]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const config = $('Set Login Config [Webhook]').first().json;\nconst capResult = $input.first().json;\n\nfunction toProxyURL(proxy) {\n  if (!proxy) return '';\n  if (proxy.startsWith('http')) return proxy;\n  const parts = proxy.split(':');\n  if (parts.length === 4) {\n    return `http://${parts[2]}:${parts[3]}@${parts[0]}:${parts[1]}`;\n  }\n  return proxy;\n}\n\nlet cookieStr = '';\nlet ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36';\n\nif (capResult.data && capResult.data.solution) {\n  const solution = capResult.data.solution;\n  const cookies = solution.cookies;\n  cookieStr = cookies && typeof cookies === 'object'\n    ? Object.entries(cookies).map(([k, v]) => `${k}=${v}`).join('; ')\n    : (cookies || '');\n  if (solution.userAgent) ua = solution.userAgent;\n}\n\nconst chromeMatch = ua.match(/Chrome\\/(\\d+)/);\nconst chromeVer = chromeMatch ? chromeMatch[1] : '145';\nconst secChUa = `\"Chromium\";v=\"${chromeVer}\", \"Not A(Brand\";v=\"8\", \"Google Chrome\";v=\"${chromeVer}\"`;\n\nconst params = new URLSearchParams();\nparams.set(config.usernameField, config.usernameValue);\nparams.set(config.passwordField, config.passwordValue);\n\nconst headers = {\n  'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',\n  'accept-language': 'en-US,en;q=0.9',\n  'content-type': 'application/x-www-form-urlencoded',\n  'sec-ch-ua': secChUa,\n  'sec-ch-ua-mobile': '?0',\n  'sec-ch-ua-platform': '\"Windows\"',\n  'sec-fetch-dest': 'document',\n  'sec-fetch-mode': 'navigate',\n  'sec-fetch-site': 'same-origin',\n  'sec-fetch-user': '?1',\n  'upgrade-insecure-requests': '1',\n  'user-agent': ua\n};\n\nif (cookieStr) headers['cookie'] = cookieStr;\n\nreturn [{ json: {\n  url: config.loginActionURL,\n  method: 'POST',\n  proxy: toProxyURL(config.proxy),\n  headers,\n  body: params.toString()\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        48,
        480
      ],
      "id": "cf666666-6666-6666-6666-666666666612",
      "name": "准备 TLS 登录请求 [Webhook]"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:7878/fetch",
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {
          "timeout": 60000
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        256,
        480
      ],
      "id": "cf666666-6666-6666-6666-666666666613",
      "name": "通过 TLS 服务器提交登录 [Webhook]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "login-if-002",
              "leftValue": "={{ $json.status < 400 && String($json.body || '').includes($('Set Login Config [Webhook]').item.json.successMarker) }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        448,
        496
      ],
      "id": "cf666666-6666-6666-6666-666666666614",
      "name": "登录成功? [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-028",
              "name": "action",
              "value": "account_login",
              "type": "string"
            },
            {
              "id": "login-029",
              "name": "status",
              "value": "success",
              "type": "string"
            },
            {
              "id": "login-030",
              "name": "message",
              "value": "配置的账户登录流程成功(通过 Cloudflare Challenge 绕过)",
              "type": "string"
            },
            {
              "id": "login-031",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        640,
        320
      ],
      "id": "cf666666-6666-6666-6666-666666666615",
      "name": "标记登录成功 [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "login-032",
              "name": "action",
              "value": "account_login",
              "type": "string"
            },
            {
              "id": "login-033",
              "name": "status",
              "value": "failed",
              "type": "string"
            },
            {
              "id": "login-034",
              "name": "statusCode",
              "value": "={{ $json.status }}",
              "type": "number"
            },
            {
              "id": "login-035",
              "name": "message",
              "value": "登录响应未匹配配置的成功标志",
              "type": "string"
            },
            {
              "id": "login-036",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        656,
        544
      ],
      "id": "cf666666-6666-6666-6666-666666666616",
      "name": "标记登录失败 [Webhook]"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        880,
        416
      ],
      "id": "cf666666-6666-6666-6666-666666666617",
      "name": "返回登录结果"
    }
  ],
  "connections": {
    "每 24 小时": {
      "main": [
        [
          {
            "node": "设置登录配置 [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "设置登录配置 [定时]": {
      "main": [
        [
          {
            "node": "解决 Cloudflare 挑战 [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "解决 Cloudflare 挑战 [定时]": {
      "main": [
        [
          {
            "node": "准备 TLS 登录请求 [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "准备 TLS 登录请求 [定时]": {
      "main": [
        [
          {
            "node": "通过 TLS 服务器提交登录 [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "通过 TLS 服务器提交登录 [定时]": {
      "main": [
        [
          {
            "node": "登录成功? [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "登录成功? [定时]": {
      "main": [
        [
          {
            "node": "标记登录成功 [定时]",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "标记登录失败 [定时]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "接收登录请求": {
      "main": [
        [
          {
            "node": "设置登录配置 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "设置登录配置 [Webhook]": {
      "main": [
        [
          {
            "node": "解决 Cloudflare 挑战 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "解决 Cloudflare 挑战 [Webhook]": {
      "main": [
        [
          {
            "node": "准备 TLS 登录请求 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "准备 TLS 登录请求 [Webhook]": {
      "main": [
        [
          {
            "node": "通过 TLS 服务器提交登录 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "通过 TLS 服务器提交登录 [Webhook]": {
      "main": [
        [
          {
            "node": "登录成功? [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "登录成功? [Webhook]": {
      "main": [
        [
          {
            "node": "标记登录成功 [Webhook]",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "标记登录失败 [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "标记登录成功 [Webhook]": {
      "main": [
        [
          {
            "node": "返回登录结果",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "标记登录失败 [Webhook]": {
      "main": [
        [
          {
            "node": "返回登录结果",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}
### 示例 3:Turnstile — 解决器 API

此工作流创建了一个 POST 端点,用于解决 Cloudflare Turnstile 验证挑战并返回令牌。它是上面Cloudflare Challenge Solver API的 Turnstile 等价物——但更简单:无代理、无 TLS 服务器,仅需 3 个节点。

流程:

Copy
Webhook (POST /solver-turnstile) → 解决 Turnstile (CapSolver) → 响应 Webhook
  1. Webhook — 接收包含 websiteURL 和 websiteKey 的 POST 请求
  2. 解决 Turnstile — CapSolver 使用 AntiTurnstileTaskProxyLess 解决 Turnstile 挑战
  3. 响应 Webhook — 返回解决方案数据(包括 token)

与 Cloudflare Challenge 的主要区别: Turnstile 返回的是一个 token 字符串,而不是 cf_clearance cookie。你会在后续请求中以 cf-turnstile-response 请求头(或表单字段,取决于网站)发送此令牌。无需代理。

试用

bash Copy
curl -X POST https://your-n8n-instance.com/webhook/solver-turnstile \
  -H "Content-Type: application/json" \
  -d '{
    "websiteURL": "https://target-site.com/page",
    "websiteKey": "0x4AAAAAAA..."
  }'

导入此工作流

复制下方 JSON,然后通过 n8n 中的 菜单 → 从 JSON 导入 导入。导入后,在“解决 Turnstile”节点中选择你的 CapSolver 凭据。

点击展开工作流 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Turnstile \u2014 Solver API\n\n### How it works\n\n1. Receives a solver request through a webhook.\n2. Solves the Turnstile CAPTCHA using a specialized solver node.\n3. Sends the response back via a webhook response.\n\n### Setup steps\n\n- [ ] Configure webhook URL for receiving requests.\n- [ ] Set up credentials for the capSolver node.\n- [ ] Ensure response webhook URL is correctly set up.\n\n### Customization\n\nThe solver node configuration can be adjusted to handle different types of Turnstile CAPTCHAs.",
        "width": 480,
        "height": 560
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -848,
        -80
      ],
      "id": "d52f67cb-cb00-430f-bd76-b74cf4fe6184",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Handle solver request\n\nReceives and processes a request to solve a Turnstile CAPTCHA, then sends the result back.",
        "width": 832,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -288,
        -80
      ],
      "id": "21f62617-eaa2-41ae-8fb8-c8502f21c275",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "solver-turnstile",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -240,
        32
      ],
      "id": "ts-001",
      "name": "Receive Solver Request",
      "webhookId": "a7ef0297-8455-44bd-9305-26c179f040b5",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "Cloudflare Turnstile",
        "websiteURL": "={{ $json.body.websiteURL }}",
        "websiteKey": "={{ $json.body.websiteKey }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        80,
        32
      ],
      "id": "ts-002",
      "name": "Solve Turnstile",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json.data) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        400,
        32
      ],
      "id": "ts-003",
      "name": "Respond to Webhook"
    }
  ],
  "connections": {
    "Receive Solver Request": {
      "main": [
        [
          {
            "node": "Solve Turnstile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Turnstile": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}
### 示例 4:Turnstile 抓取 — 价格 & 产品监控

此工作流程解决 Cloudflare Turnstile 验证,使用解决后的 token 获取产品页面,提取价格和产品名称,并与之前的值进行比较 —— 在有变动时发出警报。它遵循与上文 Cloudflare Challenge 抓取示例相同的双触发模式(定时 + webhook)。

与 Cloudflare Challenge 抓取的主要区别: 无需 TLS 服务器,无需代理,无需 Prepare TLS Request 代码节点。Turnstile token 作为 cf-turnstile-response 头直接通过 n8n 内置的 HTTP 请求节点发送。

定时路径:

Copy
每 6 小时 → 设置目标配置 → 解决 Turnstile → 抓取产品页面
           → 提取数据 → 比较数据 → 数据变动? → 构建警报 / 无变动

Webhook 路径:

Copy
Webhook 触发 → 解决 Turnstile → 抓取产品页面
             → 提取数据 → 比较数据 → 数据变动? → 构建警报 / 无变动
             → 响应 Webhook

关键行为:

  • CapSolver 操作为 Cloudflare Turnstile(非 Cloudflare Challenge)—— 内部使用 AntiTurnstileTaskProxyLess
  • token 作为 cf-turnstile-response 头随抓取请求发送
  • 无需代理格式转换 —— Turnstile 解决不使用代理
  • 标准 n8n HTTP 请求节点 —— 无需 TLS 服务器,因为没有 cf_clearance Cookie 进行指纹匹配
  • 与 Challenge 版本相同的价格比较逻辑:使用 $workflow.staticData.lastPrice 实现跨执行持久化
  • Webhook 路径直接从 POST 体读取 websiteURL 和 websiteKey(无需 Set Target Config 节点)
点击展开工作流程 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Turnstile 抓取 \u2014 价格 & 产品监控\n\n### 工作原理\n\n1. 工作流程每 6 小时或通过 webhook 触发。\n2. 设置目标配置,包括网站 URL 和 Key。\n3. 解决 Turnstile captcha 以访问产品页面。\n4. 抓取产品数据并提取相关内容。\n5. 比较提取的数据与之前的数据以识别变更。\n6. 若有变更,则生成警报,并对 webhook 请求发送响应。\n\n### 设置步骤\n\n- [ ] 配置调度触发器间隔。\n- [ ] 设置 webhook URL 以实现实时触发。\n- [ ] 确保验证码解决凭证有效。\n- [ ] 配置目标网站 URL 和产品 Key。\n\n### 自定义\n\n调度触发的间隔可根据监控需求调整。",
        "width": 480,
        "height": 896
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1184,
        -240
      ],
      "id": "1824aba9-471e-4052-912e-888d939349df",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## 定时抓取触发器\n\n每 6 小时启动工作流程并设置目标配置。",
        "width": 512,
        "height": 304,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -624,
        -80
      ],
      "id": "89b7d6a2-b4bf-4a7d-8667-ce3f31d2eb92",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "content": "## 定时验证码解决和抓取\n\n基于定时解决验证码并抓取产品页面数据。",
        "width": 1472,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        16,
        -64
      ],
      "id": "f39fc94b-d0ae-4141-9491-fa82702a72fc",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## 定时数据评估\n\n比较新抓取的数据与之前的数据,检测变更并生成警报。",
        "width": 240,
        "height": 528,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1536,
        -240
      ],
      "id": "0d31be95-f94c-40fd-99cd-7d988109d3f4",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "content": "## Webhook 抓取触发器\n\n通过 webhook 触发抓取工作流程,实现实时更新。",
        "width": 240,
        "height": 352,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -624,
        256
      ],
      "id": "e4a50f47-180c-484e-bf70-09a46543e01b",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "content": "## Webhook 验证码解决和抓取\n\n为 webhook 触发解决验证码并抓取产品页面数据。",
        "width": 1472,
        "height": 272,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        16,
        336
      ],
      "id": "1167cf0e-d561-40fa-b366-331a20d99a31",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "content": "## Webhook 数据评估与响应\n\n评估数据变更,并将结果响应给 webhook。",
        "width": 672,
        "height": 432,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1536,
        320
      ],
      "id": "0dab95b9-d762-43cf-9a11-b92212a8c2a7",
      "name": "Sticky Note6"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -576,
        48
      ],
      "id": "ts-s-01",
      "name": "Every 6 Hours"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cfg-001",
              "name": "websiteURL",
              "value": "https://YOUR-TARGET-SITE.com/product-page",
              "type": "string"
            },
            {
              "id": "cfg-002",
              "name": "websiteKey",
              "value": "YOUR_SITE_KEY_HERE",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -256,
        48
      ],
      "id": "ts-s-02",
      "name": "设置目标配置 [定时]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Turnstile",
        "websiteURL": "={{ $json.websiteURL }}",
        "websiteKey": "={{ $json.websiteKey }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        64,
        48
      ],
      "id": "ts-s-03",
      "name": "解决 Turnstile",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $('Set Target Config [Schedule]').first().json.websiteURL }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "user-agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
            },
            {
              "name": "cf-turnstile-response",
              "value": "={{ $json.data.solution.token }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {}
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        384,
        48
      ],
      "id": "ts-s-04",
      "name": "抓取产品页面"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "price",
              "cssSelector": ".product-price, [data-price], .price"
            },
            {
              "key": "productName",
              "cssSelector": "h1, .product-title"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        704,
        48
      ],
      "id": "ts-s-05",
      "name": "提取数据"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || 'Product';\nconst parsePrice = (str) => { if (!str) return null; const match = str.match(/[\\d]+\\.?\\d*/); return match ? parseFloat(match[0].replace(',', '')) : null; };\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? 'dropped' : 'increased') : 'unchanged';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\nreturn [{ json: { productName, currentPrice, previousPrice: previousPrice || '首次检测', changed, direction, diff: changed ? `$${diff}` : null, checkedAt: new Date().toISOString() } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1072,
        48
      ],
      "id": "ts-s-06",
      "name": "比较数据"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "if-1",
              "leftValue": "={{ $json.changed }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1344,
        48
      ],
      "id": "ts-s-07",
      "name": "数据变动?"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a1",
              "name": "alert",
              "value": "=价格 {{ $json.direction }}:{{ $json.productName }}:{{ $json.previousPrice }} \u2192 {{ $json.currentPrice }}",
              "type": "string"
            },
            {
              "id": "a2",
              "name": "severity",
              "value": "={{ $json.direction === 'dropped' ? 'deal' : 'info' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1584,
        -48
      ],
      "id": "ts-s-08",
      "name": "构建警报"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "n1",
              "name": "status",
              "value": "no_change",
              "type": "string"
            },
            {
              "id": "n2",
              "name": "currentPrice",
              "value": "={{ $json.currentPrice }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1584,
        128
      ],
      "id": "ts-s-09",
      "name": "无变动"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "price-monitor-turnstile",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -576,
        448
      ],
      "id": "ts-s-10",
      "name": "Webhook 触发",
      "webhookId": "6a4f76c7-fc5c-440d-96cb-75c9c3bebcdb",
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "operation": "Cloudflare Turnstile",
        "websiteURL": "={{ $json.body.websiteURL }}",
        "websiteKey": "={{ $json.body.websiteKey }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        64,
        448
      ],
      "id": "ts-s-11",
      "name": "解决 Turnstile [W]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver account"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $('Webhook Trigger').item.json.body.websiteURL }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "user-agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
            },
            {
              "name": "cf-turnstile-response",
              "value": "={{ $json.data.solution.token }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {}
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        384,
        448
      ],
      "id": "ts-s-12",
      "name": "抓取产品页面 [W]"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "price",
              "cssSelector": ".product-price, [data-price], .price"
            },
            {
              "key": "productName",
              "cssSelector": "h1, .product-title"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [
        704,
        448
      ],
      "id": "ts-s-13",
      "name": "提取数据 [W]"
    },
    {
      "parameters": {
        "jsCode": "const staticData = $workflow.staticData;\nconst currentPrice = $input.first().json.price;\nconst previousPrice = staticData.lastPrice;\nconst productName = $input.first().json.productName || 'Product';\nconst parsePrice = (str) => { if (!str) return null; const match = str.match(/[\\d]+\\.?\\d*/); return match ? parseFloat(match[0].replace(',', '')) : null; };\nconst currentNum = parsePrice(currentPrice);\nconst previousNum = parsePrice(previousPrice);\nstaticData.lastPrice = currentPrice;\nstaticData.lastChecked = new Date().toISOString();\nconst changed = previousNum !== null && currentNum !== null && currentNum !== previousNum;\nconst direction = changed ? (currentNum < previousNum ? 'dropped' : 'increased') : 'unchanged';\nconst diff = changed ? Math.abs(currentNum - previousNum).toFixed(2) : '0';\nreturn [{ json: { productName, currentPrice, previousPrice: previousPrice || '首次检测', changed, direction, diff: changed ? `$${diff}` : null, checkedAt: new Date().toISOString() } }];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        448
      ],
      "id": "ts-s-14",
      "name": "比较数据 [W]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "if-2",
              "leftValue": "={{ $json.changed }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1344,
        448
      ],
      "id": "ts-s-15",
      "name": "数据变动? [W]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a4",
              "name": "alert",
              "value": "=价格 {{ $json.direction }}:{{ $json.productName }}:{{ $json.previousPrice }} \u2192 {{ $json.currentPrice }}",
              "type": "string"
            },
            {
              "id": "a5",
              "name": "severity",
              "value": "={{ $json.direction === 'dropped' ? 'deal' : 'info' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1584,
        432
      ],
      "id": "ts-s-16",
      "name": "构建警报 [W]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "n4",
              "name": "status",
              "value": "no_change",
              "type": "string"
            },
            {
              "id": "n5",
              "name": "currentPrice",
              "value": "={{ $json.currentPrice }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1584,
        592
      ],
      "id": "ts-s-17",
      "name": "无变动 [W]"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        2064,
        544
      ],
      "id": "ts-s-18",
      "name": "响应 Webhook"
    }
  ],
  "connections": {
    "Every 6 Hours": {
      "main": [
        [
          {
            "node": "Set Target Config [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Target Config [Schedule]": {
      "main": [
        [
          {
            "node": "Solve Turnstile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Turnstile": {
      "main": [
        [
          {
            "node": "Fetch Product Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Product Page": {
      "main": [
        [
          {
            "node": "Extract Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Data": {
      "main": [
        [
          {
            "node": "Compare Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Data": {
      "main": [
        [
          {
            "node": "Data Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Changed?": {
      "main": [
        [
          {
            "node": "Build Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Solve Turnstile [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Turnstile [W]": {
      "main": [
        [
          {
            "node": "Fetch Product Page [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Product Page [W]": {
      "main": [
        [
          {
            "node": "Extract Data [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Data [W]": {
      "main": [
        [
          {
            "node": "Compare Data [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Data [W]": {
      "main": [
        [
          {
            "node": "Data Changed? [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Changed? [W]": {
      "main": [
        [
          {
            "node": "Build Alert [W]",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Change [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Alert [W]": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "No Change [W]": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}
### 示例 5:Turnstile 账户登录

此工作流实现对受 Turnstile 保护的网站的自动登录。它遵循与上文 Cloudflare Challenge 账户登录相同的双触发模式(定时 + webhook)——但没有使用 TLS 服务器、代理或自定义代码节点。

与 Cloudflare Challenge 登录的主要区别: 无需 TLS 服务器,无需代理,无需 Prepare TLS Login Request 代码节点。Turnstile token 作为 cf-turnstile-response 表单字段,直接通过 n8n 内置的 HTTP 请求节点提交。凭证以标准的 form-urlencoded 请求体参数形式发送。

定时路径:

Copy
每 24 小时 → 设置登录配置 → 解决 Turnstile → 提交登录
            → 登录成功? → 标记成功 / 标记失败

Webhook 路径:

Copy
Webhook 触发 → 解决 Turnstile → 提交登录
              → 登录成功? → 标记成功 / 标记失败 → 回复 Webhook

关键行为:

  • CapSolver 操作为 Cloudflare Turnstile(非 Cloudflare Challenge)——使用 AntiTurnstileTaskProxyLess,不需要代理
  • token 作为 POST 请求体中的 cf-turnstile-response 表单字段提交(非 CF Challenge 中的 cookie 头)
  • 无需自定义代码节点——HTTP 请求节点直接处理 form-urlencoded 格式提交,包含 email、password 和 cf-turnstile-response 字段
  • 登录成功? 通过检查 statusCode < 400 且响应体中包含 successMarker 来判断——与 CF Challenge 登录一致的模式
  • 定时路径中的账户凭据写死在“设置登录配置”,Webhook 路径则从 POST 请求体读取 usernameValue、passwordValue、usernameField、passwordField、loginActionURL、successMarker
  • Webhook 路径支持动态字段名——每个表单字段的 name 参数是表达式(例如 $('Webhook Trigger').item.json.body.usernameField || 'email'),调用者可以指定其站点的字段名
点击展开工作流 JSON
json Copy
{
  "nodes": [
    {
      "parameters": {
        "content": "## Turnstile 账户登录\n\n### 工作原理\n\n1. 使用定时器每 24 小时触发一次登录流程。\n2. 设置登录配置并解决 Turnstile 验证。\n3. 提交登录表单,检测登录是否成功。\n4. 在定时流程中标记登录尝试为成功或失败。\n5. 另外,可通过 webhook 触发启动登录流程,并执行类似的步骤。\n\n### 配置步骤\n\n- [ ] 通过设置调度器实现周期性执行。\n- [ ] 配置 webhook 端点以支持外部触发。\n- [ ] 设置登录凭据和 URL 配置。\n\n### 自定义\n\n根据需要调整调度器时间或修改 webhook 响应处理。",
        "width": 480,
        "height": 896
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -1168,
        -160
      ],
      "id": "8ca80c21-2de5-41e7-b3e4-de184fc1d8fe",
      "name": "便签"
    },
    {
      "parameters": {
        "content": "## 定时登录流程\n\n每 24 小时触发以执行登录步骤",
        "width": 1920,
        "height": 448,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -608,
        -160
      ],
      "id": "533be07a-6fe7-4ada-a40c-7a7749ba968d",
      "name": "便签1"
    },
    {
      "parameters": {
        "content": "## Webhook 登录流程\n\n处理由 webhook 触发的登录请求",
        "width": 2288,
        "height": 416,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -608,
        320
      ],
      "id": "c6a8e296-2bfb-4ffa-8819-aa420e936589",
      "name": "便签2"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -560,
        48
      ],
      "id": "ts-l-01",
      "name": "每 24 小时"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "l1",
              "name": "websiteURL",
              "value": "https://YOUR-LOGIN-PAGE.com",
              "type": "string"
            },
            {
              "id": "l2",
              "name": "websiteKey",
              "value": "YOUR_SITE_KEY_HERE",
              "type": "string"
            },
            {
              "id": "l3",
              "name": "successMarker",
              "value": "account-dashboard",
              "type": "string"
            },
            {
              "id": "l4",
              "name": "userAgent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -240,
        48
      ],
      "id": "ts-l-02",
      "name": "设置登录配置 [定时]"
    },
    {
      "parameters": {
        "operation": "Cloudflare Turnstile",
        "websiteURL": "={{ $json.websiteURL }}",
        "websiteKey": "={{ $json.websiteKey }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        256,
        48
      ],
      "id": "ts-l-03",
      "name": "解决 Turnstile [定时]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Set Login Config [Schedule]').item.json.websiteURL }}/login",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "content-type",
              "value": "application/x-www-form-urlencoded"
            },
            {
              "name": "user-agent",
              "value": "={{ $('Set Login Config [Schedule]').item.json.userAgent }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "email",
              "value": "your-email@example.com"
            },
            {
              "name": "password",
              "value": "YOUR_ACCOUNT_PASSWORD"
            },
            {
              "name": "cf-turnstile-response",
              "value": "={{ $json.data.solution.token }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        576,
        48
      ],
      "id": "ts-l-04",
      "name": "提交登录 [定时]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "lif1",
              "leftValue": "={{ $json.statusCode < 400 && String($json.body || $json.data || '').includes($('Set Login Config [Schedule]').item.json.successMarker) }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        896,
        48
      ],
      "id": "ts-l-05",
      "name": "登录成功? [定时]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "status",
              "value": "success",
              "type": "string"
            },
            {
              "id": "s2",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1168,
        -48
      ],
      "id": "ts-l-06",
      "name": "标记成功"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "f1",
              "name": "status",
              "value": "failed",
              "type": "string"
            },
            {
              "id": "f2",
              "name": "statusCode",
              "value": "={{ $json.statusCode }}",
              "type": "number"
            },
            {
              "id": "f3",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1168,
        128
      ],
      "id": "ts-l-07",
      "name": "标记失败"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "account-login-turnstile",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -560,
        544
      ],
      "id": "ts-l-08",
      "name": "Webhook 触发",
      "webhookId": "9c7a53a4-d3ee-495b-9381-3a9425bb1b36"
    },
    {
      "parameters": {
        "operation": "Cloudflare Turnstile",
        "websiteURL": "={{ $json.body.websiteURL }}",
        "websiteKey": "={{ $json.body.websiteKey }}",
        "optional": {}
      },
      "type": "n8n-nodes-capsolver.capSolver",
      "typeVersion": 1,
      "position": [
        256,
        544
      ],
      "id": "ts-l-09",
      "name": "解决 Turnstile [Webhook]",
      "credentials": {
        "capSolverApi": {
          "id": "BeBFMAsySMsMGeE9",
          "name": "CapSolver 账户"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Webhook Trigger').item.json.body.loginActionURL }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "content-type",
              "value": "application/x-www-form-urlencoded"
            },
            {
              "name": "user-agent",
              "value": "={{ $('Webhook Trigger').item.json.body.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "={{ $('Webhook Trigger').item.json.body.usernameField || 'email' }}",
              "value": "={{ $('Webhook Trigger').item.json.body.usernameValue }}"
            },
            {
              "name": "={{ $('Webhook Trigger').item.json.body.passwordField || 'password' }}",
              "value": "={{ $('Webhook Trigger').item.json.body.passwordValue }}"
            },
            {
              "name": "cf-turnstile-response",
              "value": "={{ $json.data.solution.token }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        576,
        544
      ],
      "id": "ts-l-10",
      "name": "提交登录 [Webhook]"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "lif2",
              "leftValue": "={{ $json.statusCode < 400 && String($json.body || $json.data || '').includes($('Webhook Trigger').item.json.body.successMarker) }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        896,
        544
      ],
      "id": "ts-l-11",
      "name": "登录成功? [Webhook]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ws1",
              "name": "status",
              "value": "success",
              "type": "string"
            },
            {
              "id": "ws2",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1168,
        432
      ],
      "id": "ts-l-12",
      "name": "标记成功 [W]"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "wf1",
              "name": "status",
              "value": "failed",
              "type": "string"
            },
            {
              "id": "wf2",
              "name": "statusCode",
              "value": "={{ $json.statusCode }}",
              "type": "number"
            },
            {
              "id": "wf3",
              "name": "checkedAt",
              "value": "={{ new Date().toISOString() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1168,
        576
      ],
      "id": "ts-l-13",
      "name": "标记失败 [W]"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        1536,
        544
      ],
      "id": "ts-l-14",
      "name": "回复 Webhook"
    }
  ],
  "connections": {
    "Every 24 Hours": {
      "main": [
        [
          {
            "node": "Set Login Config [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Login Config [Schedule]": {
      "main": [
        [
          {
            "node": "Solve Turnstile [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Turnstile [Schedule]": {
      "main": [
        [
          {
            "node": "Submit Login [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Login [Schedule]": {
      "main": [
        [
          {
            "node": "Login OK? [Schedule]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Login OK? [Schedule]": {
      "main": [
        [
          {
            "node": "Mark Success",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Solve Turnstile [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Solve Turnstile [Webhook]": {
      "main": [
        [
          {
            "node": "Submit Login [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submit Login [Webhook]": {
      "main": [
        [
          {
            "node": "Login OK? [Webhook]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Login OK? [Webhook]": {
      "main": [
        [
          {
            "node": "Mark Success [W]",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Mark Failed [W]",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Success [W]": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark Failed [W]": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "instanceId": "962ff0267b713be0344b866fa54daae28de8ed2144e2e6867da355dae193ea1f"
  }
}
## 结论

你已经在 n8n 中构建了一个完整的 Cloudflare Challenge 绕过流水线——无需浏览器自动化、无需 Puppeteer、无需 Playwright。仅需三个组件协同工作:CapSolver 用于解决挑战、一个 Go TLS 服务器用于伪装 Chrome 的网络指纹,以及一个 n8n 工作流来协调所有环节。

关键见解是,解决挑战只是问题的一半。没有在后续抓取中匹配 TLS 指纹,cf_clearance 就毫无作用——Cloudflare 检查的是握手过程,而不仅仅是 Cookie。httpcloak TLS 服务器处理了这层,使抓取在网络层面上无法区分于真实的 Chrome 浏览器。

该仓库现在为你提供了用于 Cloudflare 保护站点的实际起始模板:

  • Challenge Solver API — 暴露一个 webhook,为外部应用返回原始的 cf_clearance + userAgent
  • Scraping — 从受 Cloudflare 保护的页面提取价格和产品数据,并进行变更检测
  • Account login — 登录你自己的 Cloudflare 保护账户
  • Turnstile Solver API — 一个三节点 webhook,返回 Turnstile 令牌(无需代理、无需 TLS 服务器)
  • Turnstile Scraping — 使用 n8n 内置 HTTP Request 节点抓取受 Turnstile 保护的价格监控页面
  • Turnstile Account Login — 使用 form-urlencoded 证书和已解决的令牌登录受 Turnstile 保护的网站

Solver API 是最简单的切入点——4 个节点,无需 TLS 服务器。Turnstile 工作流则更简单——完全不需要代理或 TLS 服务器,因为 Turnstile 返回的是令牌(非 IP 绑定的 Cookie),且不做 TLS 指纹识别。对于直接抓取页面的 Cloudflare Challenge 工作流,CapSolver 解决挑战,TLS 服务器发出实际请求。配置好占位符,保持工作流未激活直到匹配目标,然后再激活。

提示: 这些工作流使用调度 + webhook 触发器,但你可以将触发节点替换成任何 n8n 触发器——手动、应用事件、表单提交等。抓取数据后,使用 n8n 内置节点将结果保存到 Google Sheets、数据库、云存储,或通过 Telegram/Slack/Email 发送提醒。


准备好开始了吗? 注册 CapSolver 并使用优惠码 n8n,首次充值享额外 8% 奖励!

CapSolver bonus code banner

常见问题

什么是 AntiCloudflareTask,它与 AntiTurnstileTaskProxyLess 有何不同?

AntiCloudflareTask 解决的是完整的 Cloudflare Bot Management 挑战——那种完全阻止访问的网站“Just a moment…”屏幕。它需要代理,因为 CapSolver 必须通过浏览器加载实际受保护页面。AntiTurnstileTaskProxyLess 解决嵌入代码中的 Turnstile 小部件(登录表单、注册表单等),不需要代理。挑战不同,任务类型也不同。

为什么我不能直接使用 n8n 的 HTTP Request 节点抓取页面?

n8n 的 HTTP Request 节点使用的是 Go 标准库的 net/http,该库的 TLS 指纹与众不同,容易被 Cloudflare 识别。即使拥有有效的 cf_clearance Cookie,如果请求的 TLS 指纹不符合已知浏览器对应的指纹,Cloudflare 仍会重新发起挑战。TLS 服务器通过使用 httpcloak 来伪装成真实的 Chrome TLS 堆栈解决了这个问题。

为什么我的数据中心代理一直失败?

Cloudflare 的机器人评分会对 IP 地址赋予风险分数。数据中心 IP(如 AWS、GCP、VPS 提供商等)是众所周知的高风险 IP。AntiCloudflareTask 使用你的代理加载挑战页面,如果 Cloudflare 识别该 IP 是数据中心,它要么提供 CapSolver 无法解决的更难挑战,要么直接挑战失败。住宅和移动 IP 风险分数较低,更可靠通过。

Turnstile 工作流需要 TLS 服务器或代理吗?

不需要。Turnstile 本质上与 Cloudflare Challenge 不同。Turnstile 返回一个短期有效的 token,作为请求头或表单字段提交——它不绑定特定 IP 或 TLS 指纹。CapSolver 使用 AntiTurnstileTaskProxyLess 解决 Turnstile,完全不需要代理。由于发送的是令牌(非 IP 绑定 Cookie),n8n 内置 HTTP Request 节点可以正常使用——无需伪装 TLS 指纹。

cf_clearance 会过期吗?

会的。过期时间取决于网站的 Cloudflare 配置,可能从几分钟到 24 小时不等。对于定期抓取任务,调度工作流(每 6 小时)会定期重新解决挑战。对于按需抓取,webhook 路径会在每次请求时解决新的挑战。

查看更多

Web ScrapingApr 22, 2026

Rust网络爬虫架构:可扩展的数据提取

学习可扩展的Rust网络爬虫架构,包括reqwest、scraper、异步爬取、无头浏览器爬取、代理轮换以及符合规范的验证码处理。

Ethan Collins
Ethan Collins
Web ScrapingMar 02, 2026

面向开发者的浏览器自动化:2026年掌握Selenium与验证码

通过这份2026年指南,掌握浏览器自动化开发。学习Selenium WebDriver Java、Actions接口以及如何使用CapSolver解决验证码。

Sora Fujimoto

目录

Sora Fujimoto
Web ScrapingFeb 17, 2026

如何在Nanobot中使用CapSolver解决验证码

使用 Nanobot 和 CapSolver 自动化验证码解决。使用 Playwright 自主解决 reCAPTCHA 和 Cloudflare。

Anh Tuan
Anh Tuan
Web ScrapingFeb 10, 2026

数据即服务(DaaS):它是什么以及为何在2026年重要

了解2026年的数据即服务(DaaS)。探索其优势、应用场景以及如何通过实时洞察和可扩展性改变企业。

Rajinder Singh
Rajinder Singh
Web Scraping