When a real person visits a restaurant page with no menu, Doozer reads the restaurant's own website for it, for free, and stages what it finds for review. Nothing goes live until approved.
"If they're looking for a menu, show it to them. If we don't have the menu, get it." In September 2026, 1,224 live food and drink sites had a website we could read and only three showed a menu. The Menu Finder closes that gap without spending money and without publishing anything nobody checked.
includes/menu-acquire.php.
business that has its own website and no approved menu, the page queues one job. The page itself does only in-memory checks; the crawler check, a once-a-day marker and the caps (60 a day, 12 an hour) run after the response is sent. Each business is tried at most once every 30 days.
twenty seconds: the homepage, then the page its "Menu" link points to. It trusts, in order: schema.org Menu data, known site-builder markup (Wix Restaurants, BentoBox), then "name ... price" lines on the menu page. A page of text becomes a menu only with at least five priced items that pass a strict junk filter. PDF menus and ordering links (Toast, ChowNow, DoorDash storefronts and so on) are kept as links, never parsed.
enrichment.scraped_menu, and the businessappears in the Enrichment Review queue under "Found on their website".
includes/enrichment-trust.php. A menu or product list reaches a page, its structured data or its price range only while it matches the stamp written when the operator approved it on the review page. Before this existed, the scraper wrote straight onto live pages: 34 sections were unreviewed, and most were junk (a deli's gift card as its "menu", a law firm's sentence about nursing-home costs, a hijacked domain's gambling spam). Those came off the pages the day the gate shipped and wait in the queue.
Each staged menu also carries notes the live page never reads, and the review page shows them:
scraped_menu_jev): for every line, how likely it is to be somethinga customer can order, judged with its section heading; and whether the list as a whole is a menu, a slice of one (drinks only) or merchandise and rewards text. Doubtful lines show struck through, borderline ones amber, and "Drop flagged lines" removes the struck ones only when the operator presses it. On the first 86 staged menus it struck 175 of 3,816 lines and called 5 lists merchandise; no real menu scored as merchandise.
scraped_menu_flags): five or more sections that each hold one linemeans the parser took descriptions for headings. Jev does not catch this; the rule does.
items_fp) and ignored once it changes.dz menuacquire verdict=1 adds them to menus staged before they existed.
Before staging, four rules measured on those 3,816 lines drop add-on sections, cut-off fragments and descriptions in the name slot, and turn bare sizes ("Half Loaf") into "Garlic Cheese Bread (Half Loaf)". They removed 70 lines, all junk.
A survey of 40 live restaurant websites (September 2026):
domain) and are skipped.
never pretends to be a browser; those are retried after 30 days.
third yielded priced items. The rest were image menus, JavaScript-only pages or no menu at all; those surface as links for the operator.
A business's website is third-party data, and so is every redirect and link it offers. includes/net-guard.php resolves each host before connecting and refuses private, loopback, link-local (cloud metadata) and carrier-NAT addresses and odd numeric forms. It allows ports 80 and 443 only, and pins the connection to the vetted address so DNS cannot change its answer between the check and the connect. The older site scraper and the structured-data fetcher go through the same guard. Parsing is bounded by the page's shape, not only its size.
dz menuacquire id=<business> does a dry run: fetch, parse and report, with no writes.live=1 stages the result, and force=1 ignores the 30-day wait.dz menuacquire stats=1 shows outcomes and jobs.menu_acquire_enabled=0 stops it, including jobs already queued. The caps aremenu_acquire_daily_cap and menu_acquire_hourly_cap.
includes/visit-reads.php applies the same gate to two older reads that had no trigger at all:
and phone. It's free, capped at 150 a day, and re-read every 90 days. Only the business's own site is read, because a chain locator's corporate profiles must never bind to a local store. Every attempt is stamped, found or not, so a site with nothing to read is not fetched daily.
cached. It's one model call of about 3 cents, capped at 20 a day, and a failed attempt waits 30 days rather than paying again. Facts feed Enrichment Review and content generation; they never rewrite a live page.
dz visitreads shows what visits have earned; on=0 stops it.
Related: Live Composition, which may lead a page with the menu only once it is approved.