{
  "version": "1.0.0",
  "exported_at": "2026-06-02T00:00:00.000Z",
  "project": {
    "name": "Jumia Data Scraper",
    "description": "Best-effort Jumia product search scraper inferred from the Octoparse template. Extracts product title, current price, old price, discount, rating, reviews, product URL, image URL, keyword, page number, and raw product text from Jumia keyword search pages. Keywords are configured as editable Jumia search URLs in the Navigate urls[] list. Pagination is handled by clicking an enabled Next Page link until none remains, appending all pages to jumia-data-scraper.csv. Live testing showed Jumia serving a Cloudflare 'Just a moment...' security verification page to the automation browser; if products are not exposed after waiting, the template writes a diagnostic row documenting the blocker. For production use, run with an already-verified persistent browser profile, suitable region/IP, or proxy configuration.",
    "color": "bg-[#f1c21b]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.jumia.com.ng/catalog/?q=phone"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "jumia",
          "keyword-search",
          "cloudflare-aware"
        ]
      }
    },
    {
      "block_id": "wait-for-page-load-1",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 480,
      "position_y": 260,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 840,
      "position_y": 260,
      "config": {
        "selector": "body",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "duration": 25,
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "scroll-1",
      "block_type": "process",
      "title": "Scroll",
      "description": "Scroll the page",
      "position_x": 1920,
      "position_y": 260,
      "config": {
        "direction": "down",
        "amount": 1800,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2280,
      "position_y": 260,
      "config": {
        "duration": 4,
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 2640,
      "position_y": 260,
      "config": {
        "jsCode": "(async function(){\n  function clean(v){ return (v || '').replace(/\\s+/g, ' ').trim(); }\n  function abs(v){ try { return v ? new URL(v, location.href).href : ''; } catch(e) { return v || ''; } }\n  function attr(el,n){ return clean(el && el.getAttribute ? el.getAttribute(n) : ''); }\n  function text(el){ return clean(el && el.textContent); }\n  function priceFromText(s){ var m = clean(s).match(/(₦|KSh|EGP|GH₵|GHS|DZD|MAD|د\\.?م\\.?|€|\\$|USD|NGN|KES|EGP)\\s?[0-9][0-9.,]*/i); return m ? m[0] : ''; }\n  function badUrl(u){ return /\\/customer|\\/cart|\\/wishlist|\\/account|\\/login|\\/help|\\/catalog\\/?$|\\/category\\//i.test(u || ''); }\n  function firstText(root, selectors){ for (var i=0;i<selectors.length;i++){ try { var el=root.querySelector(selectors[i]); var t=text(el); if(t) return t; } catch(e){} } return ''; }\n  function firstLink(root){ if(root && root.matches && root.matches('a[href]')) return root; try { return root.querySelector('a.core[href], a[href*=\".html\"], a[href]'); } catch(e){ return null; } }\n  function firstImage(root){ var img=null; try { img=root.querySelector('img.img, img'); } catch(e){} if(!img && root && root.tagName && root.tagName.toLowerCase()==='img') img=root; return img; }\n  function imageUrl(root){ var img=firstImage(root); return img ? abs(attr(img,'data-src') || attr(img,'data-lazy-src') || attr(img,'src') || img.src || '') : ''; }\n  function imageAlt(root){ var img=firstImage(root); return img ? clean(attr(img,'alt') || attr(img,'title')) : ''; }\n  document.querySelectorAll('.uscraper-jumia-row,#uscraper-jumia-normalized').forEach(function(n){n.remove();});\n  var host=document.createElement('div'); host.id='uscraper-jumia-normalized'; host.style.position='absolute'; host.style.left='-99999px'; host.style.top='0'; document.body.appendChild(host);\n  var seen=new Set();\n  function addProduct(p){\n    p=p||{}; p.title=clean(p.title); p.currentPrice=clean(p.currentPrice); p.productUrl=abs(p.productUrl); p.imageUrl=abs(p.imageUrl); p.rawText=clean(p.rawText);\n    if(!p.title && !p.currentPrice && !p.productUrl && !p.imageUrl && !p.rawText) return;\n    var key=(p.productUrl||'')+'|'+(p.title||'')+'|'+(p.currentPrice||'')+'|'+(p.rawText||'').slice(0,80);\n    if(seen.has(key)) return; seen.add(key);\n    var row=document.createElement('div'); row.className='uscraper-jumia-row';\n    row.dataset.countryDomain=location.hostname;\n    row.dataset.keyword=(new URL(location.href)).searchParams.get('q') || '';\n    row.dataset.pageNumber=(new URL(location.href)).searchParams.get('page') || '1';\n    row.dataset.title=p.title||'';\n    row.dataset.currentPrice=p.currentPrice||'';\n    row.dataset.oldPrice=p.oldPrice||'';\n    row.dataset.discount=p.discount||'';\n    row.dataset.rating=p.rating||'';\n    row.dataset.reviews=p.reviews||'';\n    row.dataset.productUrl=p.productUrl||'';\n    row.dataset.imageUrl=p.imageUrl||'';\n    row.dataset.rawText=p.rawText||'';\n    row.dataset.isDiagnostic=p.isDiagnostic?'true':'false';\n    host.appendChild(row);\n  }\n  function extractFromCard(card){\n    var link=firstLink(card); var img=firstImage(card); var raw=text(card); var url=abs(attr(link,'href'));\n    var title=firstText(card,['.name','[data-testid=\"product-title\"]','[data-qa-locator=\"product-title\"]','.title','h3','h2','a.core','a[href]']) || imageAlt(card) || clean(attr(link,'title')) || clean(attr(link,'aria-label'));\n    var price=firstText(card,['.prc','[data-testid=\"product-price\"]','[data-qa-locator=\"product-price\"]','.price','[class*=\"price\"]']) || priceFromText(raw);\n    var oldPrice=firstText(card,['.old','.old-price','[class*=\"old\"]']);\n    var discount=firstText(card,['.bdg._dsct','.bdg','[class*=\"discount\"]']);\n    var rating=firstText(card,['.stars','[class*=\"rating\"]']);\n    var reviews=firstText(card,['.rev','[class*=\"review\"]']);\n    if(badUrl(url) && !price && !title) return;\n    if(!title && !price && !url && !img) return;\n    addProduct({title:title,currentPrice:price,oldPrice:oldPrice,discount:discount,rating:rating,reviews:reviews,productUrl:url,imageUrl:imageUrl(card),rawText:raw});\n  }\n  function extractFromDocument(doc){\n    var cardSelectors='article.prd, article.c-prd, article, [data-qa-locator=\"product-item\"], [data-testid=\"product-card\"], .prd, .c-prd';\n    try { doc.querySelectorAll(cardSelectors).forEach(extractFromCard); } catch(e){}\n    try { doc.querySelectorAll('a.core[href], a[href*=\".html\"]').forEach(function(a){ var card=a.closest('article,li,section,div') || a; extractFromCard(card); }); } catch(e){}\n    try { doc.querySelectorAll('script[type=\"application/ld+json\"]').forEach(function(s){ parseJsonProducts(s.textContent); }); } catch(e){}\n  }\n  function parseJsonProducts(txt){\n    function visit(o){\n      if(!o) return;\n      if(Array.isArray(o)){ o.forEach(visit); return; }\n      if(typeof o !== 'object') return;\n      var type=o['@type']; var isProduct=false;\n      if(typeof type==='string' && /Product|Offer|ListItem/i.test(type)) isProduct=true;\n      if(Array.isArray(type) && type.join(' ').match(/Product|Offer|ListItem/i)) isProduct=true;\n      if(o.itemListElement) visit(o.itemListElement);\n      if(o.item) visit(o.item);\n      if(o.offers && typeof o.offers==='object') visit(o.offers);\n      if(isProduct || (o.name && (o.price || o.offers || o.image || o.url))){\n        var offer=Array.isArray(o.offers)?o.offers[0]:o.offers||{};\n        var img=Array.isArray(o.image)?o.image[0]:o.image;\n        addProduct({title:o.name||o.title||'',currentPrice:o.price||offer.price||'',oldPrice:'',discount:'',rating:(o.aggregateRating&&o.aggregateRating.ratingValue)||'',reviews:(o.aggregateRating&&o.aggregateRating.reviewCount)||'',productUrl:o.url||'',imageUrl:img||'',rawText:JSON.stringify(o).slice(0,500)});\n      }\n      Object.keys(o).forEach(function(k){ if(k!=='offers' && k!=='item' && k!=='itemListElement') visit(o[k]); });\n    }\n    try { visit(JSON.parse(txt)); } catch(e){}\n  }\n  extractFromDocument(document);\n  try {\n    var ctrl=new AbortController(); var tid=setTimeout(function(){ctrl.abort();},10000);\n    var res=await fetch(location.href,{credentials:'include',signal:ctrl.signal}); clearTimeout(tid);\n    var html=await res.text();\n    var doc=new DOMParser().parseFromString(html,'text/html');\n    extractFromDocument(doc);\n    var ldMatches=html.match(/<script[^>]+application\\/ld\\+json[^>]*>([\\s\\S]*?)<\\/script>/gi)||[];\n    ldMatches.forEach(function(block){ parseJsonProducts(block.replace(/^<script[^>]*>/i,'').replace(/<\\/script>$/i,'')); });\n  } catch(e) {}\n  if(document.querySelectorAll('.uscraper-jumia-row').length===0){\n    addProduct({title:'',currentPrice:'',productUrl:location.href,imageUrl:'',rawText:'DIAGNOSTIC: No Jumia product rows were exposed to the browser after waiting for security verification. Page may still be blocked, geo-filtered, empty, or served as an anti-bot/challenge page. Title: '+document.title+' Body sample: '+clean(document.body ? document.body.innerText.slice(0,900) : ''),isDiagnostic:true});\n  }\n  return document.querySelectorAll('.uscraper-jumia-row').length;\n})();",
        "waitForCompletion": true,
        "timeout": 35,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-3",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3000,
      "position_y": 260,
      "config": {
        "duration": 1,
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3360,
      "position_y": 260,
      "config": {
        "rowSelector": ".uscraper-jumia-row",
        "fileName": "jumia-data-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "country_domain",
            "selector": "ROW.dataset.countryDomain || location.hostname || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "keyword",
            "selector": "ROW.dataset.keyword || new URL(window.location.href).searchParams.get('q') || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "page_number",
            "selector": "ROW.dataset.pageNumber || new URL(window.location.href).searchParams.get('page') || '1'",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "title",
            "selector": "ROW.dataset.title || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "current_price",
            "selector": "ROW.dataset.currentPrice || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "old_price",
            "selector": "ROW.dataset.oldPrice || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "discount",
            "selector": "ROW.dataset.discount || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "ROW.dataset.rating || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "reviews",
            "selector": "ROW.dataset.reviews || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "product_url",
            "selector": "ROW.dataset.productUrl || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "image_url",
            "selector": "ROW.dataset.imageUrl || ''",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "is_diagnostic",
            "selector": "ROW.dataset.isDiagnostic || 'false'",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "raw_product_text",
            "selector": "ROW.dataset.rawText || ''",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3720,
      "position_y": 260,
      "config": {
        "selector": "a.pg[aria-label='Next Page']:not(._dis), a[aria-label='Next Page']:not(._dis), a[rel='next']:not(._dis), a[href*='page='][aria-label*='Next' i]",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3720,
      "position_y": 580,
      "config": {
        "selector": "a.pg[aria-label='Next Page']:not(._dis), a[aria-label='Next Page']:not(._dis), a[rel='next']:not(._dis), a[href*='page='][aria-label*='Next' i]",
        "timeout": 15,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-page-load-3",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 4080,
      "position_y": 580,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3720,
      "position_y": 900,
      "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": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-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-page-load-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-2",
      "from_connector_id": "right",
      "to_block_id": "scroll-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "scroll-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": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "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": "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": "element-exists-1",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-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-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 156,
      "width": 4280,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "sleep-1",
          "wait-for-page-load-2",
          "sleep-2",
          "sleep-3",
          "wait-for-page-load-3"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1848,
      "position_y": 156,
      "width": 1040,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "scroll-1",
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 3288,
      "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": 3648,
      "position_y": 156,
      "width": 380,
      "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 Jumia product search scraper inferred from the Octoparse template. Extracts product title, current price, old price, discount, rating, reviews, product URL, image URL, keyword, page number, and raw product text from Jumia keyword search pages. Keywords are configured as editable Jumia search URLs in the Navigate urls[] list. Pagination is handled by clicking an enabled Next Page link until none remains, appending all pages to jumia-data-scraper.csv. Live testing showed Jumia serving a Cloudflare 'Just a moment...' security verification page to the automation browser; if products are not exposed after waiting, the template writes a diagnostic row documenting the blocker. For production use, run with an already-verified persistent browser profile, suitable region/IP, or proxy 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 1 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "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: `(async function(){\n  function clean(v){ return (v || '').replace(/\\s+/g, ' ').trim(); }\n  function a...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2840,
      "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 (country_domain, keyword, page_number, title, current_price). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 3560,
      "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 `a.pg[aria-label='Next Page']:not(._dis), a[aria-label='Next Page']:not(._dis), a[rel='next']:not(._dis), a[href*='page='`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3920,
      "position_y": 240,
      "width": 340,
      "height": 170,
      "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": 3920,
      "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": 3920,
      "position_y": 880,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}