{
  "version": "1.0.0",
  "exported_at": "2026-06-03T00:00:00.000Z",
  "project": {
    "name": "Seek Listing Scraper by URL",
    "description": "Scrapes SEEK Australia job search/listing result pages by URL. The workflow loops through configured SEEK listing URLs, extracts visible job cards, then follows the enabled Next pagination URL until no safe next page remains or a page has no job cards. Output fields mirror the Octoparse Seek Listing Scraper: input/page metadata, total jobs, filters, job title, URL, company, company URL, logo URL, location, salary, bullet points, intro, featured flag, and listing date. Pagination uses JavaScript URL navigation and row-existence guards to avoid SEEK sticky-element click failures and empty out-of-range pagination pages. If SEEK presents Cloudflare/CAPTCHA, pause and solve it manually in the browser before continuing.",
    "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": 260,
      "config": {
        "urls": [
          "https://www.seek.com.au/customer-service-jobs/in-Sydney-NSW-2000/remote",
          "https://www.seek.com.au/jobs-in-engineering/civil-structural-engineering/in-South-Australia-SA?page=2",
          "https://www.seek.com.au/jobs"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "seek",
          "jobs",
          "multi-url"
        ]
      }
    },
    {
      "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
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 260,
      "config": {
        "duration": 3
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "selector": "article[data-automation=\"normalJob\"], article[data-automation=\"premiumJob\"], article[data-automation=\"standoutJob\"], article[data-automation=\"job-card\"]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "rowSelector": "article[data-automation=\"normalJob\"], article[data-automation=\"premiumJob\"], article[data-automation=\"standoutJob\"], article[data-automation=\"job-card\"]",
        "fileName": "seek-listing-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "Input_URL",
            "selector": "window.location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Total_job",
            "selector": "(() => { const t = document.querySelector('[data-automation=\"totalJobsCount\"], [data-automation=\"job-count\"], h1')?.textContent?.trim() || ''; const m = (t || document.body.innerText).match(/[\\d,]+\\s+[^\\n]{0,80}jobs?/i); return m ? m[0].trim() : t; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Input_keyword",
            "selector": "(() => { const path = decodeURIComponent(location.pathname); const first = path.split('/').filter(Boolean)[0] || ''; if (first === 'jobs') return ''; return first.replace(/-jobs.*$/i, '').replace(/^jobs-in-/i, '').replace(/-/g, ' ').trim(); })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Input_classification",
            "selector": "(() => { const path = decodeURIComponent(location.pathname); const m = path.match(/jobs-in-([^/]+)/i); return m ? m[1].replace(/-/g, ' ').trim() : 'Any classification'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Input_where",
            "selector": "(() => { const path = decodeURIComponent(location.pathname); const m = path.match(/\\/in-([^/?#]+)/i); return m ? m[1].replace(/-/g, ' ').trim() : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Filter_Work_types",
            "selector": "(() => { const q = new URLSearchParams(location.search); return q.get('worktype') || q.get('worktypes') || 'All work types'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Filter_Remote",
            "selector": "(() => location.pathname.toLowerCase().includes('/remote') || location.search.toLowerCase().includes('remote') ? 'Remote' : '')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Filter_salary_from",
            "selector": "(() => { const q = new URLSearchParams(location.search); const s = q.get('salaryrange') || q.get('salary') || ''; const m = s.match(/(\\d+)/); return m ? m[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Filter_salary_to",
            "selector": "(() => { const q = new URLSearchParams(location.search); const s = q.get('salaryrange') || q.get('salary') || ''; const nums = s.match(/\\d+/g); return nums && nums.length > 1 ? nums[1] : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Filter_listed_time",
            "selector": "(() => { const q = new URLSearchParams(location.search); return q.get('daterange') || q.get('listedtime') || 'listed any time'; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_title",
            "selector": "document.title",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Page_URL",
            "selector": "window.location.href",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Job_title",
            "selector": "[data-automation=\"jobTitle\"]",
            "attribute": "text"
          },
          {
            "name": "Job_URL",
            "selector": "(() => { const title = ROW.querySelector('[data-automation=\"jobTitle\"]'); const a = title?.closest('a') || title?.querySelector('a') || ROW.querySelector('a[href*=\"/job/\"]'); return a ? a.href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Company",
            "selector": "[data-automation=\"jobCompany\"]",
            "attribute": "text"
          },
          {
            "name": "Company_URL",
            "selector": "(() => { const company = ROW.querySelector('[data-automation=\"jobCompany\"]'); const a = company?.closest('a') || company?.querySelector('a') || ROW.querySelector('a[href*=\"advertiserid=\"], a[href$=\"-jobs\"]'); return a ? a.href : ''; })()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Company_logo_URL",
            "selector": "img[src*=\"serpLogo\"], img[alt*=\"logo\" i]",
            "attribute": "src"
          },
          {
            "name": "Job_location",
            "selector": "[data-automation=\"jobLocation\"]",
            "attribute": "text"
          },
          {
            "name": "Salary",
            "selector": "[data-automation=\"jobSalary\"]",
            "attribute": "text"
          },
          {
            "name": "Bullet_points",
            "selector": "(() => Array.from(ROW.querySelectorAll('ul li')).map(li => li.textContent.trim()).filter(Boolean).filter((v, i, a) => a.indexOf(v) === i).join(' | '))()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Short_intro",
            "selector": "[data-automation=\"jobShortDescription\"], [data-automation=\"jobAdDetails\"]",
            "attribute": "text"
          },
          {
            "name": "If_featured",
            "selector": "(() => /\\bfeatured\\b/i.test(ROW.textContent || '') ? 'Featured' : '')()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "Job_listing_date",
            "selector": "[data-automation=\"jobListingDate\"]",
            "attribute": "text"
          }
        ]
      }
    },
    {
      "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": "(() => { document.querySelectorAll('#uscraper-has-next').forEach(e => e.remove()); const currentUrl = new URL(window.location.href); const currentPage = parseInt(currentUrl.searchParams.get('page') || '1', 10) || 1; const links = Array.from(document.querySelectorAll('a[title=\"Next\"], a[aria-label=\"Next\"], a[rel~=\"next\"], a[data-automation^=\"page-\"]')); const candidates = links.map(a => { try { const u = new URL(a.href, location.href); const p = parseInt(u.searchParams.get('page') || '', 10); return { a, u, p }; } catch (e) { return null; } }).filter(Boolean).filter(x => x.u.href !== location.href && x.p && x.p > currentPage && x.a.getAttribute('aria-hidden') !== 'true' && x.a.getAttribute('aria-disabled') !== 'true' && x.a.getAttribute('tabindex') !== '-1' && !/disabled/i.test(x.a.className || '')); candidates.sort((x, y) => x.p - y.p); const next = candidates[0]; if (next) { const marker = document.createElement('div'); marker.id = 'uscraper-has-next'; marker.setAttribute('data-next-url', next.u.href); marker.style.display = 'none'; document.body.appendChild(marker); } })();",
        "waitForCompletion": true,
        "timeout": 10
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2280,
      "position_y": 260,
      "config": {
        "selector": "#uscraper-has-next"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on the page",
      "position_x": 2640,
      "position_y": 260,
      "config": {
        "jsCode": "(() => { const marker = document.querySelector('#uscraper-has-next'); const nextUrl = marker?.getAttribute('data-next-url'); if (nextUrl) { window.location.href = nextUrl; } })();",
        "waitForCompletion": true,
        "timeout": 10
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3000,
      "position_y": 260,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3000,
      "position_y": 580,
      "config": {
        "duration": 3
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 1560,
      "position_y": 580,
      "config": {}
    }
  ],
  "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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "element-exists-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "true",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-2",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-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": "element-exists-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-1",
      "from_connector_id": "true",
      "to_block_id": "inject-javascript-2",
      "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": "inject-javascript-2",
      "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-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "element-exists-2",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 156,
      "width": 3200,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-page-load-2",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1128,
      "position_y": 156,
      "width": 1400,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-2",
          "element-exists-1",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 1488,
      "position_y": 156,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "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": [
          "inject-javascript-1",
          "inject-javascript-2"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes SEEK Australia job search/listing result pages by URL. The workflow loops through configured SEEK listing URLs, extracts visible job cards, then follows the enabled Next pagination URL until no safe next page remains or a page has no job cards. Output fields mirror the Octoparse Seek Listing Scraper: input/page metadata, total jobs, filters, job title, URL, company, company URL, logo URL, location, salary, bullet points, intro, featured flag, and listing date. Pagination uses JavaScript URL navigation and row-existence guards to avoid SEEK sticky-element click failures and empty out-of-range pagination pages. If SEEK presents Cloudflare/CAPTCHA, pause and solve it manually in the browser before continuing.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `article[data-automation=\"normalJob\"], article[data-automation=\"premiumJob\"], article[data-automation=\"standoutJob\"], art`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 240,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Structured export with JS columns (Input_URL, Total_job, Input_keyword, Input_classification, Input_where). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 240,
      "width": 340,
      "height": 137,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { document.querySelectorAll('#uscraper-has-next').forEach(e => e.remove()); const currentUrl ...` 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-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `#uscraper-has-next`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 240,
      "width": 340,
      "height": 136,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const marker = document.querySelector('#uscraper-has-next'); const nextUrl = marker?.getAtt...` 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-2"
      }
    },
    {
      "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": 1760,
      "position_y": 560,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}