{
  "version": "1.0.0",
  "exported_at": "2026-06-02T00:55:00.000Z",
  "project": {
    "name": "Zoro Product Scraper",
    "description": "Best-effort scraper for zoro.com product listing pages. Extracts product listing data matching the Octoparse Zoro Product Scraper preview: data type, input URL, page number, product name, URL, brand, Zoro site part, part, price, scrape date, and stock status. Zoro returned HTTP 403 DataDome CAPTCHA during analysis and in test execution. This template includes a CAPTCHA/device-check detection branch that pauses and refreshes to allow a persistent browser profile or manual clearance, then attempts extraction. Pagination is implemented with a click-Next loop and append-mode CSV output so all reachable listing pages are collected.",
    "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": {
        "url": "https://www.zoro.com/b/ZORO%20SELECT/?fqc%3Acategory=z4",
        "color": "bg-[#4589ff]",
        "tags": [
          "zoro",
          "listing",
          "entry"
        ]
      }
    },
    {
      "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
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 792,
      "position_y": 220,
      "config": {
        "selector": "iframe[src*=\"captcha-delivery.com\"], iframe[title*=\"DataDome\"], iframe[title*=\"CAPTCHA\"], script[src*=\"captcha-delivery.com\"]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1128,
      "position_y": 520,
      "config": {
        "duration": 20
      }
    },
    {
      "block_id": "refresh-1",
      "block_type": "process",
      "title": "Refresh",
      "description": "Refresh the current page",
      "position_x": 1464,
      "position_y": 520,
      "config": {}
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 1800,
      "position_y": 520,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "selector": "a[href*=\"/i/G\"]",
        "timeout": 45,
        "visible": true
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2472,
      "position_y": 520,
      "config": {
        "rowSelector": "a[href*=\"/i/G\"]",
        "fileName": "zoro-product-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "data_type",
            "selector": "(() => 'list')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "input_keyword",
            "selector": "(() => window.location.href)()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "page_num",
            "selector": "(() => { const u = new URL(window.location.href); const fromUrl = u.searchParams.get('page') || u.searchParams.get('p'); if (fromUrl) return fromUrl; const active = document.querySelector('[aria-current=\"page\"], .active, [class*=\"active\"]'); const n = active && (active.textContent || '').match(/\\d+/); return n ? n[0] : '1'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "option",
            "selector": "(() => '')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "product_name",
            "selector": "(() => { const txt = (ROW.textContent || '').trim().replace(/\\s+/g, ' '); if (txt) return txt; const img = ROW.querySelector('img'); return img ? (img.alt || img.title || '').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "product_url",
            "selector": "",
            "attribute": "href"
          },
          {
            "name": "brand",
            "selector": "(() => { const card = ROW.closest('[data-za], [data-testid], [data-qa], [data-product-sku], [data-product-id], li, article, div') || ROW.parentElement || ROW; const candidates = Array.from(card.querySelectorAll('[data-testid*=\"brand\"], [data-qa*=\"brand\"], [class*=\"brand\"], a[href*=\"fqv%3Abrand\"], a[href*=\"brand=\"]')); for (const el of candidates) { const t = (el.textContent || '').trim().replace(/\\s+/g, ' '); if (t) return t; } const text = card.innerText || ''; const m = text.match(/Brand\\s*:?\\s*([^\\n]+)/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "site_part",
            "selector": "(() => { const href = ROW.href || ROW.getAttribute('href') || ''; return (href.split('/i/')[1] || '').split('/')[0] || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "part",
            "selector": "(() => { const href = ROW.href || ROW.getAttribute('href') || ''; const before = href.split('/i/')[0] || ''; const slug = before.split('/').filter(Boolean).pop() || ''; const token = (slug.split('-').pop() || '').toUpperCase(); return token.replace(/[^A-Z0-9]/g, ''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "price",
            "selector": "(() => { const card = ROW.closest('[data-za], [data-testid], [data-qa], [data-product-sku], [data-product-id], li, article, div') || ROW.parentElement || ROW; const text = card.innerText || ''; const m = text.match(/(?:US\\s*)?\\$\\s*[0-9,]+(?:\\.[0-9]{2})?/); if (!m) return ''; const val = m[0].trim(); return val.startsWith('US') ? val : 'US' + val; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "scrape_date",
            "selector": "(() => new Date().toISOString())()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "stock",
            "selector": "(() => { const card = ROW.closest('[data-za], [data-testid], [data-qa], [data-product-sku], [data-product-id], li, article, div') || ROW.parentElement || ROW; const text = card.innerText || ''; const m = text.match(/\\b(In Stock|Out of Stock|Backordered|Available|Unavailable|Ships[^\\n]*|Usually ships[^\\n]*)\\b/i); return m ? m[1].trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2808,
      "position_y": 520,
      "config": {
        "selector": "a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]), button[aria-label*=\"Next\"]:not([disabled]), a[rel=\"next\"], a.pagination-next:not(.disabled), a[href*=\"page=\"]:last-child"
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 2808,
      "position_y": 520,
      "config": {}
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3144,
      "position_y": 520,
      "config": {
        "selector": "a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]), button[aria-label*=\"Next\"]:not([disabled]), a[rel=\"next\"], a.pagination-next:not(.disabled)",
        "timeout": 15
      }
    },
    {
      "block_id": "wait-for-page-load-3",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "timeout": 30
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3816,
      "position_y": 520,
      "config": {
        "duration": 2
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 4152,
      "position_y": 758,
      "config": {
        "selector": "a[href*=\"/i/G\"]",
        "timeout": 45,
        "visible": true
      }
    }
  ],
  "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "refresh-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "refresh-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-2",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-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": "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": "element-exists-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "false",
      "to_block_id": "end-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "true",
      "to_block_id": "click-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "click-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-3",
      "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-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-2",
      "from_connector_id": "right",
      "to_block_id": "structured-export-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": 4352,
      "height": 834,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "refresh-1",
          "wait-for-page-load-2",
          "wait-for-element-1",
          "wait-for-page-load-3",
          "sleep-2",
          "wait-for-element-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 720,
      "position_y": 116,
      "width": 2672,
      "height": 596,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "element-exists-2",
          "click-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2400,
      "position_y": 416,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 2736,
      "position_y": 416,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort scraper for zoro.com product listing pages. Extracts product listing data matching the Octoparse Zoro Product Scraper preview: data type, input URL, page number, product name, URL, brand, Zoro site part, part, price, scrape date, and stock status. Zoro returned HTTP 403 DataDome CAPTCHA during analysis and in test execution. This template includes a CAPTCHA/device-check detection branch that pauses and refreshes to allow a persistent browser profile or manual clearance, then attempts extraction. Pagination is implemented with a click-Next loop and append-mode CSV output so all reachable listing pages are collected.",
      "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 `iframe[src*=\"captcha-delivery.com\"], iframe[title*=\"DataDome\"], iframe[title*=\"CAPTCHA\"], script[src*=\"captcha-delivery.`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 992,
      "position_y": 200,
      "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 (data_type, input_keyword, page_num, option, product_name). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2672,
      "position_y": 500,
      "width": 340,
      "height": 132,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]), button[aria-label*=\"Next\"]:not([disabled]), a[rel=\"next\"], a.paginati`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3008,
      "position_y": 500,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Pagination click — add waits after this block; the page reloads asynchronously.",
      "color": "#ee5396",
      "position_x": 3344,
      "position_y": 500,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-1"
      }
    }
  ]
}