8
n8n 中文网amn8n.com

通过多平台爬取和邮件报告实现酒店价格自动比对

高级

这是一个Market Research领域的自动化工作流,包含 19 个节点。主要使用 If, Set, Code, Webhook, EmailSend 等节点。 通过多平台爬取和邮件报告自动化酒店价格比较

前置要求
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证
  • Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "m3rGdoXJdGFTSnT6",
  "meta": {
    "instanceId": "dd69efaf8212c74ad206700d104739d3329588a6f3f8381a46a481f34c9cc281",
    "templateCredsSetupCompleted": true
  },
  "name": "通过多平台爬取和邮件报告实现酒店价格自动比对",
  "tags": [],
  "nodes": [
    {
      "id": "fa0570b5-623e-4c19-b6de-f47b5e5fd9ac",
      "name": "Webhook - 接收请求",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -944,
        -128
      ],
      "webhookId": "hotel-price-webhook",
      "parameters": {
        "path": "hotel-price-check",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "cdac9db5-4b10-45e6-940f-4a98ecbe8c4a",
      "name": "解析与验证请求",
      "type": "n8n-nodes-base.code",
      "position": [
        -720,
        -128
      ],
      "parameters": {
        "jsCode": "// Enhanced NLP Parser for Hotel Requests\nconst input = $input.first().json.body;\nconst query = input.message || input.query || input.text || '';\nconst userEmail = input.email || input.user_email || '';\nconst userName = input.name || input.user_name || 'Guest';\n\n// Greeting handler\nconst greetings = ['hi', 'hello', 'hey', 'start', 'help'];\nif (greetings.some(g => query.toLowerCase().includes(g)) && query.split(' ').length < 5) {\n  return [{\n    json: {\n      status: 'greeting',\n      response: `Hi ${userName}! 👋 I'm your Hotel Price Assistant.\\n\\nJust tell me:\\n✅ Hotel name\\n✅ City\\n✅ Check-in & check-out dates\\n\\nExample: \"Hilton Hotel in Singapore from 15th March to 18th March\"`,\n      userEmail,\n      userName\n    }\n  }];\n}\n\n// Date parsing with multiple formats\nfunction parseDate(text) {\n  const monthMap = {\n    jan: 0, january: 0, feb: 1, february: 1, mar: 2, march: 2,\n    apr: 3, april: 3, may: 4, jun: 5, june: 5,\n    jul: 6, july: 6, aug: 7, august: 7, sep: 8, september: 8,\n    oct: 9, october: 9, nov: 10, november: 10, dec: 11, december: 11\n  };\n\n  // Format: \"15th March\" or \"March 15\"\n  const dateRegex = /(\\d{1,2})(st|nd|rd|th)?\\s+(jan|january|feb|february|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|september|oct|october|nov|november|dec|december)|(jan|january|feb|february|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|september|oct|october|nov|november|dec|december)\\s+(\\d{1,2})(st|nd|rd|th)?/gi;\n  \n  const matches = [...text.matchAll(dateRegex)];\n  const dates = [];\n  const currentYear = new Date().getFullYear();\n  \n  matches.forEach(match => {\n    const day = parseInt(match[1] || match[6]);\n    const monthStr = (match[3] || match[4]).toLowerCase();\n    const month = monthMap[monthStr];\n    \n    if (day && month !== undefined) {\n      dates.push(new Date(currentYear, month, day));\n    }\n  });\n  \n  return dates.sort((a, b) => a - b);\n}\n\nconst dates = parseDate(query);\nlet checkInDate = null;\nlet checkOutDate = null;\n\nif (dates.length >= 2) {\n  checkInDate = dates[0];\n  checkOutDate = dates[1];\n} else if (dates.length === 1) {\n  checkInDate = dates[0];\n  // Default 3-night stay\n  checkOutDate = new Date(checkInDate);\n  checkOutDate.setDate(checkOutDate.getDate() + 3);\n}\n\n// Extract city\nconst cityPattern = /\\b(in|at|near)\\s+([a-z\\s]{3,20})(?=\\s+from|\\s+check|,|$)/i;\nconst cityMatch = query.match(cityPattern);\nlet city = cityMatch ? cityMatch[2].trim() : '';\n\n// Common cities fallback\nconst commonCities = ['singapore', 'mumbai', 'delhi', 'bangalore', 'goa', 'london', 'paris', 'dubai', 'new york', 'tokyo', 'bangkok'];\ncity = city || commonCities.find(c => query.toLowerCase().includes(c)) || '';\n\n// Extract hotel name\nlet hotelName = query\n  .replace(/from\\s+\\d/gi, '')\n  .replace(/to\\s+\\d/gi, '')\n  .replace(/check[- ]?in/gi, '')\n  .replace(/check[- ]?out/gi, '')\n  .replace(/\\d{1,2}(st|nd|rd|th)?\\s+\\w+/gi, '')\n  .replace(new RegExp(`\\\\b(in|at|near)\\\\s+${city}\\\\b`, 'gi'), '')\n  .replace(/hotel|room|price|want|need|know|please|for|dates?/gi, '')\n  .trim();\n\n// Clean hotel name\nhotelName = hotelName.split(/[,.]/).filter(p => p.trim().length > 2)[0]?.trim() || '';\n\n// Validation\nconst errors = [];\nif (!hotelName || hotelName.length < 3) errors.push('hotel name');\nif (!city) errors.push('city');\nif (!checkInDate) errors.push('check-in date');\nif (!checkOutDate) errors.push('check-out date');\n\nif (errors.length > 0) {\n  return [{\n    json: {\n      status: 'missing_info',\n      response: `I need more information. Please provide: ${errors.join(', ')}.\\n\\nExample: \"Marriott Hotel in Dubai from 20th December to 23rd December\"`,\n      userEmail,\n      userName\n    }\n  }];\n}\n\n// Format dates\nconst formatDate = (date) => {\n  const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n  return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`;\n};\n\nconst nights = Math.ceil((checkOutDate - checkInDate) / (1000 * 60 * 60 * 24));\n\nreturn [{\n  json: {\n    status: 'ready',\n    hotelName: hotelName.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' '),\n    city: city.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' '),\n    checkIn: formatDate(checkInDate),\n    checkOut: formatDate(checkOutDate),\n    checkInISO: checkInDate.toISOString().split('T')[0],\n    checkOutISO: checkOutDate.toISOString().split('T')[0],\n    nights,\n    userEmail,\n    userName,\n    originalQuery: query\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e6bc8693-f361-4e71-bc5b-3fee26331176",
      "name": "检查是否就绪",
      "type": "n8n-nodes-base.if",
      "position": [
        -496,
        -128
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{$json.status}}",
              "value2": "ready"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3bfffcf2-29fc-4a34-9aef-d88bebc1291f",
      "name": "爬取Booking.com",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -272,
        -416
      ],
      "parameters": {
        "url": "https://api.example.com/scrape/booking",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "hotel",
              "value": "={{$json.hotelName}}"
            },
            {
              "name": "city",
              "value": "={{$json.city}}"
            },
            {
              "name": "checkIn",
              "value": "={{$json.checkInISO}}"
            },
            {
              "name": "checkOut",
              "value": "={{$json.checkOutISO}}"
            },
            {
              "name": "platform",
              "value": "booking"
            }
          ]
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true
    },
    {
      "id": "1fbf0962-d1e2-4b18-9b5f-8ddbbbec6060",
      "name": "爬取Agoda",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -272,
        -224
      ],
      "parameters": {
        "url": "https://api.example.com/scrape/agoda",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "hotel",
              "value": "={{$json.hotelName}}"
            },
            {
              "name": "city",
              "value": "={{$json.city}}"
            },
            {
              "name": "checkIn",
              "value": "={{$json.checkInISO}}"
            },
            {
              "name": "checkOut",
              "value": "={{$json.checkOutISO}}"
            },
            {
              "name": "platform",
              "value": "agoda"
            }
          ]
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true
    },
    {
      "id": "9044675b-7dbd-43b1-9dd7-2adc3f176d48",
      "name": "爬取Expedia",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -272,
        -32
      ],
      "parameters": {
        "url": "https://api.example.com/scrape/expedia",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "hotel",
              "value": "={{$json.hotelName}}"
            },
            {
              "name": "city",
              "value": "={{$json.city}}"
            },
            {
              "name": "checkIn",
              "value": "={{$json.checkInISO}}"
            },
            {
              "name": "checkOut",
              "value": "={{$json.checkOutISO}}"
            },
            {
              "name": "platform",
              "value": "expedia"
            }
          ]
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true
    },
    {
      "id": "401b0e56-d028-4345-8b4a-0920e6a678f5",
      "name": "汇总与比较",
      "type": "n8n-nodes-base.code",
      "position": [
        -48,
        -224
      ],
      "parameters": {
        "jsCode": "// Aggregate and compare prices from all platforms\nconst items = $input.all();\nconst searchData = items[0].json;\n\nconst prices = [];\nconst errors = [];\n\n// Process each platform result\nitems.slice(1).forEach((item, index) => {\n  const platforms = ['Booking.com', 'Agoda', 'Expedia'];\n  const platform = platforms[index];\n  \n  if (item.json.error) {\n    errors.push(`${platform}: Unable to fetch`);\n  } else if (item.json.price) {\n    prices.push({\n      platform,\n      price: parseFloat(item.json.price),\n      currency: item.json.currency || 'USD',\n      roomType: item.json.roomType || 'Standard Room',\n      url: item.json.url || '#',\n      availability: item.json.availability !== false\n    });\n  }\n});\n\n// Sort by price\nprices.sort((a, b) => a.price - b.price);\n\nif (prices.length === 0) {\n  return [{\n    json: {\n      status: 'no_results',\n      message: 'Unable to find prices. The hotel might not be available for these dates.',\n      errors,\n      ...searchData\n    }\n  }];\n}\n\nconst bestDeal = prices[0];\nconst avgPrice = prices.reduce((sum, p) => sum + p.price, 0) / prices.length;\nconst savings = prices.length > 1 ? prices[prices.length - 1].price - bestDeal.price : 0;\n\nreturn [{\n  json: {\n    status: 'success',\n    ...searchData,\n    results: prices,\n    bestDeal,\n    avgPrice: Math.round(avgPrice),\n    savings: Math.round(savings),\n    totalResults: prices.length,\n    errors\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0e569c2e-fd37-4bf8-a80e-385dd6580b10",
      "name": "格式化邮件报告",
      "type": "n8n-nodes-base.code",
      "position": [
        176,
        -224
      ],
      "parameters": {
        "jsCode": "// Format beautiful email with comparison\nconst data = $input.first().json;\n\nif (data.status === 'no_results') {\n  return [{\n    json: {\n      subject: `❌ No Results - ${data.hotelName}, ${data.city}`,\n      html: `\n        <div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;\">\n          <h2>❌ Unable to Find Prices</h2>\n          <p>We couldn't find available prices for:</p>\n          <div style=\"background: #f5f5f5; padding: 15px; border-radius: 8px;\">\n            <strong>${data.hotelName}</strong><br>\n            📍 ${data.city}<br>\n            📅 ${data.checkIn} to ${data.checkOut} (${data.nights} nights)\n          </div>\n          <p style=\"margin-top: 20px;\">Possible reasons:</p>\n          <ul>\n            <li>Hotel not available on these dates</li>\n            <li>Hotel name might be different on booking platforms</li>\n            <li>No rooms available</li>\n          </ul>\n          <p>Please try:</p>\n          <ul>\n            <li>Different dates</li>\n            <li>Checking the exact hotel name</li>\n            <li>Another hotel in ${data.city}</li>\n          </ul>\n        </div>\n      `,\n      ...data\n    }\n  }];\n}\n\nconst { hotelName, city, checkIn, checkOut, nights, results, bestDeal, avgPrice, savings } = data;\n\nconst resultsHtml = results.map((r, i) => `\n  <tr style=\"${i === 0 ? 'background: #e8f5e9;' : ''}\">\n    <td style=\"padding: 12px; border-bottom: 1px solid #ddd;\">\n      ${i === 0 ? '🏆 ' : ''}<strong>${r.platform}</strong>\n    </td>\n    <td style=\"padding: 12px; border-bottom: 1px solid #ddd;\">${r.roomType}</td>\n    <td style=\"padding: 12px; border-bottom: 1px solid #ddd; text-align: right;\">\n      <strong style=\"color: ${i === 0 ? '#2e7d32' : '#333'}; font-size: 18px;\">\n        ${r.currency} ${r.price.toLocaleString()}\n      </strong>\n    </td>\n    <td style=\"padding: 12px; border-bottom: 1px solid #ddd; text-align: center;\">\n      <a href=\"${r.url}\" style=\"background: #1976d2; color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px; display: inline-block;\">View</a>\n    </td>\n  </tr>\n`).join('');\n\nconst html = `\n<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n</head>\n<body style=\"margin: 0; padding: 20px; background: #f5f5f5; font-family: Arial, sans-serif;\">\n  <div style=\"max-width: 650px; margin: 0 auto; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);\">\n    \n    <!-- Header -->\n    <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center;\">\n      <h1 style=\"margin: 0; font-size: 28px;\">🏨 Hotel Price Comparison</h1>\n      <p style=\"margin: 10px 0 0 0; opacity: 0.9;\">Best deals for your stay</p>\n    </div>\n\n    <!-- Hotel Info -->\n    <div style=\"padding: 25px; background: #f8f9fa;\">\n      <h2 style=\"margin: 0 0 15px 0; color: #333;\">${hotelName}</h2>\n      <div style=\"color: #666; line-height: 1.8;\">\n        <div>📍 <strong>Location:</strong> ${city}</div>\n        <div>📅 <strong>Check-in:</strong> ${checkIn}</div>\n        <div>📅 <strong>Check-out:</strong> ${checkOut}</div>\n        <div>🌙 <strong>Duration:</strong> ${nights} night${nights > 1 ? 's' : ''}</div>\n      </div>\n    </div>\n\n    <!-- Best Deal Highlight -->\n    <div style=\"margin: 20px; padding: 20px; background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); border-radius: 8px; border-left: 4px solid #667eea;\">\n      <div style=\"font-size: 14px; color: #667eea; font-weight: bold; margin-bottom: 8px;\">🏆 BEST DEAL FOUND</div>\n      <div style=\"font-size: 24px; font-weight: bold; color: #333; margin-bottom: 5px;\">\n        ${bestDeal.currency} ${bestDeal.price.toLocaleString()}\n      </div>\n      <div style=\"color: #666;\">on ${bestDeal.platform} • ${bestDeal.roomType}</div>\n      ${savings > 0 ? `<div style=\"margin-top: 10px; color: #2e7d32; font-weight: bold;\">💰 Save ${bestDeal.currency} ${savings.toLocaleString()} compared to highest price!</div>` : ''}\n    </div>\n\n    <!-- Price Comparison Table -->\n    <div style=\"padding: 0 20px 20px 20px;\">\n      <h3 style=\"color: #333; margin-bottom: 15px;\">📊 Price Comparison</h3>\n      <table style=\"width: 100%; border-collapse: collapse; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1);\">\n        <thead>\n          <tr style=\"background: #f5f5f5;\">\n            <th style=\"padding: 12px; text-align: left; color: #666; font-weight: 600;\">Platform</th>\n            <th style=\"padding: 12px; text-align: left; color: #666; font-weight: 600;\">Room Type</th>\n            <th style=\"padding: 12px; text-align: right; color: #666; font-weight: 600;\">Price</th>\n            <th style=\"padding: 12px; text-align: center; color: #666; font-weight: 600;\">Action</th>\n          </tr>\n        </thead>\n        <tbody>\n          ${resultsHtml}\n        </tbody>\n      </table>\n      \n      <div style=\"margin-top: 15px; padding: 15px; background: #f8f9fa; border-radius: 8px; text-align: center; color: #666;\">\n        <strong>Average Price:</strong> ${bestDeal.currency} ${avgPrice.toLocaleString()}\n      </div>\n    </div>\n\n    <!-- Footer -->\n    <div style=\"padding: 20px; background: #f8f9fa; border-top: 1px solid #e0e0e0; text-align: center; color: #666; font-size: 13px;\">\n      <p style=\"margin: 0 0 10px 0;\">✨ Prices are subject to availability and may change.</p>\n      <p style=\"margin: 0;\">Happy travels! 🌍</p>\n    </div>\n\n  </div>\n</body>\n</html>\n`;\n\nreturn [{\n  json: {\n    subject: `🏨 ${hotelName} - Best Price: ${bestDeal.currency} ${bestDeal.price.toLocaleString()}`,\n    html,\n    ...data\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f1d4a0a7-0441-4609-8e35-0db86378de93",
      "name": "发送邮件报告",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        400,
        -320
      ],
      "webhookId": "e180876f-05b9-4290-891b-9ce57726fe26",
      "parameters": {
        "options": {},
        "subject": "={{$json.subject}}",
        "toEmail": "={{$json.userEmail}}",
        "fromEmail": "noreply@hotelassistant.com"
      },
      "credentials": {
        "smtp": {
          "id": "G1kyF8cSWTZ4vouN",
          "name": "SMTP -test"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "3a3a94aa-23f4-4758-9fb7-bac33b35ecca",
      "name": "Webhook响应(成功)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        624,
        -320
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ {\\n  \"success\": true,\\n  \"message\": \"Price comparison sent to your email!\",\\n  \"hotel\": $json.hotelName,\\n  \"bestPrice\": $json.bestDeal.price,\\n  \"platform\": $json.bestDeal.platform\\n} }}"
      },
      "typeVersion": 1
    },
    {
      "id": "1a4fd9f4-b08e-4c7e-9ab5-cc9c0e9cdf7f",
      "name": "Webhook响应(信息)",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        848,
        48
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ {\\n  \"success\": false,\\n  \"message\": $json.response,\\n  \"status\": $json.status\\n} }}"
      },
      "typeVersion": 1
    },
    {
      "id": "460b6b8d-679e-4876-9f45-b0b6aa432f8e",
      "name": "日志分析",
      "type": "n8n-nodes-base.set",
      "position": [
        400,
        -32
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{ {\\n  \"timestamp\": $now,\\n  \"query\": $json.originalQuery,\\n  \"hotel\": $json.hotelName,\\n  \"city\": $json.city,\\n  \"checkIn\": $json.checkIn,\\n  \"checkOut\": $json.checkOut,\\n  \"bestPrice\": $json.bestDeal?.price,\\n  \"platform\": $json.bestDeal?.platform,\\n  \"totalResults\": $json.totalResults,\\n  \"userEmail\": $json.userEmail\\n} }}"
      },
      "typeVersion": 3.2
    },
    {
      "id": "c7984244-c4d7-4343-88d6-422e1f54a06f",
      "name": "保存至Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        624,
        -32
      ],
      "parameters": {
        "columns": {
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "append",
        "sheetName": "Analytics",
        "documentId": "your-google-sheet-id",
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "id": "ScSS2KxGQULuPtdy",
          "name": "Google Sheets- test"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "0de90b41-12aa-4997-96f9-ee90d6b59ebe",
      "name": "便利贴",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -304
      ],
      "parameters": {
        "width": 150,
        "height": 352,
        "content": "用户发送自然语言查询"
      },
      "typeVersion": 1
    },
    {
      "id": "4cda970d-9585-43e2-b188-f7662e9ecee2",
      "name": "便利贴1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -768,
        -304
      ],
      "parameters": {
        "color": 4,
        "width": 192,
        "height": 352,
        "content": "提取酒店、城市、日期"
      },
      "typeVersion": 1
    },
    {
      "id": "6ca495c6-d8bd-483d-a79a-8c4b4d8e58ef",
      "name": "便利贴2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -304
      ],
      "parameters": {
        "color": 3,
        "width": 150,
        "height": 352,
        "content": "验证所有必需信息"
      },
      "typeVersion": 1
    },
    {
      "id": "b120b69f-566c-4c91-af14-8ee7171d0288",
      "name": "便利贴3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -400
      ],
      "parameters": {
        "color": 5,
        "width": 150,
        "height": 352,
        "content": "比价并寻找最优方案"
      },
      "typeVersion": 1
    },
    {
      "id": "6835fed2-3bed-4591-85b3-7e0ea31c3ec0",
      "name": "便利贴4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        -400
      ],
      "parameters": {
        "color": 3,
        "width": 150,
        "height": 352,
        "content": "创建精美HTML报告"
      },
      "typeVersion": 1
    },
    {
      "id": "c661889d-6ab1-48ed-8044-f7616aa53999",
      "name": "便利贴5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        368,
        -464
      ],
      "parameters": {
        "width": 662,
        "height": 656,
        "content": "发送响应"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "7e3f322c-14e9-4659-a3eb-8265b957e295",
  "connections": {
    "Scrape Agoda": {
      "main": [
        [
          {
            "node": "Aggregate & Compare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Analytics": {
      "main": [
        [
          {
            "node": "Save to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Ready": {
      "main": [
        [
          {
            "node": "Scrape Booking.com",
            "type": "main",
            "index": 0
          },
          {
            "node": "Scrape Agoda",
            "type": "main",
            "index": 0
          },
          {
            "node": "Scrape Expedia",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Webhook Response (Info)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Expedia": {
      "main": [
        [
          {
            "node": "Aggregate & Compare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email Report": {
      "main": [
        [
          {
            "node": "Webhook Response (Success)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Booking.com": {
      "main": [
        [
          {
            "node": "Aggregate & Compare",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate & Compare": {
      "main": [
        [
          {
            "node": "Format Email Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Email Report": {
      "main": [
        [
          {
            "node": "Send Email Report",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Analytics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Google Sheets": {
      "main": [
        [
          {
            "node": "Webhook Response (Info)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Validate Request": {
      "main": [
        [
          {
            "node": "Check If Ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Receive Request": {
      "main": [
        [
          {
            "node": "Parse & Validate Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 市场调研

需要付费吗?

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

工作流信息
难度等级
高级
节点数量19
分类1
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Oneclick AI Squad

Oneclick AI Squad

@oneclick-ai

The AI Squad Initiative is a pioneering effort to build, automate and scale AI-powered workflows using n8n.io. Our mission is to help individuals and businesses integrate AI agents seamlessly into their daily operations from automating tasks and enhancing productivity to creating innovative, intelligent solutions. We design modular, reusable AI workflow templates that empower creators, developers and teams to supercharge their automation with minimal effort and maximum impact.

外部链接
在 n8n.io 查看

分享此工作流