{
  "version": "1.0.0",
  "exported_at": "2026-06-02T00:00:00.000Z",
  "project": {
    "name": "eBay Product Details Scraper for Germany",
    "description": "Scrapes public eBay.de product detail pages from a list of detailed item URLs. Extracts product title, condition, price, shipping, item location, delivery, returns, and condition detail text. Navigation uses navigate.urls[] plus loop-continue so multiple eBay.de detail URLs can be processed into one appended CSV. This is a detail-page scraper, not a paginated listing scraper. Best-effort selectors are used because eBay may vary layout, show cookie banners, hide duplicate product-title nodes, or trigger bot/CAPTCHA checks.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.ebay.de/itm/395231391077?itmmeta=01HVT5QAK0FWPAQ7BMR995A882&hash=item5c05a06d65:g:PgMAAOSwU8Vkygls&itmprp=enc%3AAQAJAAAAwE4CVYb209eEElMV0VxodbQmG%2BaT%2FxW6kWw%2BGyQw9b09ZdKlXL3G7DBLTDSe%2F4WkY%2B20kcIdeg7zRgRp13gK%2FQAYJVN5%2FIjY6djYgMCU2H8%2B8MxDlQHs21xXGYAuNkwZ2fNiltL4uJ6g4dxQeTmSWttwr4Dj0I5TVAUMN7Rab83J%2Bn6WqL4vzLg6AG5d9IGmNDQ9UolMao79u%2FSyClu6cA0gAWtXFiWYbDKI4OtgBpvM%2B9TrcVvOnZ0o2eiF8AaNrA%3D%3D%7Ctkp%3ABk9SR5aq3cXeYw"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "ebay.de",
          "detail-urls",
          "multi-url"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 480,
      "position_y": 260,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 840,
      "position_y": 260,
      "config": {
        "selector": "#gdpr-banner-accept, button#gdpr-banner-accept, button[data-testid=\"gdpr-banner-accept\"], button[aria-label=\"Alle akzeptieren\"], button[aria-label=\"Accept\"]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 1200,
      "position_y": 100,
      "config": {
        "selector": "#gdpr-banner-accept, button#gdpr-banner-accept, button[data-testid=\"gdpr-banner-accept\"], button[aria-label=\"Alle akzeptieren\"], button[aria-label=\"Accept\"]",
        "timeout": 10
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1560,
      "position_y": 100,
      "config": {
        "duration": 2
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1920,
      "position_y": 580,
      "config": {
        "selector": "h1.x-item-title__mainTitle, h1, .x-price-primary",
        "timeout": 30,
        "visible": false
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2280,
      "position_y": 580,
      "config": {
        "rowSelector": "body",
        "fileName": "ebay-produkt-details-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "produkt_titel",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const selectors=['h1.x-item-title__mainTitle span','h1.x-item-title__mainTitle','h1 span[itemprop=\"name\"]','h1']; for (const sel of selectors){ const els=[...ROW.querySelectorAll(sel)]; for (const e of els){ const v=norm(txt(e).replace(/^Details zu\\s*/i,'')); if(v && v.length>3) return v; } } return norm(document.title.replace(/\\|\\s*eBay.*$/i,'')); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "artikelzustand",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const canon=s=>{ const t=norm((s||'').replace(/^Artikelzustand:?\\s*/i,'')); if(/Nicht funktionsfähig/i.test(t)) return 'Nicht funktionsfähig'; if(/Als Ersatzteil/i.test(t)) return 'Als Ersatzteil'; if(/Akzeptabel/i.test(t)) return 'Akzeptabel'; if(/Sehr gut/i.test(t)) return 'Sehr gut'; if(/Gebraucht/i.test(t)) return 'Gebraucht'; if(/\\bGut\\b/i.test(t)) return 'Gut'; if(/NeuNeu|\\bNeu\\b|Neu:/i.test(t)) return 'Neu'; return ''; }; const selectors=['.x-item-condition-text .ux-textspans','.x-item-condition-text span','[data-testid=\"x-item-condition\"] span','.ux-section__item']; for (const sel of selectors){ for (const e of ROW.querySelectorAll(sel)){ const v=canon(txt(e)); if(v) return v; } } const blocks=[...ROW.querySelectorAll('.ux-labels-values, [class*=\"ux-labels-values\"]')]; for (const b of blocks){ const label=norm(txt(b.querySelector('.ux-labels-values__labels-content, [class*=\"labels-content\"]'))); if (/Artikelzustand|Zustand/i.test(label)) { const v=canon(txt(b.querySelector('.ux-labels-values__values-content, [class*=\"values-content\"]')) || txt(b)); if(v) return v; } } return canon(txt(ROW)); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "preis",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||e.getAttribute('content')||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const selectors=['.x-price-primary span.ux-textspans','.x-price-primary span','[data-testid=\"x-price-primary\"] span','[itemprop=\"price\"]']; for (const sel of selectors){ const els=[...ROW.querySelectorAll(sel)]; for (const e of els){ const v=norm(txt(e)); if(v && /€|EUR|\\d/.test(v)) return v; } } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "versand",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const cleanup=s=>norm((s||'').replace(/Weitere Details\\s*für Versand/ig,'').replace(/für VersandStandort:/i,' Standort:').replace(/Standort:\\s*.*$/i,'').replace(/Lieferung:\\s*.*$/i,'')); const blocks=[...ROW.querySelectorAll('.ux-labels-values, [class*=\"ux-labels-values\"]')]; for (const b of blocks){ const label=norm(txt(b.querySelector('.ux-labels-values__labels-content, [class*=\"labels-content\"]'))); if (/Versand/i.test(label)) { const v=cleanup(txt(b.querySelector('.ux-labels-values__values-content, [class*=\"values-content\"]')) || txt(b)); if(v) return v; } } const all=norm(txt(ROW)).replace(/für VersandStandort:/i,' Standort:'); const m=all.match(/(Ggf\\. kein Versand[^.]*\\.|Kein Versand[^.]*\\.|Kostenloser Versand[^.]*\\.|EUR\\s*[\\d,.]+\\s*Versand[^.]*\\.)/i); if(m) return cleanup(m[1]); return cleanup(txt(ROW.querySelector('[data-testid=\"ux-labels-values-shipping\"]'))); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "standort_in",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const clean=s=>norm((s||'').replace(/^Standort:?\\s*/i,'').replace(/^(Artikelstandort|Standort in):?\\s*/i,'')); const blocks=[...ROW.querySelectorAll('.ux-labels-values, [class*=\"ux-labels-values\"]')]; for (const b of blocks){ const label=norm(txt(b.querySelector('.ux-labels-values__labels-content, [class*=\"labels-content\"]'))); const value=norm(txt(b.querySelector('.ux-labels-values__values-content, [class*=\"values-content\"]')) || txt(b)); if (/Artikelstandort|Standort|Standort in/i.test(label)) { const v=clean(value); if(v) return v; } const embedded=value.replace(/für VersandStandort:/i,' Standort:'); const m=embedded.match(/Standort:\\s*(.*?)(?:\\s*(?:Lieferung|Rücknahmen?|Rückgabe|Zahlungen|Artikelmerkmale):|$)/i); if(m && clean(m[1])) return clean(m[1]); } const all=norm(txt(ROW)).replace(/für VersandStandort:/i,' Standort:'); const m=all.match(/Standort:\\s*(.*?)(?:\\s*(?:Lieferung|Rücknahmen?|Rückgabe|Zahlungen|Artikelmerkmale):|$)/i); return m ? clean(m[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "lieferung",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const clean=s=>norm((s||'').replace(/^Lieferung:?\\s*/i,'')); const blocks=[...ROW.querySelectorAll('.ux-labels-values, [class*=\"ux-labels-values\"]')]; for (const b of blocks){ const label=norm(txt(b.querySelector('.ux-labels-values__labels-content, [class*=\"labels-content\"]'))); if (/Lieferung|Lieferdatum|Voraussichtliche Lieferung|Lieferung erfolgt/i.test(label)) { const v=clean(txt(b.querySelector('.ux-labels-values__values-content, [class*=\"values-content\"]'))); if(v) return v; } } const all=norm(txt(ROW)); const m=all.match(/Lieferung:?\\s*(.*?)(?:\\s*(?:Rücknahmen?|Rückgabe|Zahlungen|Artikelmerkmale):|$)/i); if(m && clean(m[1]).length < 250) return clean(m[1]); const delivery=clean(txt(ROW.querySelector('[data-testid=\"ux-labels-values-delivery\"]'))); return delivery; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "ruecknahmen",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const clean=s=>norm((s||'').replace(/^Rücknahmen?:?\\s*/i,'').replace(/Informationen zum Widerrufsrecht.*$/i,'').replace(/- Informationen zu Rückgaben.*$/i,'')); const blocks=[...ROW.querySelectorAll('.ux-labels-values, [class*=\"ux-labels-values\"]')]; for (const b of blocks){ const label=norm(txt(b.querySelector('.ux-labels-values__labels-content, [class*=\"labels-content\"]'))); if (/Rücknahme|Rückgabe|Rücknahmen|Returns/i.test(label)) { const v=clean(txt(b.querySelector('.ux-labels-values__values-content, [class*=\"values-content\"]'))); if(v) return v; } } const all=norm(txt(ROW)); const m=all.match(/(Keine Rückgabe\\.|Keine Rücknahme\\.|\\d+\\s*(?:Tage|Monat|Monate)\\s*Rückgabe[^.]*\\.)/i); if(m) return clean(m[1]); const ret=clean(txt(ROW.querySelector('[data-testid=\"ux-labels-values-returns\"]'))); return ret; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "detail_des_artikelzustandes",
            "selector": "(() => { const txt=e=>(e?(e.innerText||e.textContent||''):''); const norm=s=>(s||'').replace(/\\s+/g,' ').trim(); const cleanup=s=>{ let v=norm(s||''); if(/Über den Zustand/i.test(v)) v=v.split(/Über den Zustand/i).pop(); v=v.replace(/Alle Zustandsdefinitionen.*$/i,'').replace(/wird in neuem Fenster.*$/i,'').replace(/^Artikelzustand:?\\s*/i,''); return norm(v); }; const isCond=t=>/^(Neu|Gebraucht|Sehr gut|Gut|Akzeptabel|Als Ersatzteil|Nicht funktionsfähig):/i.test(t); const candidates=[]; const els=[...ROW.querySelectorAll('.x-item-condition-text, [data-testid=\"x-item-condition\"], .ux-section__item, .ux-textspans, span, div')]; for (const e of els){ const v=cleanup(txt(e)); if(isCond(v) && v.length>20) candidates.push(v); } const all=norm(txt(ROW)); for (const part of all.split(/Über den Zustand/i)){ const m=part.match(/(Neu|Gebraucht|Sehr gut|Gut|Akzeptabel|Als Ersatzteil|Nicht funktionsfähig):\\s*.*?(?=(?:Alle Zustandsdefinitionen|wird in neuem Fenster|$))/i); if(m){ const v=cleanup(m[0]); if(isCond(v) && v.length>20) candidates.push(v); } } candidates.sort((a,b)=>b.length-a.length); return candidates[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2640,
      "position_y": 580,
      "config": {
        "duration": 2
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 580,
      "config": {}
    }
  ],
  "connections": [
    {
      "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": "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-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "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": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": -4,
      "width": 2840,
      "height": 776,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 768,
      "position_y": -4,
      "width": 2480,
      "height": 776,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "click-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2208,
      "position_y": 476,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes public eBay.de product detail pages from a list of detailed item URLs. Extracts product title, condition, price, shipping, item location, delivery, returns, and condition detail text. Navigation uses navigate.urls[] plus loop-continue so multiple eBay.de detail URLs can be processed into one appended CSV. This is a detail-page scraper, not a paginated listing scraper. Best-effort selectors are used because eBay may vary layout, show cookie banners, hide duplicate product-title nodes, or trigger bot/CAPTCHA checks.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `#gdpr-banner-accept, button#gdpr-banner-accept, button[data-testid=\"gdpr-banner-accept\"], button[aria-label=\"Alle akzept`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1040,
      "position_y": 240,
      "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 (produkt_titel, artikelzustand, preis, versand, standort_in). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 560,
      "width": 340,
      "height": 133,
      "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": 3200,
      "position_y": 560,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}