Utiliser Gemini AI pour générer un rapport de recherche de marché à partir des avis Google Maps

Intermédiaire

Ceci est unMarket Research, AI Summarizationworkflow d'automatisation du domainecontenant 15 nœuds.Utilise principalement des nœuds comme Set, Code, Gmail, Aggregate, HttpRequest. Générer des rapports de recherche de marché à partir des avis Google Maps avec Gemini AI

Prérequis
  • Compte Google et informations d'identification Gmail API
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
  • Clé API Google Gemini
Aperçu du workflow
Visualisation des connexions entre les nœuds, avec support du zoom et du déplacement
Exporter le workflow
Copiez la configuration JSON suivante dans n8n pour importer et utiliser ce workflow
{
  "meta": {
    "instanceId": "fec3a82e5888606db7f18bce6d6a86acc72be4b93a270dd22cfc357c585bfc28"
  },
  "nodes": [
    {
      "id": "7a561138-c149-4f92-97b7-087771017521",
      "name": "Start Workflow",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -608,
        288
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "acc1a472-587e-4dc2-b215-e986ca907163",
      "name": "Configuration des entrées utilisateur",
      "type": "n8n-nodes-base.set",
      "position": [
        -416,
        288
      ],
      "parameters": {
        "fields": {
          "values": [
            {
              "name": "search_query",
              "stringValue": "nhà hàng quận 1 TP.HCM"
            },
            {
              "name": "search_location",
              "stringValue": "@10.8231,106.6297,12z"
            },
            {
              "name": "language_code",
              "stringValue": "vi"
            },
            {
              "name": "analysis_focus",
              "stringValue": "restaurant"
            },
            {
              "name": "city_name",
              "stringValue": "TP. Hồ Chí Minh"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "9c3ced66-5ec2-4e70-8150-e11711bc3f90",
      "name": "Recherche dynamique de lieux",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -208,
        288
      ],
      "parameters": {
        "url": "https://serpapi.com/search.json",
        "options": {},
        "jsonQuery": "={\n  \"engine\": \"google_maps\",\n  \"q\": \"{{ $('User Input Configuration').item.json.search_query }}\",\n  \"ll\": \"{{ $('User Input Configuration').item.json.search_location }}\",\n  \"hl\": \"{{ $('User Input Configuration').item.json.language_code }}\",\n  \"api_key\": \"ffbea3b40ea41b0e10dbceb1302b81209669f83c5b9b75ed8309dbc3f2ae624d\"\n}",
        "sendQuery": true,
        "specifyQuery": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "0062335c-2c19-4132-938c-37a7013668ed",
      "name": "Extraction améliorée des données",
      "type": "n8n-nodes-base.code",
      "position": [
        -16,
        288
      ],
      "parameters": {
        "jsCode": "// Enhanced extraction with better filtering and validation\nconst localResults = $input.first().json.local_results || [];\nconst userConfig = $('User Input Configuration').item.json;\n\nconsole.log(`Processing ${localResults.length} search results for: ${userConfig.search_query}`);\n\n// Extract and filter places with more comprehensive data\nconst places = localResults.map(place => {\n  // Extract additional data that might be useful for analysis\n  const placeData = {\n    place_name: place.title || 'Unknown',\n    address: place.address || 'No address',\n    rating: place.rating || 'No rating',\n    reviews_count: place.reviews || 0,\n    reviews_link: place.reviews_link || null,\n    data_id: place.data_id || null,\n    type: place.type || 'Unknown',\n    place_id: place.place_id || null,\n    lsig: place.lsig || null,\n    phone: place.phone || null,\n    website: place.website || null,\n    thumbnail: place.thumbnail || null,\n    operating_hours: place.operating_hours || null,\n    price_level: place.price_level || null,\n    plus_code: place.plus_code || null,\n    coordinates: {\n      lat: place.gps_coordinates?.latitude || null,\n      lng: place.gps_coordinates?.longitude || null\n    },\n    // Add context about what we're analyzing\n    search_context: {\n      original_query: userConfig.search_query,\n      analysis_focus: userConfig.analysis_focus,\n      city: userConfig.city_name,\n      search_timestamp: new Date().toISOString()\n    }\n  };\n  \n  return placeData;\n}).filter(place => {\n  // Enhanced filtering - keep places that have either reviews_link OR sufficient basic data\n  return place.reviews_link || (place.rating && place.rating !== 'No rating') || place.reviews_count > 0;\n});\n\nconsole.log(`Filtered to ${places.length} places with review data or sufficient information`);\nconsole.log(`Analysis focus: ${userConfig.analysis_focus} in ${userConfig.city_name}`);\n\n// Return array of places with enhanced metadata\nreturn places.map(place => ({ json: place }));"
      },
      "typeVersion": 2
    },
    {
      "id": "136b7156-e38c-43a2-95d5-38a76c8e067a",
      "name": "Boucle sur les lieux",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        192,
        80
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "31112897-af1c-4476-85b9-81a7f287a7ca",
      "name": "Obtenir le contenu des avis",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        400,
        240
      ],
      "parameters": {
        "url": "={{ $json.reviews_link }}",
        "options": {
          "timeout": 30000,
          "allowUnauthorizedCerts": true
        },
        "jsonQuery": "{\n  \"api_key\": \"ffbea3b40ea41b0e10dbceb1302b81209669f83c5b9b75ed8309dbc3f2ae624d\"\n}",
        "sendQuery": true,
        "specifyQuery": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "c9f7f981-8476-4ba1-8448-5f29ae13d989",
      "name": "Combinaison améliorée des données",
      "type": "n8n-nodes-base.code",
      "position": [
        592,
        384
      ],
      "parameters": {
        "jsCode": "// Enhanced data combination with error handling\nconst currentItem = $('Loop Over Places').item;\nconst reviewsResponse = $input.first().json;\nconst userConfig = $('User Input Configuration').item.json;\n\n// Enhanced combined data with more comprehensive information\nconst combinedData = {\n  // Basic place information\n  place_name: currentItem.json.place_name,\n  address: currentItem.json.address,\n  rating: currentItem.json.rating,\n  reviews_count: currentItem.json.reviews_count,\n  data_id: currentItem.json.data_id,\n  type: currentItem.json.type,\n  phone: currentItem.json.phone,\n  website: currentItem.json.website,\n  coordinates: currentItem.json.coordinates,\n  operating_hours: currentItem.json.operating_hours,\n  price_level: currentItem.json.price_level,\n  \n  // Reviews content with error handling\n  reviews_content: reviewsResponse || { error: 'No reviews data available' },\n  \n  // Enhanced metadata for analysis\n  analysis_metadata: {\n    search_query: userConfig.search_query,\n    analysis_focus: userConfig.analysis_focus,\n    target_city: userConfig.city_name,\n    data_collection_time: new Date().toISOString(),\n    has_reviews: !!(reviewsResponse && reviewsResponse.reviews),\n    review_source: currentItem.json.reviews_link || 'No review link',\n    place_completeness_score: calculateCompletenessScore(currentItem.json)\n  }\n};\n\n// Function to calculate how complete the place data is\nfunction calculateCompletenessScore(placeData) {\n  const fields = ['place_name', 'address', 'rating', 'phone', 'website', 'operating_hours'];\n  const filledFields = fields.filter(field => \n    placeData[field] && placeData[field] !== 'Unknown' && placeData[field] !== 'No address'\n  );\n  return Math.round((filledFields.length / fields.length) * 100);\n}\n\nconsole.log(`Processing ${combinedData.analysis_metadata.analysis_focus}: ${combinedData.place_name}`);\nconsole.log(`Data completeness: ${combinedData.analysis_metadata.place_completeness_score}%`);\n\nreturn { json: combinedData };"
      },
      "typeVersion": 2
    },
    {
      "id": "68e789cd-2df5-4783-8891-5cd4d6eb0553",
      "name": "Collecter toutes les données",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        800,
        288
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "d1084cb2-0d34-4f6a-8a51-f87b1c01c6c3",
      "name": "Préparer les données d'analyse",
      "type": "n8n-nodes-base.code",
      "position": [
        992,
        288
      ],
      "parameters": {
        "jsCode": "// Enhanced data preparation with flexible analysis structure\nconst allPlaces = $input.all();\nconst userConfig = $('User Input Configuration').item.json;\n\nconsole.log(`Preparing analysis for ${allPlaces.length} ${userConfig.analysis_focus} locations in ${userConfig.city_name}`);\n\n// Create comprehensive analysis dataset\nlet analysisPrompt = `MARKET RESEARCH DATA ANALYSIS\\n`;\nanalysisPrompt += `Research Focus: ${userConfig.analysis_focus}\\n`;\nanalysisPrompt += `Location: ${userConfig.city_name}\\n`;\nanalysisPrompt += `Search Query: \"${userConfig.search_query}\"\\n`;\nanalysisPrompt += `Total Locations Found: ${allPlaces.length}\\n`;\nanalysisPrompt += `Analysis Date: ${new Date().toLocaleDateString('vi-VN')}\\n\\n`;\n\n// Enhanced data summary\nanalysisPrompt += `=== DATASET OVERVIEW ===\\n`;\nlet totalReviews = 0;\nlet avgRating = 0;\nlet placesWithReviews = 0;\nlet completenessScores = [];\n\nallPlaces.forEach((place, index) => {\n  const data = place.json;\n  \n  // Calculate statistics\n  if (data.reviews_count && typeof data.reviews_count === 'number') {\n    totalReviews += data.reviews_count;\n  }\n  \n  if (data.rating && data.rating !== 'No rating') {\n    avgRating += parseFloat(data.rating) || 0;\n  }\n  \n  if (data.reviews_content && data.reviews_content.reviews) {\n    placesWithReviews++;\n  }\n  \n  if (data.analysis_metadata && data.analysis_metadata.place_completeness_score) {\n    completenessScores.push(data.analysis_metadata.place_completeness_score);\n  }\n  \n  // Add detailed place information\n  analysisPrompt += `\\n${index + 1}. ${data.place_name}\\n`;\n  analysisPrompt += `   Address: ${data.address}\\n`;\n  analysisPrompt += `   Rating: ${data.rating}/5 (${data.reviews_count || 0} reviews)\\n`;\n  \n  // Add contact and operational info if available\n  if (data.phone) analysisPrompt += `   Phone: ${data.phone}\\n`;\n  if (data.website) analysisPrompt += `   Website: ${data.website}\\n`;\n  if (data.operating_hours) analysisPrompt += `   Hours: ${JSON.stringify(data.operating_hours)}\\n`;\n  if (data.price_level) analysisPrompt += `   Price Level: ${data.price_level}\\n`;\n  \n  // Add review samples if available\n  if (data.reviews_content && data.reviews_content.reviews) {\n    analysisPrompt += `   Recent Reviews:\\n`;\n    data.reviews_content.reviews.slice(0, 5).forEach(review => {\n      const reviewText = review.snippet || review.text || review.comment || 'No text available';\n      const reviewRating = review.rating || 'No rating';\n      analysisPrompt += `   - \"${reviewText.substring(0, 200)}...\" (${reviewRating}/5)\\n`;\n    });\n  }\n  \n  analysisPrompt += `   Data Completeness: ${data.analysis_metadata?.place_completeness_score || 'N/A'}%\\n`;\n  analysisPrompt += `\\n${'-'.repeat(60)}\\n`;\n});\n\n// Add summary statistics\nconst avgRatingCalculated = allPlaces.length > 0 ? (avgRating / allPlaces.length).toFixed(2) : 0;\nconst avgCompleteness = completenessScores.length > 0 ? \n  (completenessScores.reduce((a, b) => a + b, 0) / completenessScores.length).toFixed(1) : 'N/A';\n\nanalysisPrompt += `\\n=== SUMMARY STATISTICS ===\\n`;\nanalysisPrompt += `Total Places Analyzed: ${allPlaces.length}\\n`;\nanalysisPrompt += `Places with Review Data: ${placesWithReviews}\\n`;\nanalysisPrompt += `Total Reviews Collected: ${totalReviews}\\n`;\nanalysisPrompt += `Average Rating: ${avgRatingCalculated}/5\\n`;\nanalysisPrompt += `Average Data Completeness: ${avgCompleteness}%\\n`;\n\nreturn {\n  json: {\n    analysis_prompt: analysisPrompt,\n    raw_data: allPlaces.map(place => place.json),\n    summary_stats: {\n      total_places: allPlaces.length,\n      places_with_reviews: placesWithReviews,\n      total_reviews: totalReviews,\n      average_rating: avgRatingCalculated,\n      average_completeness: avgCompleteness,\n      search_context: userConfig\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0cdd33f4-447d-4246-98bf-410e562686c1",
      "name": "Analyse de marché par IA",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1168,
        288
      ],
      "parameters": {
        "text": "=Bạn là chuyên gia phân tích và nghiên cứu thị trường chuyên sâu với khả năng tạo ra các báo cáo insights chi tiết và actionable.\n\nBạn đã nhận được dữ liệu thị trường thực tế về \"{{ $('User Input Configuration').item.json.analysis_focus }}\" tại {{ $('User Input Configuration').item.json.city_name }} với đầy đủ thông tin từ khách hàng thực tế.\n\n=== DỮ LIỆU PHÂN TÍCH ===\n{{ JSON.stringify($json.raw_data, null, 2) }}\n\n=== NHIỆM VỤ PHÂN TÍCH ===\n\nHãy tạo một báo cáo phân tích thị trường toàn diện với cấu trúc sau:\n\n**I. TÓM TẮT ĐIỀU HÀNH (EXECUTIVE SUMMARY)**\n- Insights chính trong 3-4 câu\n- Cơ hội kinh doanh lớn nhất\n- Khuyến nghị hành động ngay lập tức\n\n**II. PHÂN TÍCH TỔNG QUAN THỊ TRƯỜNG**\n- Quy mô và mật độ thị trường {{ $('User Input Configuration').item.json.analysis_focus }} tại {{ $('User Input Configuration').item.json.city_name }}\n- Phân khúc giá và chất lượng dịch vụ\n- Phân bố địa lý và khu vực hot\n- Mức độ cạnh tranh và gap thị trường\n\n**III. PHÂN TÍCH TRẢI NGHIỆM KHÁCH HÀNG**\n- Top 5 yếu tố được đánh giá cao nhất (với % khách hàng mention)\n- Top 5 vấn đề phổ biến nhất (với tần suất xuất hiện)\n- Sentiment analysis tổng thể\n- Customer journey và pain points chính\n\n**IV. COMPETITIVE LANDSCAPE**\n- Phân tích 3-5 player mạnh nhất\n- Điểm mạnh/yếu của từng competitor\n- Pricing strategy và value proposition\n- Market positioning gaps\n\n**V. INSIGHTS VÀ XU HƯỚNG**\n- Behavioral patterns của target customers\n- Emerging trends và changing preferences\n- Seasonal/temporal patterns nếu có\n- Technology adoption và digital readiness\n\n**VI. KHUYẾN NGHỊ CHIẾN LƯỢC**\n- Business model tối ưu cho thị trường này\n- Pricing strategy và revenue streams\n- Marketing approach và customer acquisition\n- Operational excellence priorities\n- Investment areas và resource allocation\n\n**VII. ACTION PLAN**\n- 3 hành động ngay lập tức (next 30 days)\n- 3 initiatives trung hạn (3-6 months)\n- Long-term strategy (6-12 months)\n\nYêu cầu:\n- Sử dụng số liệu cụ thể từ dữ liệu thực tế\n- Đưa ra insights độc đáo, không chỉ mô tả\n- Tập trung vào actionable recommendations\n- Viết bằng tiếng Việt chuyên nghiệp nhưng dễ hiểu\n- Highlight key metrics và success factors\n- Đề xuất KPIs để theo dõi success",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.1
    },
    {
      "id": "82c701cb-a88a-410c-bda4-aaaf4c5227ef",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1152,
        480
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "id": "MT1SUsx0Hy6XtV22",
          "name": "Google Gemini(PaLM) Api account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2ec72fea-962e-45d4-aa48-abf02c0d80de",
      "name": "Préparer le contenu de l'e-mail",
      "type": "n8n-nodes-base.code",
      "position": [
        1536,
        176
      ],
      "parameters": {
        "jsCode": "// Simplified email preparation - Analysis content only\nconst reportContent = $input.first().json.output;\nconst userConfig = $('User Input Configuration').item.json;\nconst summaryStats = $('Prepare Analysis Data').item.json.summary_stats;\n\n// Generate dynamic subject based on analysis focus\nconst analysisType = userConfig.analysis_focus;\nconst cityName = userConfig.city_name;\nconst currentDate = new Date().toLocaleDateString('vi-VN');\n\nconst emailSubject = `📊 Báo Cáo Phân Tích Thị Trường ${analysisType} ${cityName} - ${currentDate}`;\n\n// Enhanced HTML formatting function\nfunction formatToHTML(content) {\n  let html = content\n    // Convert markdown-style formatting\n    .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n    .replace(/\\*([^*]+)\\*/g, '<em>$1</em>')\n    \n    // Convert main sections (I., II., III., etc.)\n    .replace(/\\*\\*([IVX]+\\. [^*]+)\\*\\*/g, '<h2 style=\"color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 8px; margin-top: 30px;\">$1</h2>')\n    \n    // Convert subsections\n    .replace(/\\*\\*([^*]+:)\\*\\*/g, '<h3 style=\"color: #34495e; margin-top: 20px; margin-bottom: 10px;\">$1</h3>')\n    \n    // Convert numbered lists with enhanced styling\n    .replace(/^(\\d+\\. )/gm, '<div style=\"margin: 8px 0;\"><strong style=\"color: #3498db;\">$1</strong>')\n    \n    // Convert bullet points with different levels\n    .replace(/^- /gm, '<div style=\"margin-left: 20px; margin: 5px 0;\">• ')\n    .replace(/^  - /gm, '<div style=\"margin-left: 40px; margin: 5px 0;\">◦ ')\n    .replace(/^    - /gm, '<div style=\"margin-left: 60px; margin: 5px 0;\">▪ ')\n    \n    // Close div tags and handle line breaks\n    .replace(/(• [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n    .replace(/(◦ [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n    .replace(/(▪ [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n    .replace(/\\n\\n/g, '</p><p>')\n    .replace(/\\n/g, '<br>');\n\n  return '<p>' + html + '</p>'.replace(/<p><\\/p>/g, '');\n}\n\n// Create comprehensive HTML email template - Analysis focused\nconst htmlContent = `\n<!DOCTYPE html>\n<html lang=\"vi\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>${emailSubject}</title>\n    <style>\n        body {\n            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n            line-height: 1.6;\n            color: #333;\n            max-width: 900px;\n            margin: 0 auto;\n            padding: 20px;\n            background-color: #f8f9fa;\n        }\n        .container {\n            background-color: white;\n            padding: 40px;\n            border-radius: 12px;\n            box-shadow: 0 4px 20px rgba(0,0,0,0.1);\n        }\n        .header {\n            text-align: center;\n            margin-bottom: 40px;\n            padding: 30px;\n            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n            color: white;\n            border-radius: 10px;\n        }\n        .header h1 {\n            margin: 0;\n            font-size: 26px;\n            font-weight: bold;\n        }\n        .header .subtitle {\n            font-size: 16px;\n            margin-top: 10px;\n            opacity: 0.95;\n        }\n        .stats-overview {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n            gap: 20px;\n            margin: 30px 0;\n            padding: 25px;\n            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);\n            border-radius: 10px;\n        }\n        .stat-item {\n            text-align: center;\n            padding: 15px;\n            background: white;\n            border-radius: 8px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n        }\n        .stat-number {\n            font-size: 24px;\n            font-weight: bold;\n            color: #3498db;\n        }\n        .stat-label {\n            font-size: 12px;\n            color: #7f8c8d;\n            margin-top: 5px;\n        }\n        h2 {\n            color: #2c3e50;\n            border-bottom: 2px solid #3498db;\n            padding-bottom: 8px;\n            margin-top: 30px;\n        }\n        h3 {\n            color: #34495e;\n            margin-top: 20px;\n            margin-bottom: 10px;\n        }\n        .highlight {\n            background: #fff3cd;\n            border: 1px solid #ffeaa7;\n            padding: 15px;\n            border-radius: 5px;\n            margin: 15px 0;\n        }\n        .footer {\n            margin-top: 40px;\n            padding: 25px;\n            background: #34495e;\n            color: white;\n            text-align: center;\n            border-radius: 10px;\n            font-size: 14px;\n        }\n        @media (max-width: 600px) {\n            body { padding: 10px; }\n            .container { padding: 20px; }\n            .stats-overview { grid-template-columns: 1fr; }\n        }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <div class=\"header\">\n            <h1>📊 BÁO CÁO PHÂN TÍCH THỊ TRƯỜNG</h1>\n            <div class=\"subtitle\">${analysisType} tại ${cityName}</div>\n            <div style=\"font-size: 14px; margin-top: 15px; opacity: 0.9;\">\n                Ngày phân tích: ${new Date().toLocaleDateString('vi-VN', { \n                    year: 'numeric', \n                    month: 'long', \n                    day: 'numeric',\n                    hour: '2-digit',\n                    minute: '2-digit'\n                })}\n            </div>\n        </div>\n        \n        ${formatToHTML(reportContent)}\n        \n        <div class=\"footer\">\n            <p><strong>🚀 Market Research Analytics System</strong></p>\n            <p>📧 Báo cáo được tạo tự động từ dữ liệu thực tế \n            <p>🎯 Phân tích: ${analysisType} | 📍 Địa điểm: ${cityName}</p>\n            <p style=\"margin-top: 15px; font-size: 12px; opacity: 0.8;\">\n                Query gốc: \"${userConfig.search_query}\"\n            </p>\n        </div>\n    </div>\n</body>\n</html>\n`;\n\n// Create simplified plain text version - Analysis only\nconst plainTextContent = `\nBÁO CÁO PHÂN TÍCH THỊ TRƯỜNG - ${analysisType.toUpperCase()} ${cityName.toUpperCase()}\n${'='.repeat(60)}\n\nSỐ LIỆU TỔNG QUAN:\n- Địa điểm phân tích: ${summaryStats.total_places}\n- Tổng đánh giá: ${summaryStats.total_reviews}\n- Rating TB: ${summaryStats.average_rating}/5\n- Có data chi tiết: ${summaryStats.places_with_reviews}\n\n${reportContent.replace(/\\*\\*([^*]+)\\*\\*/g, '$1').replace(/\\*/g, '')}\n\n${'='.repeat(60)}\nBáo cáo tự động - ${currentDate}\n`;\n\nreturn {\n    json: {\n        htmlContent: htmlContent,\n        plainTextContent: plainTextContent,\n        subject: emailSubject,\n        analysis_metadata: {\n            analysis_type: analysisType,\n            city: cityName,\n            total_locations: summaryStats.total_places,\n            total_reviews: summaryStats.total_reviews,\n            average_rating: summaryStats.average_rating,\n            search_query: userConfig.search_query,\n            generation_time: new Date().toISOString()\n        }\n    }};"
      },
      "typeVersion": 2
    },
    {
      "id": "7dd14586-d25d-4b12-baef-2be0c6987456",
      "name": "Envoyer le rapport par e-mail",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1728,
        384
      ],
      "webhookId": "4632d0f8-2acf-4508-acf5-f2d1e9a77bbf",
      "parameters": {
        "sendTo": "truong11062002@gmail.com",
        "message": "={{ $json.htmlContent }}",
        "options": {
          "ccList": "",
          "bccList": "",
          "replyTo": ""
        },
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "9hqpp5Ew9HbGDqNu",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "1ecc0e85-5ec2-4f9f-a6e8-23a3f7f826a8",
      "name": "Statut final et journalisation",
      "type": "n8n-nodes-base.code",
      "position": [
        1936,
        384
      ],
      "parameters": {
        "jsCode": "// Final workflow status and comprehensive logging\nconst emailResult = $input.first().json;\nconst userConfig = $('User Input Configuration').item.json;\nconst summaryStats = $('Prepare Analysis Data').item.json.summary_stats;\nconst analysisMetadata = $('Prepare Email Content').item.json.analysis_metadata;\n\nconst completionTime = new Date().toISOString();\n\nconsole.log('🎉 ========= FLEXIBLE MARKET ANALYSIS COMPLETED =========');\nconsole.log(`📊 Analysis Type: ${userConfig.analysis_focus}`);\nconsole.log(`🏙️  Target City: ${userConfig.city_name}`);\nconsole.log(`🔍 Search Query: \"${userConfig.search_query}\"`);\nconsole.log(`📍 Total Locations Analyzed: ${summaryStats.total_places}`);\nconsole.log(`💬 Total Reviews Processed: ${summaryStats.total_reviews}`);\nconsole.log(`⭐ Average Rating: ${summaryStats.average_rating}/5`);\nconsole.log(`📧 Email Report Sent Successfully`);\nconsole.log(`⏰ Completion Time: ${completionTime}`);\nconsole.log('=========================================================');\n\n// Generate comprehensive final report\nconst workflowSummary = {\n  workflow_status: 'COMPLETED_SUCCESSFULLY',\n  workflow_type: 'FLEXIBLE_MARKET_RESEARCH_ANALYSIS',\n  execution_metadata: {\n    analysis_focus: userConfig.analysis_focus,\n    target_location: userConfig.city_name,\n    search_query: userConfig.search_query,\n    language_code: userConfig.language_code,\n    completion_time: completionTime\n  },\n  data_collection_results: {\n    total_places_found: summaryStats.total_places,\n    places_with_review_data: summaryStats.places_with_reviews,\n    total_reviews_collected: summaryStats.total_reviews,\n    average_rating_calculated: summaryStats.average_rating,\n    data_completeness_average: summaryStats.average_completeness\n  },\n  analysis_outputs: {\n    ai_model_used: 'Google_Gemini',\n    report_format: 'Comprehensive_Market_Analysis',\n    email_delivery_status: emailResult.messageId ? 'SUCCESS' : 'UNKNOWN',\n    email_subject: analysisMetadata.subject,\n    email_recipient: 'truong11062002@gmail.com'\n  },\n  workflow_capabilities: {\n    flexible_search_query: true,\n    dynamic_location_targeting: true,\n    multi_language_support: true,\n    comprehensive_data_extraction: true,\n    ai_powered_analysis: true,\n    automated_report_generation: true,\n    email_delivery: true\n  },\n  next_steps_recommendations: [\n    'Review the detailed market analysis report in your email',\n    'Customize search parameters for different market segments',\n    'Set up recurring analysis for market monitoring',\n    'Expand analysis to additional cities or business categories',\n    'Integrate insights into business strategy planning'\n  ]\n};\n\nreturn { json: workflowSummary };"
      },
      "typeVersion": 2
    },
    {
      "id": "599485d6-b773-4462-83d4-e0124c6450d6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -832,
        -1248
      ],
      "parameters": {
        "width": 944,
        "height": 1392,
        "content": "# 🚀 Market Research Analytics System\n\n> **Transform Google Maps data into actionable business insights with AI-powered analysis**\n\n## 📋 Overview\n\nThis n8n workflow automatically collects business data from Google Maps, analyzes customer reviews using AI, and generates comprehensive market research reports delivered straight to your inbox.\n\n---\n\n## ⚡ Quick Start Guide\n\n### Step 1: Configure Your Search Parameters\n\nNavigate to the **\"Configuration Variables\"** node and update these fields:\n\n```json\n{\n  \"search_query\": \"restaurants downtown\",        // Your target business type + location\n  \"search_location\": \"@40.7589,-73.9851,12z\",   // Coordinates (lat,lng,zoom)\n  \"language_code\": \"en\",                         // Language: en, vi, es, fr, etc.\n  \"analysis_focus\": \"restaurant\",                // Business category for analysis\n  \"city_name\": \"New York City\"                   // Target city name\n}\n```\n\n**🔍 Need coordinates?** Use [LatLong.net](https://www.latlong.net/) to find your target location coordinates.\n\n---\n\n### Step 2: Setup API Keys\n\n#### 🔑 SerpAPI (Google Maps Data)\n1. **Get API Key:** Visit [SerpAPI Google Maps Reviews](https://serpapi.com/google-maps-reviews-api)\n2. **Sign up** for free account (100 searches/month included)\n3. **Copy your API key** from the dashboard\n4. **Update workflow:** Replace `YOUR_SERPAPI_KEY_HERE` in both HTTP nodes\n\n#### 🤖 Google Gemini AI (Analysis)\n1. **Get API Key:** Visit [Google AI Studio](https://ai.google.dev/gemini-api/docs/api-key)\n2. **Create new API key** (free tier available)\n3. **Setup in n8n:** Add credentials in \"Google Gemini Chat Model\" node\n\n---\n\n### Step 3: Configure Email Delivery\n\n1. **Open** the **\"Send Email Report\"** node\n2. **Update recipient:** Change `your-email@example.com` to your email\n3. **Gmail setup:** Connect your Gmail account in n8n credentials\n\n---\n\n### Step 4: Execute & Enjoy! 🎉"
      },
      "typeVersion": 1
    }
  ],
  "pinData": {
    "Start Workflow": [
      {}
    ]
  },
  "connections": {
    "7a561138-c149-4f92-97b7-087771017521": {
      "main": [
        [
          {
            "node": "acc1a472-587e-4dc2-b215-e986ca907163",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "68e789cd-2df5-4783-8891-5cd4d6eb0553": {
      "main": [
        [
          {
            "node": "d1084cb2-0d34-4f6a-8a51-f87b1c01c6c3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "136b7156-e38c-43a2-95d5-38a76c8e067a": {
      "main": [
        [
          {
            "node": "68e789cd-2df5-4783-8891-5cd4d6eb0553",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "31112897-af1c-4476-85b9-81a7f287a7ca",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7dd14586-d25d-4b12-baef-2be0c6987456": {
      "main": [
        [
          {
            "node": "1ecc0e85-5ec2-4f9f-a6e8-23a3f7f826a8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0cdd33f4-447d-4246-98bf-410e562686c1": {
      "main": [
        [
          {
            "node": "2ec72fea-962e-45d4-aa48-abf02c0d80de",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "31112897-af1c-4476-85b9-81a7f287a7ca": {
      "main": [
        [
          {
            "node": "c9f7f981-8476-4ba1-8448-5f29ae13d989",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9c3ced66-5ec2-4e70-8150-e11711bc3f90": {
      "main": [
        [
          {
            "node": "0062335c-2c19-4132-938c-37a7013668ed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "c9f7f981-8476-4ba1-8448-5f29ae13d989": {
      "main": [
        [
          {
            "node": "136b7156-e38c-43a2-95d5-38a76c8e067a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0062335c-2c19-4132-938c-37a7013668ed": {
      "main": [
        [
          {
            "node": "136b7156-e38c-43a2-95d5-38a76c8e067a",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "d1084cb2-0d34-4f6a-8a51-f87b1c01c6c3": {
      "main": [
        [
          {
            "node": "0cdd33f4-447d-4246-98bf-410e562686c1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2ec72fea-962e-45d4-aa48-abf02c0d80de": {
      "main": [
        [
          {
            "node": "7dd14586-d25d-4b12-baef-2be0c6987456",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "82c701cb-a88a-410c-bda4-aaaf4c5227ef": {
      "ai_languageModel": [
        [
          {
            "node": "0cdd33f4-447d-4246-98bf-410e562686c1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "acc1a472-587e-4dc2-b215-e986ca907163": {
      "main": [
        [
          {
            "node": "9c3ced66-5ec2-4e70-8150-e11711bc3f90",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Foire aux questions

Comment utiliser ce workflow ?

Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.

Dans quelles scénarios ce workflow est-il adapté ?

Intermédiaire - Étude de marché, Résumé IA

Est-ce payant ?

Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.

Informations sur le workflow
Niveau de difficulté
Intermédiaire
Nombre de nœuds15
Catégorie2
Types de nœuds10
Description de la difficulté

Adapté aux utilisateurs expérimentés, avec des workflows de complexité moyenne contenant 6-15 nœuds

Auteur

I help Sales & Marketing teams save time with custom n8n workflows. With over 5 years of automation experience, I offer free consultations—book now!

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34