{
  "version": "1.0.0",
  "exported_at": "2026-05-31T14:05:00.000Z",
  "project": {
    "name": "Odds Portal Scraper",
    "description": "Scrapes OddsPortal.com event odds pages from a list of input URLs. For each event URL, it safely dismisses the visible OneTrust cookie banner if present, waits for bookmaker odds rows, and appends title, time, final result, final page URL, bookmaker, bookmaker link, 1/X/2 odds, payout, and scrape time. Navigation uses a multi-URL loop because the Octoparse template is driven by listing/page URLs; no in-page pagination was detected on the analyzed event page. OddsPortal may show CAPTCHA, geoblocking, or region-dependent bookmaker rows.",
    "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": 100,
      "position_y": 260,
      "config": {
        "width": 1920,
        "height": 1080,
        "color": "bg-[#4589ff]"
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 460,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.oddsportal.com/soccer/england/premier-league-2021-2022/brighton-norwich-829eE4xb/",
          "https://www.oddsportal.com/football/europe/europa-league/liverpool-atalanta-pUok14Z1/",
          "https://www.oddsportal.com/football/england/premier-league/chelsea-burnley-8djaszDj/",
          "https://www.oddsportal.com/football/england/premier-league/brentford-manchester-united-U5ierGcp/"
        ],
        "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": 820,
      "position_y": 260,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1180,
      "position_y": 260,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1540,
      "position_y": 260,
      "config": {
        "jsCode": "(() => { const selectors = ['#onetrust-accept-btn-handler', '#accept-recommended-btn-handler', 'button[id*=\\\"accept\\\" i]']; const candidates = selectors.flatMap(sel => Array.from(document.querySelectorAll(sel))); const visible = candidates.find(el => { const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none' && !el.disabled; }); if (visible) { visible.click(); return 'clicked visible cookie accept button'; } return 'no visible cookie accept button'; })();",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1900,
      "position_y": 260,
      "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": 2260,
      "position_y": 260,
      "config": {
        "selector": "a.bookmakerImg[href$=\"/link/\"]",
        "timeout": 45,
        "visible": true,
        "color": "bg-[#42be65]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2620,
      "position_y": 260,
      "config": {
        "rowSelector": "a.bookmakerImg[href$=\"/link/\"]",
        "fileName": "odds-portal-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "Title",
            "selector": "(function(){return (document.querySelector('[data-testid=\"breadcrumb-current-page\"]')?.innerText || document.querySelector('h1')?.innerText || '').replace(/\\s+-\\s+Odds.*$/,'').replace(/\\s+/g,' ').trim();})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Time",
            "selector": "(function(){const t=document.body.innerText; const m=t.match(/(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\\s+\\d{2}\\s+[A-Za-z]{3}\\s+\\d{4},\\s+\\d{1,2}:\\d{2}/); return m?m[0].replace(/\\s+/g,' ').trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Final_result",
            "selector": "(function(){const t=document.body.innerText.replace(/\\s+/g,' '); const m=t.match(/Final result\\s+\\d+\\s*:\\s*\\d+\\s*\\([^)]*\\)/i) || t.match(/Final result\\s+[^ ]+(?:\\s+[^ ]+){0,8}/i); return m?m[0].trim():'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_URL",
            "selector": "location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Bookmakers",
            "selector": "(function(){const href=ROW.getAttribute('href'); const links=Array.from(document.querySelectorAll('a')).filter(a=>a.getAttribute('href')===href || a.href===ROW.href); const txt=links.map(a=>(a.innerText||'').trim()).find(Boolean); const img=ROW.querySelector('img') || links.map(a=>a.querySelector('img')).find(Boolean); if(txt) return txt; if(img && img.alt) return img.alt; return (href||'').split('/').filter(Boolean).slice(-2,-1)[0]||'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Link",
            "selector": "(function(){const href=ROW.getAttribute('href')||''; try{return new URL(href, location.origin).href;}catch(e){return ROW.href||href;}})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Ratio_1",
            "selector": "(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const href=ROW.getAttribute('href'); const links=Array.from(document.querySelectorAll('a')).filter(a=>a.getAttribute('href')===href || a.href===ROW.href); const bookmaker=clean(links.map(a=>a.innerText).find(s=>clean(s)) || ROW.querySelector('img')?.alt || ''); let el=ROW, txt=''; for(let i=0;i<10&&el;i++,el=el.parentElement){const t=clean(el.innerText||el.textContent); const low=t.toLowerCase(); const idx=bookmaker?low.indexOf(bookmaker.toLowerCase()):-1; const after=idx>=0?t.slice(idx+bookmaker.length):t; const tokens=after.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]; if(after.includes('%') && tokens.length>=4){txt=after; break;}} const odds=(txt.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]).filter(v=>!v.includes('%')&&v!=='1'&&v!=='2'); return odds[0]||'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Ratio_X",
            "selector": "(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const href=ROW.getAttribute('href'); const links=Array.from(document.querySelectorAll('a')).filter(a=>a.getAttribute('href')===href || a.href===ROW.href); const bookmaker=clean(links.map(a=>a.innerText).find(s=>clean(s)) || ROW.querySelector('img')?.alt || ''); let el=ROW, txt=''; for(let i=0;i<10&&el;i++,el=el.parentElement){const t=clean(el.innerText||el.textContent); const low=t.toLowerCase(); const idx=bookmaker?low.indexOf(bookmaker.toLowerCase()):-1; const after=idx>=0?t.slice(idx+bookmaker.length):t; const tokens=after.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]; if(after.includes('%') && tokens.length>=4){txt=after; break;}} const odds=(txt.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]).filter(v=>!v.includes('%')&&v!=='1'&&v!=='2'); return odds[1]||'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Ratio_2",
            "selector": "(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const href=ROW.getAttribute('href'); const links=Array.from(document.querySelectorAll('a')).filter(a=>a.getAttribute('href')===href || a.href===ROW.href); const bookmaker=clean(links.map(a=>a.innerText).find(s=>clean(s)) || ROW.querySelector('img')?.alt || ''); let el=ROW, txt=''; for(let i=0;i<10&&el;i++,el=el.parentElement){const t=clean(el.innerText||el.textContent); const low=t.toLowerCase(); const idx=bookmaker?low.indexOf(bookmaker.toLowerCase()):-1; const after=idx>=0?t.slice(idx+bookmaker.length):t; const tokens=after.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]; if(after.includes('%') && tokens.length>=4){txt=after; break;}} const odds=(txt.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]).filter(v=>!v.includes('%')&&v!=='1'&&v!=='2'); return odds[2]||'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Payout",
            "selector": "(function(){const clean=s=>(s||'').replace(/\\s+/g,' ').trim(); const href=ROW.getAttribute('href'); const links=Array.from(document.querySelectorAll('a')).filter(a=>a.getAttribute('href')===href || a.href===ROW.href); const bookmaker=clean(links.map(a=>a.innerText).find(s=>clean(s)) || ROW.querySelector('img')?.alt || ''); let el=ROW, txt=''; for(let i=0;i<10&&el;i++,el=el.parentElement){const t=clean(el.innerText||el.textContent); const low=t.toLowerCase(); const idx=bookmaker?low.indexOf(bookmaker.toLowerCase()):-1; const after=idx>=0?t.slice(idx+bookmaker.length):t; const tokens=after.match(/[+-]?\\d+(?:\\.\\d+)?%?/g)||[]; if(after.includes('%') && tokens.length>=4){txt=after; break;}} const tokens=txt.match(/[+-]?\\d+(?:\\.\\d+)?%/g)||[]; return tokens[0]||'';})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Current_Time",
            "selector": "new Date().toISOString()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2980,
      "position_y": 260,
      "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-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-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": 28,
      "position_y": 156,
      "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": 388,
      "position_y": 156,
      "width": 2120,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1468,
      "position_y": 156,
      "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": 2548,
      "position_y": 156,
      "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": 2908,
      "position_y": 156,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes OddsPortal.com event odds pages from a list of input URLs. For each event URL, it safely dismisses the visible OneTrust cookie banner if present, waits for bookmaker odds rows, and appends title, time, final result, final page URL, bookmaker, bookmaker link, 1/X/2 odds, payout, and scrape time. Navigation uses a multi-URL loop because the Octoparse template is driven by listing/page URLs; no in-page pagination was detected on the analyzed event page. OddsPortal may show CAPTCHA, geoblocking, or region-dependent bookmaker rows.",
      "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 4 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 660,
      "position_y": 240,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const selectors = ['#onetrust-accept-btn-handler', '#accept-recommended-btn-handler', 'butt...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1740,
      "position_y": 240,
      "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 (Title, Time, Final_result, Page_URL, Bookmakers). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2820,
      "position_y": 240,
      "width": 340,
      "height": 129,
      "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": 3180,
      "position_y": 240,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}