{
  "version": "1.0.0",
  "exported_at": "2026-06-02T18:00:00.000Z",
  "project": {
    "name": "GoodFirms Scraper Service Directory",
    "description": "Scrapes GoodFirms.co service directory listing pages by URL, extracting company_name, profile_tagline, company_logo, rating, number_of_review, url_company, firm_pricing, firm_employees, firm_founded, and firm_location. Navigation strategy: iterates the supplied GoodFirms category URLs, waits/retries once if GoodFirms shows a bot/security verification page, builds stable synthetic rows from detected provider cards, exports rows in append mode, follows detected Next pagination, and advances to the next input URL. Limitation observed in autonomous testing: GoodFirms served a persistent security verification page to the automated browser; when that happens, this template intentionally skips the blocked URL rather than exporting invalid security-page data. For best results, run with a trusted browser profile/session, appropriate network reputation, or after manually passing site verification.",
    "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": 456,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.goodfirms.co/artificial-intelligence",
          "https://www.goodfirms.co/directory/platforms/top-web-design-companies",
          "https://www.goodfirms.co/ecommerce-development-companies/woocommerce"
        ],
        "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": 792,
      "position_y": 220,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 1128,
      "position_y": 220,
      "config": {
        "jsCode": "(() => { const btn = Array.from(document.querySelectorAll('button,a')).find(el => /^(accept|accept all|allow all|agree|got it|ok|close)$/i.test((el.textContent || '').trim())); if (btn) btn.click(); return true; })();",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1464,
      "position_y": 220,
      "config": {
        "duration": 10,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "text-contains-1",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 1800,
      "position_y": 220,
      "config": {
        "selector": "body",
        "text": "not a bot",
        "caseSensitive": false,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 2136,
      "position_y": 520,
      "config": {
        "duration": 10,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "refresh-1",
      "block_type": "process",
      "title": "Refresh",
      "description": "Reload current page",
      "position_x": 2472,
      "position_y": 520,
      "config": {
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 2808,
      "position_y": 520,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-3",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 3144,
      "position_y": 520,
      "config": {
        "duration": 8,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "text-contains-2",
      "block_type": "process",
      "title": "Text Contains",
      "description": "Check if page contains text",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "selector": "body",
        "text": "not a bot",
        "caseSensitive": false,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "inject-javascript-2",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 3480,
      "position_y": 520,
      "config": {
        "jsCode": "(() => { const old = document.querySelector('#uscraper-goodfirms-results'); if (old) old.remove(); document.querySelectorAll('[data-uscraper-goodfirms-next]').forEach(el => el.removeAttribute('data-uscraper-goodfirms-next')); const pageText = document.body ? (document.body.innerText || document.body.textContent || '') : ''; if (/security service|malicious bots|not a bot|cloudflare/i.test(pageText)) return { blocked: true, rows: 0 }; const clean = v => (v || '').replace(/\\s+/g, ' ').trim(); const abs = href => { try { return new URL(href, location.href).href; } catch (_) { return href || ''; } }; const isGoodFirms = href => { try { const h = new URL(href, location.href).hostname; return h === 'goodfirms.co' || h.endsWith('.goodfirms.co'); } catch (_) { return false; } }; const isExternal = href => { try { const u = new URL(href, location.href); return /^https?:/i.test(u.href) && !(u.hostname === 'goodfirms.co' || u.hostname.endsWith('.goodfirms.co')) && !/cloudflare\\.com/i.test(u.hostname); } catch (_) { return false; } }; const uniq = arr => Array.from(new Set(arr.filter(Boolean))); const extract = el => { const text = clean(el.innerText || el.textContent || ''); const links = Array.from(el.querySelectorAll('a[href],a[data-href]')); const imgs = Array.from(el.querySelectorAll('img[src],img[data-src],img[data-lazy-src]')); const headings = Array.from(el.querySelectorAll('h1,h2,h3,h4,.firm-name,.company-name,[class*=name],[class*=title]')).map(x => clean(x.textContent)).filter(t => t && t.length < 100 && !/^(visit website|view profile|reviews?|next|previous|read more|sponsored)$/i.test(t)); const profileLinks = links.filter(a => isGoodFirms(a.getAttribute('href') || a.getAttribute('data-href') || '')).map(a => clean(a.textContent)).filter(t => t && t.length < 100 && !/^(visit website|view profile|reviews?|read more)$/i.test(t)); const company_name = uniq(headings.concat(profileLinks))[0] || ''; const ext = links.find(a => /visit\\s+website|website/i.test(clean(a.textContent)) && isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')) || links.find(a => isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')); const url_company = ext ? abs(ext.getAttribute('href') || ext.getAttribute('data-href') || '') : ''; const logo = imgs.find(img => /assets\\.goodfirms|goodfirms/i.test(img.getAttribute('src') || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || '')) || imgs[0]; const company_logo = logo ? abs(logo.getAttribute('data-src') || logo.getAttribute('data-lazy-src') || logo.getAttribute('src') || '') : ''; const ratingMatch = text.match(/\\b[0-5](?:\\.\\d)?\\b(?=\\s*(?:\\/\\s*5|\\d+\\s+Reviews?|Reviews?))/i) || text.match(/Rating\\s*:?\\s*([0-5](?:\\.\\d)?)/i); const rating = ratingMatch ? (ratingMatch[1] || ratingMatch[0]) : ''; const reviewMatch = text.match(/\\b\\d+\\s+Reviews?\\b/i); const number_of_review = reviewMatch ? reviewMatch[0] : ''; const priceMatch = text.match(/(<\\s*)?\\$\\d[\\d,]*(?:\\s*-\\s*\\$\\d[\\d,]*)?\\s*\\/\\s*hr/i); const firm_pricing = priceMatch ? priceMatch[0].replace(/\\s+/g, ' ').trim() : ''; const noRate = text.replace(/(<\\s*)?\\$\\d[\\d,]*(?:\\s*-\\s*\\$\\d[\\d,]*)?\\s*\\/\\s*hr/ig, ' '); const employeeMatches = noRate.match(/\\b\\d{1,3}(?:,\\d{3})?\\s*-\\s*\\d{1,3}(?:,\\d{3})?\\b|\\b\\d{1,3}(?:,\\d{3})?\\+\\b/g); const firm_employees = employeeMatches ? (employeeMatches.find(v => !/^(19|20)\\d{2}$/.test(v.trim())) || '') : ''; const foundedMatch = text.match(/\\b(19|20)\\d{2}\\b/); const firm_founded = foundedMatch ? foundedMatch[0] : ''; const locNode = Array.from(el.querySelectorAll('[class*=location],[class*=address],[class*=city],[class*=country]')).map(x => clean(x.textContent)).find(t => t && t.length < 120 && !/Reviews?|\\/\\s*hr|Visit Website|View Profile/i.test(t)); const locMatch = text.match(/\\b[A-Z][a-zA-Z .'-]+,\\s*[A-Z][a-zA-Z .'-]+\\b(?!\\s*Reviews?)/); const firm_location = locNode || (locMatch ? locMatch[0] : ''); const taglineNode = Array.from(el.querySelectorAll('p,.tagline,.profile-tagline,.firm-tagline,.description,[class*=desc]')).map(x => clean(x.textContent)).find(t => t && t.length > 5 && t.length < 220 && t !== company_name && !/Reviews?|\\/\\s*hr|Visit Website|View Profile/i.test(t)); let profile_tagline = taglineNode || ''; if (!profile_tagline && company_name) { const after = text.slice(text.indexOf(company_name) + company_name.length).replace(number_of_review, '').replace(firm_pricing, '').trim(); profile_tagline = after.split(/Visit Website|View Profile|\\$\\d|\\d+ Reviews?/i)[0].trim().slice(0, 180); } return { company_name, profile_tagline, company_logo, rating, number_of_review, url_company, firm_pricing, firm_employees, firm_founded, firm_location, sourceText: text }; }; const possible = []; Array.from(document.querySelectorAll('[class*=firm],[class*=company],[class*=listing],[class*=provider],[class*=service],[class*=profile],[class*=card],article,li,section,div')).forEach(el => { if (el.id === 'uscraper-goodfirms-results' || el.closest('#uscraper-goodfirms-results')) return; const text = clean(el.innerText || el.textContent || ''); if (text.length < 25 || text.length > 5000) return; if (/security service|malicious bots|not a bot|cloudflare/i.test(text)) return; const hasSignals = /\\b\\d+\\s+Reviews?\\b/i.test(text) || /\\/\\s*hr/i.test(text) || /Visit\\s+Website/i.test(text) || Array.from(el.querySelectorAll('a[href],a[data-href]')).some(a => isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')); if (hasSignals) possible.push(el); }); const scored = possible.map(el => { const d = extract(el); let score = 0; if (d.company_name && !/^www\\.goodfirms\\.co$/i.test(d.company_name)) score += 5; if (d.url_company) score += 5; if (d.number_of_review) score += 4; if (d.firm_pricing) score += 4; if (d.company_logo) score += 2; if (d.firm_employees) score += 1; if (d.firm_founded) score += 1; if (d.firm_location) score += 1; return { el, d, score, len: d.sourceText.length }; }).filter(x => x.score >= 6).sort((a,b) => a.len - b.len || b.score - a.score); const selected = []; for (const item of scored) { if (selected.some(s => s.el.contains(item.el))) continue; if (selected.some(s => item.el.contains(s.el))) continue; const key = (item.d.company_name || '') + '|' + (item.d.url_company || ''); if (selected.some(s => ((s.d.company_name || '') + '|' + (s.d.url_company || '')) === key && key !== '|')) continue; selected.push(item); if (selected.length >= 50) break; } const box = document.createElement('div'); box.id = 'uscraper-goodfirms-results'; box.style.cssText = 'display:block!important;visibility:visible!important;opacity:1!important;position:relative!important;background:#fff!important;color:#000!important;padding:8px!important;margin:8px!important;border:2px solid #42be65!important;'; selected.forEach((item, idx) => { const d = item.d; const row = document.createElement('div'); row.className = 'us-row'; row.setAttribute('data-index', String(idx + 1)); row.style.cssText = 'display:block!important;visibility:visible!important;min-height:24px!important;'; ['company_name','profile_tagline','company_logo','rating','number_of_review','url_company','firm_pricing','firm_employees','firm_founded','firm_location'].forEach(k => { const span = document.createElement('span'); span.className = k; span.textContent = clean(d[k]); row.appendChild(span); }); box.appendChild(row); }); document.body.prepend(box); const next = Array.from(document.querySelectorAll('a[href],button,[role=button]')).find(el => { if (el.closest('#uscraper-goodfirms-results')) return false; const text = clean(el.textContent || ''); const aria = clean(el.getAttribute('aria-label') || el.getAttribute('title') || ''); const rel = clean(el.getAttribute('rel') || ''); const cls = clean(el.className || ''); const disabled = el.disabled || el.getAttribute('aria-disabled') === 'true' || /disabled/i.test(cls) || /disabled/i.test(clean(el.closest('li')?.className || '')); return !disabled && (/^(next|next page|›|»|>)$/i.test(text) || /next/i.test(aria) || /next/i.test(rel) || /next/i.test(cls)); }); if (next) next.setAttribute('data-uscraper-goodfirms-next', 'true'); return { rows: selected.length, next: !!next }; })();",
        "waitForCompletion": true,
        "timeout": 20,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-exists-2",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 2304,
      "position_y": 800,
      "config": {
        "selector": "#uscraper-goodfirms-results .us-row",
        "color": "bg-[#42be65]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2640,
      "position_y": 800,
      "config": {
        "selector": "#uscraper-goodfirms-results .us-row",
        "timeout": 20,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2976,
      "position_y": 800,
      "config": {
        "rowSelector": "#uscraper-goodfirms-results .us-row",
        "fileName": "goodfirms-scraper-service-directory.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "company_name",
            "selector": ".company_name",
            "attribute": "text"
          },
          {
            "name": "profile_tagline",
            "selector": ".profile_tagline",
            "attribute": "text"
          },
          {
            "name": "company_logo",
            "selector": ".company_logo",
            "attribute": "text"
          },
          {
            "name": "rating",
            "selector": ".rating",
            "attribute": "text"
          },
          {
            "name": "number_of_review",
            "selector": ".number_of_review",
            "attribute": "text"
          },
          {
            "name": "url_company",
            "selector": ".url_company",
            "attribute": "text"
          },
          {
            "name": "firm_pricing",
            "selector": ".firm_pricing",
            "attribute": "text"
          },
          {
            "name": "firm_employees",
            "selector": ".firm_employees",
            "attribute": "text"
          },
          {
            "name": "firm_founded",
            "selector": ".firm_founded",
            "attribute": "text"
          },
          {
            "name": "firm_location",
            "selector": ".firm_location",
            "attribute": "text"
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3312,
      "position_y": 800,
      "config": {
        "selector": "[data-uscraper-goodfirms-next=\"true\"]",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3648,
      "position_y": 800,
      "config": {
        "selector": "[data-uscraper-goodfirms-next=\"true\"]",
        "timeout": 15,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-page-load-3",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3984,
      "position_y": 800,
      "config": {
        "timeout": 45,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-4",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 4320,
      "position_y": 800,
      "config": {
        "duration": 6,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-3",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 4656,
      "position_y": 800,
      "config": {
        "jsCode": "(() => { const old = document.querySelector('#uscraper-goodfirms-results'); if (old) old.remove(); document.querySelectorAll('[data-uscraper-goodfirms-next]').forEach(el => el.removeAttribute('data-uscraper-goodfirms-next')); const pageText = document.body ? (document.body.innerText || document.body.textContent || '') : ''; if (/security service|malicious bots|not a bot|cloudflare/i.test(pageText)) return { blocked: true, rows: 0 }; const clean = v => (v || '').replace(/\\s+/g, ' ').trim(); const abs = href => { try { return new URL(href, location.href).href; } catch (_) { return href || ''; } }; const isGoodFirms = href => { try { const h = new URL(href, location.href).hostname; return h === 'goodfirms.co' || h.endsWith('.goodfirms.co'); } catch (_) { return false; } }; const isExternal = href => { try { const u = new URL(href, location.href); return /^https?:/i.test(u.href) && !(u.hostname === 'goodfirms.co' || u.hostname.endsWith('.goodfirms.co')) && !/cloudflare\\.com/i.test(u.hostname); } catch (_) { return false; } }; const uniq = arr => Array.from(new Set(arr.filter(Boolean))); const extract = el => { const text = clean(el.innerText || el.textContent || ''); const links = Array.from(el.querySelectorAll('a[href],a[data-href]')); const imgs = Array.from(el.querySelectorAll('img[src],img[data-src],img[data-lazy-src]')); const headings = Array.from(el.querySelectorAll('h1,h2,h3,h4,.firm-name,.company-name,[class*=name],[class*=title]')).map(x => clean(x.textContent)).filter(t => t && t.length < 100 && !/^(visit website|view profile|reviews?|next|previous|read more|sponsored)$/i.test(t)); const profileLinks = links.filter(a => isGoodFirms(a.getAttribute('href') || a.getAttribute('data-href') || '')).map(a => clean(a.textContent)).filter(t => t && t.length < 100 && !/^(visit website|view profile|reviews?|read more)$/i.test(t)); const company_name = uniq(headings.concat(profileLinks))[0] || ''; const ext = links.find(a => /visit\\s+website|website/i.test(clean(a.textContent)) && isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')) || links.find(a => isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')); const url_company = ext ? abs(ext.getAttribute('href') || ext.getAttribute('data-href') || '') : ''; const logo = imgs.find(img => /assets\\.goodfirms|goodfirms/i.test(img.getAttribute('src') || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || '')) || imgs[0]; const company_logo = logo ? abs(logo.getAttribute('data-src') || logo.getAttribute('data-lazy-src') || logo.getAttribute('src') || '') : ''; const ratingMatch = text.match(/\\b[0-5](?:\\.\\d)?\\b(?=\\s*(?:\\/\\s*5|\\d+\\s+Reviews?|Reviews?))/i) || text.match(/Rating\\s*:?\\s*([0-5](?:\\.\\d)?)/i); const rating = ratingMatch ? (ratingMatch[1] || ratingMatch[0]) : ''; const reviewMatch = text.match(/\\b\\d+\\s+Reviews?\\b/i); const number_of_review = reviewMatch ? reviewMatch[0] : ''; const priceMatch = text.match(/(<\\s*)?\\$\\d[\\d,]*(?:\\s*-\\s*\\$\\d[\\d,]*)?\\s*\\/\\s*hr/i); const firm_pricing = priceMatch ? priceMatch[0].replace(/\\s+/g, ' ').trim() : ''; const noRate = text.replace(/(<\\s*)?\\$\\d[\\d,]*(?:\\s*-\\s*\\$\\d[\\d,]*)?\\s*\\/\\s*hr/ig, ' '); const employeeMatches = noRate.match(/\\b\\d{1,3}(?:,\\d{3})?\\s*-\\s*\\d{1,3}(?:,\\d{3})?\\b|\\b\\d{1,3}(?:,\\d{3})?\\+\\b/g); const firm_employees = employeeMatches ? (employeeMatches.find(v => !/^(19|20)\\d{2}$/.test(v.trim())) || '') : ''; const foundedMatch = text.match(/\\b(19|20)\\d{2}\\b/); const firm_founded = foundedMatch ? foundedMatch[0] : ''; const locNode = Array.from(el.querySelectorAll('[class*=location],[class*=address],[class*=city],[class*=country]')).map(x => clean(x.textContent)).find(t => t && t.length < 120 && !/Reviews?|\\/\\s*hr|Visit Website|View Profile/i.test(t)); const locMatch = text.match(/\\b[A-Z][a-zA-Z .'-]+,\\s*[A-Z][a-zA-Z .'-]+\\b(?!\\s*Reviews?)/); const firm_location = locNode || (locMatch ? locMatch[0] : ''); const taglineNode = Array.from(el.querySelectorAll('p,.tagline,.profile-tagline,.firm-tagline,.description,[class*=desc]')).map(x => clean(x.textContent)).find(t => t && t.length > 5 && t.length < 220 && t !== company_name && !/Reviews?|\\/\\s*hr|Visit Website|View Profile/i.test(t)); let profile_tagline = taglineNode || ''; if (!profile_tagline && company_name) { const after = text.slice(text.indexOf(company_name) + company_name.length).replace(number_of_review, '').replace(firm_pricing, '').trim(); profile_tagline = after.split(/Visit Website|View Profile|\\$\\d|\\d+ Reviews?/i)[0].trim().slice(0, 180); } return { company_name, profile_tagline, company_logo, rating, number_of_review, url_company, firm_pricing, firm_employees, firm_founded, firm_location, sourceText: text }; }; const possible = []; Array.from(document.querySelectorAll('[class*=firm],[class*=company],[class*=listing],[class*=provider],[class*=service],[class*=profile],[class*=card],article,li,section,div')).forEach(el => { if (el.id === 'uscraper-goodfirms-results' || el.closest('#uscraper-goodfirms-results')) return; const text = clean(el.innerText || el.textContent || ''); if (text.length < 25 || text.length > 5000) return; if (/security service|malicious bots|not a bot|cloudflare/i.test(text)) return; const hasSignals = /\\b\\d+\\s+Reviews?\\b/i.test(text) || /\\/\\s*hr/i.test(text) || /Visit\\s+Website/i.test(text) || Array.from(el.querySelectorAll('a[href],a[data-href]')).some(a => isExternal(a.getAttribute('href') || a.getAttribute('data-href') || '')); if (hasSignals) possible.push(el); }); const scored = possible.map(el => { const d = extract(el); let score = 0; if (d.company_name && !/^www\\.goodfirms\\.co$/i.test(d.company_name)) score += 5; if (d.url_company) score += 5; if (d.number_of_review) score += 4; if (d.firm_pricing) score += 4; if (d.company_logo) score += 2; if (d.firm_employees) score += 1; if (d.firm_founded) score += 1; if (d.firm_location) score += 1; return { el, d, score, len: d.sourceText.length }; }).filter(x => x.score >= 6).sort((a,b) => a.len - b.len || b.score - a.score); const selected = []; for (const item of scored) { if (selected.some(s => s.el.contains(item.el))) continue; if (selected.some(s => item.el.contains(s.el))) continue; const key = (item.d.company_name || '') + '|' + (item.d.url_company || ''); if (selected.some(s => ((s.d.company_name || '') + '|' + (s.d.url_company || '')) === key && key !== '|')) continue; selected.push(item); if (selected.length >= 50) break; } const box = document.createElement('div'); box.id = 'uscraper-goodfirms-results'; box.style.cssText = 'display:block!important;visibility:visible!important;opacity:1!important;position:relative!important;background:#fff!important;color:#000!important;padding:8px!important;margin:8px!important;border:2px solid #42be65!important;'; selected.forEach((item, idx) => { const d = item.d; const row = document.createElement('div'); row.className = 'us-row'; row.setAttribute('data-index', String(idx + 1)); row.style.cssText = 'display:block!important;visibility:visible!important;min-height:24px!important;'; ['company_name','profile_tagline','company_logo','rating','number_of_review','url_company','firm_pricing','firm_employees','firm_founded','firm_location'].forEach(k => { const span = document.createElement('span'); span.className = k; span.textContent = clean(d[k]); row.appendChild(span); }); box.appendChild(row); }); document.body.prepend(box); const next = Array.from(document.querySelectorAll('a[href],button,[role=button]')).find(el => { if (el.closest('#uscraper-goodfirms-results')) return false; const text = clean(el.textContent || ''); const aria = clean(el.getAttribute('aria-label') || el.getAttribute('title') || ''); const rel = clean(el.getAttribute('rel') || ''); const cls = clean(el.className || ''); const disabled = el.disabled || el.getAttribute('aria-disabled') === 'true' || /disabled/i.test(cls) || /disabled/i.test(clean(el.closest('li')?.className || '')); return !disabled && (/^(next|next page|›|»|>)$/i.test(text) || /next/i.test(aria) || /next/i.test(rel) || /next/i.test(cls)); }); if (next) next.setAttribute('data-uscraper-goodfirms-next', 'true'); return { rows: selected.length, next: !!next }; })();",
        "waitForCompletion": true,
        "timeout": 20,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "element-exists-3",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 4992,
      "position_y": 800,
      "config": {
        "selector": "#uscraper-goodfirms-results .us-row",
        "color": "bg-[#42be65]"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 5328,
      "position_y": 800,
      "config": {
        "selector": "#uscraper-goodfirms-results .us-row",
        "timeout": 20,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 3816,
      "position_y": 520,
      "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": "text-contains-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "true",
      "to_block_id": "sleep-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-1",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "refresh-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "refresh-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": "text-contains-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "true",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "text-contains-2",
      "from_connector_id": "false",
      "to_block_id": "inject-javascript-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-2",
      "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": "wait-for-element-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": "wait-for-element-1",
      "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-4",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-4",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-3",
      "from_connector_id": "right",
      "to_block_id": "element-exists-3",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-3",
      "from_connector_id": "true",
      "to_block_id": "wait-for-element-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "element-exists-3",
      "from_connector_id": "false",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-2",
      "from_connector_id": "right",
      "to_block_id": "structured-export-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": 384,
      "position_y": 116,
      "width": 5192,
      "height": 876,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "sleep-2",
          "refresh-1",
          "wait-for-page-load-2",
          "sleep-3",
          "wait-for-element-1",
          "wait-for-page-load-3",
          "sleep-4",
          "wait-for-element-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1056,
      "position_y": 116,
      "width": 3848,
      "height": 876,
      "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": 1728,
      "position_y": 116,
      "width": 3512,
      "height": 876,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "text-contains-1",
          "text-contains-2",
          "element-exists-2",
          "element-exists-1",
          "click-1",
          "element-exists-3",
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2904,
      "position_y": 696,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "structured-export-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes GoodFirms.co service directory listing pages by URL, extracting company_name, profile_tagline, company_logo, rating, number_of_review, url_company, firm_pricing, firm_employees, firm_founded, and firm_location. Navigation strategy: iterates the supplied GoodFirms category URLs, waits/retries once if GoodFirms shows a bot/security verification page, builds stable synthetic rows from detected provider cards, exports rows in append mode, follows detected Next pagination, and advances to the next input URL. Limitation observed in autonomous testing: GoodFirms served a persistent security verification page to the automated browser; when that happens, this template intentionally skips the blocked URL rather than exporting invalid security-page data. For best results, run with a trusted browser profile/session, appropriate network reputation, or after manually passing site verification.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-text-contains-1",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `body`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2000,
      "position_y": 200,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-1"
      }
    },
    {
      "id": "note-block-text-contains-2",
      "element_type": "note",
      "title": "Note: Text Contains",
      "content": "Condition block: checks `body`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3680,
      "position_y": 500,
      "width": 340,
      "height": 131,
      "z_index": 22,
      "data": {
        "block_id": "text-contains-2"
      }
    },
    {
      "id": "note-block-inject-javascript-2",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => { const old = document.querySelector('#uscraper-goodfirms-results'); if (old) old.remove(); d...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 3680,
      "position_y": 500,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-2"
      }
    },
    {
      "id": "note-block-element-exists-2",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `#uscraper-goodfirms-results .us-row`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 2504,
      "position_y": 780,
      "width": 340,
      "height": 142,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-2"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-goodfirms-results .us-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 3176,
      "position_y": 780,
      "width": 340,
      "height": 116,
      "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-goodfirms-next=\"true\"]`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3512,
      "position_y": 780,
      "width": 340,
      "height": 142,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-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": 4016,
      "position_y": 500,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}