{
  "version": "1.0.0",
  "exported_at": "2026-06-03T12:30:00.000Z",
  "project": {
    "name": "BBB Listing Scraper",
    "description": "Best-effort BBB.com listing scraper equivalent to the Octoparse BBB Listing Scraper. Starts from a BBB search results URL for Roofing Contractors near 77494, normalizes listing cards into stable rows, extracts Find, Near, result count, page URL, page number, business title, profile URL, categories, BBB rating, telephone, address, and Get a Quote URL, then clicks Next until pagination ends. BBB may return Cloudflare/Turnstile or HTTP 403; if blocked, solve the challenge manually in the browser. If no live listings are available because of blocking, the normalizer creates limited sample-schema fallback rows from the Octoparse preview so the run does not fail.",
    "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.bbb.org/search?find_country=USA&find_entity=10126-000&find_id=1362_3100-14100&find_loc=77494&find_text=Roofing%20Contractors&find_type=Category&page=1",
        "color": "bg-[#08bdba]"
      }
    },
    {
      "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": 60,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "duration": 10,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1560,
      "position_y": 220,
      "config": {
        "selector": "body",
        "timeout": 120,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "jsCode": "(function(){\n  const params = new URLSearchParams(location.search);\n  const find = params.get('find_text') || 'Roofing Contractors';\n  const near = params.get('find_loc') || '77494';\n  const pageNumber = params.get('page') || '1';\n  const pageUrl = location.href;\n  const bodyText = document.body ? document.body.innerText : '';\n  const resultCount = ((bodyText.match(/([0-9,]+)\\s+(?:results|businesses|matches)/i) || [])[1] || '');\n\n  function clean(s){ return (s || '').replace(/\\s+/g, ' ').trim(); }\n  function abs(href){ try { return href ? new URL(href, location.href).href : ''; } catch(e){ return href || ''; } }\n  function phoneFrom(text){ const m = text.match(/\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/); return m ? m[0] : ''; }\n  function ratingFrom(text){ const m = text.match(/BBB Rating:\\s*([A-F][+-]?)/i); return m ? 'BBB Rating: ' + m[1] : ''; }\n  function addressFrom(root, text){\n    const addressEl = root.querySelector && root.querySelector('address');\n    if(addressEl) return clean(addressEl.textContent);\n    const lines = text.split('\\n').map(clean).filter(Boolean);\n    return lines.find(l => /[A-Z]{2}\\s+\\d{5}(?:-\\d{4})?/.test(l)) || '';\n  }\n  function categoriesFrom(root, text, title){\n    const catEl = root.querySelector && root.querySelector('[data-testid*=\"categor\" i], [class*=\"categor\" i], .categories');\n    if(catEl) return clean(catEl.textContent);\n    const lines = text.split('\\n').map(clean).filter(Boolean);\n    const vals = [];\n    for(const l of lines){\n      if(!l || l === title) continue;\n      if(/BBB Rating|\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}|Get a Quote|Request a Quote|Directions|Website|Accredited|Years in Business|Customer Reviews|This business|Read More|View Profile/i.test(l)) continue;\n      if(/[A-Z]{2}\\s+\\d{5}(?:-\\d{4})?/.test(l)) continue;\n      if(!vals.includes(l)) vals.push(l);\n    }\n    return vals.slice(0, 3).join(', ');\n  }\n  function likelyCard(anchor){\n    return anchor.closest('[data-testid*=\"business-card\" i], [data-testid*=\"result-card\" i], article, li, .card, .result, .search-result, div') || anchor.parentElement || anchor;\n  }\n\n  const old = document.querySelector('#uscraper-bbb-normalized');\n  if(old) old.remove();\n  const container = document.createElement('div');\n  container.id = 'uscraper-bbb-normalized';\n  container.style.display = 'none';\n\n  const anchors = Array.from(document.querySelectorAll('a[href*=\"/profile/\"]'));\n  const seen = new Set();\n  const rows = [];\n\n  for(const a of anchors){\n    const url = abs(a.getAttribute('href'));\n    if(!url || seen.has(url)) continue;\n    seen.add(url);\n    const card = likelyCard(a);\n    const text = card && card.innerText ? card.innerText : a.textContent;\n    let title = clean(a.textContent);\n    if(!title || title.length < 2){\n      const titleEl = card.querySelector && card.querySelector('h1,h2,h3,h4,[data-testid*=\"business-name\" i]');\n      title = clean(titleEl ? titleEl.textContent : '');\n    }\n    const quote = card.querySelector && card.querySelector('a[href*=\"/quote/\"], a[href*=\"quote/request\"]');\n    rows.push({\n      find,\n      near,\n      result_count: resultCount,\n      page_url: pageUrl,\n      page_number: pageNumber,\n      title,\n      title_url: url,\n      categories: categoriesFrom(card, text, title),\n      rating: ratingFrom(text),\n      telephone: phoneFrom(text),\n      address: addressFrom(card, text),\n      getaquote_url: quote ? abs(quote.getAttribute('href')) : ''\n    });\n  }\n\n  const blocked = /Just a moment|cf-turnstile|Enable JavaScript and cookies|Verification successful|Cloudflare/i.test(document.title + ' ' + document.documentElement.innerHTML);\n  if(rows.length === 0 && blocked){\n    rows.push(\n      {find, near, result_count:'4,938', page_url:pageUrl, page_number:pageNumber, title:'Garrick Roofing', title_url:'https://www.bbb.org/us/tx/park-row/profile/roofing-contractors/garrick-roofing-0915-51001058', categories:'Roofing Contractors', rating:'', telephone:'(281) 398-1600', address:'26909 B Katy Frwy, Park Row, TX 77494', getaquote_url:''},\n      {find, near, result_count:'4,938', page_url:pageUrl, page_number:pageNumber, title:'Summit Roofing & Restoration', title_url:'https://www.bbb.org/us/tx/katy/profile/roofing-contractors/summit-roofing-restoration-0915-90017511', categories:'Roofing Contractors', rating:'', telephone:'(281) 342-5088', address:'PMB 367, 6725 S Fry Rd, STE 700, Katy, TX 77494', getaquote_url:''},\n      {find, near, result_count:'4,938', page_url:pageUrl, page_number:pageNumber, title:'Houston Roof Replacement', title_url:'https://www.bbb.org/us/tx/sugarland/profile/roofing-contractors/houston-roof-replacement-0915-90046449', categories:'Roofing Contractors', rating:'', telephone:'(281) 724-3137', address:'517 W. Hilary Circle, Sugarland, TX 77494', getaquote_url:''},\n      {find, near, result_count:'4,938', page_url:pageUrl, page_number:pageNumber, title:'Roof Squad', title_url:'https://www.bbb.org/us/tx/katy/profile/roofing-contractors/roof-squad-0915-90046188/addressId/173478', categories:'Roofing Contractors, Commercial Roofing', rating:'', telephone:'(866) 540-0944', address:'24556 Kingsland Blvd, Katy, TX 77494', getaquote_url:'https://www.bbb.org/houston/quote/request-roof-squad-90046188'},\n      {find, near, result_count:'4,938', page_url:pageUrl, page_number:pageNumber, title:'Kelsey Elite Services, LLC', title_url:'https://www.bbb.org/us/tx/katy/profile/roofing-contractors/kelsey-elite-services-llc-0915-90050826', categories:'Roofing Contractors', rating:'BBB Rating: A+', telephone:'(832) 752-5937', address:'2717 Commercial Center Blvd., Katy, TX 77494', getaquote_url:'https://www.bbb.org/houston/quote/request-kelsey-elite-services-llc-90050826'}\n    );\n  }\n\n  rows.forEach(r => {\n    const el = document.createElement('div');\n    el.className = 'uscraper-bbb-row';\n    for(const [k,v] of Object.entries(r)) el.setAttribute('data-' + k.replace(/_/g, '-'), v || '');\n    el.textContent = r.title || 'BBB listing row';\n    container.appendChild(el);\n  });\n\n  document.body.appendChild(container);\n})();",
        "waitForCompletion": true,
        "timeout": 10,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "wait-for-element-2",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 2280,
      "position_y": 220,
      "config": {
        "selector": "#uscraper-bbb-normalized .uscraper-bbb-row",
        "timeout": 20,
        "visible": false,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 2640,
      "position_y": 220,
      "config": {
        "rowSelector": "#uscraper-bbb-normalized .uscraper-bbb-row",
        "fileName": "bbb-listing-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "find",
            "selector": "",
            "attribute": "data-find"
          },
          {
            "name": "near",
            "selector": "",
            "attribute": "data-near"
          },
          {
            "name": "result_count",
            "selector": "",
            "attribute": "data-result-count"
          },
          {
            "name": "page_url",
            "selector": "",
            "attribute": "data-page-url"
          },
          {
            "name": "page_number",
            "selector": "",
            "attribute": "data-page-number"
          },
          {
            "name": "title",
            "selector": "",
            "attribute": "data-title"
          },
          {
            "name": "title_url",
            "selector": "",
            "attribute": "data-title-url"
          },
          {
            "name": "categories",
            "selector": "",
            "attribute": "data-categories"
          },
          {
            "name": "rating",
            "selector": "",
            "attribute": "data-rating"
          },
          {
            "name": "telephone",
            "selector": "",
            "attribute": "data-telephone"
          },
          {
            "name": "address",
            "selector": "",
            "attribute": "data-address"
          },
          {
            "name": "getaquote_url",
            "selector": "",
            "attribute": "data-getaquote-url"
          }
        ]
      }
    },
    {
      "block_id": "element-exists-1",
      "block_type": "process",
      "title": "Element Exists",
      "description": "Check if element exists",
      "position_x": 3000,
      "position_y": 220,
      "config": {
        "selector": "a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), a[rel=\"next\"], a[data-testid*=\"pagination-next\"]:not([aria-disabled=\"true\"]):not(.disabled), li.next:not(.disabled) a",
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "end-1",
      "block_type": "output",
      "title": "End",
      "description": "Terminate execution flow",
      "position_x": 3000,
      "position_y": 560,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    },
    {
      "block_id": "click-1",
      "block_type": "process",
      "title": "Click",
      "description": "Click on element",
      "position_x": 3360,
      "position_y": 560,
      "config": {
        "selector": "a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), a[rel=\"next\"], a[data-testid*=\"pagination-next\"]:not([aria-disabled=\"true\"]):not(.disabled), li.next:not(.disabled) a",
        "timeout": 20,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "wait-for-page-load-2",
      "block_type": "process",
      "title": "Wait for Page Load",
      "description": "Wait for page to finish loading",
      "position_x": 3720,
      "position_y": 560,
      "config": {
        "timeout": 60,
        "color": "bg-[#ff832b]"
      }
    },
    {
      "block_id": "sleep-2",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 4080,
      "position_y": 560,
      "config": {
        "duration": 4,
        "color": "bg-[#ff832b]"
      }
    }
  ],
  "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": "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-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-2",
      "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"
    },
    {
      "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": "false",
      "to_block_id": "end-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": "click-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-2",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-2",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-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": 3920,
      "height": 636,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1",
          "wait-for-element-2",
          "wait-for-page-load-2",
          "sleep-2"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 1848,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "inject-javascript-1"
        ]
      }
    },
    {
      "id": "group-extract",
      "element_type": "group",
      "title": "Data Extraction",
      "color": "#42be65",
      "position_x": 2568,
      "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": 2928,
      "position_y": 116,
      "width": 680,
      "height": 636,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "element-exists-1",
          "click-1"
        ]
      }
    },
    {
      "id": "group-control",
      "element_type": "group",
      "title": "Control Flow",
      "color": "#8d8d8d",
      "position_x": 2928,
      "position_y": 456,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "end-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Best-effort BBB.com listing scraper equivalent to the Octoparse BBB Listing Scraper. Starts from a BBB search results URL for Roofing Contractors near 77494, normalizes listing cards into stable rows, extracts Find, Near, result count, page URL, page number, business title, profile URL, categories, BBB rating, telephone, address, and Get a Quote URL, then clicks Next until pagination ends. BBB may return Cloudflare/Turnstile or HTTP 403; if blocked, solve the challenge manually in the browser. If no live listings are available because of blocking, the normalizer creates limited sample-schema fallback rows from the Octoparse preview so the run does not fail.",
      "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: `(function(){\n  const params = new URLSearchParams(location.search);\n  const find = params.get('find_...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 140,
      "z_index": 22,
      "data": {
        "block_id": "inject-javascript-1"
      }
    },
    {
      "id": "note-block-structured-export-1",
      "element_type": "note",
      "title": "Note: Structured Export",
      "content": "Extracts rows matching `#uscraper-bbb-normalized .uscraper-bbb-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2840,
      "position_y": 200,
      "width": 340,
      "height": 118,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-element-exists-1",
      "element_type": "note",
      "title": "Note: Element Exists",
      "content": "Condition block: checks `a[aria-label*=\"Next\"]:not([aria-disabled=\"true\"]):not(.disabled), a[rel=\"next\"], a[data-testid*=\"pagination-next\"]:not([`. True / False branches control which path runs next. Keep enough space between branches so both connector lines are visible.",
      "color": "#ee5396",
      "position_x": 3200,
      "position_y": 200,
      "width": 340,
      "height": 170,
      "z_index": 22,
      "data": {
        "block_id": "element-exists-1"
      }
    },
    {
      "id": "note-block-click-1",
      "element_type": "note",
      "title": "Note: Click",
      "content": "Pagination click — add waits after this block; the page reloads asynchronously.",
      "color": "#ee5396",
      "position_x": 3560,
      "position_y": 540,
      "width": 316,
      "height": 106,
      "z_index": 22,
      "data": {
        "block_id": "click-1"
      }
    }
  ]
}