GrabFood Restaurant Scraper: Menus, Ratings & Delivery Data avatar

GrabFood Restaurant Scraper: Menus, Ratings & Delivery Data

Pricing

from $1.00 / 1,000 restaurant results

Go to Apify Store
GrabFood Restaurant Scraper: Menus, Ratings & Delivery Data

GrabFood Restaurant Scraper: Menus, Ratings & Delivery Data

Scrape GrabFood restaurants across Southeast Asia, including SG, MY, TH, VN, PH, ID, KH, and MM. Search by keyword or URL and extract 45+ fields: restaurant name, address, GPS, cuisine, ratings, reviews, promos, delivery time, fees, opening hours, and full menus with item prices and images.

Pricing

from $1.00 / 1,000 restaurant results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

75

Total users

19

Monthly active users

8 hours

Issues response

3 days ago

Last modified

Share

GrabFood Restaurants Scraper: Menus, Ratings, Delivery Fees and Promos

GrabFood Restaurants Scraper turns GrabFood, the food delivery arm of Grab, into structured restaurant and menu data across 8 Southeast Asian countries: Singapore, Malaysia, Thailand, Vietnam, Philippines, Indonesia, Cambodia and Myanmar. Get restaurant names, addresses, cuisine tags, star ratings and review counts, delivery time and fees, opening hours, promotions and price tiers, plus the complete menu (categories, items, descriptions, prices and images) when you turn it on. Search by keyword within a region, or paste GrabFood links, then export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • Two ways to find restaurants. Search by keyword inside a region, or paste ready made GrabFood restaurant pages and listing pages directly.
  • Full menu on demand. Turn on menu extraction and get every category and item, with description, price, discounted price, takeaway price, image and modifier group count.
  • 8 regions, one actor. Singapore, Malaysia, Thailand, Vietnam, Philippines, Indonesia, Cambodia and Myanmar, each with prices in its own local currency.
  • Delivery detail that matters for ordering. Delivery time, delivery fee (with any discount applied), delivery options such as delivery, takeaway or dine in, and distance from the search point.
  • Structured promotions. Promo labels come through as readable text, for example "S$5.00 off", not just a yes or no flag.
  • Filters and sorting built in. Keep only restaurants matching a cuisine tag, a minimum rating or a minimum review count, then sort by rating, review count, distance or delivery time.
  • Built for schedules. Turn on incremental mode to get only new, updated and reappeared restaurants on every recurring run, and a run that cannot read GrabFood fails loudly instead of quietly returning an empty dataset.

Use Cases

  • Delivery aggregation and comparison apps: pull live menus, prices and delivery fees to compare against other platforms.
  • Market and competitor research: track promotions, price tiers and ratings across cuisines, chains and cities.
  • Menu and pricing analytics: build a historical menu and price dataset for a chain across countries.
  • Local lead generation: find restaurants by cuisine, rating or review count to build outreach or partnership lists.
  • Daily monitoring: watch a search or a list of restaurants for new promotions, rating changes or menu updates.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"SGDD00000"
name"Sample Burger Place"
chainName"Sample Burger"
description"Sample restaurant blurb" (restaurant level; empty when the outlet has none)
cuisine["Burger", "Fast Food", "Halal"]
fullAddress"1 Sample Street, #01-01"
city / countryCode"Singapore" / "SG"
rating4.3
voteCount1200
priceTag2
isOpentrue
estimatedDeliveryTime35
distanceInKm1.34
hasPromotrue
promoLabels["S$5.00 off"]
deliveryOptions"DELIVERY_TAKEAWAY_DINEIN"
deliveryFee{ price, priceDisplay, discountedPrice, hasDiscountedPrice }
openHoursper day hours plus a displayedHours summary string
menuarray of { name, itemCount, items: [{ name, price, discountedPrice, imageUrl, modifierGroupCount }] }
menuCategoryCount / menuItemCount12 / 86
grabUrl"https://food.grab.com/sg/en/restaurant/sample-burger-place/SGDD00000"
region"SG"
scrapedAtISO timestamp

