Restaurant Menu Scraper avatar

Restaurant Menu Scraper

Pricing

from $80.00 / 1,000 medium cache extractions

Go to Apify Store
Restaurant Menu Scraper

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

Douglas Page

Maintained by Community

Actor stats

2

Bookmarked

178

Total users

9

Monthly active users

7.1 hours

Issues response

19 days ago

Last modified

Security

Limited permissions

Share

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

FieldTypeRequiredDescription
modeenumNoSet to "url" (this is the default)
urlstringYesRestaurant website URL (e.g. https://franklinbbq.com)
locationstringNoFor multi-location restaurants, specify which location (e.g. "San Diego")
freshnessenumNoHow fresh the data should be (default: med_cache)

URL List Mode

FieldTypeRequiredDescription
modeenumYesSet to "urls"
urlsstring[]YesList of restaurant website URLs to scrape
locationstringNoOptional location context for multi-location restaurants
freshnessenumNoHow fresh the data should be (default: med_cache)

Search Mode

FieldTypeRequiredDescription
modeenumYesSet to "search"
querystringYesCuisine or restaurant type (e.g. "sushi", "Mexican", "Italian", "BBQ")
locationstringYesCity or area to search (e.g. "Pasadena, CA", "Austin, TX")
freshnessenumNoHow fresh the data should be (default: med_cache)
maxResultsintegerNoMaximum 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.

TierMax AgePer resultPer 1,000
long_cache180 days$0.07$70
med_cache60 days$0.08$80
short_cache14 days$0.09$90
fresh1 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": false inside extractionConfidence.
  • 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, or low so 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:

ValueMeaningBilled
highRich menu — 20+ items across 3+ categories, with or without pricesYes
mediumSome items found, but thin, or several prices failed validationNo
lowFewer than 10 items — too little to be usefulNo

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.