DuckDuckGo Scraper - Local Business Data, Yelp, Apple Maps
Pricing
$2.20 / 1,000 business delivereds
DuckDuckGo Scraper - Local Business Data, Yelp, Apple Maps
Local businesses Google Maps misses: 82 of 164 businesses returned here were not in Google Maps at all, measured on 10 category and city pairs. Website filled for 97.9% of rows, 44 fields per business, from Yelp and Apple Maps data. DuckDuckGo local business data API, Yelp data scraper.
Pricing
$2.20 / 1,000 business delivereds
Rating
0.0
(0)
Developer
Snow Leo Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
DuckDuckGo Local Business Scraper
Half the businesses this Actor returns are not in Google Maps results for the same search.
That is the whole reason to run it. It reads DuckDuckGo's local business search, which is built on Yelp and Apple Maps data — a different index from Google's, with a different set of businesses in it. If you already scrape Google Maps, this is the half of the local market you are not seeing.
- $0.42 per 1,000 businesses. The median price in this niche is $2.87 per 1,000 across 178 Actors that publish a per-result price; the cheapest serious Yelp-sourced competitor is $0.50.
- 44 fields per business, including price level, amenities, opening hours by day, and up to 5 review excerpts with the review text itself.
- No API key, no login, no proxy, no browser. Python 3.8, standard library only. That is why it costs what it costs.
How much of this is actually different from Google Maps?
Measured 16 September 2026 on 10 category + city pairs — coffee and dentists
in Austin, plumbers in Denver, hair salons in Miami, auto repair in Phoenix,
pizza in Chicago, florists in Seattle, gyms in Boston, bakeries in Portland,
vets in Atlanta.
For each pair, Google Maps was swept to its own ceiling (94–160 businesses per pair, far deeper than one page), and business names on both sides were reduced to a comparable form — no case, no accents, no
the / llc / inc / co / ltd| Businesses returned here | 164 |
| Of those, also found in Google Maps | 82 |
| Not in Google Maps at all | 82 (50%) |
Reproduce it: python3 tools/measure.py (the overlap script is in recon/).
The spread between pairs is wide, and worth knowing before you plan a run. Florists in Seattle overlap almost completely with Google (17 of 19 were in both); auto repair in Phoenix barely overlaps at all (2 of 18). An earlier, smaller run over 6 pairs read 62% — the number here is from the larger sample, because a representative measurement beats a flattering one.
There is a second, quieter difference. The website field — the one you need, because that is where an email address comes from — is filled for 97.9% of businesses here (375 rows) against 79.7% in a Google Maps sample of 300 rows taken the same day.
What you get per business
44 fields. Fill rates below are measured on 375 live businesses across 20 searches — ten US cities and ten cities in Europe, Canada and India.
Always there (100%): name, address, country_code, latitude,
longitude, timezone, yelp_url, place_id, data_providers.
| Field | Filled |
|---|---|
website, website_domain | 97.9% |
category, categories | 96.0% |
phone, phone_display | 94.7% |
city | 84.3% |
hours, hours_text (per weekday) | 82.7% |
rating | 80.0% |
reviews_count | 80.0% |
reviews (excerpt, stars, author, date) | 78.7% |
image_url, image_urls | 76.8% |
yelp_business_id | 49.6% |
neighborhood | 42.7% |
price_level, price_text ($ to $$$$) | 32.3% |
amenities, amenity_list | 24.8% |
menu_url | 14.4% |
The last four look thin worldwide because they are restaurant and bar fields. On a US hospitality sample (142 businesses across 8 searches — restaurants, bars, hotels, breweries, spas) the same fields read: review text 91.5%, amenities 74.6%, price level 57.7%, menu 55.6%.
Amenities are structured booleans, not a text blob: wifi_free, wifi_paid,
parking, parking_valet, outdoor_seating, wheelchair_accessible,
dogs_allowed, restaurant_takeout, restaurant_delivery.
Reviews carry the actual text. Of the 517 businesses checked, 265 came with 3 reviews, 121 with 5, and 92 with none.
data_providers tells you where each field came from — the source labels it
itself, per business, e.g.
{"address": "yelp", "hours": "yelp", "phone": "yelp", "url": "apple"}apple_name, apple_address, apple_phone), so you can see when
the two indexes disagree about the same place.
The source lies quietly, and this Actor catches it
This is the single most important thing on the page.
Lie one: a location it does not know is silently replaced by the location of
the machine that asked. Ask for coffee zzqqxxnotacity and the source answers
HTTP 200, no error, no warning — with businesses near whoever sent the request.
This was measured in the cloud, not on a laptop, because that is where it actually bites. From a laptop the swap is obvious: a couple of cafes with foreign addresses. From an Apify container, the same nonsense location returned 20 real coffee shops in Leesburg, Virginia — a real town, real businesses, real phone numbers, real opening hours. Nothing about that dataset looks wrong. A typo in one city name would have handed you a clean, plausible, completely useless lead list, and you would have paid for it.
With the check on, all 20 were dropped and the run reported
location-not-recognised. With it off, they are delivered as-is.
Lie two: the location is understood and the businesses are still somewhere
else. bakery mexico city is parsed correctly — the source echoes back
where: "mexico city" — and then returns a bakery in Oklahoma City, USA.
Verify location is on by default and handles both. Every response is checked
against what you asked for, row by row; rows that fail are dropped, counted, and
reported in the REPORT record as dropped_location_not_recognised and
dropped_location_mismatch, with the offending locations listed under
unrecognised_locations. Postcodes still work: search 78704 and the source
normalises it to Austin, TX, which the check accepts.
Turn it off with verifyLocation: false if you want the raw response.
There is no page 2, so volume comes from asking more questions
One search returns about 20 businesses — 14 to 21 across the 20-search sample —
and the source has no paging of any kind. Nine offset parameter names
(s, start, page, offset, max_results, count, n, first) and five
coordinate/bounding-box names (latitude/longitude, lat/lon,
is_lat/is_lon, bbox_tl/bbox_br, map_bbox) were all accepted silently
and all returned the same first page. One lever exists: the search string.
Two options use that lever.
Deep search (expandQueries) repeats every search with 12 measured
prefixes — cheap, top rated, open now, independent, famous, near,
popular, 24 hour, new, small, affordable, downtown. The list is not
guessed: 16 prefixes were tried and 3 dropped for returning nothing new.
Measured with the shipped code:
| Search | Queries | Plain | With deep search |
|---|---|---|---|
| coffee, Austin TX | 13 | 18 | 67 (×3.7) |
| plumber, Denver CO | 13 | 19 | 123 (×6.5) |
Districts (areas) prefix each location with a district, suburb or
postcode. Combining both levers on coffee in Austin — 58 searches over 12
districts and postcodes — returned 334 unique businesses against 19 from a
single search, a factor of 17.6.
Both cost requests: deep search is 13× the requests. maxQueries caps the total
per run (default 120), and the cheapest combinations always run first, so
hitting the cap costs you depth, not cities.
Monitor mode
incremental: true remembers what it already delivered, in a named key-value
store that survives between runs, and labels every row NEW, UPDATED or
UNCHANGED. Unchanged businesses are not written and not charged unless you ask
for them with emitUnchanged.
The fingerprint covers what actually changes and is worth watching: name,
category, address, phone, website, rating, review count, opening hours, price
level, permanently_closed. It deliberately excludes open_now and the other
instant flags — they change by the minute, and including them would mark half a
city as "updated" on every run and bill you for it.
Filters, and what they cost
Every filter runs before the delivery limit and before you are charged, so you pay for matches, not for candidates.
onlyWithWebsite, onlyWithoutWebsite (the lead list for anyone selling
websites), onlyWithPhone, onlyWithReviewText, skipPermanentlyClosed,
minRating, minReviews, minPriceLevel, maxPriceLevel, categoryContains,
nameContains, websiteDomainContains (find everyone on Squarespace or Wix),
countryCodes, requiredAmenities.
A filter never drops a business just because the field is missing. A business
with no rating survives minRating; a business whose amenities are unknown
survives requiredAmenities. Missing data is not a mismatch. The only
exceptions are the filters that ask about presence itself — onlyWithWebsite
and friends — where absence is the answer.
maxItems limits what you receive, not what is crawled. Ask for 100
businesses with a filter on and you get up to 100 matching businesses, not 100
candidates of which three survive.
When the result is empty, it says why
A green run with an empty dataset is the worst thing a paid Actor can do. This
one always names the reason, in the log and in the REPORT record's
empty_reason:
no-search-given, rating-filter-impossible, price-filter-impossible,
website-filter-contradicts-domain, reviews-filter-without-reviews,
charge-limit-reached, source-returned-nothing, location-not-recognised,
location-mismatch, filtered-out, nothing-changed, nothing-collected.
The first six are decidable from your input alone, and they are checked before a single request is sent — you are not billed time for a run whose result was already determined by the form you filled in.
Honest limits
Things this Actor does not do, or does worse than someone else. Named here rather than left for you to discover.
- No emails. The source never carries one. It carries a website for 97.9% of businesses, and getting an email means crawling that website — which is a different job and a different Actor.
- Up to 5 review excerpts per business, not the full review history. The excerpts come from the search response; there is no paging into older reviews. If you need every review of one business, use a dedicated Yelp review scraper.
- No direct business URLs. The endpoint is a search endpoint. You can search for a business by name and city, but you cannot hand it a Yelp page URL.
- No sort order. The source returns its own relevance order and accepts no sort parameter.
- Coverage is uneven outside big US cities and big categories. In the
20-search sample every search returned 14–21 businesses, including London,
Berlin, Paris, Amsterdam, Toronto, Dublin, Rome, Vienna, Madrid, Manchester
and Mumbai. But a spot check of thinner pairings returned far less:
plumber toronto0,cafe sydney1,bakery mexico city1,sushi tokyo3,gym dubai7. Check your pairing before planning a big run. - Six fields exist in the source and are always empty, so they are not
shipped:
popular_hours,facebook_id,instagram_id,twitter_id,order_url,embed. Checked across 517 live businesses — not one was ever filled. A column that is always empty pretends to a completeness that is not there. - The source's distance fields are deliberately dropped.
distanceis always 0, anddistance_to_user_metersis the distance to the machine that sent the request — in the cloud, a datacentre. In testing it read 8,948,075 metres from a laptop to an Austin coffee shop. That is not a number you can use. - Raw searches (
customQueries) cannot be location-checked, because there is no separate location to check against. - Leaving
locationsempty cannot be location-checked either, for the same reason — you did not name a place, so there is nothing to compare against, and the source answers with whatever is nearest the container. The run says so in the log rather than handing you the rows quietly.
Speed and cost
The source tolerated 12 back-to-back requests with no pause in 23.8 seconds — 30.2 requests per minute, zero HTTP 429s. It throttles itself with a ~2 second response time, so the Actor paces requests rather than hammering it. Budget roughly 2 seconds per search: a 120-search run takes about 4–5 minutes.
Runs on 512 MB. No proxy cost, no browser, no API key.
Reproducing every number on this page
Nothing here is an estimate.
python3 tools/measure.py ceiling # ~20 businesses per search, 14-21 rangepython3 tools/measure.py paging # 14 parameter names, 1 distinct pagepython3 tools/measure.py lies # both silent-fallback behaviourspython3 tools/measure.py rate # requests per minute, 429 countpython3 tools/measure.py expand # deep search multiplierspython3 tools/measure.py fill # field fill rates, dead fieldspython3 tools/measure.py prices # niche prices from the Apify store API
Results land in tools/measurements.json. python3 tools/verify_readme.py
checks every number in this file against that file, against the input schema and
against the code, and fails if any of them drift apart.
Tests: python3 -m unittest discover tests — 78 tests across logic without
network, live source, and run lifecycle (interruption, resume, repeat run,
memory between runs, charge limit).
What else is this called?
Also asked for as location verification geocoding, which is the verifyLocation input.