Restaurant Menu Scraper
Pricing
from $80.00 / 1,000 medium cache extractions
Restaurant Menu Scraper
Extract structured menu data from restaurant websites: items, prices, descriptions, categories. Handles Toast, Square, Popmenu and PDFs. Search by name + location or provide URLs. Billable flag on every row. ~55% of submitted URLs return a billable menu; failures and low-quality results are free.
Pricing
from $80.00 / 1,000 medium cache extractions
Rating
5.0
(1)
Developer
Douglas Page
Maintained by CommunityActor stats
2
Bookmarked
178
Total users
9
Monthly active users
7.1 hours
Issues response
19 days ago
Last modified
Categories
Security
Limited permissionsShare
Extract structured menu data from independent and local restaurant websites. Returns clean JSON with categories, items, prices, and descriptions.
Works best with independent restaurants, local chains, and fine dining establishments that publish their own menus online.
Three Ways to Use
URL Mode (default)
Give it a restaurant URL and get back a structured menu. The scraper finds the menu on the site — whether it's an HTML page, a PDF, or listed on a third-party platform like Yelp or DoorDash — and extracts everything into clean JSON.
URL List Mode
Give it a list of restaurant URLs and it scrapes each one sequentially. Results appear in the dataset as each restaurant completes. Failed extractions in bulk runs are free.
Search Mode
Tell it a cuisine and a city, and it discovers restaurants in that area via Google Places, then scrapes each one. Results appear in the dataset one at a time as each restaurant completes. Results are capped at 25 per run by default — see maxResults below.
Input
URL Mode
| Field | Type | Required | Description |
|---|---|---|---|
mode | enum | No | Set to "url" (this is the default) |
url | string | Yes | Restaurant website URL (e.g. https://franklinbbq.com) |
location | string | No | For multi-location restaurants, specify which location (e.g. "San Diego") |
freshness | enum | No | How fresh the data should be (default: med_cache) |
URL List Mode
| Field | Type | Required | Description |
|---|---|---|---|
mode | enum | Yes | Set to "urls" |
urls | string[] | Yes | List of restaurant website URLs to scrape |
location | string | No | Optional location context for multi-location restaurants |
freshness | enum | No | How fresh the data should be (default: med_cache) |
Search Mode
| Field | Type | Required | Description |
|---|---|---|---|
mode | enum | Yes | Set to "search" |
query | string | Yes | Cuisine or restaurant type (e.g. "sushi", "Mexican", "Italian", "BBQ") |
location | string | Yes | City or area to search (e.g. "Pasadena, CA", "Austin, TX") |
freshness | enum | No | How fresh the data should be (default: med_cache) |
maxResults | integer | No | Maximum restaurants to scrape (default: 25, max 200) |
The word "restaurants" is automatically added to your search query, so just enter the cuisine type.
About maxResults and run timeouts. A dense city can turn up well over a
hundred restaurants, but scraping is sequential at roughly 1.5 minutes each, and
the default Apify run timeout is 1 hour — so about 25 is what comfortably fits.
Candidates are ranked by rating weighted by review count before the cap is
applied, so a highly-rated place with thousands of reviews is kept over one with
a perfect score and three reviews.
If you raise maxResults, raise the run's Timeout in the run options as well.
At ~1.5 min per restaurant, 100 restaurants needs roughly 2.5 hours. A run that
hits its timeout is stopped partway, and you are still charged for the results it
produced before being stopped.
Freshness Tiers
Controls how recently the menu data was scraped. Cached data is faster and cheaper.
| Tier | Max Age | Per result | Per 1,000 |
|---|---|---|---|
long_cache | 180 days | $0.07 | $70 |
med_cache | 60 days | $0.08 | $80 |
short_cache | 14 days | $0.09 | $90 |
fresh | 1 day | $0.10 | $100 |
Billing
You only pay for successful, quality extractions. Failed scrapes and low-quality results are free in both URL and Search modes.
- Menu with items and prices → billed
- Menu with items but no prices → billed. Many restaurants publish a full menu
without listing prices. The extraction is complete and the content is delivered, so it
is charged. These rows carry
"has_prices": falseinsideextractionConfidence. - Thin or partial extraction (too few items, or prices that failed validation) → free
- Failed extraction (site blocked, no menu found, timeout) → free
Every dataset row includes a billable field (true or false) so you can audit exactly
what was charged. There is also a one-time Actor Start event of $0.00005 per run.
What It Extracts
Each restaurant produces a structured menu with:
- Categories — Appetizers, Entrées, Desserts, Drinks, etc.
- Menu items — Name, description, and price for each dish
- Extraction confidence —
high,medium, orlowso you know how complete the data is - Completeness grade — AI-verified assessment of whether the menu is complete (see below)
- Restaurant name — The official business name as shown on the website
The scraper handles HTML menus, PDF menus, and third-party platforms (Yelp, DoorDash, etc.), automatically choosing the best source.
Quality Signals
Every extraction is scored, and the result appears in two fields on each dataset row.
confidence is a single summary value:
| Value | Meaning | Billed |
|---|---|---|
high | Rich menu — 20+ items across 3+ categories, with or without prices | Yes |
medium | Some items found, but thin, or several prices failed validation | No |
low | Fewer than 10 items — too little to be useful | No |
extractionConfidence carries the underlying counts so you can apply your own threshold:
item_count, price_count, category_count, description_count, price_ratio,
has_prices, has_descriptions, and category_names.
A high result with "has_prices": false is a complete menu whose restaurant does not
publish prices online. It is billed.
Important Limitations
- Fast food chains are excluded from search results. National fast food chains (McDonald's, Subway, Taco Bell, Chick-fil-A, etc.) are automatically filtered out. These large multi-location chains frequently use dynamic menu systems that don't scrape reliably, and their menus are widely available through other channels.
- Best results come from independent restaurants that publish their own menu on their website or as a PDF. Regional and fine dining chains with individual location pages also work well.
- Some restaurants will fail — not every website has a scrapeable menu. The dataset will include both successes and failures so you can see exactly what worked.
Output Examples
URL Mode
Input:
{"url": "https://franklinbbq.com"}
Dataset row:
{"success": true,"url": "https://franklinbbq.com","restaurantName": "Franklin Barbecue","itemCount": 45,"confidence": "high","extractionConfidence": {"confidence": "high","item_count": 45,"has_prices": true,"has_descriptions": true,"price_coverage": 0.89},"menu": {"categories": [{"name": "Barbecue","items": [{"name": "Brisket","price": "$28.00/lb","description": "Oak-smoked for 18 hours"}]}]}}
URL List Mode
Input:
{"mode": "urls","urls": ["https://franklinbbq.com","https://cocina35.com","https://saltyflamerestaurant.com"]}
Run summary (available in the OUTPUT key-value store after completion):
{"mode": "urls","totalUrls": 3,"successful": 2,"failed": 1,"billableCount": 2,"nonBillableFailures": 1}
Dataset rows use the same format as URL Mode, one row per restaurant.
Search Mode
Input:
{"mode": "search","query": "sushi","location": "Pasadena, CA"}
Dataset rows (one per restaurant, streamed as each completes):
{"success": true,"url": "https://sushigen.com","restaurantName": "Sushi Gen","searchQuery": "sushi","searchLocation": "Pasadena, CA","serperTitle": "Sushi Gen","serperAddress": "123 Main St, Pasadena, CA 91101","itemCount": 62,"confidence": "high","extractionConfidence": {"confidence": "high","item_count": 62,"has_prices": true,"has_descriptions": true,"price_coverage": 0.95},"menu": {"categories": [{"name": "Nigiri","items": [{"name": "Salmon","price": "$6.50","description": "Two pieces, fresh Atlantic salmon"}]}]}}
Failed restaurants also appear in the dataset:
{"success": false,"url": "https://example-restaurant.com","restaurantName": "Example Restaurant","searchQuery": "sushi","searchLocation": "Pasadena, CA","error": "all_sources_failed","errorMessage": "Could not extract menu from any available source"}
Run summary (available in the OUTPUT key-value store after completion):
{"mode": "search","query": "sushi","location": "Pasadena, CA","totalFound": 20,"excludedFastFood": 2,"skippedNoWebsite": 3,"totalScraped": 15,"successful": 12,"failed": 3}
Use Cases
- Restaurant aggregators — Build menu databases for a city or region
- Food delivery apps — Import menus from local restaurants automatically
- Market research — Compare pricing across restaurants by cuisine and area
- Price monitoring — Track how menu prices change over time
- Location intelligence — Map restaurant offerings by geography
About the developer
Built and maintained by Menus API. See also our Business Profile Enricher and Contact Finder for extracting non-menu business data.
Questions or feature requests? Open an issue on the Issues tab.