JustDial Scraper [From $2💰] | India Business Leads
Pricing
from $1.65 / 1,000 results
JustDial Scraper [From $2💰] | India Business Leads
Extract Indian business leads from JustDial: name, category, phone, address, locality, pincode, rating, review count, verified badge, coordinates and profile URL. Uses JustDial's own JSON APIs - no browser needed. Built for lead generation, local SEO and India market research.
Pricing
from $1.65 / 1,000 results
Rating
0.0
(0)
Developer
Ahmed Jasarevic
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
8 days ago
Last modified
Categories
Share
JustDial Scraper — Scrape Indian Business Listings And Phone Numbers
The JustDial Scraper extracts Indian business leads from JustDial (justdial.com) — India's largest local business directory — as lead-ready JSON with 19 fields per business: phone, address, locality, pincode, rating, review count, verified badge, coordinates and canonical profile URL. It reads JustDial's own internal JSON listing APIs over plain HTTP (Crawlee's CheerioCrawler) — no browser, no DOM scraping, no JavaScript rendering.
Main use cases
- B2B lead generation India — call-ready rows (name, phone, address, pincode), deduped across runs with
docId. - India company contact database — rebuild a category × city list on a schedule instead of buying a static one.
- Local business leads India — plumbers, dentists or any JustDial category, one city or twenty.
- Local SEO India data — NAP-consistent name/address/phone rows plus
profileUrlfor citations. - India market research data — category density,
rating,reviewCount,verifiedper market. - SMB prospecting data India — rank prospects by rating, reviews and verified badge.
Build Indian Business Leads Without Official API Access
JustDial serves listings through its own internal JSON APIs — the endpoints the justdial.com frontend calls when you scroll. The Actor walks four endpoint/wap combos in order with a mobile User-Agent, pins the first surface that answers for the rest of the search, sanity-checks every row and writes one dataset item per business. Plain HTTP + Cheerio only: 256 MB RAM, ~30 businesses per request, no per-business requests.
Input Parameters
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
searchUrls | string[] | No | – | JustDial category/search URLs — most reliable mode; category id goes straight to the internal API, e.g. https://www.justdial.com/Pune/Stationery-Shops/nct-10453443 |
queries | string[] | No | – | Free-text searches used with city, e.g. ["plumbers","dentists"] |
city | string | No | Pune | Indian city for queries (Pune, Mumbai, Bangalore, Delhi, Hyderabad, …) |
area | string | No | "" | Optional locality filter, e.g. Koregaon Park; empty = whole city |
nationalCatId | string | No | "" | Optional JustDial national category id (nct-…) for queries |
maxItemsPerSearch | integer | No | 50 | Businesses per search (1–500). The only limit you need — page count derives at 30 rows/page; the Actor stops on its own |
proxy | object | No | {"useApifyProxy": true, "apifyProxyCountry": "IN", "apifyProxyGroups": ["RESIDENTIAL"]} | Indian residential is the reliable setting; swap in proxyUrls if the pool is flagged |
Nothing is required, but rows appear only with a searchUrls entry or queries string. Everything else is internal and tuned: 25 parallel requests, 400 per run, 6 fresh proxy IPs per page, ≤25 pages per search. Empty run? Almost always the proxy pool — If every IP is refused.
Output Fields
One item per business in the run's default dataset — 19 fields:
| Field | Type | Description |
|---|---|---|
name | string | Business name as listed |
category | string | JustDial category; arrives as type on write-wap* surfaces, normalised here |
phone | string | Contact number incl. +91; absent if none on file |
address | string | Full street address with locality, city, pincode as rendered |
locality | string | Neighbourhood/area without city and pincode |
city | string | Upstream casing varies (Pune/pune) — compare case-insensitively |
pincode | string | Postal code as string (leading zeroes survive) |
rating | number | Average rating 0–5; null when unrated |
reviewCount | number | Total reviews behind rating; null when none |
verified | boolean | JustDial verified badge; unknown/unverified is false |
latitude, longitude | number | WGS84 pin coordinates; null when not geocoded |
thumbnail | string | Catalogue image URL on JustDial's image CDN; null if no photo |
profileUrl | string | Canonical justdial.com profile URL from city + docId; null only without docId |
docId | string | JustDial's stable business id — dedupe key across runs |
searchTerm, searchUrl | string | The query/category and the search URL that produced the row |
apiSurface | string | Endpoint that answered: read-wap1, read-wap2, write-wap1, write-wap2 |
scrapedAt | string | UTC ISO 8601 collection timestamp |
hours,websiteand review text are not available — no non-JS endpoint serves them. See Why there are no profile pages.
Views. The dataset schema ships with the Actor, so the Output tab renders a table instead of raw JSON:
| View | Shows | Use it for |
|---|---|---|
| Overview (default) | photo, name, category, rating, reviews, verified, city, locality, profile link | skimming results |
| Contact | name, phone, address, locality, city, pincode, profile link | lead generation |
| Map data | name, city, locality, pincode, latitude, longitude | mapping and territory analysis |
Views change Console display only — JSON, CSV and Excel exports always contain all 19 fields. Each field carries title/description/example in the schema, which is what the API returns in the run's output property and what AI agents read when chaining this Actor.
Example Input And Output
Category URLs (most reliable mode)
{"searchUrls": ["https://www.justdial.com/Pune/Stationery-Shops/nct-10453443","https://www.justdial.com/Mumbai/General-Physician-Doctors/nct-10892680"],"maxItemsPerSearch": 50,"proxy": { "useApifyProxy": true, "apifyProxyCountry": "IN", "apifyProxyGroups": ["RESIDENTIAL"] }}
Free-text queries
{ "queries": ["dentists", "plumbers"], "city": "Pune", "maxItemsPerSearch": 50 }
One dataset item
{"docId": "022PXX22.XX22.191204000717.B8L5","name": "Dr Salaah Qureshi","category": "General Physician Doctors","phone": "07947159457","address": "Bon Bon Lane, Juhu Versova Link Road, Andheri West, Mumbai","locality": "Juhu","city": "Mumbai","pincode": "400049","rating": 4.5,"reviewCount": 210,"verified": true,"profileUrl": "https://www.justdial.com/Mumbai/Dr-Salaah-Qureshi/022PXX22-XX22-191204000717-B8L5","searchTerm": "General Physician Doctors","apiSurface": "read-wap1","scrapedAt": "2026-10-02T10:30:00.000Z"}
Scrape JustDial Business Listings And Phone Numbers Without A Browser
The four-surface fallback chain
Four endpoint/wap combos are tried in order; on a blocked, non-JSON or empty response the Actor falls through, and the first surface that answers is pinned for the rest of that search.
| # | id | Endpoint | Response shape |
|---|---|---|---|
| 1 | read-wap1 | /api/india_api_read/26june2015/searchziva.php?wap=1 | results[] — objects with named keys (docId, name, VNumber, address, NewAddress, city, compRating, totalReviews, verified, …) |
| 2 | read-wap2 | same endpoint, wap=2 | same as #1 |
| 3 | write-wap1 | /api/india_api_write/20march2020/searchziva.php?wap=1 | results: { columns[], data[][] } — columnar |
| 4 | write-wap2 | same endpoint, wap=2 | same as #3 |
All four use the mobile User-Agent; surface 1 answers first in practice, 2–4 are fallbacks, 3/4 differ in payload shape.
The edge keys off User-Agent, not the wap flag
A live probe of all eight combinations (read/write × wap=1|2 × mobile/desktop) proved it:
| Client | Result |
|---|---|
| mobile UA | 200 JSON on read and write; wap=1 and wap=2 byte-identical |
| desktop UA | the 14-byte <HTML>\n</HTML> Akamai stub — 4 out of 4 |
Desktop variants were guaranteed failures — and expensive: each dead surface burns a full maxIpAttempts round of fresh IPs, so three of them meant 18 of every 30 requests went to endpoints that could never answer. With only live surfaces, the same IP budget buys roughly 2× real attempts. /api/resultsPageListing is dead too (its sw8 + search_id/trkid/jdlt signature is not reproduced): POST → stub, GET → {"error":1,"message":"Invalid request"}, searchListJson → 404.
Columnar parsing, decoy arrays and the sanity check
Surfaces 3/4 return the same fields columnar (results.columns / results.data), zipped into named rows — type, not category. Decoy arrays (AlsoListedIn, nearmeDetails, keywordResults) are business-shaped enough to pass for results, so the Actor reads results first. A row is dropped unless it has a real docId, a name that is not a copy of the locality, and a rating in 0–5; a whole page failing that check moves the Actor to the next surface instead of reporting "0 matches".
Why there are no profile pages (no hours, no website)
Profile-fetching was removed — it cannot work and was harmful:
- The page is a client-rendered React shell (HTTP 200,
window.__INITIAL_STATE__is{}, no ld+json, ~218 KB app bundle, business name absent). - It redirects to the homepage, overwriting the correct
profileUrl. detailsFetched: truewas a lie (set even on empty shells).- Cost ~1 request per business (26 of 29 requests in the diagnostic run) and returned nothing.
- The listing API has 75 fields, none of them hours or website.
Hours/website/review text need JavaScript — out of scope; the Actor stays Cheerio-only (256 MB, no Chrome).
Fingerprint handling: IP reputation, not the fingerprint
got-scraping's default desktop Chrome 147 spoof (TLS profile + generated headers) contradicts a mobile UA + wap=1, so it is disabled in preNavigationHooks — plain HTTP/1.1, Node's default TLS, mobile headers only. IP reputation is what gets refused, not the fingerprint: a plain residential fetch returned 30 listings/page (totalNumberofResults: 149) five times out of five until a request burst flipped it to the stub; a browser-grade TLS/HTTP2 fingerprint (curl-cffi-node) changed nothing and was dropped rather than carried as a 35 MB native dependency. The probe ran from a non-Indian IP (BA, Sarajevo) and got full Pune data — not geo-fenced. Only IP rotation works; maxIpAttempts sets how hard it tries.
JustDial Scraper Limits, Anti-Bot Defence And Troubleshooting
JustDial sits behind Akamai (AkamaiGHost / Bot Manager), serving a stub to datacenter and to flagged residential IPs. Measured decision order:
- Desktop
User-Agent→ stub immediately — 4 of 4 requests, even on a perfect IP. - Flagged IP reputation → stub, regardless of country (an unflagged non-Indian residential IP got full data — not geo-fenced).
/api/resultsPageListing→ stub on GET and POST (signature not reproduced).
The Actor detects blocks (403/429/451, Access Denied, errors.edgesuite.net, or the ~14-byte stub) and logs them; runs every request on a fresh proxy IP, retrying up to maxIpAttempts times; and falls through to the next surface.
Blocked runs: set proxy to apifyProxyCountry: "IN" + apifyProxyGroups: ["RESIDENTIAL"] — the most effective setting, because Indian traffic reads as normal local traffic.
Why one IP per request, and no cookie warm-up
The block is IP-level across the whole domain and a flagged IP stays flagged, so a sticky session would lock the run onto it. The Actor retires its Crawlee session after every request so Apify hands out a different IP next time (the pattern working scrapers here use: fresh session per page, up to 6 IPs). No cookie warm-up either — Akamai only issues a usable _abck/bm_sz after JavaScript runs, and replaying a cookie from a previous IP is an IP/cookie mismatch, a stronger bot signal than none.
Platform measurement: datacenter fails, RESIDENTIAL is the setting
From Apify's datacenter IPs every surface returns HTTP 403 and 0 items; apifyProxyCountry: "IN" helps but those are still datacenter IPs, blocked intermittently. "apifyProxyGroups": ["RESIDENTIAL"] is the reliable setting while the pool serves unflagged IPs, which varies. 6 fresh IPs refused everywhere means the pool is flagged now — use proxy.proxyUrls or re-run later. robots.txt disallows /api/, /api/india_api_read, /_next/ — hit deliberately as the site's own data endpoints.
Pricing and run limits
- CheerioCrawler only — 256 MB default memory (512 MB max), no browser → low compute cost.
result$0.0018/business (FREE), $0.00165 at GOLD+, plusActor Start$0.00005/run.- 400 requests/run cap +
maxItemsPerSearch= no runaway; concurrency fixed at 25. - ~30 businesses/request → run cost ≈
searches × pages; no per-business requests. - Free tier: 10-business preview; paying users: unlimited.
- Residential proxy traffic counts against the account's allowance (a response is tens of KB).
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
🔁 … retrying on a fresh IP (2/6) | Normal — previous IP refused | Nothing; up to 6 IPs/page |
… 6 fresh IPs refused | Pool handed out blocked IPs | Pool flagged: use proxy.proxyUrls or re-run later |
🚫 blocked (HTTP …) | Akamai refused your IP | proxy with apifyProxyCountry: "IN" + RESIDENTIAL, re-run |
🚫 soft-blocked (HTTP 200, empty) | 200 with an empty envelope — same block, quieter | Same fix; commonest failure, looks like "0 matches" |
no usable rows (mis-mapped) | Rows failed the sanity check | Harmless; next surface. Report if persistent everywhere |
Empty dataset, ~0 matches | Query too narrow vs category URL | Use a category searchUrls URL, not free-text queries |
hours / website missing | No non-JS endpoint serves them | Not fixable — see Why there are no profile pages |
| Free user sees 10 rows | Preview cap | Expected — upgrade |
If every IP is refused
The edge refuses many ranges and the share varies. Measured Oct 2026: one run had 50 consecutive refusals on 50 different IPs while two egress probes through the same sessions returned HTTP 200 — the proxy worked, JustDial rejected the exit IPs; minutes later, same settings, other runs got clean Indian egress and 10–26 real listings with zero blocks. The pool is unreliable, not dead: maxIpAttempts helps when IPs are flagged, but raising it after repeated refusals only costs time. Part of the refusals was self-inflicted — older builds cycled desktop-UA surfaces that can only return the stub, which is why only JSON-verified surfaces remain.
Each run probes egress via ipwho.is (🌐 Egress IP for this request: … (IN)). apifyProxyCountry: "IN" is a preference, not a guarantee: the pool may serve another country or return 407 … no usable proxies from country 'IN'. Then use your own proxy:
{ "proxy": { "useApifyProxy": false, "proxyUrls": ["http://user:password@gate.provider.com:8000"] } }
proxyUrls overrides apifyProxyGroups/apifyProxyCountry (dropped when set, ProxyConfiguration rejects the combination); the first egress-IP log confirms it works.
Integrations & Automation
- Apify Console — run manually, or save the input as an Actor task and schedule it daily/weekly to refresh one category per city.
- Apify API —
POST https://api.apify.com/v2/acts/cyha8usQ64pSophWE/runs?token=YOUR_TOKENwith the input JSON as body; pollGET https://api.apify.com/v2/actor-runs/{runId}; readGET https://api.apify.com/v2/datasets/{datasetId}/items?format=json. - Webhooks — fire on
actor.run.succeededto push rows into your CRM, Sheet or queue the moment a run ends. - Zapier / Make — the Apify integration exposes runs and dataset items as triggers/actions, so each run appends leads automatically.
- Recurring schedule — weekly monitoring of one category × one city keeps a lead pool fresh.
Related Actors
| Actor | Notes |
|---|---|
| lead-extractor---justdial | JustDial leads — 737 users, 5.0★, $0.0028/record |
| justdial-business-scraper | 459 users, $0.002/result; claims decoded phone, timing, website, photos, "30M+ listings across 1000+ cities" |
| justdial-business-scraper | 214 users, 5.0★, $0.03/business; claims WhatsApp, email, GPS, hours, year, turnover |
| indiamart-supplier-scraper | IndiaMART B2B suppliers — 542 users, 4.55★, $0.0017/result |
| sulekha-scraper | Sulekha local-service listings, same India lead-gen cluster |
FAQ
Is there an official JustDial API?
No public official API exists. RapidAPI wrappers and scraper-API vendors fill the gap; the data actually comes from JustDial's own internal JSON endpoints (searchziva.php and friends), which this Actor calls over plain HTTP.
What's the best way to get Indian business leads with phone numbers?
Scrape a live directory (JustDial for hyperlocal, phone-first listings), scrape a B2B marketplace (IndiaMART for suppliers), or buy a static database. Live scraping stays current and dedupable — this Actor returns phone, address, pincode, rating, reviews and coordinates per business with docId as merge key, browserless.
JustDial vs IndiaMART vs Sulekha — which is better for lead generation?
B2B versus hyperlocal: IndiaMART is supplier/manufacturer discovery (its Store actors target "suppliers"); JustDial and Sulekha are local service and retail directories, JustDial the phone-first one. Local consumer leads → JustDial; factory/trade supply → IndiaMART.
Why is JustDial blocking my scraper?
Akamai Bot Manager decides in order: desktop UA → 14-byte stub immediately (4 of 4); flagged IP reputation → stub regardless of country; datacenter IPs → HTTP 403. Fix: Indian residential proxies (apifyProxyCountry: "IN" + apifyProxyGroups: ["RESIDENTIAL"]), one fresh IP per request (up to 6/page) and no cookie replay across IPs.
How do I extract JustDial phone numbers without a browser?
Call the internal listing JSON APIs with plain HTTP + Cheerio: read-wap* returns objects with named keys (VNumber → phone), write-wap* the same fields columnar. A mobile User-Agent is mandatory — desktop gets the stub on every surface.
Is scraping JustDial legal?
JustDial's terms prohibit data mining, scraping and crawling, and robots.txt disallows the API paths used here. The output is public business contact data, but running it may breach the ToS and could attract liability under India's IT Act, 2000. Compliance is the user's responsibility.
What are the alternatives to this JustDial scraper?
thirdwatch/justdial-business-scraper (459 users, $0.002/result), memo23/justdial-business-scraper (214 users, 5.0★, $0.03/business, claims WhatsApp/email/GPS/hours), happitap/lead-extractor---justdial (737 users, $0.0028/record), tugelbay/justdial-leads-extractor ($0.00288/business), iskander/just-dial-extractor-scraper ($0.005/business); adjacent: IndiaMART, Sulekha, AskLaila and Yellow Pages India actors.
SEO Keywords
justdial scraper, scrape justdial, justdial api, justdial business listings, justdial phone number extractor, justdial leads extractor, justdial data extraction, indian business leads, india business phone number database, b2b lead generation india, local business leads india, india company contact database, local business directory data india, local seo india data, india market research data, smb prospecting data india, justdial alternative api, justdial alternatives india, indiamart vs justdial, sulekha vs justdial, apify justdial, justdial scraper mumbai, justdial scraper delhi, justdial scraper bangalore, business leads delhi ncr
For AI Agents & LLM Apps
Purpose: extract structured Indian business listings (name, phone, address, rating, coordinates) from JustDial's internal JSON listing APIs without a browser.
Minimal working input:
{ "searchUrls": ["https://www.justdial.com/Pune/Stationery-Shops/nct-10453443"], "maxItemsPerSearch": 50 }
Variant — free-text mode: { "queries": ["dentists"], "city": "Pune", "maxItemsPerSearch": 50 }
Output fields: name, category, phone, address, locality, city, pincode, rating, reviewCount, verified, latitude, longitude, thumbnail, profileUrl, docId, searchTerm, searchUrl, apiSurface, scrapedAt.
Behaviors an agent should know:
- No required fields; without
searchUrls/queriesa run returns no rows.searchUrlsis most reliable;queriesusescity(defaultPune) + optionalarea/nationalCatId. maxItemsPerSearch(default 50, 1–500) is the only limit to set; page count derives at 30 rows/page. Internals: 400 req/run cap, concurrency 25, ≤25 pages/search, 6 fresh IPs/page (maxIpAttempts).- Proxy defaults to Apify residential India;
proxy.proxyUrlsoverrides and dropsapifyProxyCountry/apifyProxyGroups(ProxyConfigurationrejects the combination). docIdis the dedupe key;profileUrlderives from city +docId;apiSurfacenames the endpoint;citycasing varies — compare case-insensitively.- Rows never include
hours,websiteor review text — they need JavaScript, which this Actor never runs. - Empty dataset ≈ flagged proxy pool (
6 fresh IPs refused/soft-blocked (HTTP 200, empty results)), not a wrong query.
Billing: pay-per-event — result $0.0018/business (FREE; $0.00165 at GOLD+) plus Actor Start $0.00005/run.
Legal & Compliance Disclaimer
- Independent project, not affiliated with, endorsed by, sponsored by or associated with JustDial / JD Software.
- It accesses only JustDial's publicly reachable internal listing JSON endpoints over plain HTTP — no login, no account, no CAPTCHA or credential bypass — though those paths (
/api/,/api/india_api_read,/_next/) are disallowed by justdial.comrobots.txtand JustDial's terms prohibit data mining, scraping and crawling. - Users are solely responsible for their own compliance with JustDial's Terms of Service and applicable data-protection law (India's Information Technology Act, 2000; GDPR/CCPA where they apply).
- The output contains personal contact data (business phone numbers and addresses) and must not be used for unsolicited commercial outreach in violation of applicable law.