Prices are in the region's currency, in minor units (for example cents), with a formatted display string where GrabFood provides one. Rating and review count come from GrabFood's own aggregate score and rating count on its public web pages; the public site does not expose individual written reviews, so this actor does not invent any. Incremental mode adds changeType (NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED), changedFields, firstSeenAt and lastSeenAt to every record; these are absent from a normal run's output. The Output tab offers three views: Restaurants (one row per restaurant), Menu items (one row per menu item, unwound from the restaurant's nested menu; menu items are never separate dataset items) and Changes (incremental rows only). Each run also saves a short run summary (counts of pushed, filtered, unchanged-suppressed and failed restaurants) under the OUTPUT key of the key-value store.

Delivery queries use the selected country's default city point unless Delivery latitude and Delivery longitude are both supplied. deliveryContext identifies the actual point, its observation time and whether the restaurant was listed for that point. listed_for_pin is discovery evidence, not confirmed delivery eligibility; deliverable remains null because these responses do not confirm checkout eligibility. isOpen, delivery fees and item availability are separate observations. Restaurant latitude and longitude, when present, refer to the outlet, not the delivery point. menuObservedAt records when menu details were read. Category and item available are independent booleans or null for unknown.

For Vietnam, currencyCode is VND, currencyExponent is 0 and priceInCurrencyUnits is the same whole-dong amount as price; do not divide VND amounts by 100. priceDisplay and discountedPriceDisplay preserve formatted menu prices where present. For other currencies, conversion is supplied only when the currency exponent is known.

How to Use

  1. Pick a mode: search (keyword within a region) or url (paste GrabFood links).
  2. Fill in the fields for that mode: region and search keywords for search mode, or a list of GrabFood URLs for URL mode. Add cuisine, rating or review filters and a sort order if you want them.
  3. Turn Fetch full menu on or off, and set Max restaurants to control run size and cost.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

Search for a keyword in one region:

{
"mode": "search",
"region": "SG",
"searchQueries": ["ramen"],
"maxItems": 30
}

Browse a region's recommended feed:

{
"mode": "url",
"startUrls": ["https://food.grab.com/th/en/restaurants"],
"maxItems": 50
}

Supported URL shapes (URL mode)

ShapeExampleBehaviour
Keyword listinghttps://food.grab.com/sg/en/restaurants?search=sushiRestaurants matching the keyword
Region listinghttps://food.grab.com/sg/en/restaurantsThe region's recommended feed
Restaurant pagehttps://food.grab.com/sg/en/restaurant/luckin-coffee-punggol-plaza-delivery/4-C7WFRLKCA2XTEXThat restaurant, fully enriched
Without language segmenthttps://food.grab.com/sg/restaurants?search=sushi, https://food.grab.com/sg/restaurant/<slug>/<id>Same as the full form
Country home pagehttps://food.grab.com/sg/en/Treated as the recommended feed (a warning is logged)

A page= parameter is not honoured (the listing is read from the start, and the run is treated as a partial scope so no EXPIRED rows are produced) and a sortBy= parameter is ignored, use the Sort by input; both log a warning. Chain pages, city cuisine or dish collections and restaurant links without a name slug are skipped with a warning that says why, never silently.

Default URLs: if you run URL mode without touching the URLs field, the actor scrapes six example URLs covering every supported shape: keyword listing, region listing, one restaurant page, the language-less listing and restaurant forms, and the country home page. The language-less forms and the home page resolve to the same restaurants as the full forms, so repeats are collapsed. Cap the cost with Max restaurants (default 20).

Filtered and sorted search:

{
"mode": "search",
"region": "MY",
"searchQueries": ["burger"],
"cuisineFilter": ["halal"],
"minRating": 4,
"sortBy": "rating",
"maxItems": 25
}

Vietnam at a public delivery point

Minimal VN input with a public Ho Chi Minh City test point:

{
"mode": "search",
"region": "VN",
"language": "vn",
"searchQueries": ["cฦกm"],
"deliveryLatitude": 10.77,
"deliveryLongitude": 106.70,
"fetchMenu": true,
"maxItems": 1
}

