8
n8n 中文网amn8n.com

通过Webhooks使用ScreenshotMachine API按需生成网站截图

中级

这是一个Building Blocks领域的自动化工作流,包含 12 个节点。主要使用 If, Code, Webhook, HttpRequest, RespondToWebhook 等节点。 通过Webhooks使用ScreenshotMachine API按需生成网站截图

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "cXnFp41w8eVCH9tx",
  "meta": {
    "instanceId": "1777696fb9fddfee653e70940936c2b1e28ba1f1bde53b7182fbd6eb01988706",
    "templateCredsSetupCompleted": true
  },
  "name": "通过 Webhooks 使用 ScreenshotMachine API 按需生成网站截图",
  "tags": [],
  "nodes": [
    {
      "id": "57d3821e-cba4-47fc-b92a-c1dd2e6337ca",
      "name": "注意:Webhook 输入",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1500,
        1740
      ],
      "parameters": {
        "height": 340,
        "content": "此节点监听传入的 POST 请求。它期望接收包含 'url' 属性(您想要截图的网站 URL)的 JSON 正文。"
      },
      "typeVersion": 1
    },
    {
      "id": "6b55c36a-1e8f-4609-af02-e09347b1f06e",
      "name": "注意:URL 验证与安全",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2060,
        1700
      ],
      "parameters": {
        "color": 3,
        "width": 400,
        "height": 360,
        "content": "此关键节点验证传入的 'url' 以防止服务器端请求伪造(SSRF)漏洞。它检查有效的 HTTP/HTTPS 协议,并确保 URL 不指向内部/私有 IP 地址或本地主机。"
      },
      "typeVersion": 1
    },
    {
      "id": "4d67e039-3198-4ff1-b72b-94dccf3125df",
      "name": "注意:截图 API 调用(GET)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2500,
        1660
      ],
      "parameters": {
        "color": 4,
        "width": 340,
        "height": 320,
        "content": "此节点使用验证后的 URL 向 ScreenshotMachine API 发起 HTTP GET 请求。请记得将 URL 参数中的 'YOUR_API_KEY' 替换为您的实际 API 密钥。"
      },
      "typeVersion": 1
    },
    {
      "id": "296d8f30-1015-4070-acc0-7e1b2e3cbac8",
      "name": "注意:Webhook 响应",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2880,
        1780
      ],
      "parameters": {
        "color": 5,
        "width": 340,
        "height": 480,
        "content": "1. 如果 URL 无效或被安全检查阻止,它将发送清晰的错误消息。"
      },
      "typeVersion": 1
    },
    {
      "id": "548687ec-3af4-4d2b-a8ac-93eecaabcb6c",
      "name": "注意:解析 URL(HEAD 请求)",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1780,
        1820
      ],
      "parameters": {
        "color": 2,
        "height": 340,
        "content": "此节点对传入的 URL 执行 HEAD 请求。"
      },
      "typeVersion": 1
    },
    {
      "id": "86316119-a064-4052-a27c-07c957c71802",
      "name": "接收 URL Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1560,
        1920
      ],
      "webhookId": "caf8f5dc-4834-45bb-96b0-d4b508f93e1b",
      "parameters": {
        "path": "caf8f5dc-4834-45bb-96b0-d4b508f93e1b",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "1abb0d7c-9064-4e75-89be-cc7c409ecb84",
      "name": "解析 URL(HEAD 请求)",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueErrorOutput",
      "position": [
        1840,
        2000
      ],
      "parameters": {
        "url": "={{$json.body.url}}",
        "method": "HEAD",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "1a9218c1-a132-4109-88da-0ac24236de34",
      "name": "验证 URL 防止 SSRF",
      "type": "n8n-nodes-base.code",
      "position": [
        2120,
        1900
      ],
      "parameters": {
        "jsCode": "for (const item of $input.all()) {\n  let isValidUrl = false; // Start with false, will be set to true if passes checks\n  let errorMessage = '';\n  let finalUrl = null; // This will store the validated/resolved URL\n\n  const headResponse = item.json; // Assuming the HTTP Request node outputs directly to item.json\n\n  // Check if the HEAD request was successful (status 2xx or 3xx for redirects)\n  const statusCode = headResponse.statusCode;\n  console.log('DEBUG: HEAD Request Status Code:', statusCode);\n  console.log('DEBUG: HEAD Request Headers:', JSON.stringify(headResponse.headers));\n\n  if (statusCode >= 200 && statusCode < 400) {\n    // Get the final URL after redirects, or original URL if no redirect\n    let resolvedUrlString = headResponse.headers?.['x-final-url'] || headResponse.url || headResponse.request?.url;\n\n    console.log('DEBUG: Resolved URL string from HEAD request:', resolvedUrlString);\n\n    if (resolvedUrlString) {\n        finalUrl = resolvedUrlString;\n\n        // Manual parsing of protocol and hostname from resolvedUrlString\n        let protocol = '';\n        let hostname = '';\n\n        const protocolMatch = finalUrl.match(/^(\\w+):\\/\\//);\n        if (protocolMatch && protocolMatch[1]) {\n            protocol = protocolMatch[1];\n            // Remove protocol and leading slashes for hostname extraction\n            let tempUrl = finalUrl.substring(protocol.length + 3); // e.g., \"https://www.example.com/path\" -> \"www.example.com/path\"\n            const slashIndex = tempUrl.indexOf('/');\n            if (slashIndex !== -1) {\n                hostname = tempUrl.substring(0, slashIndex); // e.g., \"www.example.com\"\n            } else {\n                hostname = tempUrl; // e.g., \"www.example.com\" (if no path)\n            }\n        }\n        console.log('DEBUG: Manually parsed Protocol:', protocol, 'Hostname:', hostname);\n\n        // Basic protocol check\n        if (protocol === 'http' || protocol === 'https') {\n            isValidUrl = true; // Protocol is good so far\n        } else {\n            isValidUrl = false;\n            errorMessage = 'Only HTTP or HTTPS protocols are allowed.';\n            console.log('ERROR: Protocol check failed. Detected protocol:', protocol);\n        }\n\n        // Check for private IPs or localhost to prevent SSRF (only if valid so far)\n        if (isValidUrl && hostname) { // Ensure hostname was extracted\n            if (hostname === 'localhost' || hostname === '127.0.0.1') {\n                isValidUrl = false;\n                errorMessage = 'Access to localhost is not allowed for security reasons.';\n                console.log('ERROR: Localhost or 127.0.0.1 detected.');\n            } else {\n                const parts = hostname.split('.');\n                // Check if it's an IPv4 address (4 parts) and all parts are numbers\n                if (parts.length === 4 && parts.every(part => !isNaN(parseInt(part)) && isFinite(part) && parseInt(part) >= 0 && parseInt(part) <= 255)) {\n                    const ip = parts.map(Number);\n                    if (ip[0] === 10 || (ip[0] === 172 && ip[1] >= 16 && ip[1] <= 31) || (ip[0] === 192 && ip[1] === 168)) {\n                        isValidUrl = false;\n                        errorMessage = 'Access to private IP addresses is not allowed for security reasons.';\n                        console.log('ERROR: Private IPv4 address detected:', ip);\n                    }\n                }\n            }\n        } else if (!hostname) { // If hostname couldn't be extracted, it's not valid\n             isValidUrl = false;\n             errorMessage = 'Could not determine hostname from URL.';\n             console.log('ERROR: Hostname could not be extracted.');\n        }\n\n    } else { // No resolved URL string from HEAD request\n        isValidUrl = false;\n        errorMessage = 'Failed to get a resolved URL string from HEAD request response.';\n        console.log('ERROR: No resolved URL string found in HEAD request output.');\n    }\n  } else { // HEAD request returned non-2xx/3xx status\n    isValidUrl = false;\n    errorMessage = `URL is unreachable or returned status code ${statusCode || 'unknown'}.`;\n    console.log(`ERROR: HEAD request failed with status code: ${statusCode}`);\n  }\n\n  // Add validation status to the item's JSON\n  item.json.isValidUrl = isValidUrl;\n  item.json.errorMessage = errorMessage;\n  item.json.validatedUrl = isValidUrl ? finalUrl : null;\n  item.json.statusCode = statusCode; // Add status code for debug from HEAD request\n  console.log('DEBUG: Final isValidUrl for this item:', isValidUrl, 'Final errorMessage:', errorMessage);\n}\n\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "922a97b7-1df7-40f6-836e-b8ade7a48176",
      "name": "如果 URL 有效",
      "type": "n8n-nodes-base.if",
      "position": [
        2380,
        1980
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "9c45d5bc-62c0-487c-a4ad-e2409785cf94",
              "operator": {
                "type": "string",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.errorMessage ?? $json.error}}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "627362d9-f789-4810-9e2b-5678ee5ac209",
      "name": "拍摄截图",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2620,
        1840
      ],
      "parameters": {
        "url": "=https://api.screenshotmachine.com?key=YOUR_API_KEY&url={{$json.validatedUrl}}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "68c3d952-de02-4de1-bd4e-363a28e1ab05",
      "name": "响应截图数据",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        1920
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.2
    },
    {
      "id": "38161c96-327c-42af-ab14-5825f6f2aafd",
      "name": "响应验证错误",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3000,
        2080
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "={{ $json.error.  }}"
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e3b7d5c4-f779-4012-9bd1-91135725e0ac",
  "connections": {
    "IF URL Valid": {
      "main": [
        [
          {
            "node": "Take Screenshot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond with Validation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Take Screenshot": {
      "main": [
        [
          {
            "node": "Respond with Screenshot Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive URL Webhook": {
      "main": [
        [
          {
            "node": "Resolve URL (HEAD Request)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate URL for SSRF": {
      "main": [
        [
          {
            "node": "IF URL Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Resolve URL (HEAD Request)": {
      "main": [
        [
          {
            "node": "Validate URL for SSRF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "IF URL Valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

中级 - 构建模块

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量12
分类1
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

作者
ist00dent

ist00dent

@ist00dent

I’m a dedicated automation engineer passionate about no-code and low-code solutions. I design and implement robust n8n workflows—integrating APIs, databases, and messaging—to eliminate manual tasks and accelerate delivery. Leveraging Python and C#, I build scalable, adaptable automations that empower teams to focus on high-value work.

外部链接
在 n8n.io 查看

分享此工作流