{
  "version": "1.0.0",
  "exported_at": "2026-05-31T00:00:00.000Z",
  "project": {
    "name": "Tokopedia Scraper",
    "description": "Best-effort Tokopedia listing scraper equivalent to the Octoparse template. It is designed to extract Product_Name, Seller_Name, Detail_Page_URL, Price, Rating, and Sold from Tokopedia listing/search result pages, with lazy-load scrolling and pagination. Autonomous tests showed Tokopedia rejecting all tested Tokopedia navigation URLs with net::ERR_HTTP2_PROTOCOL_ERROR before page content loaded, including www.tokopedia.com /search, /find, and m.tokopedia.com routes. This appears to be a Tokopedia/browser/network anti-bot or protocol issue outside selector logic. If navigation succeeds in your environment, the template scrolls, annotates product cards, exports rows to tokopedia-scraper.csv, and clicks Next until no enabled next-page control remains. If navigation fails, try a residential/Indonesian proxy, non-headless mode, manual CAPTCHA/browser verification, or an updated Chrome/WebDriver network configuration.",
    "color": "bg-[#42be65]",
    "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": 260,
      "config": {
        "width": 1366,
        "height": 900
      }
    },
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 480,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.tokopedia.com/search?st=product&q=iphone%20case",
          "https://www.tokopedia.com/search?st=product&q=huawei",
          "https://www.tokopedia.com/search?st=product&q=xiaomi"
        ],
        "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": 840,
      "position_y": 260,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "duration": 8
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "selector": "body",
        "timeout": 45,
        "visible": true
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on the page",
      "position_x": 1920,
      "position_y": 260,
      "config": {
        "jsCode": "return new Promise(async resolve => {\n  const pause = ms => new Promise(r => setTimeout(r, ms));\n  const clean = value => String(value || '').replace(/\\s+/g, ' ').trim();\n  const txt = el => clean(el && el.textContent);\n  const badHref = href => /\\/(search|find|help|discovery|cart|login|register|wishlist|inbox)(\\?|\\/|$)/i.test(href || '');\n  function normalizedUrl(href) {\n    try {\n      const u = new URL(href, location.href);\n      if (u.hostname === 'm.tokopedia.com') u.hostname = 'www.tokopedia.com';\n      return u.href;\n    } catch (e) {\n      return href || '';\n    }\n  }\n  function bestText(parts, reject) {\n    return (parts || []).find(p => p && p.length > 2 && p.length < 190 && !(reject || []).some(rx => rx.test(p))) || '';\n  }\n  function annotateProducts() {\n    document.querySelectorAll('[data-uscraper-product-card]').forEach(el => {\n      el.removeAttribute('data-uscraper-product-card');\n      el.removeAttribute('data-uscraper-product-name');\n      el.removeAttribute('data-uscraper-seller-name');\n      el.removeAttribute('data-uscraper-detail-url');\n      el.removeAttribute('data-uscraper-price');\n      el.removeAttribute('data-uscraper-rating');\n      el.removeAttribute('data-uscraper-sold');\n    });\n    const anchors = Array.from(document.querySelectorAll('a[href]')).filter(a => {\n      const href = normalizedUrl(a.href || a.getAttribute('href') || '');\n      return /tokopedia\\.com\\//i.test(href) && !badHref(href) && /tokopedia\\.com\\/[^/?#]+\\/[^/?#]+/i.test(href);\n    });\n    const seen = new Set();\n    for (const a of anchors) {\n      let card = a;\n      for (let i = 0; i < 9 && card && card.parentElement; i++) {\n        if (/Rp\\s?[\\d.]+/i.test(txt(card)) && txt(card).length > 20) break;\n        card = card.parentElement;\n      }\n      if (!card || !/Rp\\s?[\\d.]+/i.test(txt(card)) || seen.has(card)) continue;\n      seen.add(card);\n      const full = txt(card);\n      const parts = Array.from(card.querySelectorAll('span, div, p, h1, h2, h3')).map(txt).filter(Boolean);\n      const nameEl = card.querySelector('[data-testid=\"spnSRPProdName\"], [data-testid*=\"ProdName\"], [class*=\"prd_link-product-name\"], [class*=\"product-name\"], [class*=\"ProductName\"]');\n      const sellerEl = card.querySelector('[data-testid=\"spnSRPProdTabShopName\"], [data-testid*=\"ShopName\"], [data-testid*=\"shopName\"], [class*=\"shop-name\"], [class*=\"merchant\"], [class*=\"Merchant\"]');\n      const priceEl = card.querySelector('[data-testid=\"spnSRPProdPrice\"], [data-testid*=\"ProdPrice\"], [class*=\"price\"], [class*=\"Price\"]');\n      const ratingEl = card.querySelector('[data-testid=\"spnSRPProdRating\"], [data-testid*=\"ProdRating\"], [class*=\"rating\"], [class*=\"Rating\"]');\n      const soldEl = card.querySelector('[data-testid=\"spnSRPProdSold\"], [data-testid*=\"ProdSold\"], [class*=\"sold\"], [class*=\"Sold\"]');\n      const price = txt(priceEl) || ((full.match(/Rp\\s?[\\d.]+(?:,\\d+)?/) || [''])[0]);\n      const sold = txt(soldEl) || ((full.match(/(?:Terjual|Sold)\\s*([0-9.,]+\\s*(?:rb|jt|k|m)?\\+?)/i) || [])[1] || (full.match(/([0-9.,]+\\s*(?:rb|jt|k|m)?\\+?)\\s*(?:terjual|sold)/i) || [])[1] || '');\n      const rating = txt(ratingEl) || ((full.match(/(?:^|\\s)([1-5](?:[.,]\\d)?)(?=\\s*(?:\\||Terjual|Sold|$))/i) || [])[1] || '');\n      let name = txt(nameEl);\n      if (!name) name = bestText(parts, [/^Rp/i, /terjual|sold/i, /^\\d(?:[.,]\\d)?$/, /gratis ongkir|official store|star|rating|cashback|cod|ad$/i]);\n      if (!name) name = clean(txt(a).replace(/Rp\\s?[\\d.].*$/i, ''));\n      let seller = txt(sellerEl);\n      if (!seller) seller = bestText(parts.filter(p => p !== name && p !== price && p !== rating && p !== sold), [/^Rp/i, /terjual|sold/i, /^\\d(?:[.,]\\d)?$/, /gratis ongkir|cashback|cod|rating|star|ad$/i]);\n      card.setAttribute('data-uscraper-product-card', 'true');\n      card.setAttribute('data-uscraper-product-name', clean(name));\n      card.setAttribute('data-uscraper-seller-name', clean(seller));\n      card.setAttribute('data-uscraper-detail-url', clean(normalizedUrl(a.href || a.getAttribute('href') || '')));\n      card.setAttribute('data-uscraper-price', clean(price));\n      card.setAttribute('data-uscraper-rating', clean(rating));\n      card.setAttribute('data-uscraper-sold', clean(String(sold).replace(/^Terjual\\s*/i, '').replace(/^Sold\\s*/i, '')));\n    }\n  }\n  function annotateNext() {\n    document.querySelectorAll('[data-uscraper-next-page]').forEach(el => el.removeAttribute('data-uscraper-next-page'));\n    const controls = Array.from(document.querySelectorAll('button, a')).filter(el => {\n      const label = clean(el.getAttribute('aria-label') || el.getAttribute('title') || el.textContent);\n      const disabled = el.disabled || el.getAttribute('aria-disabled') === 'true' || /disabled/i.test(el.className || '');\n      return !disabled && /(next|berikut|selanjutnya|laman berikut|halaman berikut|›|>)/i.test(label);\n    });\n    const next = controls[controls.length - 1];\n    if (next) next.setAttribute('data-uscraper-next-page', 'true');\n  }\n  window.scrollTo(0, 0);\n  await pause(1200);\n  for (let i = 0; i < 16; i++) {\n    annotateProducts();\n    window.scrollBy(0, Math.max(700, Math.floor(window.innerHeight * 0.85)));\n    await pause(750);\n  }\n  annotateProducts();\n  annotateNext();\n  resolve({ cards: document.querySelectorAll('[data-uscraper-product-card=\"true\"]').length, hasNext: !!document.querySelector('[data-uscraper-next-page=\"true\"]'), url: location.href });\n});",
        "waitForCompletion": true,
        "timeout": 60
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2280,
      "position_y": 260,
      "config": {
        "duration": 1
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 260,
      "config": {
        "rowSelector": "[data-uscraper-product-card=\"true\"]",
        "fileName": "tokopedia-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "Product_Name",
            "selector": "ROW.getAttribute('data-uscraper-product-name') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Seller_Name",
            "selector": "ROW.getAttribute('data-uscraper-seller-name') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Detail_Page_URL",
            "selector": "ROW.getAttribute('data-uscraper-detail-url') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Price",
            "selector": "ROW.getAttribute('data-uscraper-price') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Rating",
            "selector": "ROW.getAttribute('data-uscraper-rating') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Sold",
            "selector": "ROW.getAttribute('data-uscraper-sold') || ''",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3000,
      "position_y": 260,
      "config": {
        "selector": "[data-uscraper-next-page=\"true\"]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3360,
      "position_y": 580,
      "config": {
        "selector": "[data-uscraper-next-page=\"true\"]",
        "timeout": 15
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3720,
      "position_y": 580,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "sleep-3",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 4080,
      "position_y": 580,
      "config": {
        "duration": 5
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3000,
      "position_y": 900,
      "config": {}
    }
  ],
  "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": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-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": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-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": "click-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": "sleep-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-3",
      "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": "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": 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": 408,
      "position_y": 156,
      "width": 3920,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "sleep-2",
          "wait-for-page-load-2",
          "sleep-3"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1848,
      "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": 2568,
      "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": 2928,
      "position_y": 156,
      "width": 680,
      "height": 936,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "click-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort Tokopedia listing scraper equivalent to the Octoparse template. It is designed to extract Product_Name, Seller_Name, Detail_Page_URL, Price, Rating, and Sold from Tokopedia listing/search result pages, with lazy-load scrolling and pagination. Autonomous tests showed Tokopedia rejecting all tested Tokopedia navigation URLs with net::ERR_HTTP2_PROTOCOL_ERROR before page content loaded, including www.tokopedia.com /search, /find, and m.tokopedia.com routes. This appears to be a Tokopedia/browser/network anti-bot or protocol issue outside selector logic. If navigation succeeds in your environment, the template scrolls, annotates product cards, exports rows to tokopedia-scraper.csv, and clicks Next until no enabled next-page control remains. If navigation fails, try a residential/Indonesian proxy, non-headless mode, manual CAPTCHA/browser verification, or an updated Chrome/WebDriver network configuration.",
      "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 3 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 680,
      "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: `return new Promise(async resolve => {\n  const pause = ms => new Promise(r => setTimeout(r, ms));\n  c...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "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 (Product_Name, Seller_Name, Detail_Page_URL, Price, Rating). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 240,
      "width": 340,
      "height": 133,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `[data-uscraper-next-page=\"true\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 240,
      "width": 340,
      "height": 141,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "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": 3560,
      "position_y": 560,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-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": 880,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}