使用Firecrawl、GPT-4.1监控竞争对手价格并发送警报到Gmail
中级
这是一个Market Research, AI Summarization领域的自动化工作流,包含 15 个节点。主要使用 Code, Gmail, Merge, OpenAi, GoogleSheets 等节点。 使用Firecrawl、GPT-4.1、Sheets和Gmail警报监控竞争对手价格
前置要求
- •Google 账号和 Gmail API 凭证
- •OpenAI API Key
- •Google Sheets API 凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "wesSFaik8lD7g9lq",
"meta": {
"instanceId": "b91e510ebae4127f953fd2f5f8d40d58ca1e71c746d4500c12ae86aad04c1502",
"templateCredsSetupCompleted": true
},
"name": "使用 Firecrawl、GPT-4.1 监控竞争对手价格并发送警报到 Gmail",
"tags": [
{
"id": "7zsdOA50QGm7RNqx",
"name": "Monitoring",
"createdAt": "2025-10-23T16:41:17.031Z",
"updatedAt": "2025-10-23T16:41:17.031Z"
},
{
"id": "BL8TsHYj5FkNYzfi",
"name": "E-commerce",
"createdAt": "2025-10-23T16:41:16.985Z",
"updatedAt": "2025-10-23T16:41:16.985Z"
},
{
"id": "dlf9zFSN3j6s2jgO",
"name": "Business Intelligence",
"createdAt": "2025-10-23T16:41:17.008Z",
"updatedAt": "2025-10-23T16:41:17.008Z"
},
{
"id": "lpozR2Ct8reF9bCk",
"name": "AI",
"createdAt": "2025-10-23T16:41:17.062Z",
"updatedAt": "2025-10-23T16:41:17.062Z"
}
],
"nodes": [
{
"id": "1a7684b3-eb26-414f-ad30-e613306b50b1",
"name": "📊 读取历史数据",
"type": "n8n-nodes-base.googleSheets",
"notes": "Loads previous scan data for comparison",
"position": [
-1472,
176
],
"parameters": {
"options": {},
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aGWAWeimeVnptYQh",
"name": "Google Sheets account 2"
}
},
"typeVersion": 4.7
},
{
"id": "9239f63d-2717-4248-918a-b886016c9a98",
"name": "🔀 合并当前与历史数据",
"type": "n8n-nodes-base.merge",
"notes": "Combines current scrape with historical data for comparison",
"position": [
-1296,
0
],
"parameters": {
"mode": "combine",
"options": {},
"fieldsToMatchString": "rawResponse.message.content"
},
"typeVersion": 3
},
{
"id": "3b453791-3aca-4563-97d7-34d1bc751824",
"name": "🔍 检测价格和库存变化",
"type": "n8n-nodes-base.code",
"notes": "Intelligent change detection with alert level classification",
"position": [
-1120,
0
],
"parameters": {
"jsCode": "// Compare current prices with historical and detect changes\nconst results = [];\n\nfor (const item of $input.all()) {\n const current = item.json;\n \n // Skip error items\n if (current.error) {\n results.push({ json: current });\n continue;\n }\n \n // Find historical data for this competitor\n const historical = $('📊 Read Historical Data').all()\n .find(h => h.json.competitorName === current.competitorName);\n \n let alertLevel = 'none';\n let changes = [];\n \n if (historical && historical.json.currentPrice) {\n const oldPrice = parseFloat(historical.json.currentPrice);\n const newPrice = parseFloat(current.currentPrice);\n const priceChange = newPrice - oldPrice;\n const priceChangePercent = ((priceChange / oldPrice) * 100).toFixed(2);\n \n current.priceChange = priceChange;\n current.priceChangePercent = parseFloat(priceChangePercent);\n current.previousPrice = oldPrice;\n \n // Determine alert level based on price changes\n if (Math.abs(priceChangePercent) >= 20) {\n alertLevel = 'critical';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 10) {\n alertLevel = 'warning';\n changes.push(`Price ${priceChange > 0 ? 'increased' : 'decreased'} by ${Math.abs(priceChangePercent)}%`);\n } else if (Math.abs(priceChangePercent) >= 5) {\n alertLevel = 'info';\n changes.push(`Minor price change: ${priceChangePercent}%`);\n }\n \n // Check if it's a new low price\n const historicalLow = parseFloat(historical.json.lowestPrice || oldPrice);\n if (newPrice < historicalLow) {\n current.isNewLow = true;\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('🎯 NEW LOWEST PRICE!');\n }\n current.lowestPrice = Math.min(newPrice, historicalLow);\n \n // Stock level changes\n if (historical.json.stockLevel !== current.stockLevel) {\n if (current.stockLevel === 'Out of Stock') {\n alertLevel = 'critical';\n changes.push('📦 Product went OUT OF STOCK');\n } else if (current.stockLevel === 'Low Stock') {\n alertLevel = alertLevel === 'none' ? 'warning' : alertLevel;\n changes.push('⚠️ Stock level is LOW');\n } else if (historical.json.stockLevel === 'Out of Stock' && current.inStock) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push('✅ Back in stock!');\n }\n }\n \n // Rating changes\n const oldRating = parseFloat(historical.json.rating || 0);\n const newRating = parseFloat(current.rating || 0);\n const ratingChange = newRating - oldRating;\n \n if (Math.abs(ratingChange) >= 0.5) {\n alertLevel = alertLevel === 'none' ? 'info' : alertLevel;\n changes.push(`⭐ Rating ${ratingChange > 0 ? 'improved' : 'dropped'} by ${Math.abs(ratingChange).toFixed(1)} stars`);\n }\n \n // Review count changes\n const oldReviews = parseInt(historical.json.reviewCount || 0);\n const newReviews = parseInt(current.reviewCount || 0);\n const reviewDiff = newReviews - oldReviews;\n \n if (reviewDiff > 0) {\n changes.push(`💬 ${reviewDiff} new review${reviewDiff > 1 ? 's' : ''}`);\n }\n } else {\n // First time seeing this competitor\n alertLevel = 'info';\n changes.push('🆕 First time tracking this competitor');\n current.lowestPrice = current.currentPrice;\n }\n \n current.alertLevel = alertLevel;\n current.changesSummary = changes.join(' | ');\n current.hasChanges = alertLevel !== 'none';\n \n results.push({ json: current });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "c83f80f9-8954-406e-a43a-20299f94d4ef",
"name": "💾 更新历史数据",
"type": "n8n-nodes-base.googleSheets",
"notes": "Saves current data to historical tracking sheet",
"position": [
-944,
80
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"inStock": "={{ $json.inStock }}",
"currency": "={{ $json.currency }}",
"scrapedAt": "={{ $json.scrapedAt }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"lowestPrice": "={{ $json.lowestPrice }}",
"productName": "={{ $json.productName }}",
"reviewCount": "={{ $json.reviewCount }}",
"currentPrice": "={{ $json.currentPrice }}",
"originalPrice": "={{ $json.originalPrice }}",
"competitorName": "={{ $json.competitorName }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Historical Data"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "= {{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aGWAWeimeVnptYQh",
"name": "Google Sheets account 2"
}
},
"typeVersion": 4.7
},
{
"id": "c1ba6419-781d-4c2d-bfa6-dfe202751e19",
"name": "📝 记录警报详情",
"type": "n8n-nodes-base.googleSheets",
"notes": "Logs all alerts to separate tracking sheet",
"position": [
-944,
-80
],
"parameters": {
"columns": {
"value": {
"rating": "={{ $json.rating }}",
"timestamp": "={{ $json.scrapedAt }}",
"alertLevel": "={{ $json.alertLevel }}",
"productUrl": "={{ $json.productUrl }}",
"stockLevel": "={{ $json.stockLevel }}",
"priceChange": "={{ $json.priceChange || 0 }}",
"productName": "={{ $json.productName }}",
"currentPrice": "={{ $json.currentPrice }}",
"previousPrice": "={{ $json.previousPrice || 'N/A' }}",
"changesSummary": "={{ $json.changesSummary }}",
"competitorName": "={{ $json.competitorName }}",
"priceChangePercent": "={{ $json.priceChangePercent || 0 }}"
},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "append",
"sheetName": {
"mode": "name",
"value": "Alert Log"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEET_ID }}"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "aGWAWeimeVnptYQh",
"name": "Google Sheets account 2"
}
},
"typeVersion": 4.7
},
{
"id": "233f07c7-c121-4e14-abf1-0a917b023a11",
"name": "📊 聚合每日摘要",
"type": "n8n-nodes-base.code",
"notes": "Combines all alerts into a comprehensive summary",
"position": [
-944,
-240
],
"parameters": {
"jsCode": "// Aggregate all items for daily digest email\nconst allItems = $input.all();\n\nconst criticalAlerts = allItems.filter(item => item.json.alertLevel === 'critical');\nconst warningAlerts = allItems.filter(item => item.json.alertLevel === 'warning');\nconst infoAlerts = allItems.filter(item => item.json.alertLevel === 'info');\nconst noChanges = allItems.filter(item => item.json.alertLevel === 'none');\n\nconst summary = {\n totalCompetitors: allItems.length,\n criticalCount: criticalAlerts.length,\n warningCount: warningAlerts.length,\n infoCount: infoAlerts.length,\n noChangeCount: noChanges.length,\n timestamp: new Date().toISOString(),\n criticalAlerts: criticalAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary,\n price: `${i.json.currency} ${i.json.currentPrice}`,\n priceChange: i.json.priceChangePercent ? `${i.json.priceChangePercent}%` : 'N/A',\n stock: i.json.stockLevel,\n url: i.json.productUrl\n })),\n warningAlerts: warningAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary,\n price: `${i.json.currency} ${i.json.currentPrice}`,\n priceChange: i.json.priceChangePercent ? `${i.json.priceChangePercent}%` : 'N/A',\n stock: i.json.stockLevel\n })),\n infoAlerts: infoAlerts.map(i => ({\n competitor: i.json.competitorName,\n product: i.json.productName,\n changes: i.json.changesSummary\n }))\n};\n\nreturn [{ json: summary }];"
},
"typeVersion": 2
},
{
"id": "bb79bf76-29bd-44d9-8064-48c14622e1f5",
"name": "抓取 URL:nike.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
-176
],
"parameters": {
"url": "https://www.nike.com/sg/w/mens-shoes-nik1zy7ok",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "GbvfMx7jLyZ5wBpe",
"name": "Firecrawl account test"
}
},
"typeVersion": 1
},
{
"id": "905a1a25-92aa-4459-8d4e-8392d6ca4e61",
"name": "当点击“执行工作流”时",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-2096,
32
],
"parameters": {},
"typeVersion": 1
},
{
"id": "fb7b71da-f70d-48d8-afef-ee42bdb48567",
"name": "发送消息",
"type": "n8n-nodes-base.gmail",
"position": [
-800,
-240
],
"webhookId": "db581b6f-5b87-4fcb-be62-37ad7ab7a26f",
"parameters": {
"sendTo": " info@example.com",
"message": "The pricing of the competitors is attached",
"options": {},
"subject": "Shoes pricing"
},
"credentials": {
"gmailOAuth2": {
"id": "TbUa3sBo9ouHTYFh",
"name": "Gmail account 3"
}
},
"typeVersion": 2.1
},
{
"id": "5c08b0c2-5bc1-4594-be36-938e67308a1f",
"name": "抓取 URL:adidas.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
-16
],
"parameters": {
"url": "=https://www.adidas.com/us/men-shoes",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "GbvfMx7jLyZ5wBpe",
"name": "Firecrawl account test"
}
},
"typeVersion": 1
},
{
"id": "57ac5f72-cc18-4919-bdea-4e16939b8080",
"name": "抓取 URL:sneakerpricer.com",
"type": "@mendable/n8n-nodes-firecrawl.firecrawl",
"position": [
-1904,
144
],
"parameters": {
"url": "=https://www.sneakerpricer.com/us-EN",
"operation": "scrape",
"scrapeOptions": {
"options": {
"formats": {
"format": [
{
"type": "json",
"prompt": "price of the shoe"
}
]
},
"headers": {}
}
},
"requestOptions": {}
},
"credentials": {
"firecrawlApi": {
"id": "GbvfMx7jLyZ5wBpe",
"name": "Firecrawl account test"
}
},
"typeVersion": 1
},
{
"id": "5eb1ffd8-d98d-4682-b200-92ab2432fd81",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2656,
-400
],
"parameters": {
"width": 2032,
"height": 880,
"content": "## 介绍"
},
"typeVersion": 1
},
{
"id": "738fd9c2-91ff-4f15-b7a3-1aa0e4c1f8a1",
"name": "将非结构化的 AI 文本转换为有组织、可用的数据字段",
"type": "n8n-nodes-base.code",
"notes": "Parses and validates the AI extracted data",
"position": [
-1504,
-16
],
"parameters": {
"jsCode": "// Parse AI response and clean data\nconst items = [];\n\nfor (const item of $input.all()) {\n try {\n // Parse the AI response\n let parsed;\n const response = item.json.choices?.[0]?.message?.content || item.json.message || '';\n \n // Remove markdown code blocks if present\n const cleaned = response.replace(/```json\\n?|```\\n?/g, '').trim();\n \n try {\n parsed = JSON.parse(cleaned);\n } catch (e) {\n // Try to extract JSON from the response\n const jsonMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n parsed = JSON.parse(jsonMatch[0]);\n } else {\n throw new Error('Could not parse JSON from AI response');\n }\n }\n \n // Enrich with metadata\n items.push({\n json: {\n ...parsed,\n scrapedAt: new Date().toISOString(),\n priceChange: 0, // Will be calculated in comparison\n priceChangePercent: 0,\n isNewLow: false,\n alertLevel: 'none'\n }\n });\n } catch (error) {\n console.error('Failed to parse item:', error.message);\n // Add error item for debugging\n items.push({\n json: {\n error: error.message,\n rawResponse: item.json,\n competitorName: 'Parse Error',\n scrapedAt: new Date().toISOString()\n }\n });\n }\n}\n\nreturn items;"
},
"typeVersion": 2
},
{
"id": "2a1d727b-8147-42e7-bfcd-c3ed37af7bc7",
"name": "🤖 使用 GPT-4.1-mini AI 提取产品数据",
"type": "n8n-nodes-base.openAi",
"notes": "Uses OpenAI to intelligently extract structured data from HTML",
"position": [
-1696,
-16
],
"parameters": {
"prompt": {
"messages": [
{
"role": "system",
"content": "You are a precise e-commerce data extraction expert. Extract shoes information from HTML and return ONLY valid JSON with no markdown formatting.\n\nExtract these fields:\n- productName: string\n- currentPrice: number (numeric value only, no currency symbols)\n- originalPrice: number (if discounted, otherwise same as currentPrice)\n- currency: string (USD, EUR, etc.)\n- inStock: boolean\n- stockLevel: string (\"In Stock\", \"Low Stock\", \"Out of Stock\", \"Limited\", etc.)\n- rating: number (0-5 scale)\n- reviewCount: number\n- lastUpdated: string (current ISO timestamp)\n- productUrl: string (from context)\n- competitorName: string (from context)\n\nReturn ONLY the JSON object, no explanations."
},
{
"content": "HTML Content:\n{{ $json.body }}\n\nProduct URL: {{ $json.url || 'unknown' }}\nCompetitor: {{ $json.competitor || 'unknown' }}\n\nExtract the product data as JSON:"
}
]
},
"options": {
"temperature": 0.1
},
"resource": "chat",
"chatModel": "gpt-4.1-mini",
"requestOptions": {}
},
"credentials": {
"openAiApi": {
"id": "OGYj7DgYv5GFLFZk",
"name": "OpenAi account 2"
}
},
"typeVersion": 1.1
},
{
"id": "652a2128-6e01-4d6e-8bcb-b3f844cec2a8",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1584,
-352
],
"parameters": {
"color": 6,
"width": 352,
"height": 240,
"content": "## Google Sheets 结构"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "703c4545-13fd-46b1-9a69-7e8c6ec4c656",
"connections": {
"Send a message": {
"main": [
[]
]
},
"Scrape URL: nike.com": {
"main": [
[
{
"node": "🤖 AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"Scrape URL: adidas.com": {
"main": [
[
{
"node": "🤖 AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"📊 Read Historical Data": {
"main": [
[
{
"node": "🔀 Merge Current with Historical",
"type": "main",
"index": 1
}
]
]
},
"📊 Aggregate Daily Digest": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
},
"Scrape URL: sneakerpricer.com": {
"main": [
[
{
"node": "🤖 AI Extract Product Data using GPT-4.1-mini",
"type": "main",
"index": 0
}
]
]
},
"🔍 Detect Price & Stock Changes": {
"main": [
[
{
"node": "📊 Aggregate Daily Digest",
"type": "main",
"index": 0
},
{
"node": "💾 Update Historical Data",
"type": "main",
"index": 0
},
{
"node": "📝 Log Alert Details",
"type": "main",
"index": 0
}
]
]
},
"🔀 Merge Current with Historical": {
"main": [
[
{
"node": "🔍 Detect Price & Stock Changes",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": "Scrape URL: nike.com",
"type": "main",
"index": 0
},
{
"node": "Scrape URL: adidas.com",
"type": "main",
"index": 0
},
{
"node": "Scrape URL: sneakerpricer.com",
"type": "main",
"index": 0
}
]
]
},
"🤖 AI Extract Product Data using GPT-4.1-mini": {
"main": [
[
{
"node": "Converts unstructured AI text into organized, usable data fields",
"type": "main",
"index": 0
}
]
]
},
"Converts unstructured AI text into organized, usable data fields": {
"main": [
[
{
"node": "🔀 Merge Current with Historical",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级 - 市场调研, AI 摘要总结
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
多站点产品价格监控:Claude-Sonnet、Google Sheets和Telegram提醒
使用Firecrawl、Claude-Sonnet AI和Telegram提醒的电商价格监控系统
Code
Wait
Merge
+10
19 节点Cheng Siong Chin
市场调研
使用 Bright Data API 和 AI 抓取分析 Google 广告并发送邮件报告
使用 Bright Data API 和 AI 抓取分析 Google 广告并发送邮件报告
Set
Code
Gmail
+15
45 节点Zacharia Kimotho
市场调研
AI-Deepseek-R1t 会议差旅审批与费用授权申请
通过Deepseek AI、Gmail和Google Sheets自动化会议差旅审批
If
Set
Code
+11
24 节点Cheng Siong Chin
文档提取
01 使用AI媒体买家分析Facebook广告表现并将洞察发送到Google Sheets
使用Gemini AI分析Facebook广告并将洞察发送到Google Sheets
If
Set
Code
+13
34 节点JJ Tham
市场调研
使用 OpenAI 自动标记和分析 Google Sheets 中的客户反馈
在 Google Sheets 中批量处理客户反馈,进行情感和情绪分析
Set
Code
Merge
+6
24 节点Parhum Khoshbakht
市场调研
将微信文章分类和总结到 Google Sheets 和 Notion
使用 GPT-4 Nano 将微信文章分类和总结到 Google Sheets 和 Notion
If
Set
Code
+9
26 节点Seven Liu
市场调研
工作流信息
难度等级
中级
节点数量15
分类2
节点类型8
作者
Cheng Siong Chin
@cschinProf. Cheng Siong CHIN serves as Chair Professor in Intelligent Systems Modelling and Simulation in Newcastle University, Singapore. His academic credentials include an M.Sc. in Advanced Control and Systems Engineering from The University of Manchester and a Ph.D. in Robotics from Nanyang Technological University.
外部链接
在 n8n.io 查看 →
分享此工作流