This is a location-scoped search, not a checkout guarantee. Supply both coordinates; invalid or partial pins fail instead of silently using another location. Address geocoding is not supported. In URL mode set Region to match the URLs; the same pin is used for listing and detail reads. Coordinate parameters pasted into a URL do not override these inputs. Do not use customer coordinates in shared examples. Apify stores run input and output; manage access and retention accordingly.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/grabfood-restaurants-scraper").call(run_input={"mode": "search", "region": "SG", "searchQueries": ["ramen"], "maxItems": 30})
for restaurant in client.dataset(run["defaultDatasetId"]).iterate_items():
print(restaurant["name"], restaurant["rating"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/grabfood-restaurants-scraper').call({ mode: 'search', region: 'SG', searchQueries: ['ramen'], maxItems: 30 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

How Max restaurants and Max pages work together

Max restaurants (maxItems) is the main cap on a run, 20 by default; set it to 0 to remove your cap (a single run still stops at 1,000 restaurants; the schema accepts up to 100,000 so saved tasks keep working, and no run walks more pages than that stop needs). Max pages (maxPages) additionally caps how many listing pages get walked per keyword search (the recommended feed is not affected); it is 0 by default, meaning no page limit, so the run stops at Max restaurants instead. Set Max pages only when you want to bound page walking regardless of Max restaurants.

Resume and recurring updates

  • Resume (resumeFromRunId) continues one specific interrupted or previous large pull: paste its run or dataset ID and this run skips restaurants already collected there, so you don't pay twice.
  • Incremental mode (incrementalMode) is for a schedule, for example daily, over the same search or URLs. The actor remembers the previous run of the same search by itself, no run ID needed. The first run returns everything as NEW. Later runs return only NEW, UPDATED and REAPPEARED restaurants by default; unchanged restaurants are suppressed and not billed unless emitUnchanged is on. EXPIRED rows are only produced once a run has fully scanned the tracked search, not when Max restaurants capped it or Resume was used. A restaurant is UPDATED when any output field changes (rating, votes, promos, open status, delivery time and fee, menu items with prices and availability, opening hours, announcements, photo URLs) except scrapedAt and anything listed in ignoreFieldsForChanges; changedFields names what moved. stateKey names a monitoring campaign. Supplied delivery pins remain separate even with the same state key; resume requires matching pin evidence in the previous dataset.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

What gets written to the connector: a condensed, human readable summary of each restaurant, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify โ†’ Settings โ†’ Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. If the picker is empty, you haven't authorized a connector yet.
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (keyword) or url (paste GrabFood links).
regionstringSGGrabFood country: SG, MY, TH, VN, PH, ID, KH or MM. Results and prices are for that country.
searchQueriesarray(none, prefilled with ramen)Keywords to search within the region. Search mode only. To browse a region's recommended feed, use URL mode with a listing URL that has no search term.
startUrlsarray6 example URLs (every supported shape)GrabFood restaurant pages, restaurants listing pages or a country home page. URL mode only.
cuisineFilterarray(empty)Keep only restaurants whose cuisine tags contain any of these terms, case insensitive. Leave empty to include all.
minRatinginteger(none)Minimum star rating, 0 to 5. Leave empty for no minimum.
minReviewsinteger0Minimum number of ratings a restaurant must have.
sortBystringdefaultdefault (GrabFood's own order), rating, reviews, distance or delivery-time. The guest search API ignores any sort parameter (probed: sortBy, sort and orderBy leave the order unchanged), so sorting stays client-side over the collected results.
fetchMenubooleantrueEnrich each restaurant with its full menu and full address. Turn off for faster, listing only runs.
includeRatingsbooleantruePopulate the rating score and review count on every record.
languagestringdefaultLanguage of the grabUrl links and of the Accept-Language the site is asked for, where the country offers it: Thailand (en, th), Vietnam (en, vn), Indonesia (en, id). Other countries are English only. Unsupported choices fall back to the country default.
deliveryLatitudenumber(none)Delivery latitude, -90 to 90. Supply with longitude; otherwise use the country default point.
deliveryLongitudenumber(none)Delivery longitude, -180 to 180. Supply with latitude.
maxItemsinteger20Hard cap on total restaurants for the run. 0 removes the cap; a single run still stops at 1,000 restaurants.
maxPagesinteger0Extra cap on how many listing pages are walked per search. 0 means no page limit; the run stops at Max restaurants instead.
proxyConfigurationobjectApify Proxy, ResidentialConnection settings. A residential connection is recommended for reliable listing loads.
mcpConnectorsarray(none)Optional: send a summary of each record to apps you authorized under Integrations.
notionParentPageUrlstring(none)Notion connector only: the page under which item pages are created.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.
resumeFromRunIdstring(none)Continue one interrupted or previous large pull without re-collecting or re-charging restaurants already saved there.
incrementalModebooleanfalseTurn on for scheduled runs: return only new, updated and reappeared restaurants after the first run.
stateKeystring(none)Name or share an incremental mode monitoring campaign. Leave empty to derive one automatically from the mode, region, search or URL, and filter setup.
emitUnchangedbooleanfalseAlso return, and bill, restaurants that have not changed since the last run. Incremental mode only.
ignoreFieldsForChangesstring[][]Incremental mode only. Fields left out of the UPDATED comparison and changedFields (still present in output). Bare top-level name, dotted path with arrays transparent (menu.items.price), or *.name at any depth. scrapedAt is always excluded.
emitExpiredbooleanfalseAlso return, and bill, restaurants no longer found after a complete scan. Incremental mode only.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"id": "SGDD00000",
"name": "Sample Burger Place",
"chainName": "Sample Burger",
"branchName": "Downtown",
"description": "Sample restaurant blurb",
"cuisine": ["Burger", "Fast Food", "Halal"],
"address": "Sample Burger Place - Downtown",
"fullAddress": "1 Sample Street, #01-01",
"street": "Sample Street",
"suburb": "Downtown",
"postcode": "000000",
"city": "Singapore",
"countryCode": "SG",
"rating": 4.3,
"voteCount": 1200,
"priceTag": 2,
"isOpen": true,
"estimatedDeliveryTime": 35,
"estimatedDeliveryTimeRange": "From 35 mins",
"distanceInKm": 1.34,
"hasPromo": true,
"promoLabels": ["S$5.00 off"],
"deliveryOptions": "DELIVERY_TAKEAWAY_DINEIN",
"deliveryFee": {
"currencyCode": "SGD",
"currencySymbol": "S$",
"price": 200,
"priceDisplay": "S$2.00",
"discountedPrice": 0,
"hasDiscountedPrice": true
},
"currency": { "code": "SGD", "symbol": "S$", "exponent": 2 },
"openHours": { "mon": "10:00-22:00", "tue": "10:00-22:00", "displayedHours": "Daily 10:00-22:00" },
"photoHref": "https://food.grab.com/sample/photo.jpg",
"grabUrl": "https://food.grab.com/sg/en/restaurant/sample-burger-place/SGDD00000",
"menuCategoryCount": 12,
"menuItemCount": 86,
"menu": [
{
"name": "Burgers",
"available": true,
"itemCount": 1,
"items": [
{
"id": "ITEM0001",
"name": "Classic Cheeseburger",
"description": "Beef patty, cheddar, lettuce, house sauce",
"available": true,
"price": 890,
"discountedPrice": 690,
"takeawayPrice": 850,
"imageUrl": "https://food.grab.com/sample/burger.jpg",
"modifierGroupCount": 2,
"isOnSpecial": true,
"savingsAmount": 200,
"discountPercent": 22
}
]
}
],
"orderValueLimit": 1500,
"announcements": ["Public holiday hours apply"],
"region": "SG",
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Plan Requirement

This actor uses Apify Proxy with the Residential group by default, already set as the input's default connection, and the exit country is aligned automatically to the selected region. A paid Apify plan is needed for residential access; Apify free plans do not include it.

Datacenter connections work intermittently: in testing, some datacenter exits loaded GrabFood normally and others were refused with an HTTP 403 "Request blocked" page, so a datacenter-only setup can fail on some runs. The actor rotates to another exit when it sees that refusal, but residential stays the reliable choice.

If the requested Apify proxy access is refused, the actor automatically tries a backup connection. If every connection fails, the run stops with one of these messages instead of returning a misleading empty dataset:

  • "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required: https://apify.com/pricing. If you entered your own proxy URLs, check that their credentials are valid."
  • "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested GrabFood pages. Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
  • "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."

FAQ

How much does it cost?

This actor charges per event, with three events: Actor Start (once per run per GB of memory, minimum one event, so a run given more memory pays for more start events), Restaurant result (once for each restaurant returned in the dataset) and Menu enrichment (once per restaurant enriched with its full menu from its own restaurant page; always in URL mode, and in search mode when Fetch full menu is on, which is the default). The Pricing tab shows the exact rate of each event for your plan. In incremental mode unchanged restaurants are not billed unless Emit unchanged is on. Use Max restaurants to cap the cost of any run; it also bounds the default URL list in URL mode.

Is an evaluation run free?

Starting a run incurs the Actor Start charge even if no restaurants are returned. Restaurant result and Menu enrichment charges apply to delivered results as described above; credits are not a zero-charge guarantee. Check the Pricing tab and the run's memory setting before starting. A separately shared static evaluation sample requires no actor run, subscription or purchase, and grants no additional content-use rights.

This actor provides technical data collection, not a license to Grab or merchant content. Grab's Vietnam terms restrict scraping, commercial reuse and public display, and reproduction of protected content. Consumer display, short-lived caching and AI ranking require appropriate upstream permission and compliance with applicable law. No approved cache lifetime or AI-use permission is granted by buying or running this actor. Obtain permission covering your intended uses, retention, attribution and removal obligations before commercial deployment.

Does the menu include availability or sold-out status?

With Fetch full menu on, menu[].items[].available and the category's available field are included when GrabFood supplies a boolean value. true means available and false means unavailable in that response; unavailable does not necessarily mean sold out. A null or omitted flag means unknown, and an item missing from the menu is not proof that it is sold out. Availability reflects the selected location and time, rather than a guaranteed inventory count.

How do I target one Indonesian outlet?

Search in region ID uses a central Jakarta delivery location unless both delivery coordinates are supplied. A keyword containing another city or branch name can still return nearby alternatives. Use URL mode with that outlet's restaurant URL to target a specific branch.

Can I get only new or changed restaurants on a schedule?

Yes. Schedule the actor from the Schedules tab with the same search or URLs and turn on Incremental mode. Each run then returns only NEW, UPDATED and REAPPEARED restaurants; unchanged ones are skipped and not billed unless Emit unchanged is on.

What's the difference between Resume and Incremental mode?

Resume (resumeFromRunId) continues one specific interrupted or capped run so you don't re-collect, or re-pay for, restaurants it already saved. Incremental mode is for running the same search again and again on a schedule and getting only what changed; it remembers the previous run by itself, no run ID needed.

Why did my run fail instead of returning an empty dataset?

The run fails outright when it cannot confirm anything from GrabFood: no restaurants were found and a search or listing request failed, the guest session could not be set up or gets rejected twice in a row, a given resumeFromRunId does not match a run or dataset this account can read, or Incremental mode is combined with resumeFromRunId on a search that already has saved state. A genuinely empty result, such as a keyword with no matches or filters that exclude everything, still succeeds with zero rows and a warning in the run log.

Does this actor return individual written reviews?

No. GrabFood's public listings expose only the aggregate rating and the total review count for a restaurant, not individual written reviews, so this actor returns that rating data and does not fabricate review text the site does not publish.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the mcpConnectors field to push a summary of each restaurant into Notion, Linear or Airtable.

๐Ÿ”— Want more e-commerce data?

Pair this actor with these related scrapers from the same team:

๐Ÿ›’ Wolt.com
From $0.8/1K. Scrape Wolt restaurants and full menus at the city scale. Extract 60+...
๐Ÿ›’ DoorDash Scraper
Extract structured doordash.com data at scale. Search by keyword or paste store URLs to...
๐Ÿ›’ Zomato Scraper
Scrape restaurants and reviews from zomato.com. Get names, cuisines, ratings and votes...
๐Ÿ›’ Lazada Scraper
Scrape Lazada products and reviews across SEA markets, including Malaysia, Singapore...
๐Ÿจ Traveloka Hotel Scraper
Scrape Traveloka hotels across Singapore, Malaysia, Indonesia, Thailand, Vietnam and the...
๐Ÿงฉ OpenTable Reviews Scraper
Scrape full OpenTable.com restaurant reviews by keyword, area, or restaurant profile...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.