{
  "version": "1.0.0",
  "exported_at": "2026-06-03T12:00:00.000Z",
  "project": {
    "name": "Transfermarktde Overall Game Schedule Scraper",
    "description": "Scrapes Transfermarkt.de Bundesliga schedule data matching the Octoparse template fields: Spieltag, Datum, Uhrzeit, Heimmannschaft, Ergebnis, Gastmannschaft, and Wettbewerb. Uses a known URL-list pagination strategy over all 34 Bundesliga 2023 matchday pages and appends results into one CSV. The workflow waits for match-report links, marks only score-like result links such as 0:4 or 3:2, then extracts those marked fixture rows, excluding odds rows and standalone Spielbericht links. Cookie consent is handled best-effort; Transfermarkt may still show region-specific consent, ads, or anti-bot protection.",
    "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": 240,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 480,
      "position_y": 240,
      "config": {
        "urls": [
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=1",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=2",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=3",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=4",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=5",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=6",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=7",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=8",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=9",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=10",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=11",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=12",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=13",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=14",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=15",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=16",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=17",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=18",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=19",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=20",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=21",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=22",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=23",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=24",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=25",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=26",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=27",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=28",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=29",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=30",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=31",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=32",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=33",
          "https://www.transfermarkt.de/bundesliga/spieltag/wettbewerb/L1?saison_id=2023&spieltag=34"
        ],
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 840,
      "position_y": 240,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1200,
      "position_y": 240,
      "config": {
        "jsCode": "(() => {\n  const labels = ['Alle akzeptieren', 'Akzeptieren', 'Einverstanden', 'Zustimmen', 'Auswahl bestätigen', 'Speichern', 'Accept all', 'Accept', 'I agree'];\n  const roots = [document];\n  document.querySelectorAll('*').forEach(el => { if (el.shadowRoot) roots.push(el.shadowRoot); });\n  for (const root of roots) {\n    const candidates = Array.from(root.querySelectorAll('button, a, input[type=\"button\"], input[type=\"submit\"]'));\n    const btn = candidates.find(el => {\n      const text = ((el.innerText || el.textContent || el.value || '') + '').trim().toLowerCase();\n      return labels.some(label => text.includes(label.toLowerCase()));\n    });\n    if (btn) { btn.click(); return true; }\n  }\n  return false;\n})()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1560,
      "position_y": 240,
      "config": {
        "duration": 2,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1920,
      "position_y": 240,
      "config": {
        "selector": "a[href*='/spielbericht/']",
        "timeout": 45,
        "visible": true,
        "color": "bg-[#42be65]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 2280,
      "position_y": 240,
      "config": {
        "jsCode": "(() => {\n  const clean = s => (s || '').replace(/\\s+/g, ' ').trim();\n  const resultRe = /^(\\d+\\s*:\\s*\\d+|-\\s*:\\s*-|verl\\.|abges\\.|annull\\.)$/i;\n  document.querySelectorAll('a[data-uscraper-fixture]').forEach(a => a.removeAttribute('data-uscraper-fixture'));\n  let count = 0;\n  for (const a of Array.from(document.querySelectorAll('a[href*=\"/spielbericht/\"]'))) {\n    const text = clean(a.innerText || a.textContent);\n    if (resultRe.test(text)) {\n      a.setAttribute('data-uscraper-fixture', '1');\n      count++;\n    }\n  }\n  return count;\n})()",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 240,
      "config": {
        "rowSelector": "a[data-uscraper-fixture='1']",
        "fileName": "transfermarkt-de-gesamtspielplan-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "spieltag",
            "selector": "(() => { const u = new URL(location.href); const q = u.searchParams.get('spieltag'); if (q) return q + '. Spieltag'; const m = location.href.match(/spieltag[=/](\\d+)/i); return m ? (m[1] + '. Spieltag') : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "datum",
            "selector": "(() => { const row = ROW.closest('tr') || ROW; const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const dateRe = /(?:Mo|Di|Mi|Do|Fr|Sa|So)\\.?\\s*\\d{1,2}\\.\\d{1,2}\\.\\d{2,4}|\\d{1,2}\\.\\d{1,2}\\.\\d{2,4}/i; const getDate = r => { const t = clean((r && (r.innerText || r.textContent)) || ''); const m = t.match(dateRe); return m ? m[0] : ''; }; const direct = getDate(row); if (direct) return direct; let p = row.previousElementSibling; let steps = 0; while (p && steps < 12) { const d = getDate(p); if (d) return d; p = p.previousElementSibling; steps++; } let n = row.nextElementSibling; steps = 0; while (n && steps < 8) { const d = getDate(n); if (d) return d; n = n.nextElementSibling; steps++; } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "uhrzeit",
            "selector": "(() => { const row = ROW.closest('tr') || ROW; const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const timeRe = /\\b\\d{1,2}:\\d{2}\\b/; const getTime = r => { const t = clean((r && (r.innerText || r.textContent)) || ''); const m = t.match(timeRe); return m ? m[0] : ''; }; const direct = getTime(row); if (direct) return direct; let p = row.previousElementSibling; let steps = 0; while (p && steps < 12) { const tm = getTime(p); if (tm) return tm; p = p.previousElementSibling; steps++; } let n = row.nextElementSibling; steps = 0; while (n && steps < 8) { const tm = getTime(n); if (tm) return tm; n = n.nextElementSibling; steps++; } return ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "heimmannschaft",
            "selector": "(() => { const row = ROW.closest('tr') || ROW; const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const cells = Array.from(row.querySelectorAll('td,th')).map(td => clean(td.innerText || td.textContent)).filter(Boolean); const ri = cells.findIndex(t => /^(\\d+\\s*:\\s*\\d+|-\\s*:\\s*-|verl\\.|abges\\.|annull\\.)$/i.test(t)); if (ri > 0) return cells[ri - 1]; const txt = clean(row.innerText || row.textContent); const m = txt.match(/(.+?)\\s+(\\d+\\s*:\\s*\\d+|-\\s*:\\s*-)\\s+(.+)/); return m ? clean(m[1]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "ergebnis",
            "selector": "(() => (ROW.innerText || ROW.textContent || '').replace(/\\s+/g, ' ').trim())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "gastmannschaft",
            "selector": "(() => { const row = ROW.closest('tr') || ROW; const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const cells = Array.from(row.querySelectorAll('td,th')).map(td => clean(td.innerText || td.textContent)).filter(Boolean); const ri = cells.findIndex(t => /^(\\d+\\s*:\\s*\\d+|-\\s*:\\s*-|verl\\.|abges\\.|annull\\.)$/i.test(t)); if (ri >= 0 && ri < cells.length - 1) return cells[ri + 1]; const txt = clean(row.innerText || row.textContent); const m = txt.match(/(.+?)\\s+(\\d+\\s*:\\s*\\d+|-\\s*:\\s*-)\\s+(.+)/); return m ? clean(m[3]) : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "wettbewerb",
            "selector": "'Bundesliga'",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 240,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "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": "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": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "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": 136,
      "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": 408,
      "position_y": 136,
      "width": 1760,
      "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": 1128,
      "position_y": 136,
      "width": 1400,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2568,
      "position_y": 136,
      "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": 2928,
      "position_y": 136,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Transfermarkt.de Bundesliga schedule data matching the Octoparse template fields: Spieltag, Datum, Uhrzeit, Heimmannschaft, Ergebnis, Gastmannschaft, and Wettbewerb. Uses a known URL-list pagination strategy over all 34 Bundesliga 2023 matchday pages and appends results into one CSV. The workflow waits for match-report links, marks only score-like result links such as 0:4 or 3:2, then extracts those marked fixture rows, excluding odds rows and standalone Spielbericht links. Cookie consent is handled best-effort; Transfermarkt may still show region-specific consent, ads, or anti-bot protection.",
      "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: `(() => {\n  const labels = ['Alle akzeptieren', 'Akzeptieren', 'Einverstanden', 'Zustimmen', 'Auswahl...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => {\n  const clean = s => (s || '').replace(/\\s+/g, ' ').trim();\n  const resultRe = /^(\\d+\\s*:\\s...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 220,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (spieltag, datum, uhrzeit, heimmannschaft, ergebnis). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 220,
      "width": 340,
      "height": 130,
      "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": 220,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}