使用 TenderNed 自动化荷兰公共采购数据收集
高级
这是一个Market Research领域的自动化工作流,包含 28 个节点。主要使用 Xml, Code, Merge, Filter, SplitOut 等节点。 使用 TenderNed 自动化荷兰公共采购数据收集
前置要求
- •可能需要目标 API 的认证凭证
使用的节点 (28)
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"meta": {
"instanceId": "89249a8a187ba6e01e16112a0d334a3aa01d510ad8f88d223e12cc0a2a8beb6b"
},
"nodes": [
{
"id": "7369f055-b55d-491c-8dee-12988a5c2dc8",
"name": "获取 XML 详情",
"type": "n8n-nodes-base.httpRequest",
"notes": "Haalt volledige XML voor elke tender",
"position": [
1296,
864
],
"parameters": {
"url": "=https://www.tenderned.nl/papi/tenderned-rs-tns/v2/publicaties/{{ $json.publicaties.publicatieId }}/public-xml",
"options": {
"timeout": 30000,
"response": {
"response": {
"responseFormat": "text"
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"notesInFlow": true,
"typeVersion": 4.2
},
{
"id": "ecb51d0b-b6a9-4b50-879d-6f7aa7240bff",
"name": "当点击\"执行工作流\"时",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-560,
768
],
"parameters": {},
"typeVersion": 1
},
{
"id": "bf05ee04-4ee0-4217-8b02-7fe5e8f67600",
"name": "拆分输出",
"type": "n8n-nodes-base.splitOut",
"position": [
432,
976
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "publicaties"
},
"typeVersion": 1
},
{
"id": "ed07bb23-b6ed-4cff-9327-d410150d061a",
"name": "遍历项目",
"type": "n8n-nodes-base.splitInBatches",
"position": [
832,
976
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "6855d01b-c1bd-429d-a2c6-610528ec192c",
"name": "XML",
"type": "n8n-nodes-base.xml",
"position": [
1472,
864
],
"parameters": {
"options": {
"trim": true
}
},
"typeVersion": 1
},
{
"id": "7c209557-d141-43d4-9fa9-ced8cc06229d",
"name": "合并",
"type": "n8n-nodes-base.merge",
"position": [
1840,
928
],
"parameters": {
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "f49fa844-4585-4274-8086-3c7bd8cef555",
"name": "获取 JSON 详情",
"type": "n8n-nodes-base.httpRequest",
"notes": "Haalt JSON details: kenmerk, pbNummerTed, trefwoorden",
"position": [
1376,
704
],
"parameters": {
"url": "=https://www.tenderned.nl/papi/tenderned-rs-tns/v2/publicaties/{{ $json.publicaties.publicatieId }}",
"options": {
"timeout": 30000
},
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth"
},
"typeVersion": 4.2
},
{
"id": "1e619b5f-9fdb-44af-95f2-1c4c45fa5703",
"name": "聚合",
"type": "n8n-nodes-base.aggregate",
"position": [
2240,
944
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "allData"
},
"typeVersion": 1
},
{
"id": "e1816ca0-4f73-449f-a9b5-7a0b9e6cd7e3",
"name": "拆分所有字段",
"type": "n8n-nodes-base.code",
"notes": "Pakt ALLE XML velden uit naar losse, bruikbare velden",
"position": [
2544,
944
],
"parameters": {
"jsCode": "// ============================================\n// VERSIE 6: COMPLETE FIX + GUNNINGSCRITERIA + PLATFORM\n// Node: \"Splits Alle Velden\"\n// Toevoegingen: Gunningscriteria wegingen + Platform detectie\n// ============================================\n\n// Extract data sources from aggregated input\nconst aggregatedData = $json.allData || [];\n\nconsole.log(`📊 Aggregate bevat ${aggregatedData.length} items`);\n\n// Initialize data containers\nlet xmlData = {};\nlet publicatiesData = {};\nlet jsonDetail = null;\n\n// Separate the three data sources\nfor (const item of aggregatedData) {\n // Check XML data\n if (item.PriorInformationNotice || item.ContractNotice || item.ContractAwardNotice) {\n xmlData = item.PriorInformationNotice || item.ContractNotice || item.ContractAwardNotice || {};\n console.log('✅ XML data gevonden');\n }\n // Check publicaties data \n else if (item.publicaties) {\n publicatiesData = item.publicaties;\n console.log('✅ Publicaties data gevonden');\n }\n // Check JSON detail data\n else if (item.publicatieId || item.kenmerk || item.pbNummerTed) {\n jsonDetail = item;\n console.log('✅ JSON detail data gevonden');\n }\n}\n\n// ============================================\n// FLATTEN FUNCTIE\n// ============================================\nfunction flattenXML(obj, prefix = '') {\n let result = {};\n for (const key in obj) {\n const value = obj[key];\n const newKey = prefix ? `${prefix}_${key}` : key;\n \n if (value === null || value === undefined) {\n result[newKey] = null;\n } else if (typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(result, flattenXML(value, newKey));\n } else if (Array.isArray(value)) {\n result[newKey] = value;\n } else {\n result[newKey] = value;\n }\n }\n return result;\n}\n\n// Flatten XML & Publicaties\nconst alleXMLVelden = flattenXML(xmlData, 'xml');\n\n// ============================================\n// FIX 1: NOTE VELD (CORRECT PAD)\n// ============================================\nconst cbcNote = alleXMLVelden['xml_cac:ProcurementProject_cbc:Note__'] || '';\nconsole.log(`✅ Note veld: ${cbcNote ? 'GEVONDEN' : 'NIET GEVONDEN'}`);\n\n// ============================================\n// FIX 2: DESCRIPTIONS (HOOFD + ALLE PERCELEN)\n// ============================================\nconst hoofdDescription = alleXMLVelden['xml_cac:ProcurementProject_cbc:Description__'] || '';\nconst lots = alleXMLVelden['xml_cac:ProcurementProjectLot'] || [];\n\nlet cbcDescription = hoofdDescription;\nlet perceelInfoArray = [];\n\nif (Array.isArray(lots) && lots.length > 0) {\n console.log(`✅ Aantal percelen: ${lots.length}`);\n \n lots.forEach((lot, index) => {\n const perceelProj = lot?.['cac:ProcurementProject'] || {};\n const perceelNaam = perceelProj?.['cbc:Name']?.['_'] || `Perceel ${index + 1}`;\n const perceelDesc = perceelProj?.['cbc:Description']?.['_'] || '';\n const perceelId = lot?.['cbc:ID']?.['_'] || '';\n \n // Duration\n const durationObj = perceelProj?.['cac:PlannedPeriod']?.['cbc:DurationMeasure'];\n const perceelDuur = durationObj?.['_'] || '';\n const perceelDuurEenheid = durationObj?.['unitCode'] || '';\n \n // CPV codes per perceel\n const mainCpv = perceelProj?.['cac:MainCommodityClassification']?.['cbc:ItemClassificationCode']?.['_'] || '';\n const addCpv = perceelProj?.['cac:AdditionalCommodityClassification']?.['cbc:ItemClassificationCode']?.['_'] || '';\n \n // Store perceel info\n perceelInfoArray.push({\n id: perceelId,\n naam: perceelNaam,\n beschrijving: perceelDesc,\n duur: perceelDuur,\n duurEenheid: perceelDuurEenheid,\n cpvMain: mainCpv,\n cpvAdditional: addCpv\n });\n \n // Add to combined description\n if (perceelDesc) {\n cbcDescription += `\\n\\n=== ${perceelNaam} ===\\n${perceelDesc}`;\n }\n \n console.log(` Perceel ${index + 1}: ${perceelNaam} (${perceelDuur} ${perceelDuurEenheid})`);\n });\n}\n\n// ============================================\n// FIX 3: GESCHATTE WAARDE\n// ============================================\nconst geschatteWaarde = alleXMLVelden['xml_cac:ProcurementProject_cac:RequestedTenderTotal_cbc:EstimatedOverallContractAmount__'] || '';\nconst geschatteWaardeCurrency = alleXMLVelden['xml_cac:ProcurementProject_cac:RequestedTenderTotal_cbc:EstimatedOverallContractAmount_currencyID'] || 'EUR';\n\nlet geschatteWaardeFormatted = '';\nif (geschatteWaarde) {\n const bedrag = parseFloat(geschatteWaarde);\n if (!isNaN(bedrag)) {\n geschatteWaardeFormatted = `€${bedrag.toLocaleString('nl-NL')}`;\n }\n}\nconsole.log(`✅ Geschatte waarde: ${geschatteWaardeFormatted || 'Niet opgegeven'}`);\n\n// ============================================\n// FIX 4: DEADLINE VOOR VRAGEN\n// ============================================\nlet deadlineVragen = '';\nlet deadlineVragenTime = '';\n\nif (Array.isArray(lots) && lots.length > 0) {\n const firstLot = lots[0];\n const infoRequestPeriod = firstLot?.['cac:TenderingProcess']?.['cac:AdditionalInformationRequestPeriod'];\n deadlineVragen = infoRequestPeriod?.['cbc:EndDate'] || '';\n deadlineVragenTime = infoRequestPeriod?.['cbc:EndTime'] || '';\n}\n\nconst deadlineVragenFull = deadlineVragen && deadlineVragenTime \n ? `${deadlineVragen} ${deadlineVragenTime}` \n : deadlineVragen;\n \nconsole.log(`✅ Deadline vragen: ${deadlineVragenFull || 'Niet opgegeven'}`);\n\n// ============================================\n// FIX 5: RAAMOVEREENKOMST TYPE\n// ============================================\nlet raamovereenkomstType = '';\nif (Array.isArray(lots) && lots.length > 0) {\n const firstLot = lots[0];\n const contractingSystems = firstLot?.['cac:TenderingProcess']?.['cac:ContractingSystem'];\n if (Array.isArray(contractingSystems)) {\n const faSystem = contractingSystems.find(cs => \n cs?.['cbc:ContractingSystemTypeCode']?.['listName'] === 'framework-agreement'\n );\n raamovereenkomstType = faSystem?.['cbc:ContractingSystemTypeCode']?.['_'] || '';\n }\n}\nconsole.log(`✅ Raamovereenkomst type: ${raamovereenkomstType || 'Niet van toepassing'}`);\n\n// ============================================\n// FIX 6: CONTACT INFORMATIE\n// ============================================\nconst orgData = alleXMLVelden['xml_ext:UBLExtensions_ext:UBLExtension_ext:ExtensionContent_efext:EformsExtension_efac:Organizations_efac:Organization_efac:Company'] || {};\n\nconst contactNaam = orgData?.['cac:Contact_cbc:Name'] || '';\nconst contactTelefoon = orgData?.['cac:Contact_cbc:Telephone'] || '';\nconst contactEmail = orgData?.['cac:Contact_cbc:ElectronicMail'] || '';\nconst organisatieWebsite = orgData?.['cbc:WebsiteURI'] || '';\n\nconsole.log(`✅ Contact: ${contactNaam || 'Niet opgegeven'}`);\n\n// ============================================\n// FIX 7: RECHTBANK / REVIEW BODY\n// ============================================\nconst touchpointData = alleXMLVelden['xml_ext:UBLExtensions_ext:UBLExtension_ext:ExtensionContent_efext:EformsExtension_efac:Organizations_efac:Organization_efac:TouchPoint'] || {};\n\nconst reviewBodyNaam = touchpointData?.['cac:PartyName_cbc:Name__'] || '';\nconst reviewBodyEmail = touchpointData?.['cac:Contact_cbc:ElectronicMail'] || '';\nconst reviewBodyTelefoon = touchpointData?.['cac:Contact_cbc:Telephone'] || '';\n\n// ============================================\n// FIX 8: GERELATEERDE PUBLICATIES\n// ============================================\nlet gerelateerdePublicatiesText = '';\nlet gerelateerdePublicatiesIds = [];\n\nif (jsonDetail?.gerelateerdePublicaties && Array.isArray(jsonDetail.gerelateerdePublicaties)) {\n jsonDetail.gerelateerdePublicaties.forEach(pub => {\n gerelateerdePublicatiesIds.push(pub.publicatieId);\n const pubDate = pub.publicatieDatum ? pub.publicatieDatum.split('T')[0] : '';\n gerelateerdePublicatiesText += `${pub.typePublicatie} (ID: ${pub.publicatieId}, Datum: ${pubDate})\\n`;\n });\n console.log(`✅ Gerelateerde publicaties: ${gerelateerdePublicatiesIds.length}`);\n}\n\n// ============================================\n// FIX 9: UBL METADATA\n// ============================================\nconst ublVersionId = alleXMLVelden['xml_cbc:UBLVersionID'] || '';\nconst customizationId = alleXMLVelden['xml_cbc:CustomizationID'] || '';\nconst noticeId = alleXMLVelden['xml_cbc:ID__'] || '';\nconst issueDate = alleXMLVelden['xml_cbc:IssueDate'] || '';\nconst plannedDate = alleXMLVelden['xml_cbc:PlannedDate'] || '';\n\n// ============================================\n// NIEUW: GUNNINGSCRITERIA WEGINGEN\n// ============================================\n\nlet gunningsCriteriaText = '';\nlet criteriaArray = [];\n\nif (Array.isArray(lots) && lots.length > 0) {\n lots.forEach((lot, lotIndex) => {\n const tenderingTerms = lot?.['cac:TenderingTerms'];\n const awardingTerms = tenderingTerms?.['cac:AwardingTerms'];\n const awardingCriteria = awardingTerms?.['cac:AwardingCriterion'];\n \n if (awardingCriteria) {\n const criteriaList = Array.isArray(awardingCriteria) ? awardingCriteria : [awardingCriteria];\n const perceelNaam = lot?.['cac:ProcurementProject']?.['cbc:Name']?.['_'] || `Perceel ${lotIndex + 1}`;\n \n gunningsCriteriaText += `\\n=== ${perceelNaam} ===\\n`;\n \n criteriaList.forEach(criterion => {\n const description = criterion?.['cbc:Description']?.['_'] || '';\n const weight = criterion?.['cbc:Weight']?.['_'] || criterion?.['cbc:WeightNumeric']?.['_'] || '';\n const calculationExpression = criterion?.['cbc:CalculationExpression']?.['_'] || '';\n \n // Sub-criteria\n const subCriteria = criterion?.['cac:SubordinateAwardingCriterion'];\n \n if (description || weight) {\n const criteriaLine = weight \n ? `${description}: ${weight}%` \n : description;\n \n gunningsCriteriaText += `• ${criteriaLine}\\n`;\n \n criteriaArray.push({\n perceel: perceelNaam,\n criterium: description,\n weging: weight,\n berekening: calculationExpression\n });\n }\n \n // Process sub-criteria\n if (subCriteria) {\n const subList = Array.isArray(subCriteria) ? subCriteria : [subCriteria];\n subList.forEach(subCrit => {\n const subDesc = subCrit?.['cbc:Description']?.['_'] || '';\n const subWeight = subCrit?.['cbc:Weight']?.['_'] || subCrit?.['cbc:WeightNumeric']?.['_'] || '';\n \n if (subDesc || subWeight) {\n const subLine = subWeight \n ? `${subDesc}: ${subWeight}%` \n : subDesc;\n \n gunningsCriteriaText += ` - ${subLine}\\n`;\n \n criteriaArray.push({\n perceel: perceelNaam,\n criterium: `${description} > ${subDesc}`,\n weging: subWeight,\n parent: description\n });\n }\n });\n }\n });\n }\n });\n}\n\n// Als er geen criteria in percelen staan, zoek op hoofdniveau\nif (!gunningsCriteriaText) {\n const mainTenderingTerms = alleXMLVelden['xml_cac:TenderingTerms'];\n const mainAwardingTerms = mainTenderingTerms?.['cac:AwardingTerms'];\n const mainCriteria = mainAwardingTerms?.['cac:AwardingCriterion'];\n \n if (mainCriteria) {\n const criteriaList = Array.isArray(mainCriteria) ? mainCriteria : [mainCriteria];\n gunningsCriteriaText = '=== Gunningscriteria ===\\n';\n \n criteriaList.forEach(criterion => {\n const description = criterion?.['cbc:Description']?.['_'] || '';\n const weight = criterion?.['cbc:Weight']?.['_'] || criterion?.['cbc:WeightNumeric']?.['_'] || '';\n \n if (description || weight) {\n const criteriaLine = weight \n ? `${description}: ${weight}%` \n : description;\n \n gunningsCriteriaText += `• ${criteriaLine}\\n`;\n \n criteriaArray.push({\n criterium: description,\n weging: weight\n });\n }\n });\n }\n}\n\nconsole.log(`✅ Gunningscriteria: ${criteriaArray.length} criteria gevonden`);\n\n// ============================================\n// NIEUW: PLATFORM DETECTIE\n// ============================================\n\nlet platform = 'TenderNed'; // Default\nlet platformUrl = '';\n\n// Check de URL uit JSON detail\nif (jsonDetail?.urlTsenderWebsite) {\n platformUrl = jsonDetail.urlTsenderWebsite.toLowerCase();\n \n if (platformUrl.includes('mercell.com') || platformUrl.includes('opic.com')) {\n platform = 'Mercell';\n } else if (platformUrl.includes('negometrix.com')) {\n platform = 'Negometrix';\n } else if (platformUrl.includes('ted.europa.eu')) {\n platform = 'TED (Europa)';\n } else if (platformUrl.includes('tenderned.nl')) {\n platform = 'TenderNed';\n } else if (platformUrl.includes('pianoo.nl')) {\n platform = 'PIANOo';\n } else if (platformUrl) {\n // Onbekend platform maar wel een URL\n platform = 'Overig';\n }\n}\n\nconsole.log(`✅ Platform gedetecteerd: ${platform}`);\n\n// ============================================\n// EXTRACT CPV CODES (BESTAANDE FUNCTIONALITEIT)\n// ============================================\nconst cpvCodes = [];\nfor (const key in alleXMLVelden) {\n if (key.includes('ItemClassificationCode__') && alleXMLVelden[key]) {\n cpvCodes.push(alleXMLVelden[key]);\n }\n}\nconst uniqueCpvCodes = [...new Set(cpvCodes)];\n\n// ============================================\n// PROCESS JSON DETAIL VELDEN (BESTAAND)\n// ============================================\nlet json_trefwoorden = '-';\nlet json_hoofdOpdracht = '';\nlet json_bijkomendeOpdrachten = '';\nlet json_nutsCode = '';\n\nif (jsonDetail) {\n // Trefwoorden\n const trefwoorden = [\n jsonDetail.trefwoord1?.replace(/\\\"/g, ''),\n jsonDetail.trefwoord2?.replace(/\\\"/g, ''),\n jsonDetail.trefwoord3?.replace(/\\\"/g, '')\n ].filter(Boolean);\n json_trefwoorden = trefwoorden.length > 0 ? trefwoorden.join(', ') : '-';\n \n // CPV codes met omschrijvingen\n if (jsonDetail.cpvCodes && Array.isArray(jsonDetail.cpvCodes)) {\n const hoofdCpv = jsonDetail.cpvCodes.find(c => c.isHoofdOpdracht);\n const bijkomendeCpvs = jsonDetail.cpvCodes.filter(c => !c.isHoofdOpdracht);\n json_hoofdOpdracht = hoofdCpv ? `${hoofdCpv.code} ${hoofdCpv.omschrijving}` : '';\n json_bijkomendeOpdrachten = bijkomendeCpvs.length > 0 ? bijkomendeCpvs.map(c => `${c.code} ${c.omschrijving}`).join(', ') : '';\n }\n \n // NUTS code\n if (jsonDetail.nutsCodes && Array.isArray(jsonDetail.nutsCodes) && jsonDetail.nutsCodes.length > 0) {\n json_nutsCode = jsonDetail.nutsCodes[0].omschrijving || '';\n }\n}\n\n// ============================================\n// GET PUBLICATIE ID FOR URLs\n// ============================================\nconst publicatieId = jsonDetail?.publicatieId || publicatiesData.publicatieId || '';\nconst correctTenderNedUrl = `https://www.tenderned.nl/aankondigingen/overzicht/${publicatieId}`;\nconst originalLink = jsonDetail?.urlTsenderWebsite || correctTenderNedUrl;\n\n// ============================================\n// LOGGING\n// ============================================\nconsole.log(`✅ CPV codes: ${uniqueCpvCodes.join(', ')}`);\nconsole.log(`✅ Trefwoorden: ${json_trefwoorden}`);\nconsole.log(`✅ Beschrijving (inclusief percelen): ${cbcDescription.length} tekens`);\nconsole.log(`✅ Note: ${cbcNote.length} tekens`);\n\n// ============================================\n// RETURN ALLE VELDEN (INCLUSIEF NIEUWE)\n// ============================================\nreturn {\n json: {\n // Alle platte XML velden\n ...alleXMLVelden,\n \n // CPV codes\n cpv_codes: uniqueCpvCodes,\n cpv_main: uniqueCpvCodes[0] || null,\n cpv_all_text: uniqueCpvCodes.join(', '),\n \n // JSON detail fields (basis)\n json_publicatieId: publicatieId,\n json_kenmerk: jsonDetail?.kenmerk || null,\n json_referentieNummer: jsonDetail?.referentieNummer || '',\n json_aanbestedingNaam: jsonDetail?.aanbestedingNaam || publicatiesData.aanbestedingNaam || '',\n json_opdrachtgeverNaam: jsonDetail?.opdrachtgeverNaam || publicatiesData.opdrachtgeverNaam || '',\n json_opdrachtBeschrijving: jsonDetail?.opdrachtBeschrijving || publicatiesData.opdrachtBeschrijving || '',\n json_publicatieDatum: jsonDetail?.publicatieDatum || publicatiesData.publicatieDatum || '',\n json_sluitingsDatum: jsonDetail?.sluitingsDatum || publicatiesData.sluitingsDatum || '',\n json_pbNummerTed: jsonDetail?.pbNummerTed || '',\n json_juridischKader: jsonDetail?.juridischKaderCode?.omschrijving || '',\n json_typeOpdracht: jsonDetail?.typeOpdrachtCode?.omschrijving || '',\n json_aardOpdracht: jsonDetail?.opdrachtAardCode?.omschrijving || '',\n json_procedure: jsonDetail?.procedureCode?.omschrijving || '',\n json_europeesNationaal: jsonDetail?.nationaalOfEuropeesCode?.omschrijving || '',\n json_trefwoorden: json_trefwoorden,\n json_hoofdOpdracht: json_hoofdOpdracht,\n json_bijkomendeOpdrachten: json_bijkomendeOpdrachten,\n json_nutsCode: json_nutsCode,\n \n // FIX 1: Gecorrigeerd Note veld\n xml_cbcNote: cbcNote,\n \n // FIX 2: Gecombineerde beschrijving (hoofd + percelen)\n xml_cbcDescription: cbcDescription,\n \n // FIX 3: Geschatte waarde\n xml_geschatteWaarde: geschatteWaarde,\n xml_geschatteWaardeCurrency: geschatteWaardeCurrency,\n xml_geschatteWaardeFormatted: geschatteWaardeFormatted,\n \n // FIX 4: Deadline voor vragen\n xml_deadlineVragen: deadlineVragenFull,\n \n // FIX 5: Raamovereenkomst type\n xml_raamovereenkomstType: raamovereenkomstType,\n \n // FIX 6: Contact informatie\n xml_contactNaam: contactNaam,\n xml_contactTelefoon: contactTelefoon,\n xml_contactEmail: contactEmail,\n xml_organisatieWebsite: organisatieWebsite,\n \n // FIX 7: Review body (rechtbank)\n xml_reviewBodyNaam: reviewBodyNaam,\n xml_reviewBodyEmail: reviewBodyEmail,\n xml_reviewBodyTelefoon: reviewBodyTelefoon,\n \n // FIX 8: Gerelateerde publicaties\n json_gerelateerdePublicaties: gerelateerdePublicatiesText,\n json_gerelateerdePublicatiesIds: gerelateerdePublicatiesIds.join(', '),\n \n // FIX 9: UBL metadata\n xml_ublVersionId: ublVersionId,\n xml_customizationId: customizationId,\n xml_noticeId: noticeId,\n xml_issueDate: issueDate,\n xml_plannedDate: plannedDate,\n \n // NIEUW: Gunningscriteria wegingen\n xml_gunningsCriteria: gunningsCriteriaText,\n xml_criteriaArray: JSON.stringify(criteriaArray),\n xml_aantalCriteria: criteriaArray.length,\n \n // NIEUW: Platform detectie\n xml_platform: platform,\n xml_platformUrl: jsonDetail?.urlTsenderWebsite || '',\n \n // Perceel informatie (gestructureerd)\n percelen_aantal: perceelInfoArray.length,\n percelen_data: JSON.stringify(perceelInfoArray),\n perceel1_naam: perceelInfoArray[0]?.naam || '',\n perceel1_id: perceelInfoArray[0]?.id || '',\n perceel1_duur: perceelInfoArray[0]?.duur || '',\n perceel1_duurEenheid: perceelInfoArray[0]?.duurEenheid || '',\n perceel2_naam: perceelInfoArray[1]?.naam || '',\n perceel2_id: perceelInfoArray[1]?.id || '',\n perceel2_duur: perceelInfoArray[1]?.duur || '',\n perceel2_duurEenheid: perceelInfoArray[1]?.duurEenheid || '',\n \n // URLs\n originalLink: originalLink,\n tenderNedUrl: correctTenderNedUrl,\n \n // Timestamps\n verwerkingTimestamp: new Date().toISOString(),\n \n // Originele data backup\n originele_data_json: JSON.stringify({\n xml_data: xmlData,\n publicatie_data: publicatiesData,\n json_detail: jsonDetail,\n aggregated_raw: aggregatedData\n })\n }\n};"
},
"typeVersion": 2
},
{
"id": "cc3b434f-fbae-4e6b-8e4a-3cd59220a19f",
"name": "处理响应",
"type": "n8n-nodes-base.code",
"notes": "Extract publicaties uit API response",
"position": [
128,
976
],
"parameters": {
"jsCode": "// Verwerk de response en extract de publicaties\nconst response = $input.item.json;\n\n// Spring Boot paginated response heeft 'content' veld\nlet publicaties = [];\nif (response.content && Array.isArray(response.content)) {\n publicaties = response.content;\n} else if (Array.isArray(response)) {\n publicaties = response;\n}\n\nconsole.log(`✅ ${publicaties.length} nieuwe publicaties gevonden`);\n\nif (publicaties.length === 0) {\n return {\n json: {\n aantalPublicaties: 0,\n publicaties: [],\n bericht: 'Geen nieuwe tenders gevonden'\n }\n };\n}\n\n// Return met array voor Split node\nreturn {\n json: {\n aantalPublicaties: publicaties.length,\n publicaties: publicaties\n }\n};"
},
"notesInFlow": true,
"typeVersion": 2
},
{
"id": "aba4d103-97ef-4ead-bb1c-44a9b179deee",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-208,
-224
],
"parameters": {
"color": 6,
"width": 1280,
"height": 1056,
"content": "## TenderNed 招标抓取工作流"
},
"typeVersion": 1
},
{
"id": "b42b8cfe-b5fd-4bb3-9cee-72848ced7849",
"name": "筛选...",
"type": "n8n-nodes-base.filter",
"position": [
2944,
944
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "639f0c72-2220-4f4f-ad1f-35f14db5d89e",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.cpv_codes && Array.isArray($json.cpv_codes) && $json.cpv_codes.some(code => [\n '71000000', '71200000', '71210000', '71220000', '71221000', '71222000', '71222100', '71222200', '71223000', '71230000', '71240000', '71241000', '71242000', '71243000', '71244000', '71245000', '71246000', '71247000', '71248000', '71250000', '71251000', '71300000', '71310000', '71311000', '71311100', '71311200', '71311210', '71311220', '71311230', '71311240', '71311300', '71312000', '71313000', '71313100', '71313200', '71313400', '71313410', '71313420', '71313430', '71313440', '71313450', '71314000', '71314100', '71314200', '71314300', '71314310', '71315000', '71315100', '71315200', '71315210', '71315300', '71315400', '71315410', '71316000', '71317000', '71317100', '71317200', '71317210', '71318000', '71318100', '71319000', '71320000', '71321000', '71321100', '71321200', '71321300', '71321400', '71322000', '71322100', '71322200', '71322300', '71322400', '71322500', '71323000', '71323100', '71323200', '71324000', '71325000', '71326000', '71327000', '71328000', '71330000', '71331000', '71332000', '71333000', '71334000', '71335000', '71336000', '71337000', '71340000', '71350000', '71351000', '71351100', '71351200', '71351210', '71351220', '71351300', '71351400', '71351500', '71351600', '71351610', '71351611', '71351612', '71351700', '71351710', '71351720', '71351730', '71351800', '71351810', '71351811', '71351820', '71351900', '71351910', '71351911', '71351912', '71351913', '71351914', '71351920', '71351921', '71351922', '71351923', '71351924', '71352000', '71352100', '71352110', '71352120', '71352130', '71352140', '71352300', '71353000', '71353100', '71353200', '71354000', '71354100', '71354200', '71354300', '71354400', '71354500', '71355000', '71355100', '71355200', '71356000', '71356100', '71356200', '71356300', '71356400', '71400000', '71410000', '71420000', '71421000', '71500000', '71510000', '71520000', '71521000', '71530000', '71540000', '71541000', '71550000', '71600000', '71610000', '71620000', '71621000', '71630000', '71631000', '71631100', '71631200', '71631300', '71631400', '71631420', '71631430', '71631440', '71631450', '71631460', '71631470', '71631480', '71631490', '71632000', '71632100', '71632200', '71700000', '71730000', '71731000', '70100000', '70110000', '70120000'\n].includes(code)) }}",
"rightValue": "={{ $json.cpv_codes.some(code => ['71000000-8', '71200000-0', '71210000-3'].includes(code)) }}"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e4a9f68f-ecca-4dc7-acde-da3becc2e716",
"name": "插入行",
"type": "n8n-nodes-base.dataTable",
"position": [
3456,
944
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"dataTableId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 1
},
{
"id": "1eb5c070-8fc3-479f-99f9-e9d0899ed471",
"name": "Tenderned 发布",
"type": "n8n-nodes-base.httpRequest",
"notes": "FILTERT DIRECT IN API: alleen laatste 24 uur!",
"position": [
-208,
976
],
"parameters": {
"url": "https://www.tenderned.nl/papi/tenderned-rs-tns/v2/publicaties",
"options": {
"timeout": 30000
},
"sendQuery": true,
"authentication": "genericCredentialType",
"genericAuthType": "httpBasicAuth",
"queryParameters": {
"parameters": [
{
"name": "publicatieDatumVanaf",
"value": "=2025-01-01"
},
{
"name": "size",
"value": "100"
},
{
"name": "publicatieType",
"value": "VAK"
}
]
}
},
"notesInFlow": true,
"typeVersion": 4.2
},
{
"id": "a0a5cde9-e8ee-48ab-ac14-22ed71d0f181",
"name": "定时触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-560,
976
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.2
},
{
"id": "78c96a84-7b35-4e93-b6c8-f59ad8684067",
"name": "计划信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
1136
],
"parameters": {
"color": 5,
"width": 300,
"height": 368,
"content": "## 计划触发器"
},
"typeVersion": 1
},
{
"id": "d84a3615-f414-49d5-baee-b58b561c9c8c",
"name": "API 获取信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
-320,
1136
],
"parameters": {
"color": 5,
"width": 344,
"height": 340,
"content": "## 获取招标发布"
},
"typeVersion": 1
},
{
"id": "c6ce7ebc-3b35-45ab-bb99-3a9090f123cc",
"name": "处理响应信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
48,
1136
],
"parameters": {
"color": 7,
"width": 288,
"height": 324,
"content": "## 处理 API 响应"
},
"typeVersion": 1
},
{
"id": "18844cf6-b38e-4743-9af1-657168b75684",
"name": "拆分信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
2464,
1152
],
"parameters": {
"color": 7,
"width": 320,
"height": 292,
"content": "## 拆分为单个招标"
},
"typeVersion": 1
},
{
"id": "9771a8c2-ee4c-4241-8d7f-6fcbd51d7db2",
"name": "循环信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
1136
],
"parameters": {
"color": 7,
"width": 340,
"height": 328,
"content": "## 循环处理项目(速率限制)"
},
"typeVersion": 1
},
{
"id": "853deb2c-0cb4-41ef-b0a4-9d50ea901023",
"name": "JSON 详情信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
1248,
288
],
"parameters": {
"color": 5,
"width": 340,
"height": 392,
"content": "## 获取 JSON 详情"
},
"typeVersion": 1
},
{
"id": "b0681ea1-7fb4-4c8a-89dc-762efcc26628",
"name": "XML 详情信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
1152
],
"parameters": {
"color": 5,
"width": 340,
"height": 364,
"content": "## 获取 XML 详情"
},
"typeVersion": 1
},
{
"id": "4efb2c89-67c7-4267-8aad-89563ccc490b",
"name": "XML 解析器信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
1472,
1152
],
"parameters": {
"color": 7,
"width": 252,
"height": 288,
"content": "## 解析 XML 为 JSON"
},
"typeVersion": 1
},
{
"id": "3336713a-b66b-44c5-8535-ec5867913802",
"name": "合并信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
1744,
1152
],
"parameters": {
"color": 7,
"width": 340,
"height": 300,
"content": "## 合并所有数据"
},
"typeVersion": 1
},
{
"id": "fea0a1ef-83d5-4c59-90c2-f0a51c11d4b5",
"name": "聚合信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
2112,
1152
],
"parameters": {
"color": 7,
"width": 320,
"height": 260,
"content": "## 聚合循环结果"
},
"typeVersion": 1
},
{
"id": "9e2778bb-853f-459e-be8e-6eecce467ba6",
"name": "拆分以筛选信息",
"type": "n8n-nodes-base.stickyNote",
"position": [
352,
1136
],
"parameters": {
"color": 7,
"width": 272,
"height": 324,
"content": "## 拆分以进行筛选"
},
"typeVersion": 1
},
{
"id": "4cf90e94-5a43-4c0e-bad0-0ec8ccc1cf63",
"name": "筛选配置",
"type": "n8n-nodes-base.stickyNote",
"position": [
2832,
1152
],
"parameters": {
"color": 3,
"width": 360,
"height": 360,
"content": "## 筛选招标"
},
"typeVersion": 1
},
{
"id": "a3c38818-7340-4886-a5c2-44c3d422f2f7",
"name": "数据库设置",
"type": "n8n-nodes-base.stickyNote",
"position": [
3296,
1152
],
"parameters": {
"color": 4,
"width": 376,
"height": 360,
"content": "## 保存到数据表"
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"XML": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
},
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Splits Alle Velden",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Filter op ...": {
"main": [
[
{
"node": "Insert row",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[],
[
{
"node": "Haal XML Details",
"type": "main",
"index": 0
},
{
"node": "Haal JSON Details",
"type": "main",
"index": 0
},
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Haal XML Details": {
"main": [
[
{
"node": "XML",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Tenderned Publicaties",
"type": "main",
"index": 0
}
]
]
},
"Verwerk Response": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"Haal JSON Details": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Splits Alle Velden": {
"main": [
[
{
"node": "Filter op ...",
"type": "main",
"index": 0
}
]
]
},
"Tenderned Publicaties": {
"main": [
[
{
"node": "Verwerk Response",
"type": "main",
"index": 0
}
]
]
},
"When clicking ‘Execute workflow’": {
"main": [
[
{
"node": "Tenderned Publicaties",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 市场调研
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
源发现 - 自动搜索更及时的信息源
多平台源发现系统,集成 SerpAPI、DuckDuckGo、GitHub、Reddit 和 Bluesky
Set
Code
Limit
+18
68 节点Hybroht
市场调研
(Duc)深度研究市场模板
集成PerplexityAI研究和OpenAI内容的多层级WordPress博客生成器
If
Set
Xml
+28
132 节点Daniel Ng
人工智能
灵活新闻聚合器 - 多源集成、AI分析和可设置频道
多源新闻策展系统,集成Mistral AI分析、摘要和自定义频道
If
Set
Xml
+32
120 节点Hybroht
内容创作
使用 Bright Data API 和 AI 抓取分析 Google 广告并发送邮件报告
使用 Bright Data API 和 AI 抓取分析 Google 广告并发送邮件报告
Set
Code
Gmail
+15
45 节点Zacharia Kimotho
市场调研
WordPress博客自动化专业版(深度研究)v2.1市场
使用GPT-4o、Perplexity AI和多语言支持自动化SEO优化的博客创建
If
Set
Xml
+27
125 节点Daniel Ng
内容创作
内容聚合
使用Gemini AI从网站文章自动化社交媒体帖子发布到LinkedIn和X/Twitter
If
Set
Xml
+16
34 节点Vadim
内容创作
工作流信息
难度等级
高级
节点数量28
分类1
节点类型12
作者
Wessel Bulte
@uuesselCybersecurity and automation consultant specializing in n8n workflows for GDPR compliance, process optimization, and business integration. Helping teams streamline operations with secure, scalable automation solutions.
外部链接
在 n8n.io 查看 →
分享此工作流