{
  "version": "1.0.0",
  "exported_at": "2026-05-31T23:59:00.000Z",
  "project": {
    "name": "Yellow Pages Canada Scraper Product Details",
    "description": "Scrapes YellowPages.ca business detail pages supplied as input URLs. Uses a multi-URL navigation loop to visit each detail page, safely attempts to dismiss cookie consent with JavaScript, then exports business name, rating, address, coordinates, hours, phone, website, description, language, products/services, brands, specialities, and editor pick status to CSV. Replace the sample urls[] with your own YellowPages.ca /bus/ detail page URLs. If a supplied detail URL redirects to a YellowPages search result page, the template makes a best-effort extraction from the visible page.",
    "color": "bg-[#ffcd00]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 220,
      "config": {
        "urls": [
          "https://www.yellowpages.ca/bus/Ontario/Toronto/Bardi-s-Steak-House/156059.html?what=Steak&where=Toronto+ON&useContext=true",
          "https://www.yellowpages.ca/bus/Ontario/Toronto/Farmhouse-Tavern/100836986.html?what=Steak&where=Toronto+ON&useContext=true"
        ],
        "color": "bg-[#4589ff]",
        "tags": [
          "yellowpages",
          "detail-pages",
          "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": 220,
      "config": {
        "timeout": 30,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Execute custom JavaScript",
      "position_x": 840,
      "position_y": 220,
      "config": {
        "jsCode": "(() => {\n  const selectors = [\n    '#onetrust-accept-btn-handler',\n    '#accept-recommended-btn-handler',\n    'button[id*=\"accept\" i]',\n    'button[class*=\"accept\" i]'\n  ];\n  for (const selector of selectors) {\n    const buttons = Array.from(document.querySelectorAll(selector));\n    const visible = buttons.find(el => {\n      const style = window.getComputedStyle(el);\n      const rect = el.getBoundingClientRect();\n      return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;\n    });\n    if (visible) {\n      visible.click();\n      return 'clicked_cookie_consent';\n    }\n  }\n  return 'no_visible_cookie_consent';\n})();",
        "waitForCompletion": true,
        "timeout": 5,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 1200,
      "position_y": 220,
      "config": {
        "duration": 1,
        "color": "bg-[#a56eff]"
      }
    },
    {
      "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": 30,
        "visible": true,
        "color": "bg-[#08bdba]"
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1920,
      "position_y": 220,
      "config": {
        "rowSelector": "body",
        "fileName": "yellow-pages-canada-scraper-product-details.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "color": "bg-[#42be65]",
        "columns": [
          {
            "name": "name",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>(x&&x['@type']&&String(x['@type']).match(/LocalBusiness|Restaurant|Organization/))||x?.address||x?.telephone)||{};const q=s=>d.querySelector(s)?.textContent;let v=[lb.name,q('[itemprop=name]'),q('.merchant__name,.merchantName,.listing__name--link,.listing__name h3,.listing__name'),q('h1')].find(x=>String(x||'').trim())||'';v=String(v).replace(/\\s*\\d+(?:\\.\\d+)?\\s*\\/\\s*5\\s*$/,'').replace(/\\s*\\(\\d+\\s+Result\\(s\\)\\)\\s*/i,'').replace(/\\s+in\\s+[A-Za-z\\s]+(?:ON|QC|BC|AB|MB|SK|NS|NB|NL|PE|NT|NU|YT)\\s*$/i,'');return v.replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "rating",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.aggregateRating)||{};const visible=d.querySelector('[itemprop=ratingValue],.rating,.merchant__rating,.listing__ratings')?.textContent||'';return String(lb.aggregateRating?.ratingValue||visible).replace(/[^0-9.]/g,'').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "address",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.address)||{};const a=lb.address;let v='';if(typeof a==='string')v=a;else if(a)v=[a.streetAddress,a.addressLocality,a.addressRegion,a.postalCode].filter(Boolean).join(', ');else v=d.querySelector('[itemprop=address],.merchant__address,.listing__address,.address')?.textContent||'';return v.replace(/Get directions\\s*»?/i,'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "latitude",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.geo)||{};const h=d.documentElement.innerHTML;const m=h.match(/\"latitude\"\\s*:\\s*\"?(-?\\d+(?:\\.\\d+)?)/i)||h.match(/data-lat(?:itude)?=[\"'](-?\\d+(?:\\.\\d+)?)/i);return String(lb.geo?.latitude||m?.[1]||'')})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "longitude",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.geo)||{};const h=d.documentElement.innerHTML;const m=h.match(/\"longitude\"\\s*:\\s*\"?(-?\\d+(?:\\.\\d+)?)/i)||h.match(/data-lon(?:gitude)?=[\"'](-?\\d+(?:\\.\\d+)?)/i);return String(lb.geo?.longitude||m?.[1]||'')})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "current_status",
            "selector": "(()=>{const d=ROW.ownerDocument;const t=d.body.innerText||'';const m=t.match(/(Open until\\s+[^\\n]+|Open now|Closed now|Temporarily closed)/i);return m?m[1].replace(/\\s+/g,' ').trim():''})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "opening_hours",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.openingHoursSpecification||x?.openingHours)||{};if(lb.openingHours)return Array.isArray(lb.openingHours)?lb.openingHours.join(' | '):lb.openingHours;if(lb.openingHoursSpecification)return lb.openingHoursSpecification.map(o=>[Array.isArray(o.dayOfWeek)?o.dayOfWeek.join('/'):o.dayOfWeek,o.opens&&o.closes?o.opens+'-'+o.closes:''].filter(Boolean).join(': ')).join(' | ');const els=[...d.querySelectorAll('[itemprop=openingHours],.openHours tr,.openingHours tr,.hours tr,.merchant__hours tr,.business-hours tr,.hours-list li,.merchant__hours li')];let vals=[...new Set(els.map(e=>e.innerText||e.textContent||'').map(s=>s.replace(/\\s+/g,' ').trim()).filter(Boolean))];if(vals.length>1)return vals.join(' | ');const lines=(d.body.innerText||'').split(/\\n+/).map(s=>s.replace(/\\s+/g,' ').trim()).filter(Boolean);const days=/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)$/i;const time=/\\d{1,2}:\\d{2}\\s*(am|pm)|Closed/i;const out=[];for(let i=0;i<lines.length;i++){if(days.test(lines[i])){const day=lines[i];const slots=[];for(let j=i+1;j<Math.min(lines.length,i+5);j++){if(days.test(lines[j]))break;if(time.test(lines[j]))slots.push(lines[j]);}if(slots.length)out.push(day+': '+slots.join(' / '));}}return out.length?[...new Set(out)].join(' | '):(vals[0]||'')})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "phone",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.telephone)||{};return String(lb.telephone||d.querySelector('a[href^=tel]')?.href.replace(/^tel:/,'')||d.querySelector('[itemprop=telephone],.phone,.merchant__phone')?.textContent||'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "website",
            "selector": "(()=>{const d=ROW.ownerDocument;const links=[...d.querySelectorAll('a[href]')];const a=links.find(a=>a.href.includes('/gourl/'))||links.find(a=>/website|web site|visit/i.test(a.textContent)&&!a.href.includes('yellowpages.ca'));return a?.href||''})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "description",
            "selector": "(()=>{const d=ROW.ownerDocument;const data=[...d.scripts].map(s=>s.type.includes('ld+json')?(()=>{try{return JSON.parse(s.textContent)}catch(e){return null}})():null).flatMap(x=>Array.isArray(x)?x:[x]).filter(Boolean);const lb=data.find(x=>x?.description)||{};return String(lb.description||d.querySelector('[itemprop=description],.merchant__description,.listing__description,.description')?.textContent||d.querySelector('meta[name=description]')?.content||'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "language",
            "selector": "(()=>{const d=ROW.ownerDocument;const labs=['Language','Languages'];const hs=[...d.querySelectorAll('h2,h3,h4,strong,dt')];const h=hs.find(e=>labs.some(l=>e.textContent.toLowerCase().includes(l.toLowerCase())));if(!h)return '';const e=h.nextElementSibling||h.parentElement;return (e?.innerText||'').replace(h.innerText,'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "products_and_services",
            "selector": "(()=>{const d=ROW.ownerDocument;const labs=['Products and Services','Products & Services','Services','Products'];const hs=[...d.querySelectorAll('h2,h3,h4,strong,dt')];const h=hs.find(e=>labs.some(l=>e.textContent.toLowerCase().includes(l.toLowerCase())));if(!h)return '';const e=h.nextElementSibling||h.parentElement;return (e?.innerText||'').replace(h.innerText,'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "brands_carried",
            "selector": "(()=>{const d=ROW.ownerDocument;const labs=['Brands Carried','Brands'];const hs=[...d.querySelectorAll('h2,h3,h4,strong,dt')];const h=hs.find(e=>labs.some(l=>e.textContent.toLowerCase().includes(l.toLowerCase())));if(!h)return '';const e=h.nextElementSibling||h.parentElement;return (e?.innerText||'').replace(h.innerText,'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "specialities",
            "selector": "(()=>{const d=ROW.ownerDocument;const labs=['Specialities','Specialties','Specialty'];const hs=[...d.querySelectorAll('h2,h3,h4,strong,dt')];const h=hs.find(e=>labs.some(l=>e.textContent.toLowerCase().includes(l.toLowerCase())));if(!h)return '';const e=h.nextElementSibling||h.parentElement;return (e?.innerText||'').replace(h.innerText,'').replace(/\\s+/g,' ').trim()})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "editor_s_pick",
            "selector": "(()=>{const d=ROW.ownerDocument;const t=d.body.innerText||'';return /editor.?s pick|editior.?s pick|YP pick|√/i.test(t)?'yes':''})()",
            "attribute": "text",
            "isJs": true
          },
          {
            "name": "source_url",
            "selector": "(()=>ROW.ownerDocument.location.href)()",
            "attribute": "text",
            "isJs": true
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2280,
      "position_y": 220,
      "config": {
        "color": "bg-[#8d8d8d]"
      }
    }
  ],
  "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": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-element-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-element-1",
      "from_connector_id": "right",
      "to_block_id": "structured-export-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "structured-export-1",
      "from_connector_id": "right",
      "to_block_id": "loop-continue-1",
      "to_connector_id": "left"
    }
  ],
  "canvas_elements": [
    {
      "id": "group-load",
      "element_type": "group",
      "title": "Page Load",
      "color": "#08bdba",
      "position_x": 48,
      "position_y": 116,
      "width": 1760,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "navigate-1",
          "wait-for-page-load-1",
          "sleep-1",
          "wait-for-element-1"
        ]
      }
    },
    {
      "id": "group-interaction",
      "element_type": "group",
      "title": "Interaction",
      "color": "#a56eff",
      "position_x": 768,
      "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": 1848,
      "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": 2208,
      "position_y": 116,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes YellowPages.ca business detail pages supplied as input URLs. Uses a multi-URL navigation loop to visit each detail page, safely attempts to dismiss cookie consent with JavaScript, then exports business name, rating, address, coordinates, hours, phone, website, description, language, products/services, brands, specialities, and editor pick status to CSV. Replace the sample urls[] with your own YellowPages.ca /bus/ detail page URLs. If a supplied detail URL redirects to a YellowPages search result page, the template makes a best-effort extraction from the visible page.",
      "color": "#f1c21b",
      "position_x": 80,
      "position_y": 20,
      "width": 480,
      "height": 160,
      "z_index": 22,
      "data": {}
    },
    {
      "id": "note-block-navigate-1",
      "element_type": "note",
      "title": "Note: Navigate",
      "content": "Multi-URL loop over 2 pages. Pair with loop-continue at the end of each iteration.",
      "color": "#ee5396",
      "position_x": 320,
      "position_y": 200,
      "width": 328,
      "height": 107,
      "z_index": 22,
      "data": {
        "block_id": "navigate-1"
      }
    },
    {
      "id": "note-block-inject-javascript-1",
      "element_type": "note",
      "title": "Note: Inject JavaScript",
      "content": "Runs custom JavaScript in the page: `(() => {\n  const selectors = [\n    '#onetrust-accept-btn-handler',\n    '#accept-recommended-btn-hand...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1040,
      "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": "Structured export with JS columns (name, rating, address, latitude, longitude). These selectors are fragile — update if the site layout changes.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 200,
      "width": 340,
      "height": 128,
      "z_index": 22,
      "data": {
        "block_id": "structured-export-1"
      }
    },
    {
      "id": "note-block-loop-continue-1",
      "element_type": "note",
      "title": "Note: Loop Continue",
      "content": "Loop Continue advances a multi-URL or multi-text loop. Place at the end of the loop body with a clear back-edge to the loop start.",
      "color": "#ee5396",
      "position_x": 2480,
      "position_y": 200,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}