{
  "version": "1.0.0",
  "exported_at": "2026-06-01T05:10:00.000Z",
  "project": {
    "name": "Bol Scraper Product Details",
    "description": "Scrapes Bol.com product detail pages by URL and exports title, brand, price, image URLs, delivery text, description, specifications, and page URL. Navigation uses a multi-URL loop: add product page URLs to navigate.urls[] and the template appends each result to bol-scraper-product-details.csv. If Bol.com blocks the current IP, the template writes a diagnostic row instead of scraping product details; use a trusted residential/proxy IP or logged-in browser profile for real product data.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "set-window-size-1",
      "block_type": "process",
      "title": "Set Window Size",
      "description": "Set browser window dimensions",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 456,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.bol.com/nl/nl/p/safio-xv-7-elektrische-eeltverwijderaar-led-verichting-professionele-voetvijl-eeltrasp-eeltvijl-eelt-verwijderaar-12-delige-pedicure-set-likdoorn-eksteroog-ipx7-waterbestendig-oplaadbaar-eeltverwijderaars/9300000119197160/"
        ],
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "text-contains-1",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 1464,
      "position_y": 220,
      "config": {
        "text": "IP address",
        "selector": "body",
        "caseSensitive": false,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-2",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1800,
      "position_y": 520,
      "config": {
        "rowSelector": "body",
        "fileName": "bol-scraper-product-details.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#da1e28]",
        "columns": [
          {
            "name": "title",
            "selector": "'BLOCKED_BY_BOL_IP'",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "brand",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_urls",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "delivery",
            "selector": "''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "description",
            "selector": "String(ROW.ownerDocument.body?.innerText || 'Bol.com blocked this IP address. Use a proxy/trusted IP to scrape product details.').replace(/\\s+/g,' ').trim()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "specifications",
            "selector": "'Scraping blocked by Bol.com IP protection; product fields unavailable in this run.'",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "page_url",
            "selector": "ROW.ownerDocument.location.href",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1464,
      "position_y": 520,
      "config": {
        "selector": "#js-first-screen-accept-all-button, button[data-test='consent-modal-confirm-button'], button[data-test='cookie-accept-all'], button[aria-label*='Accepteren'], button[aria-label*='Accept']",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 1968,
      "position_y": 800,
      "config": {
        "selector": "#js-first-screen-accept-all-button, button[data-test='consent-modal-confirm-button'], button[data-test='cookie-accept-all'], button[aria-label*='Accepteren'], button[aria-label*='Accept']",
        "timeout": 8,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2304,
      "position_y": 800,
      "config": {
        "duration": 1,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2640,
      "position_y": 800,
      "config": {
        "selector": "body",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2976,
      "position_y": 800,
      "config": {
        "rowSelector": "body",
        "fileName": "bol-scraper-product-details.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "title",
            "selector": "(() => { const d=ROW.ownerDocument; const json=Array.from(d.querySelectorAll('script[type=\"application/ld+json\"]')).flatMap(s=>{try{const v=JSON.parse(s.textContent||'');return Array.isArray(v)?v:[v];}catch(e){return[];}}); const p=json.find(x=>x&&((x['@type']==='Product')||(Array.isArray(x['@type'])&&x['@type'].includes('Product')))); return String((p&&p.name)||d.querySelector('h1[data-test=\"title\"], h1[data-test=\"product-title\"], h1')?.textContent||'').replace(/\\s+/g,' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "brand",
            "selector": "(() => { const d=ROW.ownerDocument; const json=Array.from(d.querySelectorAll('script[type=\"application/ld+json\"]')).flatMap(s=>{try{const v=JSON.parse(s.textContent||'');return Array.isArray(v)?v:[v];}catch(e){return[];}}); const p=json.find(x=>x&&((x['@type']==='Product')||(Array.isArray(x['@type'])&&x['@type'].includes('Product')))); const b=p&&p.brand; return String((b&&(b.name||b))||d.querySelector('[data-test=\"brand\"], a[data-test*=\"brand\"], [class*=\"brand\"]')?.textContent||'').replace(/\\s+/g,' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "(() => { const d=ROW.ownerDocument; const json=Array.from(d.querySelectorAll('script[type=\"application/ld+json\"]')).flatMap(s=>{try{const v=JSON.parse(s.textContent||'');return Array.isArray(v)?v:[v];}catch(e){return[];}}); const p=json.find(x=>x&&((x['@type']==='Product')||(Array.isArray(x['@type'])&&x['@type'].includes('Product')))); const o=p&&p.offers; let price=Array.isArray(o)?o[0]?.price:o?.price; if(!price){ price=d.querySelector('[data-test*=\"price\"], .price-block__price, [class*=\"price\"]')?.textContent||''; } return String(price).replace(/€|\\s/g,'').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_urls",
            "selector": "(() => { const d=ROW.ownerDocument; const json=Array.from(d.querySelectorAll('script[type=\"application/ld+json\"]')).flatMap(s=>{try{const v=JSON.parse(s.textContent||'');return Array.isArray(v)?v:[v];}catch(e){return[];}}); const p=json.find(x=>x&&((x['@type']==='Product')||(Array.isArray(x['@type'])&&x['@type'].includes('Product')))); let imgs=[]; if(p&&p.image){ imgs=Array.isArray(p.image)?p.image:[p.image]; } imgs=imgs.concat(Array.from(d.querySelectorAll('meta[property=\"og:image\"], meta[name=\"twitter:image\"]')).map(m=>m.content)); imgs=imgs.concat(Array.from(d.querySelectorAll('img[src*=\"s-bol.com\"], img[srcset*=\"s-bol.com\"]')).map(i=>i.currentSrc||i.src)); if(!imgs.length){ imgs=Array.from(d.images).map(i=>i.currentSrc||i.src).filter(Boolean); } return Array.from(new Set(imgs.filter(Boolean))).join('\\n'); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "delivery",
            "selector": "(() => { const d=ROW.ownerDocument; const els=Array.from(d.querySelectorAll('[data-test*=\"delivery\"], [data-test*=\"availability\"], [class*=\"delivery\"], [class*=\"availability\"]')); const vals=els.map(e=>String(e.textContent||'').replace(/\\s+/g,' ').trim()).filter(Boolean); return vals.find(t=>/(besteld|morgen|geleverd|bezorgd|huis|delivery|available|op voorraad)/i.test(t)) || vals[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "description",
            "selector": "(() => { const d=ROW.ownerDocument; const json=Array.from(d.querySelectorAll('script[type=\"application/ld+json\"]')).flatMap(s=>{try{const v=JSON.parse(s.textContent||'');return Array.isArray(v)?v:[v];}catch(e){return[];}}); const p=json.find(x=>x&&((x['@type']==='Product')||(Array.isArray(x['@type'])&&x['@type'].includes('Product')))); const dom=d.querySelector('[data-test=\"product-description\"], [data-test=\"description\"], #product-description, .product-description, [class*=\"description\"]'); return String((p&&p.description)||dom?.textContent||'').replace(/\\s+/g,' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "specifications",
            "selector": "(() => { const d=ROW.ownerDocument; const candidates=Array.from(d.querySelectorAll('[data-test*=\"specification\"], #specifications, section')); const root=candidates.find(el=>/(Specificaties|Productinformatie|EAN|Kenmerken)/i.test(el.textContent||'')) || null; if(!root) return ''; const pairs=[]; root.querySelectorAll('dt').forEach(dt=>{const dd=dt.nextElementSibling; const k=String(dt.textContent||'').replace(/\\s+/g,' ').trim(); const v=String(dd?.textContent||'').replace(/\\s+/g,' ').trim(); if(k||v) pairs.push(k+' : '+v);}); if(!pairs.length){ root.querySelectorAll('tr').forEach(tr=>{const cells=Array.from(tr.querySelectorAll('th,td')).map(c=>String(c.textContent||'').replace(/\\s+/g,' ').trim()).filter(Boolean); if(cells.length) pairs.push(cells.join(' : '));}); } return pairs.length ? pairs.join('\\n') : String(root.textContent||'').replace(/\\s+/g,' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "page_url",
            "selector": "ROW.ownerDocument.location.href",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "color": "bg-[#ff832b]"
      }
    }
  ],
  "connections": [
    {
      "from_block_id": "set-window-size-1",
      "from_connector_id": "right",
      "to_block_id": "navigate-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "text-contains-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "true",
      "to_block_id": "structured-export-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-2",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "false",
      "to_block_id": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-entry",
      "element_type": "group",
      "title": "Entry & Setup",
      "color": "#4589ff",
      "position_x": 48,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "set-window-size-1"
        ]
      }
    },
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 384,
      "position_y": 116,
      "width": 2504,
      "height": 876,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1392,
      "position_y": 116,
      "width": 992,
      "height": 876,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "text-contains-1",
          "element-exists-1",
          "click-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1728,
      "position_y": 416,
      "width": 1496,
      "height": 576,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-2",
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Bol.com product detail pages by URL and exports title, brand, price, image URLs, delivery text, description, specifications, and page URL. Navigation uses a multi-URL loop: add product page URLs to navigate.urls[] and the template appends each result to bol-scraper-product-details.csv. If Bol.com blocks the current IP, the template writes a diagnostic row instead of scraping product details; use a trusted residential/proxy IP or logged-in browser profile for real product data.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 1 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 656,
      "position_y": 200,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-text-contains-1",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `body`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1664,
      "position_y": 200,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-1"
      }
    },
    {
      "id": "note-block-structured-export-2",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (title, brand, price, image_urls, delivery). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2000,
      "position_y": 500,
      "width": 340,
      "height": 127,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-2"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `#js-first-screen-accept-all-button, button[data-test='consent-modal-confirm-button'], button[data-test='cookie-accept-al`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1664,
      "position_y": 500,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (title, brand, price, image_urls, delivery). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 3176,
      "position_y": 780,
      "width": 340,
      "height": 127,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 2336,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}