Local-first Python restaurant menu scraper, GrabFood & foodpanda Playwright network sniffer, SQLite database explorer, and CSV/JSON export tool with CLI and Web GUI.
RestaurantScraper is a local-first menu extraction system engineered to harvest, deduplicate, and catalog food menus from dynamic delivery platforms (GrabFood, foodpanda) and restaurant websites.
Traditional HTML scrapers fail against modern single-page applications (like GrabFood's Next.js frontend). RestaurantScraper overcomes this by utilizing a passive network response sniffer in headless Playwright Chromium that intercepts internal JSON payloads in real-time, normalizes currency from minor units to Ringgit (MYR), and intelligently merges venues using fuzzy name matching and geographic proximity.
The system is designed with zero external deployment bloat: you can run it directly from your terminal with a rich CLI or open the built-in, zero-dependency local Web GUI in your browser.
| Function | CLI Command | Local Web GUI (http://127.0.0.1:8765) |
|---|---|---|
| 1. Universal Scraper | restaurantscraper scrape <URL> |
Scraper Studio: Paste URLs, select options, view live streaming terminal logs, and inspect immediate parsed item previews. |
| 2. Database Explorer | restaurantscraper db list / db view <ID> |
Database Explorer: Instant search across restaurants/items, area filtering, and categorized menu modal catalogs. |
| 3. Clean Data Export | restaurantscraper export |
Export Center: 1-click browser download of sanitized Excel-ready CSV and structured hierarchical JSON. |
RestaurantScraper includes a zero-dependency, local-first Web GUI built with vanilla JavaScript and Tailwind CSS that connects directly to the embedded SQLite database and Python scraper engine.
Browse saved restaurants, filter by delivery area, select individual venues with checkboxes, and trigger selective multi-venue exports directly from the sticky action bar.

Click Inspect Menu on any restaurant to browse dishes organized by category (e.g. Mains, Beverages, Desserts), search items, inspect ingredients & pricing in MYR, and export that single restaurant with one click.

Scrape GrabFood and foodpanda menus visually with quick URL presets, custom area/cuisine overrides, live real-time execution logs, and immediate item previews.

Export your collected data in clean, sanitized CSV (RFC 4180 compliant with formula injection protection) or nested hierarchical JSON. Choose between exporting the entire catalog or only selected restaurants.

Follow this thorough guide to install and run RestaurantScraper on your machine from scratch.
Ensure you have the following installed on your machine:
- Git: git-scm.com
- Python 3.11, 3.12, 3.13, or 3.14: Run
python3 --versionto verify.
Open your terminal (or Command Prompt / PowerShell on Windows) and clone the repository:
# Clone the repository
git clone https://github.com/ElXprogramming/RestaurantScraper.git
# Enter the project directory
cd RestaurantScraperIt is best practice to run RestaurantScraper within an isolated virtual environment:
# Create a virtual environment named .venv
python3 -m venv .venvActivate the environment based on your operating system:
-
Linux / macOS (Bash or Zsh):
source .venv/bin/activate -
Windows (PowerShell):
.venv\Scripts\Activate.ps1
(If PowerShell gives an execution policy error, run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserfirst) -
Windows (Command Prompt):
.venv\Scripts\activate.bat
-
Fish Shell:
source .venv/bin/activate.fish
Once activated, your terminal prompt will be prefixed with (.venv).
Install all core dependencies and install the project in editable mode:
# Install required packages (typer, rich, playwright, beautifulsoup4, requests, etc.)
pip install -r requirements.txt
# Install the restaurantscraper command-line tool into your environment
pip install -e .The scraper uses Playwright Chromium to intercept background JSON network traffic from GrabFood and render JavaScript-heavy restaurant websites:
# Install the Chromium browser binary
playwright install chromiumNote for Linux Server / Ubuntu users: If your system is missing system graphical libraries, run:
playwright install --with-deps chromium
RestaurantScraper works immediately out of the box with default settings. Optionally, you can configure custom settings by copying .env.example:
cp .env.example .envAvailable environment variables:
GOOGLE_PLACES_API_KEY: (Optional) Only needed if you want to discover venues using Google Places API in addition to OpenStreetMap.SCRAPER_UA: (Optional) Custom User-Agent string for responsible web requests.MENU_DB: (Optional) Path to your SQLite database file (defaults todata/menus.db).
You have two interfaces to choose from: the Local Web GUI and the Command-Line Interface (CLI).
To launch the modern interactive browser interface, run:
restaurantscraper ui
# Or alternatively: python3 -m scraper uiYour default web browser will automatically open to:
http://127.0.0.1:8765
The GUI consists of three dedicated tabs:
- Paste any GrabFood, foodpanda, or restaurant website URL into the Target URL field.
- Example GrabFood URL:
https://food.grab.com/my/en/restaurant/sushi-king-1borneo-hypermall-delivery/1-CYWJGEEFSEVKEA
- Example GrabFood URL:
- (Optional) Click Optional Overrides to specify custom restaurant name, area (
1borneooralamesra), or cuisine tags. - Check Use Headless Chromium Rendering if targeting JavaScript-heavy websites.
- Click π Run Scraper.
- Watch the real-time execution log. Once finished, a summary card will appear showing the restaurant details, address, rating, and an immediate preview table of all scraped menu items and prices in Ringgit (MYR). The items are automatically saved into your SQLite database.
- Browse all stored restaurants (the included dataset comes pre-loaded with 25 restaurants and 1,325 items around 1Borneo and Alamesra).
- Selective Export via Checkboxes:
- Click checkboxes on any individual restaurants (or check the header checkbox to select all).
- A selection action bar appears immediately: click π Export Selected CSV or π¦ Export Selected JSON to export only your chosen venues.
- Instant Search: Type in the search box to filter restaurants and menu items in real-time.
- Filter: Filter by area (1Borneo Hypermall, Alamesra Plaza) or menu completeness (Has Menu Items, Needs Scraping).
- Sort: Sort by rating, total items, lowest average price, or alphabetical order.
- Inspect Menu & Single-Restaurant Export: Click Inspect Menu on any row to open the full modal catalog:
- Click π Export CSV or π¦ Export JSON in the modal header to export only that specific restaurant's menu.
- View full address, operating hours, coordinates, and direct links.
- Click category pills (e.g. Rice, Beverages, Set Nasi, Noodles) to filter sections.
- Search for specific dishes within the restaurant's menu.
- Choose Export Scope:
- All Restaurants: Export the entire database (all 25 venues and 1,325 items).
- Selected Restaurants Only: Export only the venues currently selected in Database Explorer.
- Download CSV: Click β¬ Download CSV for an Excel-ready dataset with UTF-8 BOM encoding and protection against spreadsheet formula injection.
- Download JSON: Click β¬ Download JSON for a clean nested JSON catalog.
- Write to Disk: Click πΎ Write to exports/ directory to write updated files directly into your local
exports/folder (also respects your selection scope).
RestaurantScraper provides a Typer-powered CLI with Rich terminal formatting.
Scrape a GrabFood delivery page, foodpanda page, or restaurant website directly from your terminal:
# Scrape a GrabFood restaurant menu
restaurantscraper scrape "https://food.grab.com/my/en/restaurant/sushi-king-1borneo-hypermall-delivery/1-CYWJGEEFSEVKEA"
# Scrape with headless browser rendering for JS websites
restaurantscraper scrape "https://www.example-restaurant.com" --render
# Scrape with custom name and area overrides
restaurantscraper scrape "https://food.grab.com/..." --name "Sushi King" --area 1borneo --cuisine "Japanese"Check the database status, restaurant count, total menu items, price range, and source breakdown:
restaurantscraper statsDisplay an organized Rich table of restaurants:
# List top 20 restaurants
restaurantscraper db list --limit 20
# Filter by area
restaurantscraper db list --area 1borneo
# Search by restaurant name, cuisine, address, or item keyword
restaurantscraper db list --search "ayam"
# Sort by rating, items count, or average price
restaurantscraper db list --sort rating
restaurantscraper db list --sort items
restaurantscraper db list --sort priceInspect full restaurant details and its categorized menu:
# Inspect by restaurant name
restaurantscraper db view "After 10 Cafe"
# Or inspect by restaurant ID
restaurantscraper db view 24This outputs:
- Restaurant address, rating, operating hours, and GrabFood/website links.
- Tables for each menu category (e.g. Ayam Goreng Crispy, Set Nasi, Beverages).
- Individual item names, prices in Ringgit (MYR), descriptions, and source tags.
Query OpenStreetMap Overpass (free, no API key required) to discover eateries around 1Borneo and Alamesra:
restaurantscraper discover --source osmQuickly attach a GrabFood link, foodpanda link, or website to a restaurant:
restaurantscraper add-link "Sushi King" "https://food.grab.com/my/en/restaurant/..."Export either the entire database or only specific selected restaurants to CSV and JSON:
# Export all restaurants in the database
restaurantscraper export
# Export only specific restaurant IDs (e.g. ID 24 and ID 19)
restaurantscraper export --ids 24,19
# Export by restaurant name filter
restaurantscraper export --name "Sushi King"
# Export by area (e.g. 1borneo or alamesra)
restaurantscraper export --area 1borneo
# Export to a custom output directory
restaurantscraper export --ids 24 --out-dir my_custom_exports/Outputs:
exports/menu_items.csvexports/restaurants.json
GrabFood's web client is built with Next.js and renders menus dynamically on client-side hydration. Standard HTTP crawlers (like curl or requests) only receive an empty HTML shell.
RestaurantScraper implements a specialized passive network sniffer:
- Geolocation Lock: Headless Chromium launches with Kota Kinabalu coordinates (
6.0360, 116.1297) anden-MYlocale to bypass Grab's regional delivery locks. - Passive Sniffing: Uses Playwright's
page.on("response")event to listen for asynchronous JSON API payloads returned by Grab's gateway. - Recursive Catalog Walker: Recursively traverses nested categories, sections, item variations, and modifiers (
_walk()function inscraper/extract_delivery.py). - Price Normalization: Converts GrabFood's integer minor unit prices (
priceInMinorUnit / 100) to standard Ringgit floats (e.g.1290->RM 12.90). - Fuzzy Deduplication: When adding restaurants, the engine calculates name similarity (via
difflib.SequenceMatcher) and geographical distance (via the Haversine formula <= 150m) to automatically merge records instead of creating duplicates.
RestaurantScraper comes with a comprehensive test suite testing extraction logic, security sanitization, CLI commands, and the local GUI server:
# Run all tests
pytest testsExpected output:
tests/test_cli.py ...... [ 22%]
tests/test_scraper.py ............ [ 66%]
tests/test_security.py ........ [ 96%]
tests/test_server.py . [100%]
============================== 27 passed in 1.31s ==============================
You need to install the Chromium browser binary used by Playwright. Run:
playwright install chromiumIf running on an Ubuntu or Debian server without desktop packages:
playwright install --with-deps chromiumEnsure your virtual environment is activated (source .venv/bin/activate), or execute the package directly using Python:
python3 -m scraper ui
# or: python3 -m scraper scrape "URL"The CSV exporter automatically writes a UTF-8 BOM (utf-8-sig) header, ensuring Microsoft Excel, Apple Numbers, and LibreOffice Calc render non-ASCII characters correctly.
Any cell starting with dangerous characters (=, +, -, @) is safely prefixed with an apostrophe (') by scraper/security.py to prevent CSV Formula Injection attacks when opening files in spreadsheet software.
- Rate-Limiting: A polite delay (default: 2.0s per host) is enforced between requests.
- Robots Exclusion: Standard
robots.txtdirectives are parsed and respected. - Academic & Research Use: Cite your data sources when using this dataset for research or reporting.
Contributions, bug reports, and feature ideas are welcome! Please read CONTRIBUTING.md and our Code of Conduct before opening a pull request. To report a security issue, see SECURITY.md.
This project is licensed under the MIT License.