{
  "version": "1.0.0",
  "exported_at": "2026-05-31T00:00:00.000Z",
  "project": {
    "name": "Glassdoor Job Scraper",
    "description": "Best-effort Glassdoor job scraper equivalent to the Octoparse Glassdoor Job Scraper. It targets Glassdoor business analyst jobs in the United States, handles common overlays, clicks visible Load More / Show More Jobs controls until unavailable, normalizes job cards or embedded page data into stable scraper rows, and exports keyword, location, rating, company, job title, place, salary, post date, job description/snippet, keyword backup, and job URL to glassdoor-scraper.csv. Glassdoor frequently blocks automation with Cloudflare, CAPTCHA, login/sign-in walls, bot checks, or layout changes; when jobs cannot be accessed, the template emits a diagnostic fallback row rather than failing.",
    "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": {
        "url": "https://www.glassdoor.com/Job/united-states-business-analyst-jobs-SRCH_IL.0,13_IN1_KO14,30.htm",
        "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": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "selector": "body",
        "timeout": 45,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on page",
      "position_x": 1560,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const isVisible = el => !!(el && el.offsetParent !== null && getComputedStyle(el).visibility !== 'hidden' && getComputedStyle(el).display !== 'none'); const overlayPatterns = [/accept/i, /agree/i, /got it/i, /close/i, /not now/i, /maybe later/i, /^continue$/i]; for (const el of Array.from(document.querySelectorAll('button, a, [role=button]'))) { const label = clean((el.innerText || el.textContent || '') + ' ' + (el.getAttribute('aria-label') || '')); if (isVisible(el) && overlayPatterns.some(rx => rx.test(label)) && !/load more|show more|more jobs/i.test(label)) { try { el.click(); } catch (e) {} } } window.scrollTo(0, document.body.scrollHeight); for (const b of Array.from(document.querySelectorAll('button, a, [role=button]'))) { const text = clean((b.innerText || b.textContent || '') + ' ' + (b.getAttribute('aria-label') || '') + ' ' + (b.getAttribute('data-test') || '') + ' ' + (b.className || '')); if (isVisible(b) && !b.disabled && /(load|show)\\s+more|more\\s+jobs|show\\s+more\\s+jobs|loadmore/i.test(text) && !/less|previous|back/i.test(text)) { b.setAttribute('data-uscraper-load-more', 'true'); } else { b.removeAttribute('data-uscraper-load-more'); } } return true; })();",
        "waitForCompletion": true,
        "timeout": 15,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-visible-1",
      "block_type": "process",
      "title": "Element Visible",
      "description": "Check if element is visible",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "selector": "[data-uscraper-load-more=true]:not([disabled])",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 2280,
      "position_y": 540,
      "config": {
        "selector": "[data-uscraper-load-more=true]:not([disabled])",
        "timeout": 15,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2640,
      "position_y": 540,
      "config": {
        "duration": 3,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on page",
      "position_x": 3000,
      "position_y": 540,
      "config": {
        "jsCode": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const isVisible = el => !!(el && el.offsetParent !== null && getComputedStyle(el).visibility !== 'hidden' && getComputedStyle(el).display !== 'none'); window.scrollTo(0, document.body.scrollHeight); for (const b of Array.from(document.querySelectorAll('button, a, [role=button]'))) { const text = clean((b.innerText || b.textContent || '') + ' ' + (b.getAttribute('aria-label') || '') + ' ' + (b.getAttribute('data-test') || '') + ' ' + (b.className || '')); if (isVisible(b) && !b.disabled && /(load|show)\\s+more|more\\s+jobs|show\\s+more\\s+jobs|loadmore/i.test(text) && !/less|previous|back/i.test(text)) { b.setAttribute('data-uscraper-load-more', 'true'); } else { b.removeAttribute('data-uscraper-load-more'); } } return true; })();",
        "waitForCompletion": true,
        "timeout": 15,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "inject-javascript-3",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute JavaScript on page",
      "position_x": 1920,
      "position_y": 860,
      "config": {
        "jsCode": "(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const decode = s => { try { return clean(s.replace(/\\\\u002F/g, '/').replace(/\\\\n/g, ' ').replace(/\\\\r/g, ' ').replace(/\\\\t/g, ' ').replace(/\\\\\"/g, '\"').replace(/<[^>]*>/g, ' ')); } catch (e) { return clean(s); } }; const abs = href => { try { return href ? new URL(href, location.href).href : ''; } catch (e) { return href || ''; } }; const pick = (root, selectors) => { for (const s of selectors) { try { const el = root.querySelector(s); const txt = clean(el && (el.innerText || el.textContent)); if (txt) return txt; } catch (e) {} } return ''; }; const rows = []; const seen = new Set(); const add = r => { r.keyword = r.keyword || 'business analyst'; r.location = r.location || 'United States'; r.keyword_backup = r.keyword_backup || 'business analyst'; r.rating = clean(r.rating); r.company = clean(r.company); r.job_title = clean(r.job_title); r.place = clean(r.place); r.salary = clean(r.salary); r.post_date = clean(r.post_date); r.job_description = clean(r.job_description); r.job_url = abs(clean(r.job_url)); const key = (r.job_url || '') + '|' + r.company + '|' + r.job_title; if ((r.company || r.job_title || r.job_url) && !seen.has(key)) { seen.add(key); rows.push(r); } }; const candidateSelectors = ['li[data-test=jobListing]', 'li[class*=JobsList_jobListItem]', 'div[data-test=job-listing]', 'article[data-test=jobListing]', 'div[class*=JobCard]', 'div[class*=jobCard]', 'div[class*=JobListing]', 'div[class*=jobListing]']; for (const sel of candidateSelectors) { for (const card of Array.from(document.querySelectorAll(sel))) { const raw = card.innerText || card.textContent || ''; if (clean(raw).length < 20) continue; const lines = raw.split(/[\\n\\r]+/).map(clean).filter(Boolean); const linkEl = card.querySelector('a[href*=job-listing], a[href*=partner], a[data-test*=job], a[data-test*=Job], a[href]'); let jobTitle = pick(card, ['a[data-test=job-title]', '[data-test=job-title]', 'a[data-test=job-link]', '[class*=jobTitle]', '[class*=JobTitle]']) || clean(linkEl && (linkEl.innerText || linkEl.textContent)); let company = pick(card, ['[data-test=employer-name]', '[data-test=company-name]', '[data-test*=employer]', '[data-test*=company]', '[class*=employerName]', '[class*=EmployerName]', '[class*=companyName]', '[class*=CompanyName]', '[class*=compactEmployerName]']); let place = pick(card, ['[data-test=job-location]', '[data-test=location]', '[class*=location]', '[class*=Location]']); let salary = pick(card, ['[data-test=detailSalary]', '[data-test=salary-estimate]', '[data-test*=salary]', '[class*=salary]', '[class*=Salary]']); let postDate = pick(card, ['[data-test=job-age]', '[data-test=job-posted-date]', '[data-test*=age]', '[class*=jobAge]', '[class*=listingAge]', '[class*=ListingAge]']); let rating = pick(card, ['[data-test=rating]', '[data-test*=rating]', '[class*=rating]', '[class*=Rating]']); let description = pick(card, ['[data-test=job-description]', '[data-test=desc]', '[data-test*=description]', '[class*=jobDescription]', '[class*=JobDescription]', '[class*=description]', '[class*=Description]', '[class*=snippet]', '[class*=Snippet]']); if (!rating) { const m = raw.match(/\\b[1-5]\\.[0-9]\\b/); rating = m ? m[0] : ''; } if (!salary) { const m = raw.match(/\\$[0-9][0-9Kk,.]*(?:\\s*-\\s*\\$[0-9][0-9Kk,.]*)?(?:\\s*\\([^)]*\\))?/); salary = m ? clean(m[0]) : ''; } if (!postDate) { const m = raw.match(/\\b(?:[0-9]+d\\+?|[0-9]+ days? ago|today|just posted|30d\\+)\\b/i); postDate = m ? clean(m[0]) : ''; } if (!jobTitle && lines.length) jobTitle = lines[0]; if (!company && lines.length > 1) company = lines.find(l => l !== jobTitle && !/\\$|United States|Remote|Hybrid|[0-9]+d/i.test(l)) || ''; if (!place) place = lines.find(l => /,\\s*[A-Z]{2}\\b|Remote|United States|Hybrid/i.test(l)) || ''; if (!description) description = lines.filter(l => l !== jobTitle && l !== company && l !== place && l !== salary && l !== postDate && l !== rating).slice(0, 10).join(' | '); add({ rating, company, job_title: jobTitle, place, salary, post_date: postDate, job_description: description, job_url: linkEl ? linkEl.href : '' }); } } const q = String.fromCharCode(34); const getVal = (chunk, names) => { for (const name of names) { const p = chunk.indexOf(q + name + q); if (p < 0) continue; const c = chunk.indexOf(':', p); if (c < 0) continue; let s = chunk.indexOf(q, c + 1); if (s < 0) continue; let e = chunk.indexOf(q, s + 1); if (e > s) return decode(chunk.slice(s + 1, e)); } return ''; }; const scriptText = Array.from(document.scripts).map(s => s.textContent || '').join(' '); let pos = 0; while ((pos = scriptText.indexOf(q + 'jobTitle' + q, pos)) !== -1 && rows.length < 200) { const chunk = scriptText.slice(Math.max(0, pos - 3500), pos + 6500); add({ job_title: getVal(chunk, ['jobTitle', 'title']), company: getVal(chunk, ['employerName', 'companyName', 'name']), place: getVal(chunk, ['locationName', 'jobLocation', 'location']), salary: getVal(chunk, ['salaryEstimate', 'salary', 'payRange']), post_date: getVal(chunk, ['jobAge', 'listingAge', 'datePosted', 'discoverDate']), rating: getVal(chunk, ['rating', 'overallRating', 'starRating']), job_description: getVal(chunk, ['jobDescription', 'description', 'snippet']), job_url: getVal(chunk, ['jobUrl', 'jobLink', 'url', 'viewJobLink']) }); pos += 10; } if (!rows.length) { const bodyText = clean(document.body ? document.body.innerText : ''); add({ company: 'NO_RESULTS_OR_BLOCKED', job_title: 'Glassdoor results were not accessible', place: 'United States', salary: '', post_date: '', rating: '', job_description: clean((document.title || '') + ' | ' + bodyText.slice(0, 500)), job_url: location.href }); } const old = document.querySelector('#uscraper-glassdoor-results'); if (old) old.remove(); const container = document.createElement('div'); container.id = 'uscraper-glassdoor-results'; container.style.display = 'none'; for (const r of rows) { const row = document.createElement('div'); row.className = 'uscraper-job-row'; for (const key of ['keyword', 'location', 'rating', 'company', 'job_title', 'place', 'salary', 'post_date', 'job_description', 'keyword_backup', 'job_url']) { const span = document.createElement('span'); span.className = 'uscraper-' + key.replace(/_/g, '-'); span.textContent = r[key] || ''; row.appendChild(span); } container.appendChild(row); } document.body.appendChild(container); return rows.length; })();",
        "waitForCompletion": true,
        "timeout": 25,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2280,
      "position_y": 860,
      "config": {
        "rowSelector": ".uscraper-job-row",
        "fileName": "glassdoor-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "keyword",
            "selector": ".uscraper-keyword",
            "attribute": "text"
          },
          {
            "name": "location",
            "selector": ".uscraper-location",
            "attribute": "text"
          },
          {
            "name": "rating",
            "selector": ".uscraper-rating",
            "attribute": "text"
          },
          {
            "name": "company",
            "selector": ".uscraper-company",
            "attribute": "text"
          },
          {
            "name": "job_title",
            "selector": ".uscraper-job-title",
            "attribute": "text"
          },
          {
            "name": "place",
            "selector": ".uscraper-place",
            "attribute": "text"
          },
          {
            "name": "salary",
            "selector": ".uscraper-salary",
            "attribute": "text"
          },
          {
            "name": "post_date",
            "selector": ".uscraper-post-date",
            "attribute": "text"
          },
          {
            "name": "job_description",
            "selector": ".uscraper-job-description",
            "attribute": "text"
          },
          {
            "name": "keyword_backup",
            "selector": ".uscraper-keyword-backup",
            "attribute": "text"
          },
          {
            "name": "job_url",
            "selector": ".uscraper-job-url",
            "attribute": "text"
          }
        ]
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 2640,
      "position_y": 860,
      "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": "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": "element-visible-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-visible-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": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-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": "element-visible-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-visible-1",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-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": "end-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": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "wait-for-element-1",
          "sleep-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1488,
      "position_y": 116,
      "width": 1760,
      "height": 936,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1",
          "inject-javascript-2",
          "inject-javascript-3"
        ]
      }
    },
    {
      "id": "group-pagination",
      "element_type": "group",
      "title": "Pagination Loop",
      "color": "#ff832b",
      "position_x": 1848,
      "position_y": 116,
      "width": 680,
      "height": 616,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-visible-1",
          "click-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2208,
      "position_y": 756,
      "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": 2568,
      "position_y": 756,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort Glassdoor job scraper equivalent to the Octoparse Glassdoor Job Scraper. It targets Glassdoor business analyst jobs in the United States, handles common overlays, clicks visible Load More / Show More Jobs controls until unavailable, normalizes job cards or embedded page data into stable scraper rows, and exports keyword, location, rating, company, job title, place, salary, post date, job description/snippet, keyword backup, and job URL to glassdoor-scraper.csv. Glassdoor frequently blocks automation with Cloudflare, CAPTCHA, login/sign-in walls, bot checks, or layout changes; when jobs cannot be accessed, the template emits a diagnostic fallback row rather than failing.",
      "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 clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const isVisible = el => !!(el && ...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1760,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-element-visible-1",
      "element_type": "note",
      "title": "Note: Element Visible",
      "content": "Condition block: checks `[data-uscraper-load-more=true]:not([disabled])`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 145,
      "z_index": 22,
      "data": {
        "block_id": "element-visible-1"
      }
    },
    {
      "id": "note-block-inject-javascript-3",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const clean = s => (s || '').replace(/\\s+/g, ' ').trim(); const decode = s => { try { retur...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 840,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-3"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `.uscraper-job-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 840,
      "width": 340,
      "height": 110,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    }
  ]
}