{
  "version": "1.0.0",
  "exported_at": "2026-05-31T21:40:00.000Z",
  "project": {
    "name": "Das Telefonbuch Lead Scraper",
    "description": "Scrapes lead data from Das Telefonbuch detail URLs under adresse.dastelefonbuch.de. The workflow uses multi-URL navigation with loop-continue so every URL supplied in navigate.urls is visited and appended to das-telefonbuch-lead-scraper.csv. Extracted fields mirror the Octoparse template: company/person category, city, source URL, company name, address, phone, official website, industry and opening hours. A best-effort JavaScript step dismisses consent prompts and clicks phone-number reveal controls before extraction. This template processes all supplied detail URLs; replace or extend navigate.urls for more records.",
    "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": 220,
      "config": {
        "urls": [
          "https://adresse.dastelefonbuch.de/Berlin/1-Apparatebau-BORSIG-GmbH-Berlin-Egellsstr.html",
          "https://adresse.dastelefonbuch.de/Berlin/1-Maschinenbau-KAPP-NILES-GmbH-Co-KG-Berlin-Nordring.html"
        ],
        "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": 456,
      "position_y": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const clicked = []; const all = Array.from(document.querySelectorAll('button, a, input[type=\"button\"], input[type=\"submit\"], [role=\"button\"]')); const consentWords = ['Akzeptieren', 'Alle akzeptieren', 'Einverstanden', 'Zustimmen', 'Accept']; const consent = all.find(el => consentWords.some(t => norm(el.innerText || el.value || el.getAttribute('aria-label')).toLowerCase().includes(t.toLowerCase()))); if (consent) { try { consent.click(); clicked.push('consent'); } catch(e) {} } const phoneWords = ['Telefonnummer anzeigen', 'Nummer anzeigen', 'Telefon anzeigen', 'Rufnummer anzeigen']; const phoneButtons = all.filter(el => phoneWords.some(t => norm(el.innerText || el.value || el.getAttribute('aria-label') || el.title).toLowerCase().includes(t.toLowerCase()))); phoneButtons.slice(0, 5).forEach(el => { try { el.click(); clicked.push('phone'); } catch(e) {} }); return clicked.join(', ') || 'no consent or phone reveal controls clicked'; })();",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1128,
      "position_y": 220,
      "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": 1464,
      "position_y": 220,
      "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": 1800,
      "position_y": 220,
      "config": {
        "rowSelector": "body",
        "fileName": "das-telefonbuch-lead-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "firma_oder_person",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const path = decodeURIComponent(location.pathname); const m = path.match(/\\/\\d+-([^-]+)-/); if (m) return m[1].replace(/-/g, ' ').trim(); const el = ROW.querySelector('[itemprop=\"category\"], [class*=\"branche\" i], [class*=\"category\" i]'); return norm(el?.textContent).replace(/^Eintrag gefunden unter:\\s*/i, '').replace(/\\s+Berlin$/i, ''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "ort",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const locality = norm(ROW.querySelector('[itemprop=\"addressLocality\"]')?.textContent); if (locality) return locality; const path = decodeURIComponent(location.pathname); const m = path.match(/^\\/([^/]+)\\//); return m ? m[1].replace(/-/g, ' ').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "unternehmen_url",
            "selector": "location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "name_des_unternehmens",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const bySchema = norm(ROW.querySelector('[itemprop=\"name\"]')?.textContent); if (bySchema) return bySchema; const h1 = norm(ROW.querySelector('h1')?.textContent); if (h1) return h1; return norm(document.title.replace(/\\s*\\|.*$/g, '').replace(/Das Telefonbuch.*/i, '')); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "adresse",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const text = ROW.innerText || ''; const re = /([A-ZÄÖÜ][A-Za-zÄÖÜäöüß.\\- ]+?\\s+\\d+[A-Za-z]?)\\s*,?\\s*(\\d{5})\\s+([A-ZÄÖÜ][A-Za-zÄÖÜäöüß \\-]+)/; const match = text.match(re); if (match) return norm(match[1] + ', ' + match[2] + ', ' + match[3]); const street = norm(ROW.querySelector('[itemprop=\"streetAddress\"]')?.textContent); const zip = norm(ROW.querySelector('[itemprop=\"postalCode\"]')?.textContent); const city = norm(ROW.querySelector('[itemprop=\"addressLocality\"]')?.textContent); const schemaAddress = [street, zip, city].filter(Boolean).join(', '); if (schemaAddress) return schemaAddress; const addressEl = Array.from(ROW.querySelectorAll('address, [class]')).find(e => /adress|address|anschrift/i.test(String(e.className || e.tagName))); return addressEl ? norm(addressEl.textContent) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "telefonnummer",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const compact = v => norm(v).replace(/^tel:/i, '').replace(/\\(0\\)/g, '0').replace(/[^+0-9]/g, ''); const values = []; ROW.querySelectorAll('[itemprop=\"telephone\"], a[href^=\"tel:\"], [class*=\"phone\" i], [class*=\"telefon\" i], [class*=\"rufnummer\" i], [data-phone], [data-tel]').forEach(el => { values.push(el.textContent || ''); values.push(el.getAttribute('href') || ''); Array.from(el.attributes || []).forEach(a => { if (/tel|phone|rufnummer|telefon/i.test(a.name + ' ' + a.value)) values.push(a.value); }); }); Array.from(ROW.querySelectorAll('*')).forEach(el => Array.from(el.attributes || []).forEach(a => { if (/tel|phone|rufnummer|telefon/i.test(a.name)) values.push(a.value); })); values.push(ROW.innerText || ''); const all = values.join('\\n'); const matches = all.match(/(?:\\+49|0049|0)[\\d\\s\\/().-]{6,}\\d/g) || []; const found = matches.map(compact).find(v => v.replace(/\\D/g, '').length >= 7 && v.replace(/\\D/g, '').length <= 16); return found || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "offizielle_website",
            "selector": "(() => { const links = Array.from(ROW.querySelectorAll('a[href]')).map(a => a.href).filter(Boolean); const direct = links.find(h => { try { const u = new URL(h); return /^https?:$/i.test(u.protocol) && !/dastelefonbuch\\.de/i.test(u.hostname) && !/google|facebook|twitter|instagram|linkedin|youtube/i.test(u.hostname); } catch(e) { return false; } }); if (direct) return direct; const redirect = links.find(h => /weiterleitung|redirect|url=|target=|href=/i.test(h)); if (redirect) { try { const u = new URL(redirect); for (const p of ['url', 'u', 'target', 'href', 'website']) { const v = u.searchParams.get(p); if (v && /^https?:/i.test(v)) return decodeURIComponent(v); } } catch(e) {} } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "branche",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const blacklist = /Rückwärts|Rueckwaerts|Branchen-Suche|Städte-Suche|Staedte-Suche|Telefonbuch|Bewertung|Route|E-Mail|Website|Webseite|Startseite|Suche|Impressum|Datenschutz|Kontakt/i; const city = (() => { const m = decodeURIComponent(location.pathname).match(/^\\/([^/]+)\\//); return m ? m[1].replace(/-/g, ' ') : ''; })(); const clean = v => norm(v).replace(/^Eintrag gefunden unter:\\s*/i, '').replace(new RegExp('\\\\s+' + city + '$', 'i'), '').replace(/\\s+in\\s+.+$/i, '').trim(); const values = []; const path = decodeURIComponent(location.pathname); const pathMatch = path.match(/\\/\\d+-([^-]+)-/); if (pathMatch) values.push(pathMatch[1].replace(/-/g, ' ').trim()); ROW.querySelectorAll('[itemprop=\"category\"], [class*=\"branche\" i], [class*=\"category\" i]').forEach(e => { clean(e.textContent).split(/[,;|]/).map(clean).forEach(v => { if (v && !blacklist.test(v) && v.length < 60) values.push(v); }); }); const industryLinks = Array.from(ROW.querySelectorAll('main a[href], article a[href], [role=\"main\"] a[href]')).filter(a => /\\/Suche\\/|branche|branchen|kategorie|category/i.test(a.href)); industryLinks.forEach(a => { const v = clean(a.textContent); if (v && !blacklist.test(v) && v.length < 60) values.push(v); }); return values.filter(Boolean).filter((v, i, a) => a.indexOf(v) === i).join(', '); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "oeffnungszeit",
            "selector": "(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const candidates = Array.from(ROW.querySelectorAll('time, [class], [aria-label]')).filter(e => /öff|oeff|open|hour|zeit|time/i.test(String(e.className || '') + ' ' + String(e.getAttribute('aria-label') || ''))); const txt = candidates.map(e => norm(e.textContent || e.getAttribute('aria-label'))).filter(Boolean).find(v => /geöffnet|geschlossen|open|closed|\\d{1,2}:\\d{2}/i.test(v)); return txt || ''; })()",
            "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": 220,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "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": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-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": "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": 116,
      "width": 1664,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 720,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1728,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 2064,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes lead data from Das Telefonbuch detail URLs under adresse.dastelefonbuch.de. The workflow uses multi-URL navigation with loop-continue so every URL supplied in navigate.urls is visited and appended to das-telefonbuch-lead-scraper.csv. Extracted fields mirror the Octoparse template: company/person category, city, source URL, company name, address, phone, official website, industry and opening hours. A best-effort JavaScript step dismisses consent prompts and clicks phone-number reveal controls before extraction. This template processes all supplied detail URLs; replace or extend navigate.urls for more records.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const norm = v => (v || '').replace(/\\s+/g, ' ').trim(); const clicked = []; const all = Ar...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 992,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (firma_oder_person, ort, unternehmen_url, name_des_unternehmens, adresse). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2000,
      "position_y": 200,
      "width": 340,
      "height": 137,
      "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": 200,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}