{
  "version": "1.0.0",
  "exported_at": "2026-06-03T17:30:00.000Z",
  "project": {
    "name": "Google Image Scraper",
    "description": "Scrapes Google Images search results by keyword and exports query, title, source page URL, source site, full image URL when available, image size text, thumbnail URL, and thumbnail dimensions. Uses multiple Google Images keyword URLs with loop-continue, plus a best-effort infinite-scroll/load-more JavaScript step. Google may show CAPTCHA/consent screens or change DOM markup, so selectors are defensive and JS-based.",
    "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": 220,
      "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": 220,
      "config": {
        "urls": [
          "https://www.google.com/search?tbm=isch&hl=en&q=octoparse",
          "https://www.google.com/search?tbm=isch&hl=en&q=octopus"
        ],
        "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": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const candidates = Array.from(document.querySelectorAll('button, input[type=\"submit\"], div[role=\"button\"], span')); const accept = candidates.find(el => /^(accept all|i agree|agree|accept|alle akzeptieren|tout accepter)$/i.test((el.innerText || el.value || el.getAttribute('aria-label') || '').trim())); if (accept) { accept.click(); return 'clicked-consent'; } return 'no-consent'; })();",
        "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": 220,
      "config": {
        "duration": 2,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "selector": "a[href*=\"/imgres\"], a[href*=\"imgurl=\"], div.isv-r, div[data-ri], img[alt]",
        "timeout": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 2280,
      "position_y": 220,
      "config": {
        "jsCode": "(async () => { const sleep = ms => new Promise(r => setTimeout(r, ms)); for (let i = 0; i < 10; i++) { window.scrollTo(0, document.body.scrollHeight); await sleep(1200); const more = Array.from(document.querySelectorAll('button, input[type=\"button\"], input[type=\"submit\"], div[role=\"button\"]')).find(el => /show more|more results|load more/i.test((el.innerText || el.value || el.getAttribute('aria-label') || '').trim())); if (more) { more.click(); await sleep(1500); } } window.scrollTo(0, 0); return 'google-images-scroll-complete'; })();",
        "waitForCompletion": true,
        "timeout": 30,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2640,
      "position_y": 220,
      "config": {
        "duration": 2,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 3000,
      "position_y": 220,
      "config": {
        "rowSelector": "a[href*=\"/imgres\"], a[href*=\"imgurl=\"], div.isv-r, div[data-ri]",
        "fileName": "google-image-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "query",
            "selector": "(() => { try { return new URLSearchParams(location.search).get('q') || ''; } catch (e) { return ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "title",
            "selector": "(() => { const img = ROW.querySelector('img'); const heading = Array.from(ROW.querySelectorAll('[role=\"heading\"], h3, .toI8Rb, .mVDMnf')).map(e => (e.textContent || '').trim()).find(Boolean); const label = ROW.getAttribute('aria-label') || ROW.getAttribute('title') || ''; return (img && (img.alt || img.getAttribute('aria-label'))) || heading || label || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "title_url",
            "selector": "(() => { const a = ROW.matches && ROW.matches('a[href]') ? ROW : ROW.querySelector('a[href]'); if (!a) return ''; try { const u = new URL(a.href, location.href); return u.searchParams.get('imgrefurl') || u.searchParams.get('url') || (u.hostname.includes('google.') ? '' : u.href); } catch (e) { return a.href || ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "source",
            "selector": "(() => { const visible = Array.from(ROW.querySelectorAll('cite, .fxgdke, .wXeWr, .Ho4WGe, .Yt787')).map(e => (e.textContent || '').trim()).find(Boolean); if (visible) return visible; const a = ROW.matches && ROW.matches('a[href]') ? ROW : ROW.querySelector('a[href]'); if (!a) return ''; try { const u = new URL(a.href, location.href); const ref = u.searchParams.get('imgrefurl') || u.searchParams.get('url') || ''; return ref ? new URL(ref).hostname.replace(/^www\\./, '') : ''; } catch (e) { return ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "full_image",
            "selector": "(() => { const a = ROW.matches && ROW.matches('a[href]') ? ROW : ROW.querySelector('a[href]'); const dataUrl = ROW.getAttribute('data-iurl') || ROW.getAttribute('data-src') || ''; if (dataUrl && /^https?:/i.test(dataUrl)) return dataUrl; if (!a) return ''; try { const u = new URL(a.href, location.href); return u.searchParams.get('imgurl') || u.searchParams.get('mediaurl') || ''; } catch (e) { const href = a.href || ''; const m = href.match(/[?&](?:imgurl|mediaurl)=([^&]+)/); return m ? decodeURIComponent(m[1]) : ''; } })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "full_image_size",
            "selector": "(() => { const text = (ROW.innerText || ROW.textContent || '').replace(/\\s+/g, ' '); const m = text.match(/\\b\\d{2,5}\\s*[×x]\\s*\\d{2,5}\\b/); if (m) return m[0].replace(/x/, '×'); const w = ROW.getAttribute('data-ow') || ROW.getAttribute('data-width') || ''; const h = ROW.getAttribute('data-oh') || ROW.getAttribute('data-height') || ''; return w && h ? `${w} × ${h}` : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "thumbnail_image",
            "selector": "(() => { const img = ROW.querySelector('img'); if (!img) return ''; return img.currentSrc || img.src || img.getAttribute('data-src') || img.getAttribute('data-iurl') || ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "thumbnail_width",
            "selector": "(() => { const img = ROW.querySelector('img'); if (!img) return ''; return String(img.naturalWidth || img.width || img.getAttribute('width') || ''); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "thumbnail_height",
            "selector": "(() => { const img = ROW.querySelector('img'); if (!img) return ''; return String(img.naturalHeight || img.height || img.getAttribute('height') || ''); })()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3360,
      "position_y": 220,
      "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": "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": "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": 116,
      "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": 116,
      "width": 2480,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1128,
      "position_y": 116,
      "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": 2928,
      "position_y": 116,
      "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": 3288,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Google Images search results by keyword and exports query, title, source page URL, source site, full image URL when available, image size text, thumbnail URL, and thumbnail dimensions. Uses multiple Google Images keyword URLs with loop-continue, plus a best-effort infinite-scroll/load-more JavaScript step. Google may show CAPTCHA/consent screens or change DOM markup, so selectors are defensive and JS-based.",
      "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: `(() => { const candidates = Array.from(document.querySelectorAll('button, input[type=\"submit\"], div[...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 200,
      "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: `(async () => { const sleep = ms => new Promise(r => setTimeout(r, ms)); for (let i = 0; i < 10; i++)...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 200,
      "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 (query, title, title_url, source, full_image). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 200,
      "width": 340,
      "height": 128,
      "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": 3560,
      "position_y": 200,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}