GrabFood Scraper — Restaurants & Menus in Southeast Asia
Pricing
from $2.00 / 1,000 restaurant scrapeds
GrabFood Scraper — Restaurants & Menus in Southeast Asia
Scrape GrabFood restaurants in Singapore, Malaysia, the Philippines, Vietnam and Indonesia: cuisines, ratings, delivery fees, addresses and optional menu prices. Pay per result.
Pricing
from $2.00 / 1,000 restaurant scrapeds
Rating
0.0
(0)
Developer
Polyglot Data
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
5 days ago
Last modified
Categories
Share
Extract restaurant and menu data from GrabFood in five Southeast Asian markets: Singapore 🇸🇬, Malaysia 🇲🇾, Philippines 🇵🇭, Vietnam 🇻🇳 and Indonesia 🇮🇩.
Get restaurant names, cuisines, ratings, delivery fees and times, addresses, opening hours and, optionally, the full menu with item prices, for any point on the map or for a list of restaurant URLs. The output is flat, clean JSON that you can export to Excel, CSV or Google Sheets, or pull from the API.
✅ Why this scraper
- Works where HTTP-only scrapers fail. GrabFood's data API only answers to a guest session created by the site's own JavaScript. This actor opens GrabFood in a real browser, gets that guest session (no login), and calls the same API from inside the page.
- Polite by default. It waits 2 seconds between API calls, following the Crawl-delay in GrabFood's robots.txt.
- Pay per result. You pay only for restaurants actually saved to the dataset. Error items and failed pages are not charged.
- Clean numbers. Prices are converted from GrabFood's minor units into normal numbers (for example
"S$6.50"becomes6.5), currencies are ISO codes (SGD,MYR,THB,PHP,VND,IDR), and missing values arenull, never empty strings. - Five countries, one schema. The same fields come back from every country.
📦 What data you get
Fast = from search results. Detail = added with Include restaurant details. Menu = added with Include full menu with prices.
| Field | Type | Fast | Detail | Menu | Description |
|---|---|---|---|---|---|
url | string | ✅ | ✅ | ✅ | GrabFood restaurant page |
restaurantId | string | ✅ | ✅ | ✅ | GrabFood merchant ID |
name | string | ✅ | ✅ | ✅ | Restaurant name |
chainName | string | ✅ | ✅ | ✅ | Chain name, if any |
branchName | string | ✅ | ✅ | ✅ | Branch name |
cuisine | string[] | ✅ | ✅ | ✅ | Cuisine tags |
rating | number | ✅ | ✅ | ✅ | Average rating |
reviewCount | integer | ✅ | ✅ | ✅ | Number of ratings |
isOpen | boolean | ✅ | — | — | Open for delivery right now |
openingHours | object | — | ✅ | ✅ | Hours by day (mon … sun) plus displayedHours |
hasPromo | boolean | ✅ | — | — | Has an active promotion |
promoLabels | string[] | ✅ | — | — | Promotion labels shown on the card |
deliveryFee | number | ✅ | — | — | Delivery fee in major units |
currency | string | ✅ | ✅ | ✅ | ISO 4217 code |
estimatedDeliveryMinutes | integer | ✅ | — | — | Estimated delivery time |
deliveryTimeRange | string | ✅ | — | — | Delivery time as shown, e.g. 20-30 min |
distanceKm | number | ✅ | — | — | Distance from the search point |
address | string | — | ✅ | ✅ | Full address |
city | string | — | ✅ | ✅ | City |
postcode | string | — | ✅ | ✅ | Postal code |
imageUrl | string | ✅ | ✅ | ✅ | Main photo |
menuItemCount | integer | — | ✅ | ✅ | Total menu items |
menu | object[] | — | — | ✅ | Menu items with prices (see below) |
searchLocation | string | ✅ | ✅ | ✅ | The lat,lng point the search used |
country | string | ✅ | ✅ | ✅ | sg, my, ph, vn or id |
scrapedAt | string | ✅ | ✅ | ✅ | UTC timestamp of the scrape |
Each menu item in menu has category, name, description, price, discountedPrice, discountPct,
available and imageUrl.
⬆️ Output examples
All names and IDs below are fictional. The numbers are plausible examples, not real GrabFood data.
1. Fast mode item (search results)
{"url": "https://food.grab.com/sg/en/restaurant/online-delivery/2-EXAMPLE01","restaurantId": "2-EXAMPLE01","name": "Example Chicken Rice — Orchard","chainName": null,"branchName": "Orchard","cuisine": ["Chinese", "Local"],"rating": 4.6,"reviewCount": 812,"isOpen": true,"openingHours": null,"hasPromo": true,"promoLabels": ["20% off up to S$5"],"deliveryFee": 1.8,"currency": "SGD","estimatedDeliveryMinutes": 25,"deliveryTimeRange": "20-30 min","distanceKm": 1.2,"address": null,"city": null,"postcode": null,"imageUrl": "https://example.com/photos/example-chicken-rice.jpg","menuItemCount": null,"menu": null,"searchLocation": "1.3048,103.8318","country": "sg","scrapedAt": "2026-10-09T08:30:00.000Z"}
2. Detail item (with opening hours and address)
This record came from a search, so the fast fields are still filled. The detail fields add the address, hours and menu size.
{"url": "https://food.grab.com/sg/en/restaurant/online-delivery/2-EXAMPLE01","restaurantId": "2-EXAMPLE01","name": "Example Chicken Rice — Orchard","chainName": null,"branchName": "Orchard","cuisine": ["Chinese", "Local"],"rating": 4.6,"reviewCount": 812,"isOpen": true,"openingHours": {"sun": "10:00-21:30","mon": "10:00-21:30","tue": "10:00-21:30","wed": "10:00-21:30","thu": "10:00-21:30","fri": "10:00-22:00","sat": "10:00-22:00","displayedHours": "Daily 10:00 AM - 10:00 PM"},"hasPromo": true,"promoLabels": ["20% off up to S$5"],"deliveryFee": 1.8,"currency": "SGD","estimatedDeliveryMinutes": 25,"deliveryTimeRange": "20-30 min","distanceKm": 1.2,"address": "1 Example Street, #01-01, Singapore","city": "Singapore","postcode": "238000","imageUrl": "https://example.com/photos/example-chicken-rice.jpg","menuItemCount": 24,"menu": null,"searchLocation": "1.3048,103.8318","country": "sg","scrapedAt": "2026-10-09T08:31:12.000Z"}
3. Menu snippet (two of the 24 items)
With Include full menu with prices on, menu holds every item. This snippet shows two of them. Each price is in
major units (6.5 means S$6.50).
{"name": "Example Chicken Rice — Orchard","menuItemCount": 24,"menu": [{"category": "Chicken Rice","name": "Example Roasted Chicken Rice","description": "Half chicken, rice and chili sauce","price": 6.5,"discountedPrice": null,"discountPct": null,"available": true,"imageUrl": "https://example.com/photos/item-1.jpg"},{"category": "Sides","name": "Example Braised Egg","description": null,"price": 1.2,"discountedPrice": 1.0,"discountPct": 17,"available": true,"imageUrl": null}]}
🚀 How to use
- Pick a Country.
- Choose where to search. Either leave Search point empty to use the city center, or enter a
lat,lng. - Add Search terms (dish, cuisine or restaurant name), or leave them empty to get every restaurant near the point.
- Set Max items, turn on the detail or menu options you need, then click Start.
How to scrape restaurants near a location
Leave the search terms empty and set a point. Every restaurant near it comes back in fast mode.
{"country": "sg","latlng": "1.3048,103.8318","maxItems": 200}
For Jakarta, use "country": "id" and "latlng": "-6.2088,106.8456". Leave latlng out to use the city center.
How to search for a dish or cuisine
Each search term runs as its own search around the point, one after another. Items are saved in that order.
{"country": "my","searchTerms": ["nasi lemak", "satay"],"latlng": "3.1478,101.6953","maxItems": 100}
How to get full menus and prices
Turn on Include full menu with prices. It also turns on restaurant details, so you get the address and hours too.
{"country": "sg","startUrls": [{ "url": "https://food.grab.com/sg/en/restaurant/example-chicken-rice-orchard/2-EXAMPLE01" }],"includeMenu": true,"maxItems": 1}
Start URLs are always opened in detail mode, so they return address, openingHours and menuItemCount
without the fast-only fields (isOpen, deliveryFee, distanceKm, hasPromo).
Input fields
| Field | Type | Default | Description |
|---|---|---|---|
country | sg / my / ph / vn / id | sg | Market to search. Also sets the default city center. |
latlng | string | city center | Custom search point as lat,lng. |
searchTerms | string[] | [] | Dish, cuisine or restaurant name. Empty = all restaurants near the point. |
startUrls | request list | [] | GrabFood restaurant URLs. Each is opened in detail mode. |
maxItems | integer | 100 | Maximum number of restaurants to save. Error items do not count. |
includeDetails | boolean | false | Open each restaurant for address, opening hours and menu size. |
includeMenu | boolean | false | Also return the full menu with item prices. Implies includeDetails. |
proxyConfiguration | proxy object | Apify residential | Residential proxy is recommended. The proxy country is set from country. |
debug | boolean | false | Adds _debug diagnostic items to the dataset. For troubleshooting. |
⚡ Modes
| Fast (default) | Details | Details + Menu | |
|---|---|---|---|
| Turn on | nothing extra | includeDetails | includeMenu |
| Data | Search-level fields | + address, city, postcode, opening hours, menu size | + every menu item with prices |
| Requests per restaurant | None extra; one search call returns up to 32 restaurants | One extra request per restaurant | One extra request per restaurant (same request as Details) |
| Speed | Fastest | Slower: at least 2 s per restaurant because of the crawl delay | Same as Details; the menu is in the same response |
| Per-result fee | restaurant event | restaurant event | restaurant + menu events |
| Platform usage, measured Oct 2026 (Singapore) | ≈ $0.05 per 1,000 restaurants | ≈ $0.90 per 1,000 restaurants | ≈ $0.90 per 1,000 restaurants |
A run always starts one browser, so very small runs cost a little more per restaurant.
⚠️ Error items
If an input produces no restaurants, the actor does not fail silently. It saves an error item to the dataset
that says why. Error items have an error code and a note.
| Code | Meaning |
|---|---|
NO_RESULTS | The search returned no restaurants near the point. Try a bigger area, another term or another point. |
RESTAURANT_NOT_FOUND | A start URL did not match a GrabFood restaurant. Check the URL. |
INVALID_LOCATION | latlng could not be read. Use lat,lng, for example 1.3048,103.8318. The run stops after this item. |
BLOCKED | GrabFood refused the page after all retries (up to 4 retries per page). Try again later, or check the proxy settings. |
Example error item (no results for a search):
{"input": "durian pizza","searchLocation": "1.3048,103.8318","error": "NO_RESULTS","note": "GrabFood returned no restaurants for this search near the location."}
Error items are not charged.
🎯 Use cases
- Market research: count restaurants, cuisines and average ratings by area and see where the market is crowded.
- Competitor menu pricing: compare item prices and discounts across the restaurants that compete with you.
- Promo tracking: watch
hasPromoandpromoLabelsover time to see which restaurants run discounts and when. - B2B lead lists for suppliers: build a list of restaurant names and addresses (restaurant data only) for food suppliers or equipment sellers.
- Expansion and white-space analysis: run the same cuisine search across several points to find areas with few options.
🔌 Integrations, API & MCP
- Schedules: run it daily or hourly from the Schedules tab to track changes.
- Webhooks, Make, Zapier, n8n, Google Sheets: send results anywhere when a run finishes.
- API: start a run and get the items in one call:
curl -X POST "https://api.apify.com/v2/acts/Polyglot-Data~grabfood-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{"country":"sg","searchTerms":["chicken rice"],"latlng":"1.3048,103.8318","maxItems":50}'
Use with AI agents (MCP). The Apify MCP server lets Claude, ChatGPT and other MCP clients call this actor directly. Ask your assistant to "find chicken rice restaurants near Orchard Road in Singapore", and it runs the actor and reads back the dataset.
💰 Pricing
Pay per result. You pay a small fixed price for each restaurant saved to the dataset (restaurant event), and a
second small fee only for restaurants returned with their full menu (menu event, only when includeMenu is on).
See the Pricing tab for current prices. Platform usage is on top (see the Modes table). There is no monthly rental.
Error items and failed pages are never charged, and the actor stops when your run's maximum charge is reached.
A test run with maxItems: 20 costs little.
❓ FAQ
Which countries are supported?
Singapore, Malaysia, the Philippines, Vietnam and Indonesia, through the matching food.grab.com site. Thailand is not supported because GrabFood does not offer web ordering there.
Do I need my own proxies? No. The default is Apify residential proxy, and the proxy country follows the Country you pick. Keep it on residential.
Why does it use a browser? GrabFood's data API only answers to a guest session that the site's own JavaScript creates. The actor opens the page in a real browser to get that session (no login), then calls the same API from inside the page.
How fresh is the data? Every run reads GrabFood live when it starts. Results reflect the restaurant data at that moment.
Can I scrape a whole city? Yes. Raise Max items, or run several points across the city and merge the datasets. Each run covers the results around one point.
Why are some fields null?
Three common reasons: the field is only in fast mode or only in detail mode (see the field table); a start URL
skips the search, so fast-only fields such as isOpen and deliveryFee are null; or GrabFood did not publish
that value for the restaurant.
Something broke, or a field is missing. Open an issue in the Issues tab with the input you used and, if you can, the error item. Issues are answered quickly.
⚖️ Legal
This actor collects only publicly visible restaurant and menu information: names, addresses, cuisines, ratings, opening hours, delivery information and menu prices. It does not collect personal data, customer names or customer reviews. You are responsible for making sure your use complies with GrabFood's terms of service and with the laws that apply to you.