DuckDuckGo Scraper - Local Business Data, Yelp, Apple Maps avatar

DuckDuckGo Scraper - Local Business Data, Yelp, Apple Maps

Pricing

$2.20 / 1,000 business delivereds

Go to Apify Store
DuckDuckGo Scraper - Local Business Data, Yelp, Apple Maps

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

Snow Leo Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

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 here164
Of those, also found in Google Maps82
Not in Google Maps at all82 (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.

FieldFilled
website, website_domain97.9%
category, categories96.0%
phone, phone_display94.7%
city84.3%
hours, hours_text (per weekday)82.7%
rating80.0%
reviews_count80.0%
reviews (excerpt, stars, author, date)78.7%
image_url, image_urls76.8%
yelp_business_id49.6%
neighborhood42.7%
price_level, price_text ($ to $$$$)32.3%
amenities, amenity_list24.8%
menu_url14.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"}
. Also exposed are the Apple Maps values alongside the Yelp ones (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:

SearchQueriesPlainWith deep search
coffee, Austin TX131867 (×3.7)
plumber, Denver CO1319123 (×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 toronto 0, cafe sydney 1, bakery mexico city 1, sushi tokyo 3, gym dubai 7. 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. distance is always 0, and distance_to_user_meters is 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 locations empty 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 range
python3 tools/measure.py paging # 14 parameter names, 1 distinct page
python3 tools/measure.py lies # both silent-fallback behaviours
python3 tools/measure.py rate # requests per minute, 429 count
python3 tools/measure.py expand # deep search multipliers
python3 tools/measure.py fill # field fill rates, dead fields
python3 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.