Liens internes du blog Shopify

Avancé

Ceci est unContent Creation, AI Summarizationworkflow d'automatisation du domainecontenant 26 nœuds.Utilise principalement des nœuds comme If, Set, Code, DataTable, HttpRequest. Génération automatique d'articles de blog pertinents pour Shopify avec text-embedding-3-small d'OpenAI

Prérequis
  • Peut nécessiter les informations d'identification d'authentification de l'API cible
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
{
  "id": "01ZGpwv1rdsk5Ng8",
  "meta": {
    "instanceId": "8e9162e70be518ca153a70a16d8785f5bfc6523821e135712fb7ef93fe97a5dd",
    "templateCredsSetupCompleted": true
  },
  "name": "Internal linking shopify blog",
  "tags": [],
  "nodes": [
    {
      "id": "8848b159-986e-4b67-8bde-a38da0a25f36",
      "name": "Déclencheur programmé",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1808,
        496
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtHour": 20
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "057f8499-694c-4391-80a1-e2f7ca093d25",
      "name": "Configuration du workflow",
      "type": "n8n-nodes-base.set",
      "position": [
        -1584,
        496
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "566ed7c6-84cf-4fcf-ae6e-5607fa4c3909",
              "name": "shopifyBlogId",
              "type": "string",
              "value": ""
            },
            {
              "id": "bdcbb25a-eba1-4a26-bc4e-e4dd8eb268db",
              "name": "shopifyBlogDomain",
              "type": "string",
              "value": "<https://example.com/blogs/>"
            },
            {
              "id": "6f1aaf1c-eb2f-42ac-b16e-6e4cd8cabd9f",
              "name": "shopifyStoreName",
              "type": "string",
              "value": "<domain_before_myshopify.com>"
            },
            {
              "id": "d704d6dd-d887-40d3-9f3b-1a61010095d2",
              "name": "shopApiVersion",
              "type": "string",
              "value": "2025-07"
            },
            {
              "id": "027f4123-2194-4f6d-8411-1305e7602692",
              "name": "percent_minimum_similarity",
              "type": "number",
              "value": 70
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ce7cbd23-b0ec-4420-a262-cc16ddd9f5bd",
      "name": "Récupérer les articles depuis shopify",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1360,
        496
      ],
      "parameters": {
        "url": "=https://{{ $('Workflow Configuration').first().json.shopifyStoreName }}.myshopify.com/admin/api/{{ $('Workflow Configuration').first().json.shopApiVersion }}/blogs/{{ $('Workflow Configuration').first().json.shopifyBlogId }}/articles.json",
        "options": {},
        "sendQuery": true,
        "authentication": "predefinedCredentialType",
        "queryParameters": {
          "parameters": [
            {
              "name": "published_status",
              "value": "published"
            }
          ]
        },
        "nodeCredentialType": "shopifyAccessTokenApi"
      },
      "credentials": {
        "shopifyAccessTokenApi": {
          "id": "oDrAi4lTTxnawLSv",
          "name": "Shopify Access Token account 2"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3623adca-6827-467c-81f7-6f138377aefd",
      "name": "Nettoyer et préparer le texte",
      "type": "n8n-nodes-base.code",
      "position": [
        -1136,
        496
      ],
      "parameters": {
        "jsCode": "// Function to clean HTML and remove the related section\nfunction stripHtml(html) {\n  if (!html) return '';\n  return html\n    .replace(/<style[^>]*>.*?<\\/style>/gi, '')\n    .replace(/<script[^>]*>.*?<\\/script>/gi, '')\n    .replace(/<div class=\"related-articles\"[^>]*>[\\s\\S]*?<\\/div>/gi, '') // remove related articles \n    .replace(/<[^>]+>/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .trim();\n}\n\n// Detect what we receive from previous node\nlet allItems = $input.all();\nlet articles = [];\n\n//  1: if multiple items\nif (allItems.length > 0) {\n  allItems.forEach(item => {\n    // Verificar si el item contiene un array \"articles\"\n    if (item.json && Array.isArray(item.json.articles)) {\n      articles = articles.concat(item.json.articles);\n    } \n    // if only one item\n    else if (item.json && item.json.id && item.json.title) {\n      articles.push(item.json);\n    }\n  });\n}\n\n// Fallback if no articles found\nif (articles.length === 0 && $input.item.json) {\n  if (Array.isArray($input.item.json.articles)) {\n    articles = $input.item.json.articles;\n  } else if ($input.item.json.id && $input.item.json.title) {\n    articles = [$input.item.json];\n  }\n}\n\n\n// Process each article\nconst processedArticles = articles.map(article => {\n  const cleanContent = stripHtml(article.body_html);\n  const cleanSummary = stripHtml(article.summary_html);\n  \n  // Create text for embeddings \n  const textForEmbedding = `Título: ${article.title}\n\nResumen: ${cleanSummary}\n\nContenido: ${cleanContent.substring(0, 15000)}`;\n\n  return {\n    shopify_id: article.id,\n    blog_id: article.blog_id,\n    shopify_handle: article.handle,\n    title: article.title,\n    content: cleanContent,\n    summary: cleanSummary,\n    url: $('Workflow Configuration').first().json.shopifyBlogDomain + article.handle,\n    published_at: article.published_at,\n    text_for_embedding: textForEmbedding,\n    word_count: cleanContent.split(/\\s+/).filter(w => w.length > 0).length\n  };\n});\n\nreturn processedArticles.map(article => ({ json: article }));"
      },
      "typeVersion": 2
    },
    {
      "id": "a91f14ad-f42c-4ad9-9ddd-4a218e8b8514",
      "name": "Boucler sur les éléments",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -912,
        496
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "8a1ffb69-65c0-4cf5-a4cb-e9209a9dc0ea",
      "name": "OpenAI Obtenir les embeddings",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -464,
        512
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/embeddings",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "text-embedding-3-small"
            },
            {
              "name": "input",
              "value": "={{ $json.embedding }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "credentials": {
        "openAiApi": {
          "id": "F6XMlOYUADkfTF7M",
          "name": "OpenAi key"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "7d83e353-0272-4857-ad4e-f3baab2e62b6",
      "name": "Définir les champs d'article",
      "type": "n8n-nodes-base.set",
      "position": [
        -688,
        512
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e443624a-de8d-485f-a3a1-b567c3145c2c",
              "name": "shopify_id",
              "type": "number",
              "value": "={{ $json.shopify_id }}"
            },
            {
              "id": "139eedb1-6aa3-434a-9425-fac9234c5148",
              "name": "blog_id",
              "type": "number",
              "value": "={{ $json.blog_id }}"
            },
            {
              "id": "9950f411-833a-488f-9f6a-b17f338114bf",
              "name": "shopify_handle",
              "type": "string",
              "value": "={{ $json.shopify_handle }}"
            },
            {
              "id": "1d83dc8d-c93d-41bb-ba5e-0a01f055c0fe",
              "name": "title",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "4a2a7109-b8d1-43f7-9d69-6d1f0cbef95a",
              "name": "content",
              "type": "string",
              "value": "={{ $json.content }}"
            },
            {
              "id": "0db25418-7777-4fc2-b774-4abf85819790",
              "name": "summary",
              "type": "string",
              "value": "={{ $json.summary }}"
            },
            {
              "id": "960a20c6-77ae-434a-9646-7b562da78a2e",
              "name": "url",
              "type": "string",
              "value": "={{ $json.url }}"
            },
            {
              "id": "4080709d-46e3-4d5c-b4b8-4cc599fae16c",
              "name": "published_at",
              "type": "string",
              "value": "={{ $json.published_at }}"
            },
            {
              "id": "83f98c7d-6ce7-4b58-9034-72f7b78a59e8",
              "name": "embedding",
              "type": "string",
              "value": "={{ $json.text_for_embedding }})"
            },
            {
              "id": "9a2a581c-2c3b-4a27-bde8-a3a62590a9df",
              "name": "analyzed_at",
              "type": "string",
              "value": "={{new Date().toISOString()}}"
            },
            {
              "id": "3a53791e-f7c6-4cef-ad55-8a37c9239073",
              "name": "word_count",
              "type": "number",
              "value": "={{ $json.word_count }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "55ce71bc-afc1-4178-87a3-6fda9829bd68",
      "name": "Calculer les similarités",
      "type": "n8n-nodes-base.code",
      "position": [
        -688,
        48
      ],
      "parameters": {
        "jsCode": "const doneItems = $(\"Loop Over Items\").all();\n\nconsole.log(`📦 Total items recibidos: ${doneItems.length}`);\n\n// Extract and prepare articles\nconst allArticles = doneItems.map(item => {\n  const data = item.json;\n  \n  // Parsing embedding\n  const embedding = typeof data.embedding === 'string' \n    ? JSON.parse(data.embedding) \n    : data.embedding;\n  \n  return {\n    shopify_id: data.shopify_id,\n    blog_id: data.blog_id,\n    title: data.title,\n    url: data.url,\n    embedding: embedding\n  };\n});\n\nconst uniqueIds = new Set(allArticles.map(a => a.shopify_id));\n\nif (allArticles.length < 2) {\n  throw new Error(`❌ Se necesitan al menos 2 artículos. Solo hay ${allArticles.length}`);\n}\n\nif (uniqueIds.size < 2) {\n  throw new Error(`❌ Todos los artículos tienen el mismo ID (${Array.from(uniqueIds)[0]})`);\n}\n\n// Function to calculate similarities\nfunction cosineSimilarity(vecA, vecB) {\n  if (!vecA || !vecB || vecA.length !== vecB.length) {\n    throw new Error('❌ Embeddings inválidos o de diferente longitud');\n  }\n  \n  let dotProduct = 0;\n  let normA = 0;\n  let normB = 0;\n  \n  for (let i = 0; i < vecA.length; i++) {\n    dotProduct += vecA[i] * vecB[i];\n    normA += vecA[i] * vecA[i];\n    normB += vecB[i] * vecB[i];\n  }\n  \n  const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));\n  return Math.round(similarity * 100 * 100) / 100;\n}\n\nconst relations = [];\nlet comparacionesRealizadas = 0;\n\nfor (let i = 0; i < allArticles.length; i++) {\n  for (let j = 0; j < allArticles.length; j++) {\n    \n    // ⚠️ Rule to avoid comparing one article to itself\n    if (allArticles[i].shopify_id === allArticles[j].shopify_id) {\n      continue; // Saltar esta iteración\n    }\n    \n    comparacionesRealizadas++;\n    \n    try {\n      const similarity = cosineSimilarity(\n        allArticles[i].embedding,\n        allArticles[j].embedding\n      );\n      \n      // Only keep similarity above the setting\n      if (similarity >= $('Workflow Configuration').first().json.percent_minimum_similarity) {\n        relations.push({\n          source_shopify_id: allArticles[i].shopify_id,\n          source_blog_id: allArticles[i].blog_id,\n          source_title: allArticles[i].title,\n          related_shopify_id: allArticles[j].shopify_id,\n          related_title: allArticles[j].title,\n          related_url: allArticles[j].url,\n          relation_type: \"semantic_similarity\",\n          similarity_score: similarity\n        });\n      }\n    } catch (error) {\n      console.error(`Error comparando ${allArticles[i].shopify_id} con ${allArticles[j].shopify_id}:`, error.message);\n    }\n  }\n}\n\n// In case, not enough similarity\nif (relations.length === 0) {\n  return [{ \n    json: { \n      mensaje: \"No se encontraron relaciones con similitud >= 60%\",\n      articulos_procesados: allArticles.length,\n      articulos_unicos: uniqueIds.size,\n      comparaciones_realizadas: comparacionesRealizadas,\n      sugerencia: \"Intenta bajar el umbral de similitud o verifica que los embeddings sean correctos\",\n      muestra_articulos: allArticles.slice(0, 3).map(a => ({\n        shopify_id: a.shopify_id,\n        title: a.title,\n        longitud_embedding: a.embedding.length\n      }))\n    } \n  }];\n}\n\nreturn relations.map(rel => ({ json: rel }));"
      },
      "typeVersion": 2,
      "alwaysOutputData": false
    },
    {
      "id": "762e212e-1907-4610-9bcd-cc7b79258172",
      "name": "Upsert des articles",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -240,
        512
      ],
      "parameters": {
        "columns": {
          "value": {
            "url": "={{ $('Set article fields').item.json.url }}",
            "title": "={{ $('Set article fields').item.json.title }}",
            "blog_id": "={{ $('Set article fields').item.json.blog_id }}",
            "content": "={{ $('Set article fields').item.json.content }}",
            "summary": "={{ $('Set article fields').item.json.summary }}",
            "embedding": "={{ JSON.stringify($json.data[0].embedding) }}",
            "shopify_id": "={{ $('Set article fields').item.json.shopify_id }}",
            "word_count": "={{ $('Set article fields').item.json.word_count }}",
            "published_at": "={{ $('Set article fields').item.json.published_at }}",
            "shopify_handle": "={{ $('Set article fields').item.json.shopify_handle }}",
            "last_analyzed_at": "={{ $('Set article fields').item.json.analyzed_at }}"
          },
          "schema": [
            {
              "id": "shopify_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "shopify_id",
              "defaultMatch": false
            },
            {
              "id": "shopify_handle",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "shopify_handle",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "content",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "content",
              "defaultMatch": false
            },
            {
              "id": "summary",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "summary",
              "defaultMatch": false
            },
            {
              "id": "url",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "url",
              "defaultMatch": false
            },
            {
              "id": "published_at",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "published_at",
              "defaultMatch": false
            },
            {
              "id": "embedding",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "embedding",
              "defaultMatch": false
            },
            {
              "id": "last_analyzed_at",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "last_analyzed_at",
              "defaultMatch": false
            },
            {
              "id": "word_count",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "word_count",
              "defaultMatch": false
            },
            {
              "id": "blog_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "blog_id",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "shopify_id",
              "keyValue": "={{ $('Set article fields').item.json.shopify_id }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "kjtDGPfteUD1V64N",
          "cachedResultUrl": "/projects/XqoBcIeUrbWmIJt8/datatables/kjtDGPfteUD1V64N",
          "cachedResultName": "articles"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "6607766a-147e-49b6-b127-6655d3221c8c",
      "name": "Upsert des article_relations",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        -464,
        48
      ],
      "parameters": {
        "columns": {
          "value": {
            "related_url": "={{ $json.related_url }}",
            "source_title": "={{ $json.source_title }}",
            "related_title": "={{ $json.related_title }}",
            "source_blog_id": "={{ $json.source_blog_id }}",
            "similarity_score": "={{ $json.similarity_score }}",
            "source_shopify_id": "={{ $json.source_shopify_id }}",
            "related_shopify_id": "={{ $json.related_shopify_id }}"
          },
          "schema": [
            {
              "id": "source_shopify_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source_shopify_id",
              "defaultMatch": false
            },
            {
              "id": "source_blog_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source_blog_id",
              "defaultMatch": false
            },
            {
              "id": "source_title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source_title",
              "defaultMatch": false
            },
            {
              "id": "related_shopify_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "related_shopify_id",
              "defaultMatch": false
            },
            {
              "id": "related_title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "related_title",
              "defaultMatch": false
            },
            {
              "id": "related_url",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "related_url",
              "defaultMatch": false
            },
            {
              "id": "relation_type",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "relation_type",
              "defaultMatch": false
            },
            {
              "id": "similarity_score",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "similarity_score",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "source_shopify_id",
              "keyValue": "={{ $json.source_shopify_id }}"
            },
            {
              "keyName": "related_shopify_id",
              "keyValue": "={{ $json.related_shopify_id }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "KXwn5vzq2gEz09tM",
          "cachedResultUrl": "/projects/XqoBcIeUrbWmIJt8/datatables/KXwn5vzq2gEz09tM",
          "cachedResultName": "article_relations"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "ff514a42-ee01-448a-8951-4b6d82ecff01",
      "name": "Top 3 des articles connexes",
      "type": "n8n-nodes-base.code",
      "position": [
        -240,
        48
      ],
      "parameters": {
        "jsCode": "// ============================================\n// GROUP AND GET TOP 3 RELATED ARTICLES\n// ============================================\n\nconst relations = $(\"Calculate similarities\").all().map(item => item.json);\n\n// Group by source_shopify_id\nconst groupedBySource = relations.reduce((acc, relation) => {\n  const sourceId = relation.source_shopify_id;\n  \n  if (!acc[sourceId]) {\n    acc[sourceId] = {\n      source_shopify_id: sourceId,\n      source_blog_id: relation.source_blog_id,\n      source_title: relation.source_title,\n      source_url: relation.source_url,\n      related_articles: []\n    };\n  }\n  \n  // Add related article with its similarity score\n  acc[sourceId].related_articles.push({\n    related_shopify_id: relation.related_shopify_id,\n    related_title: relation.related_title,\n    related_url: relation.related_url,\n    similarity_score: relation.similarity_score\n  });\n  \n  return acc;\n}, {});\n\n// Convert to array and get top 3 for each source\nconst result = Object.values(groupedBySource).map(group => {\n  // Sort by similarity_score (highest first) and take top 3\n  const top3Related = group.related_articles\n    .sort((a, b) => b.similarity_score - a.similarity_score)\n    .slice(0, 3)\n    .map(article => article.related_shopify_id);\n  \n  return {\n    source_shopify_id: group.source_shopify_id,\n    related_shopify_ids: top3Related\n  };\n});\n\n// Return as separate items for next node\nreturn result.map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "088f6ac5-4c84-4c63-ba89-ab778c3ce547",
      "name": "Boucle pour les articles sources",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -16,
        48
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "28f1bf7c-db78-4f1f-8db3-b00474dd7284",
      "name": "Obtenir les related_articles précédents",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        208,
        64
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "source_shopify_id",
              "keyValue": "={{ $json.source_shopify_id }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "7RrUjeEF2Zfcxpsn",
          "cachedResultUrl": "/projects/XqoBcIeUrbWmIJt8/datatables/7RrUjeEF2Zfcxpsn",
          "cachedResultName": "article_related_links_snapshot"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "268e6a49-f852-4056-b9cf-8b912f5b19e1",
      "name": "Vérifier si l'article a déjà des liens",
      "type": "n8n-nodes-base.if",
      "position": [
        432,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "6a466a42-552d-4be0-a443-e2907f660ba4",
              "operator": {
                "type": "number",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.source_shopify_id }}",
              "rightValue": "={{ $('Loop for source articles').item.json.related_shopify_ids }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b41317b7-4a8c-4043-94e8-89b6add1c772",
      "name": "Upsert des relations",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        688,
        80
      ],
      "parameters": {
        "columns": {
          "value": {
            "source_shopify_id": "={{ $('Loop for source articles').item.json.source_shopify_id }}",
            "related_shopify_ids": "={{ $('Loop for source articles').item.json.related_shopify_ids.join() }}"
          },
          "schema": [
            {
              "id": "source_shopify_id",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source_shopify_id",
              "defaultMatch": false
            },
            {
              "id": "related_shopify_ids",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "related_shopify_ids",
              "defaultMatch": false
            },
            {
              "id": "related_urls",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "related_urls",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "source_shopify_id",
              "keyValue": "={{ $('Loop for source articles').item.json.source_shopify_id }}"
            }
          ]
        },
        "options": {},
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "7RrUjeEF2Zfcxpsn",
          "cachedResultUrl": "/projects/XqoBcIeUrbWmIJt8/datatables/7RrUjeEF2Zfcxpsn",
          "cachedResultName": "article_related_links_snapshot"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2d051e8b-9f67-4ed3-8a2a-85d19281452f",
      "name": "Préparer le HTML pour les articles connexes",
      "type": "n8n-nodes-base.code",
      "position": [
        1344,
        80
      ],
      "parameters": {
        "jsCode": "// ============================================\n// PREPARE HTML FOR RELATED ARTICLES\n// ============================================\n\n// Get source article ID from \"Upsert relations\" node\nconst sourceArticle = $('Upsert relations').first().json;\nconst sourceShopifyId = sourceArticle.source_shopify_id;\n\n// Get related articles details from \"Get related article details\" node\nconst relatedArticles = $('Get related article details').all().map(i => i.json);\n\n// Build the HTML for related articles section\nlet relatedHTML = `\n<div class=\"related-articles\" style=\"margin: 40px 0;\">\n  <h3>📚 Artículos relacionados que te pueden interesar</h3>\n  <ul style=\"list-style: none; padding: 0;\">\n`;\n\n// Add each related article as a link\nrelatedArticles.forEach(article => {\n  relatedHTML += `\n    <li style=\"margin-bottom: 10px;\">\n      <a href=\"${article.url}\">→ ${article.title}</a>\n    </li>\n  `;\n});\n\nrelatedHTML += `\n  </ul>\n</div>\n`;\n\nreturn {\n  json: {\n    source_shopify_id: sourceShopifyId,\n    related_articles_html: relatedHTML,\n    related_count: relatedArticles.length\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0e6e4ad1-cf86-4e35-a862-bc8219c99838",
      "name": "Vérifier si les articles connexes sont identiques",
      "type": "n8n-nodes-base.if",
      "position": [
        608,
        -288
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "320c269c-572b-4888-b6c0-7ceec4db0e30",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('Loop for source articles').item.json.related_shopify_ids.join() }}",
              "rightValue": "={{ $('Get previous related_articles').item.json.related_shopify_ids }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "73604394-344d-469e-b845-0f3660abccaf",
      "name": "Aucun besoin de modifier la section connexe",
      "type": "n8n-nodes-base.noOp",
      "position": [
        816,
        -304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "2bc885ba-cbc4-4623-82ac-e85759170a6d",
      "name": "Obtenir les détails des articles connexes",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1120,
        80
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "shopify_id",
              "keyValue": "={{ $json.related_shopify_id }}"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "kjtDGPfteUD1V64N",
          "cachedResultUrl": "/projects/XqoBcIeUrbWmIJt8/datatables/kjtDGPfteUD1V64N",
          "cachedResultName": "articles"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "85357d92-5b8b-4e36-acba-a1f3473237ae",
      "name": "Diviser les IDs connexes",
      "type": "n8n-nodes-base.code",
      "position": [
        896,
        80
      ],
      "parameters": {
        "jsCode": "return $input.all().flatMap(item => {\n  const ids = item.json.related_shopify_ids.split(',').map(id => id.trim());\n  return ids.map(relatedId => ({\n    json: {\n      source_shopify_id: item.json.source_shopify_id,\n      related_shopify_id: relatedId\n    }\n  }));\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "36042f8c-f7b5-48bc-949f-44f8521d407e",
      "name": "Insérer les liens connexes dans l'article",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        80
      ],
      "parameters": {
        "jsCode": "// ============================================\n// UPDATE SHOPIFY ARTICLE WITH RELATED LINKS\n// ============================================\n\nconst item = $input.item.json;\n\n// Get current article content from Shopify\nconst currentArticle = $('Get articles from shopify').first().json.articles.find(a => a.id === $input.first().json.source_shopify_id);\nlet currentContent = currentArticle.body_html || '';\n\n// Remove old related articles section if exists\ncurrentContent = currentContent.replace(\n  /<div class=\"related-articles\"[^>]*>[\\s\\S]*?<\\/div>/gi, \n  ''\n);\n\n// Add new related articles at the end\nconst updatedContent = currentContent.trim() + '\\n\\n' + item.related_articles_html;\n\nconst article = {\n  id: item.source_shopify_id,\n  body_html: updatedContent\n}\n\nreturn [{ json: { article } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1524f17c-e0b2-41dc-a948-4e085885b0a2",
      "name": "Mettre à jour l'article avec les liens",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1792,
        80
      ],
      "parameters": {
        "url": "=https://{{ $('Workflow Configuration').first().json.shopifyStoreName }}.myshopify.com/admin/api/{{ $('Workflow Configuration').first().json.shopApiVersion }}/blogs/{{ $('Workflow Configuration').first().json.shopifyBlogId }}/articles/{{ $json.article.id }}.json",
        "method": "PUT",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "shopifyAccessTokenApi"
      },
      "credentials": {
        "shopifyAccessTokenApi": {
          "id": "oDrAi4lTTxnawLSv",
          "name": "Shopify Access Token account 2"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e9d3c731-ff01-4bbc-9c8c-a6eed48abb20",
      "name": "Note adhésive",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        304
      ],
      "parameters": {
        "width": 896,
        "height": 480,
        "content": "## Analyze each article with \"text-embedding-3-small\" model and save to Data Table\n* Previously it cleaned the content for each article\n* Within this loop for each article it's generating the embedding that will then be used to analyze similarities\n* It saves each article in the \"articles\" Data Table"
      },
      "typeVersion": 1
    },
    {
      "id": "d61c23f1-2de2-4b63-9e3b-a38cbb37688d",
      "name": "Note adhésive1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -768,
        -448
      ],
      "parameters": {
        "color": 5,
        "width": 2736,
        "height": 736,
        "content": "## Core process\n* It compares each article to each other to calculate the semantic similarity \n* In that particular example it looks for article that have more than 70% semantic similarity. This can be changed in the \"Workflow Configuration\" node, the variable is called \"percent_minimum_similarity\" \n* For each article, it gets the top 3 related articles ordered by highest semantic similarity. If you want to include more related articles you can change the node \"Top 3 related articles\"\n* It then checks in the \"article_related_links_snapshot\" Data table to check if this article already had related articles. Then it checks if the related are the same or need to be updated in the article\n* The rest of the process is to get the details of each related articles, create the html section, replace it within the article if there is already one and finally update the article through shopify API"
      },
      "typeVersion": 1
    },
    {
      "id": "4d66e34f-f02c-4b30-8a7e-182b828330fd",
      "name": "Note adhésive2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1856,
        304
      ],
      "parameters": {
        "color": 4,
        "width": 864,
        "height": 480,
        "content": "## Set up\n* **Workflow Configuration node need to be updated with your info**"
      },
      "typeVersion": 1
    },
    {
      "id": "03a381af-5bf9-4fa1-a633-3e2a154b1ceb",
      "name": "Note adhésive4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1856,
        -448
      ],
      "parameters": {
        "color": 7,
        "width": 1072,
        "height": 736,
        "content": "## IMPORTANT READ BEFORE LAUNCHING THE WORKFLOW\nFor this workflow to work you first need to create three Data Tables. Below is the details of each one. \n**articles**\n** shopify_id (number)\n** shopify_handle (string)\n** title (string)\n** content (string) \n** summary (string)\n** url (string)\n** published_at (datetime)\n** embedding (string)\n** last_analyzed_at (datetime)\n** word_count (number) \n** blog_id (number) \n\n**article_relations**\n** source_shopify_id (number) \n** source_blog_id (number) \n** source_title (string)\n** related_shopify_id (number) \n** related_title (string)\n** related_url (string) \n** similarity_score (number)\n\n**article_related_links_snapshot**\n** source_shopify_id (number)\n** related_shopify_ids (string) \n\n\nThen, the only thing you need to configure is the second node \"Workflow Configuration\". "
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "ib0LqomNsNIqEoi6",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "f2d0a7cc-1be7-4878-bd20-e82dae800398",
  "connections": {
    "a91f14ad-f42c-4ad9-9ddd-4a218e8b8514": {
      "main": [
        [
          {
            "node": "55ce71bc-afc1-4178-87a3-6fda9829bd68",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "7d83e353-0272-4857-ad4e-f3baab2e62b6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "762e212e-1907-4610-9bcd-cc7b79258172": {
      "main": [
        [
          {
            "node": "a91f14ad-f42c-4ad9-9ddd-4a218e8b8514",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8848b159-986e-4b67-8bde-a38da0a25f36": {
      "main": [
        [
          {
            "node": "057f8499-694c-4391-80a1-e2f7ca093d25",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "b41317b7-4a8c-4043-94e8-89b6add1c772": {
      "main": [
        [
          {
            "node": "85357d92-5b8b-4e36-acba-a1f3473237ae",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "85357d92-5b8b-4e36-acba-a1f3473237ae": {
      "main": [
        [
          {
            "node": "2bc885ba-cbc4-4623-82ac-e85759170a6d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7d83e353-0272-4857-ad4e-f3baab2e62b6": {
      "main": [
        [
          {
            "node": "8a1ffb69-65c0-4cf5-a4cb-e9209a9dc0ea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3623adca-6827-467c-81f7-6f138377aefd": {
      "main": [
        [
          {
            "node": "a91f14ad-f42c-4ad9-9ddd-4a218e8b8514",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8a1ffb69-65c0-4cf5-a4cb-e9209a9dc0ea": {
      "main": [
        [
          {
            "node": "762e212e-1907-4610-9bcd-cc7b79258172",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "55ce71bc-afc1-4178-87a3-6fda9829bd68": {
      "main": [
        [
          {
            "node": "6607766a-147e-49b6-b127-6655d3221c8c",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ff514a42-ee01-448a-8951-4b6d82ecff01": {
      "main": [
        [
          {
            "node": "088f6ac5-4c84-4c63-ba89-ab778c3ce547",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "057f8499-694c-4391-80a1-e2f7ca093d25": {
      "main": [
        [
          {
            "node": "ce7cbd23-b0ec-4420-a262-cc16ddd9f5bd",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "088f6ac5-4c84-4c63-ba89-ab778c3ce547": {
      "main": [
        [],
        [
          {
            "node": "28f1bf7c-db78-4f1f-8db3-b00474dd7284",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6607766a-147e-49b6-b127-6655d3221c8c": {
      "main": [
        [
          {
            "node": "ff514a42-ee01-448a-8951-4b6d82ecff01",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ce7cbd23-b0ec-4420-a262-cc16ddd9f5bd": {
      "main": [
        [
          {
            "node": "3623adca-6827-467c-81f7-6f138377aefd",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "36042f8c-f7b5-48bc-949f-44f8521d407e": {
      "main": [
        [
          {
            "node": "1524f17c-e0b2-41dc-a948-4e085885b0a2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2bc885ba-cbc4-4623-82ac-e85759170a6d": {
      "main": [
        [
          {
            "node": "2d051e8b-9f67-4ed3-8a2a-85d19281452f",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1524f17c-e0b2-41dc-a948-4e085885b0a2": {
      "main": [
        [
          {
            "node": "088f6ac5-4c84-4c63-ba89-ab778c3ce547",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "28f1bf7c-db78-4f1f-8db3-b00474dd7284": {
      "main": [
        [
          {
            "node": "268e6a49-f852-4056-b9cf-8b912f5b19e1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2d051e8b-9f67-4ed3-8a2a-85d19281452f": {
      "main": [
        [
          {
            "node": "36042f8c-f7b5-48bc-949f-44f8521d407e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "268e6a49-f852-4056-b9cf-8b912f5b19e1": {
      "main": [
        [
          {
            "node": "0e6e4ad1-cf86-4e35-a862-bc8219c99838",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "b41317b7-4a8c-4043-94e8-89b6add1c772",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "73604394-344d-469e-b845-0f3660abccaf": {
      "main": [
        [
          {
            "node": "088f6ac5-4c84-4c63-ba89-ab778c3ce547",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0e6e4ad1-cf86-4e35-a862-bc8219c99838": {
      "main": [
        [
          {
            "node": "73604394-344d-469e-b845-0f3660abccaf",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "b41317b7-4a8c-4043-94e8-89b6add1c772",
            "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é ?

Avancé - Création de contenu, 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é
Avancé
Nombre de nœuds26
Catégorie2
Types de nœuds9
Description de la difficulté

Adapté aux utilisateurs avancés, avec des workflows complexes contenant 16+ nœuds

Auteur
Geoffroy

Geoffroy

@jojoq42

Just a simple guy discovering the power of AI

Liens externes
Voir sur n8n.io

Partager ce workflow

Catégories

Catégories: 34