{
  "version": "1.0.0",
  "exported_at": "2026-05-31T00:00:00.000Z",
  "project": {
    "name": "Uber Eats Restaurant Details Scraper",
    "description": "Scrapes Uber Eats restaurant/store detail URLs and exports restaurant metadata plus menu dish details. Navigation uses a multi-URL loop over restaurant detail pages and appends all results to one CSV. Best-effort template: Uber Eats is dynamic and may show address prompts, bot checks, or region-specific content. JavaScript preprocessing normalizes lazy-loaded menu cards and ignores ranking badges such as '#1 most liked'.",
    "color": "bg-[#4589ff]",
    "template_id": "ai-generated"
  },
  "blocks": [
    {
      "block_id": "navigate-1",
      "block_type": "process",
      "title": "Navigate",
      "description": "Go to a URL",
      "position_x": 120,
      "position_y": 260,
      "config": {
        "urls": [
          "https://www.ubereats.com/store/mcdonalds-la-wilshire-union/-OCvBm1NSA-SVnmNOpc8Mg?diningMode=DELIVERY",
          "https://www.ubereats.com/store/alessandros-place/xmUZ1Uj1QDi_9ZCWsn8BAg",
          "https://www.ubereats.com/store/carls-jr-501s-western/bUNN3fkNShSK4KLHZbs7LQ?diningMode=DELIVERY"
        ],
        "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": 480,
      "position_y": 260,
      "config": {
        "timeout": 45
      }
    },
    {
      "block_id": "sleep-1",
      "block_type": "process",
      "title": "Sleep",
      "description": "Wait for specified time",
      "position_x": 840,
      "position_y": 260,
      "config": {
        "duration": 4
      }
    },
    {
      "block_id": "inject-javascript-1",
      "block_type": "process",
      "title": "Inject JavaScript",
      "description": "Run custom JavaScript on the page",
      "position_x": 1200,
      "position_y": 260,
      "config": {
        "waitForCompletion": true,
        "timeout": 60,
        "jsCode": "(async()=>{const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>String(s||'').replace(/\\s+/g,' ').trim();const lineList=el=>String(el?.innerText||el?.textContent||'').split(/\\n+/).map(clean).filter(Boolean);const isBadge=s=>/^#\\d+\\s+most liked$/i.test(s)||/^(popular|sponsored|bestseller)$/i.test(s);const isNoise=s=>/^\\+|^add$|^customize$|^required$|^choose/i.test(s)||/delivery|pickup|rating|review|subtotal|service fee|estimated|closed|opens|menu|search|group order/i.test(s);try{const b=[...document.querySelectorAll('button,[role=\"button\"]')].find(x=>/accept|agree|got it|allow/i.test(clean(x.innerText||x.textContent)));if(b)b.click();}catch(e){}for(let i=0;i<10;i++){window.scrollTo(0,document.body.scrollHeight);await sleep(900);}await sleep(700);const findType=o=>{if(!o||typeof o!=='object')return null;if(Array.isArray(o)){for(const v of o){const r=findType(v);if(r)return r;}return null;}const t=o['@type'];const ts=Array.isArray(t)?t:[t];if(ts.some(x=>/Restaurant|FoodEstablishment|LocalBusiness/i.test(String(x||''))))return o;for(const k of Object.keys(o)){const r=findType(o[k]);if(r)return r;}return null;};let ld={};for(const s of document.querySelectorAll('script[type=\"application/ld+json\"]')){try{const r=findType(JSON.parse(s.textContent));if(r){ld=r;break;}}catch(e){}}const addr=ld.address||{};const geo=ld.geo||{};const ar=ld.aggregateRating||{};let body=clean(document.body.innerText);let rating=clean(ar.ratingValue);let reviews=clean(ar.reviewCount||ar.ratingCount);if(!rating){const m=body.match(/([0-5](?:\\.\\d)?)\\s*(?:\\(|•|star|rating)/i);if(m)rating=m[1];}if(!reviews){const m=body.match(/([\\d,.]+\\s*[Kk]?)\\s*(?:ratings?|reviews?)/i);if(m)reviews=m[1];}const meta={restaurant:clean(ld.name)||clean(document.querySelector('h1')?.innerText||''),url:location.href,cuisine:Array.isArray(ld.servesCuisine)?ld.servesCuisine.join(', '):clean(ld.servesCuisine),price_range:clean(ld.priceRange),locality:clean(addr.addressLocality),region:clean(addr.addressRegion),postal_code:clean(addr.postalCode),country:clean(addr.addressCountry),street:clean(addr.streetAddress),latitude:clean(geo.latitude),longitude:clean(geo.longitude),telephone:clean(ld.telephone),rating:rating,review_count:reviews};const esc=s=>String(s||'').replace(/[&<>\"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;'}[c]));const headings=[...document.querySelectorAll('h2,h3,[role=\"heading\"]')].filter(h=>clean(h.innerText||h.textContent));const categoryFor=el=>{const y=el.getBoundingClientRect().top+window.scrollY;let last='';for(const h of headings){const hy=h.getBoundingClientRect().top+window.scrollY;if(hy<y){const v=clean(h.innerText||h.textContent);if(v&&v!==meta.restaurant&&!/rating|review|info|details|delivery|pickup/i.test(v))last=v;}}return last;};const sels=['[data-testid*=\"store-item\"]','[data-testid*=\"menu-item\"]','[data-testid*=\"MenuItem\"]','[aria-label*=\"$\"]','[role=\"button\"]','li','article'];let candidates=[...new Set(sels.flatMap(sel=>[...document.querySelectorAll(sel)]))].filter(el=>{const t=clean(el.innerText||el.textContent);return /\\$\\s*\\d/.test(t)&&t.length>=8&&t.length<900;}).sort((a,b)=>clean(a.innerText||a.textContent).length-clean(b.innerText||b.textContent).length);let acceptedEls=[],seen=new Set(),rows=[];for(const el of candidates){if(acceptedEls.some(a=>el.contains(a)))continue;let lines=lineList(el).filter(l=>l.length<320&&!/^\\+$/.test(l)&&!/^\\d+\\s*cal/i.test(l));const priceLine=lines.find(l=>/\\$\\s*\\d/.test(l))||'';const pm=priceLine.match(/\\$\\s*\\d[\\d,.]*(?:\\.\\d{2})?/);const price=pm?pm[0]:'';if(!price)continue;let usable=lines.filter(l=>l!==priceLine&&!/\\$\\s*\\d/.test(l)&&!isBadge(l)&&!isNoise(l)&&l.length>1&&l.length<180);let name=usable[0]||'';if(!name&&priceLine){name=clean(priceLine.replace(/\\$\\s*\\d[\\d,.]*(?:\\.\\d{2})?.*/,''));if(isBadge(name)||isNoise(name))name='';}if(!name||/^featured items$/i.test(name)||name===meta.restaurant)continue;let desc=usable.slice(1).find(l=>l!==name&&l.length>2&&l.length<280)||'';const key=(meta.restaurant+'|'+name+'|'+price).toLowerCase();if(seen.has(key))continue;seen.add(key);acceptedEls.push(el);rows.push({...meta,dish_category:categoryFor(el),dish_name:name,price:price,description:desc,error_message:''});}let out=document.getElementById('uscraper-menu-export');if(out)out.remove();out=document.createElement('div');out.id='uscraper-menu-export';out.style.display='none';if(!rows.length)rows=[{...meta,dish_category:'',dish_name:'',price:'',description:'',error_message:'No menu item cards detected; page may be blocked, require address selection, or Uber Eats DOM changed.'}];out.innerHTML=rows.map(r=>'<div class=\"uscraper-menu-row\" data-restaurant=\"'+esc(r.restaurant)+'\" data-restaurant-url=\"'+esc(r.url)+'\" data-cuisine-type=\"'+esc(r.cuisine)+'\" data-price-range=\"'+esc(r.price_range)+'\" data-locality=\"'+esc(r.locality)+'\" data-region=\"'+esc(r.region)+'\" data-postal-code=\"'+esc(r.postal_code)+'\" data-country=\"'+esc(r.country)+'\" data-street=\"'+esc(r.street)+'\" data-latitude=\"'+esc(r.latitude)+'\" data-longitude=\"'+esc(r.longitude)+'\" data-telephone=\"'+esc(r.telephone)+'\" data-rating=\"'+esc(r.rating)+'\" data-review-count=\"'+esc(r.review_count)+'\" data-error-message=\"'+esc(r.error_message)+'\" data-dish-category=\"'+esc(r.dish_category)+'\" data-dish-name=\"'+esc(r.dish_name)+'\" data-price=\"'+esc(r.price)+'\" data-description=\"'+esc(r.description)+'\"></div>').join('');document.body.appendChild(out);return rows.length;})();"
      }
    },
    {
      "block_id": "wait-for-element-1",
      "block_type": "process",
      "title": "Wait for Element",
      "description": "Wait until element appears",
      "position_x": 1560,
      "position_y": 260,
      "config": {
        "selector": ".uscraper-menu-row",
        "timeout": 20,
        "visible": false
      }
    },
    {
      "block_id": "structured-export-1",
      "block_type": "process",
      "title": "Structured Export",
      "description": "Export data with custom columns",
      "position_x": 1920,
      "position_y": 260,
      "config": {
        "rowSelector": ".uscraper-menu-row",
        "fileName": "uber-eats-restaurant-details-scraper.csv",
        "saveLocation": "C:\\Users\\theskd\\Documents\\UScraper\\templates",
        "includeHeaders": true,
        "fileMode": "append",
        "columns": [
          {
            "name": "Restaurant",
            "selector": "",
            "attribute": "data-restaurant"
          },
          {
            "name": "Restaurant_URL",
            "selector": "",
            "attribute": "data-restaurant-url"
          },
          {
            "name": "Cuisine_type",
            "selector": "",
            "attribute": "data-cuisine-type"
          },
          {
            "name": "Price_range",
            "selector": "",
            "attribute": "data-price-range"
          },
          {
            "name": "Locality",
            "selector": "",
            "attribute": "data-locality"
          },
          {
            "name": "Region",
            "selector": "",
            "attribute": "data-region"
          },
          {
            "name": "Postal_code",
            "selector": "",
            "attribute": "data-postal-code"
          },
          {
            "name": "Country",
            "selector": "",
            "attribute": "data-country"
          },
          {
            "name": "Street",
            "selector": "",
            "attribute": "data-street"
          },
          {
            "name": "Latitude",
            "selector": "",
            "attribute": "data-latitude"
          },
          {
            "name": "Longitude",
            "selector": "",
            "attribute": "data-longitude"
          },
          {
            "name": "Telephone",
            "selector": "",
            "attribute": "data-telephone"
          },
          {
            "name": "Rating",
            "selector": "",
            "attribute": "data-rating"
          },
          {
            "name": "Review_count",
            "selector": "",
            "attribute": "data-review-count"
          },
          {
            "name": "Error_message",
            "selector": "",
            "attribute": "data-error-message"
          },
          {
            "name": "Dish_category",
            "selector": "",
            "attribute": "data-dish-category"
          },
          {
            "name": "Dish_name",
            "selector": "",
            "attribute": "data-dish-name"
          },
          {
            "name": "Price",
            "selector": "",
            "attribute": "data-price"
          },
          {
            "name": "Description",
            "selector": "",
            "attribute": "data-description"
          }
        ]
      }
    },
    {
      "block_id": "loop-continue-1",
      "block_type": "process",
      "title": "Loop Continue",
      "description": "Continue multi-input loop",
      "position_x": 2280,
      "position_y": 260,
      "config": {}
    }
  ],
  "connections": [
    {
      "from_block_id": "navigate-1",
      "from_connector_id": "right",
      "to_block_id": "wait-for-page-load-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "wait-for-page-load-1",
      "from_connector_id": "right",
      "to_block_id": "sleep-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "sleep-1",
      "from_connector_id": "right",
      "to_block_id": "inject-javascript-1",
      "to_connector_id": "left"
    },
    {
      "from_block_id": "inject-javascript-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": 156,
      "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": 1128,
      "position_y": 156,
      "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": 156,
      "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": 156,
      "width": 380,
      "height": 296,
      "z_index": 20,
      "data": {
        "memberBlockIds": [
          "loop-continue-1"
        ]
      }
    },
    {
      "id": "note-overview",
      "element_type": "note",
      "title": "Overview",
      "content": "Scrapes Uber Eats restaurant/store detail URLs and exports restaurant metadata plus menu dish details. Navigation uses a multi-URL loop over restaurant detail pages and appends all results to one CSV. Best-effort template: Uber Eats is dynamic and may show address prompts, bot checks, or region-specific content. JavaScript preprocessing normalizes lazy-loaded menu cards and ignores ranking badges such as '#1 most liked'.",
      "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: `(async()=>{const sleep=ms=>new Promise(r=>setTimeout(r,ms));const clean=s=>String(s||'').replace(/\\s...` Verify in browser if results are empty.",
      "color": "#ee5396",
      "position_x": 1400,
      "position_y": 240,
      "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-menu-row`. Confirm row count > 0 before running at scale.",
      "color": "#ee5396",
      "position_x": 2120,
      "position_y": 240,
      "width": 340,
      "height": 110,
      "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": 240,
      "width": 340,
      "height": 123,
      "z_index": 22,
      "data": {
        "block_id": "loop-continue-1"
      }
    }
  ]
}