Search DoorDash for restaurants and stores by keyword, with rating, ETA and delivery fee.
{"query":"string (what to search, e.g. 'pizza', 'chinese food', 'tacos')"}{"count":"int","query":"string","results":"array of { id, name, url, rating, reviews, distance, eta, deliveryFee, deal, category }"}Search DoorDash for restaurants and stores by keyword. Instead of navigating the results page and waiting for React to render (~8-9s: heavy JS bundle, images, hydration), this fetch()es the search HTML in-session and parses the server-rendered store cards straight out of it (~2-3s, no render wait). DoorDash renders search results server-side and embeds them as real <a href="/store/{id}"> card markup in the initial HTML - there is no separate JSON/GraphQL search endpoint to replay - so the extractor reconstructs each card's text lines from the fetched HTML (skipping <style>/<script>/Icon Loading noise) and reads them with the same field logic as before. Param: query (e.g. 'pizza', 'chinese food', 'tacos'). Returns {count, query, results:[{id, name, url, rating, reviews, distance, eta, deliveryFee, deal, category}]}. distance is in miles, eta in minutes, reviews is the rating count (k expanded, so "(10k+)" → 10000), category is 'restaurant' or 'grocery/retail'. Results reflect the delivery location DoorDash has for the session (a saved address if signed in, otherwise an IP-based default). Read-only, it never adds to cart or orders.
fetch must run from a doordash.com page so it carries the session cookies and is same-origin (this is what clears Cloudflare and scopes results to the session's address). Have a doordash.com tab open - navigate one to https://www.doordash.com/ once if needed - then run the Extractor there. A flagged automation profile still gets Cloudflare-challenged; use the user's real browser profile.doordash.com tab exists (navigate to https://www.doordash.com/ once if the group has none) - the fetch inherits that tab's session/origin.params on that tab. It fetches /search/store/{query|encode}/ in-session, parses the HTML, and returns the JSON in returns. No render wait needed. Multiple queries can run as parallel evaluates on the same tab.( async (root, params) => { const origin = 'https://www.doordash.com'; const query = String((params && params.query) || ''); const res = await fetch(origin + '/search/store/' + encodeURIComponent(query) + '/', { credentials: 'include' }); const html = await res.text(); const dom = new DOMParser().parseFromString(html, 'text/html'); // Rebuild each card's text lines from the server-rendered HTML, skipping style/script/icon noise. const linesOf = (el) => { const out = []; const walk = (n) => { for (const c of n.childNodes) { if (c.nodeType === 3) { const t = c.textContent.trim(); if (t) out.push(t); } else if (c.nodeType === 1) { const tag = c.tagName.toLowerCase(); if (tag === 'style' || tag === 'script') continue; walk(c); } } }; walk(el); return out.map((s) => s.trim()).filter(Boolean).filter((l) => l !== '•' && l !== 'Icon Loading'); }; const byId = new Map(); for (const a of dom.querySelectorAll('a[href*="/store/"]')) { const href = a.getAttribute('href') || ''; const m = href.match(//store/(\d+)/); if (!m) continue; const id = m[1]; const lines = linesOf(a); const prev = byId.get(id); if (!prev || lines.length > prev.lines.length) byId.set(id, { href, lines }); } const results = []; for (const [id, { href, lines }] of byId) { if (!lines.length) continue; const name = lines[0]; let rating = null, reviews = null, distance = null, eta = null, deliveryFee = null, deal = null; for (const l of lines.slice(1)) { let mm; if (rating == null && (mm = l.match(/^(0-5?)$/))) { rating = parseFloat(mm[1]); continue; } if (reviews == null && (mm = l.match(/^(([\d.,]+)\s*(k)?+?)$/i))) { reviews = Math.round(parseFloat(mm[1].replace(/,/g, '')) * (mm[2] ? 1000 : 1)); continue; } if (distance == null && (mm = l.match(/([\d.]+)\smi\b/))) { distance = parseFloat(mm[1]); continue; } if (eta == null && (mm = l.match(/(\d+)\smin\b/))) { eta = parseInt(mm[1], 10); continue; } if (deliveryFee == null && /delivery fee/i.test(l)) { deliveryFee = l; continue; } if (deal == null && /(off|deal|%|free|buy)/i.test(l) && !/sponsored/i.test(l)) { deal = l; continue; } } const category = //convenience/store//.test(href) ? 'grocery/retail' : 'restaurant'; results.push({ id, name, url: origin + '/store/' + id, rating, reviews, distance, eta, deliveryFee, deal, category }); } return { count: results.length, query, results }; } )
{
"expr": "typeof result === 'object' && Array.isArray(result.results)",
"type": "value"
}