I built this actor; it's a paid tool on Apify with a free trial credit.

If you sell to small and mid-sized businesses in Germany, Gelbe Seiten is still the most complete public list of who exists where: electricians, tax advisors, dentists, lawyers, agencies, restaurants, sorted by category (Branche) and city. A category page shows 50 businesses; the rest load 10 at a time behind a "Mehr Anzeigen" button, and most emails only appear on each business's own page.

I'm an 18-year-old engineering student, and I built Gelbe Seiten Scraper to turn "Steuerberater in München" into one clean JSON row per business, over plain HTTP. This post covers how it works, calling it from Python and Node, what a row looks like, and a small project: ranking a city's businesses for outreach.

How it works

The category page (/branchen/<category>/<city>) server-renders the first 50 listing cards plus the total count and the parameters of its own "load more" form. The actor reads the cards (name, phone, website, address, district, rating), then posts the same load-more request the page sends to get the rest, 10 per request, in parallel. With business pages switched on, it opens each /gsbiz/<id> page and reads the schema.org LocalBusiness JSON-LD (coordinates, opening hours, services, payment methods, last update) plus the contact block (email, fax, social links).

Free-text searches (/suche/...) are excluded by gelbeseiten.de's robots.txt, so the actor never follows a redirect there. If you type Hotels and that is not a category, it tries hotel; if nothing matches, the search is listed in the run summary and costs nothing. Filters (must have email / website / phone, minimum rating) run before anything is charged. It connects directly and only retries blocked requests through a proxy.

Calling it from Python

from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") run = client.actor("rel8ble/gelbe-seiten-scraper").call(run_input={ "categories": ["Steuerberater", "Zahnarzt"], "locations": ["München", "Hamburg"], "maxResultsPerSearch": 100, "requireEmail": True, }) for b in client.dataset(run["defaultDatasetId"]).iterate_items(): print(b["name"][:40], "|", b["phone"], "|", b["email"], "|", b["postalCode"], b["city"]) 

The same from Node:

import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' }); const run = await client.actor('rel8ble/gelbe-seiten-scraper').call({ startUrls: ['https://www.gelbeseiten.de/branchen/elektriker/berlin'], maxResultsPerSearch: 0, // every listing scrapeDetails: false, // list data only: ~50 businesses per request }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); console.log(items.length, 'electricians in Berlin'); 

What a row looks like

From a real run, trimmed:

{ "name": "hansezahn I HAMBURG, Dr. Janke und Partner, Zahnärzte", "category": "Zahnärzte", "categories": ["Zahnärzte", "Schwerpunkt Endodontie", "Implantologie"], "phone": "040 6 53 99 82", "phoneE164": "+49406539982", "fax": "040 6 54 76 54", "email": "info@hansezahn-hh.de", "website": "http://www.hansezahn-hh.de", "address": "Rodigallee 250, 22043 Hamburg", "latitude": 53.57074, "longitude": 10.125491, "rating": 4.7, "reviewCount": 3, "openingHours": ["Monday 08:00-19:00", "Tuesday 07:30-19:00", "Wednesday 07:30-18:30"], "businessTypes": ["Dentist"], "premiumListing": true, "searchCategory": "Zahnarzt", "searchLocation": "Hamburg", "position": 9, "totalInSearch": 803 } 

In a 120-business test (tax advisors and dentists in two cities), 93% of rows had an email, 90% a website and every row a phone number and coordinates.

Project: ranking a city's businesses for outreach

Say you sell websites or online booking to local businesses. The best prospects have a phone and email but a weak online presence: no website, few reviews, no opening hours online.

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items()) def score(b): s = 0 s += 3 if not b["website"] else 0 # no website: the obvious pitch  s += 2 if b["reviewCount"] < 3 else 0 # barely any reviews  s += 1 if not b["openingHours"] else 0 # hours not published  s += 1 if not b["premiumListing"] else 0 # not paying for visibility yet  return s leads = sorted((b for b in rows if b["email"] or b["phone"]), key=score, reverse=True) print(f"{'business':40} {'score':>5} {'district':18} contact") for b in leads[:15]: print(f"{b['name'][:40]:40} {score(b):5} {(b['district'] or b['city'])[:18]:18} {b['email'] or b['phone']}") 

Because every row carries searchCategory, searchLocation and coordinates, the same dataset also answers "how many dentists per district" or feeds a map for territory planning.

Pricing and limits

$0.35 per 1,000 businesses as of 3 Nov 2026, no start fee, same price with or without business pages. Filtered businesses, duplicates, unknown categories and failed requests are never charged. Categories must be Gelbe Seiten's German names (zahnarzt, not dentist), Germany only, up to 5,000 businesses per category and city (split big cities by district). Business contact data of sole traders can be personal data under the GDPR, and German law limits unsolicited ads by email and phone, so check your outreach rules.

Store page: https://apify.com/rel8ble/gelbe-seiten-scraper

Need a custom scraper or a ready dataset? Hire me on Fiverr: https://www.fiverr.com/rel8ble

This article was drafted with AI and published by me.