# Gelbe Seiten Scraper — German Business Directory Leads (`worktech/gelbeseiten-scraper`) Actor

Scrape German business leads from Gelbe Seiten (gelbeseiten.de): company name, phone, email, website, street, postal code, city, category and opening hours. Search by trade and city (Klempner Berlin), export to JSON, CSV or Excel. Business listings only, no private individuals. $1.20 per 1,000.

- **URL**: https://apify.com/worktech/gelbeseiten-scraper.md
- **Developed by:** [Per Schondell](https://apify.com/worktech) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.20 / 1,000 business records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Gelbe Seiten Scraper — German Business Directory Leads

The **Gelbe Seiten scraper** that turns Germany's largest business directory (gelbeseiten.de) into a clean B2B lead list: company name, **phone number, email, website, street, postal code (PLZ), city, district (where listed) and opening hours** for any trade in any German town. Type `Klempner` + `Berlin`, press Start, export **German business leads** to JSON, CSV or Excel.

**Business listings only.** Gelbe Seiten lists sole traders under nothing but a personal name. This actor refuses to emit private individuals, counts every one it drops, and there is no setting that turns them on. See [Business-only by design](#business-only-by-design).

### What can you do with it?

- **Firmen in Berlin mit Telefonnummer finden** — 94 % of records carry a phone number, normalised to international `+49…` (E.164) for your dialer or CRM, with the printed German format kept alongside in `phoneRaw`
- **Gelbe Seiten Branchenbuch exportieren** — one clean row per business, stable columns, ready to import into Excel, HubSpot or Pipedrive
- **Get German business leads with email addresses** — the operator's e-mail comes straight out of the search results, with no per-company detail crawl and no extra proxy cost. Coverage swings hard by trade and city: 17 of 50 on a Berlin plumber search, 0 of 38 on a Munich-suburb bakery search
- **Firmenadressen mit PLZ sammeln** — street, postal code and city on **every single record** (49/49 measured across the live captures). The city district (Stadtteil) comes too when Gelbe Seiten lists one — about 60 %, because many German towns simply do not have districts
- **Öffnungszeiten von Betrieben abrufen** — switch on *Also collect opening hours* to get the weekday opening times from each business's own profile page
- **Build B2B prospect lists by trade and city** — `Elektriker` in München, `Steuerberater` in Hamburg, `Dachdecker` nationwide
- **Handwerker-Leads für Deutschland generieren** — Klempner, Maler, Zimmerer, Sanitär, Heizung, Kfz-Werkstatt, Bäckerei, Apotheke, Zahnarzt
- **Feed AI agents and pipelines** — structured JSON via API, webhooks or the Apify MCP server, so an agent can call this scraper as a tool

### Pricing

**$1.20 per 1,000 business records** ($0.0012 per record). You pay only for records actually delivered to your dataset — a failed or empty search costs nothing, because data is pushed to the dataset *before* it is charged.

| Scenario | Records | Run cost |
|----------|---------|----------|
| Plumbers in Berlin | ~204 | ~$0.24 |
| Bakeries in one Munich suburb | ~38 | ~$0.05 |
| One trade across the 20 largest German cities | ~5,000 | ~$6.00 |
| Nationwide trade sweep (`Klempner`, all of Germany) | ~15,000 | ~$18.00 |

There is **no enriched tier and no surcharge**: name, phone, e-mail, website, street, PLZ, city, district and category all come from the search results and are all delivered at the single per-record price. Opening hours are optional and cost **no extra money** — only one extra request per business, so the run takes longer.

**No proxy required.** The default configuration uses no proxy at all, so the only platform cost on top of the per-record price is a few cents of compute. This actor runs on plain HTTP with **no headless browser**, which is what keeps that cost roughly 40× below a browser-based scraper's.

### How to scrape Gelbe Seiten (step by step)

1. Click **Try for free** — you need a free Apify account.
2. Enter one or more **Search terms (Branche)** in German (e.g. `Klempner`, `Elektriker`, `Steuerberater`).
3. Enter **Locations (Ort)** (e.g. `Berlin`, `München`, `Grünwald Kreis München`) — or leave empty to search all of Germany.
4. Optionally set **Max results per search** (default 100 per term × location) and switch on **Also collect opening hours**.
5. Leave **Proxy configuration** off — it is not needed.
6. Click **Start**, then open the **Dataset** tab and export as JSON, CSV, Excel, HTML or XML.

Prefer pasting URLs? Put Gelbe Seiten search URLs straight into **Start URLs** instead of search terms, e.g. `https://www.gelbeseiten.de/Suche/Klempner/Berlin`.

#### Input example

```json
{
  "searchTerms": ["Klempner", "Elektriker"],
  "locations": ["Berlin", "Hamburg"],
  "maxResultsPerSearch": 200,
  "scrapeOpeningHours": false,
  "requestDelayMs": 500
}
```

#### Output example (one dataset record)

```json
{
  "name": "A + M Heizung-Sanitär GmbH",
  "phone": "+491510000101",
  "phoneRaw": "0151 0000 101",
  "email": "kontakt@a-m-hsl.de",
  "website": "https://www.heizungsanitaer-berlin.de",
  "street": "Torstr. 105- 107",
  "address": "Torstr. 105- 107, 10119 Berlin",
  "postalCode": "10119",
  "city": "Berlin",
  "district": "Mitte",
  "categories": ["Klempnereien"],
  "openingHours": {
    "Montag": "Ruhetag",
    "Dienstag": "Ruhetag",
    "Mittwoch": "Ruhetag",
    "Donnerstag": "Ruhetag",
    "Freitag": "Ruhetag",
    "Samstag": "Ruhetag",
    "Sonntag": "Ruhetag"
  },
  "latitude": 52.530052,
  "longitude": 13.403331,
  "description": "seit 2005 als familiengeführtes Unternehmen für Sie im Einsatz!Herzlich willkommen beim A + M Heizung-Sanitär GmbH!Wir sind Ihr kompetenter Ansprechpartner im Bereich Sanitärinstallationen. Bei weiteren Fragen können Sie uns gerne kontaktieren. Ihr A + M Heizung-Sanitär GmbH",
  "profileUrl": "https://www.gelbeseiten.de/gsbiz/1aea7ffa-a033-49f1-a642-45a8132ae29c",
  "site": "gelbeseiten.de",
  "sourceUrl": "https://www.gelbeseiten.de/Suche/Klempner/Berlin",
  "searchTerm": "Klempner",
  "location": "Berlin",
  "scrapedAt": "2026-08-18T12:28:53+00:00"
}
```

This is verbatim parser output for a real listing — including a business that keeps no fixed opening hours, which Gelbe Seiten prints as `Ruhetag` (closed) for every day.

`openingHours`, `latitude`, `longitude` and `description` are `null` unless **Also collect opening hours** is switched on. Missing values are always explicit `null`, so CSV and Excel exports keep the same columns.

### Use it from code or AI agents

```bash
curl -X POST "https://api.apify.com/v2/acts/worktech~gelbeseiten-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms":["Klempner"],"locations":["Berlin"],"maxResultsPerSearch":100}'
```

The actor also works with n8n, Make, Zapier, webhooks and the **Apify MCP server** — its flat, structured output is designed to be consumed by AI agents directly.

### Business-only by design

Gelbe Seiten is a Branchenbuch, but plenty of sole traders are listed under nothing but a personal name, and the site marks those no differently from a GmbH. Personal data is not what a lead-generation tool should be shipping, so private individuals are walled off in four independent layers:

1. **Transport layer** — the actor only ever requests Gelbe Seiten *business searches*. There is no code path to a person directory.
2. **Input layer** — a start URL pointing at one of the white-pages directories Gelbe Seiten links to (dastelefonbuch.de, dasoertliche.de) is rejected before a single request is made, with a named error explaining why. Business profile pages and the `/branchenbuch/` SEO index are rejected too — they are not result lists.
3. **Section layer** — only result articles inside the result list are read, so ad slots and page furniture cannot become records.
4. **Record layer** — an entry whose name is nothing but a private individual's name is dropped before it becomes a record. The classifier handles hyphenated given names (`Anne-Marie Schmidt`), German particles (`Peter von Kettler`, `Ute zur Mühlen`) and surname-plus-initial (`Schulze R.`), and a **German legal form always wins**: `Anne-Marie Schmidt GmbH` is a company. So is any name carrying a trade word — `Bäckerei Ziegler` and `Bäckermeister Schulze` are businesses, not people.

Every drop is counted and written to the run **SUMMARY** as `privatePersonEntriesDropped`, so each run carries its own evidence — the field is always present, including when it is `0`.

There is deliberately **no `includePersons` input**, and the test suite asserts that no input in the schema can re-enable persons.

**What this costs you:** on a live capture of 38 bakeries, 7 entries (18 %) were bare personal names and were refused. If you are hunting *sole traders specifically*, expect roughly 15–20 % of a trade's listings to be withheld on that basis.

Businesses are not affected, and that includes the professions that are usually named after the person running them: a **legal form** (`GmbH`, `e.K.`, `GbR`…), a **trade word** (`Bäckerei`, `Klempnerei`, `Bäckermeister`…) or a **profession noun** — white-collar (`Rechtsanwalt`, `Rechtsanwälte`, `Steuerberater`, `Steuerberaterin`, `Zahnärzte`, `Architekt`, `Heilpraktiker`, `Physiotherapie`…), craft (`Bäcker`, `Metzger`, `Schreiner`, `Elektriker`, `Gärtner`, `Fotograf`, `Winzer`, `Landwirt`…) gastronomy (`Gaststätte`, `Café`, `Pizzeria`, `Ristorante`, `Imbiss`, `Weinstube`, `Bistro`…) or **English** (`Coffee`, `Coffeeshop`, `Company`, `Store`, `Group`, `Services`, `Solutions`, `Clinic`, `Studio`, `Bakery`, `Brewery`…), in either word order, and as the **stem of a compound** the way German builds words — `Grill`, `Grillbar` and `Grillstube` are all recognised — all mark a listing as a business. A **plural** profession noun always does — `Volkmann Rechtsanwälte` is a firm however the first word reads.

**Where the line is drawn, and why.** Some German trade words are also common surnames. Where the surname reading dominates — Bauer, Koch, Richter, Jäger, Schuster, Schneider, Müller, Weber, Fischer, Wagner — the listing is treated as a person, so `Koch Michael` is withheld. Where the trade reading dominates — Bäcker, Metzger, Schreiner, Gärtner, Winzer, Grill and the English `Barber` — the listing is treated as a business, so `Metzger Hans` and `Berlin Barber` are **kept**. That is a deliberate call, not an accident: Gelbe Seiten is a Branchenbuch with no private-person tab at all, so a listing named exactly `Metzger Hans` is overwhelmingly a butcher's shop trading under its owner's name. If you need the stricter reading, filter the output on `categories`.

A **major German city** in a two-word listing also reads as a trading name — `Matsuhisa Munich` and `Bistro Hamburg` are restaurants, not people — with the same exclusion applied, and applied to the whole *family* rather than name by name: any city carrying a German place-of-origin surname formation (`-burg`, `-berg`, `-bach`, `-feld`, `-heim`, `-stein`, `-thal`, `-dorf`, `-hausen`, `-furt`, `-stadt`) stays on the person side, because that is the most productive German surname formation there is — so Hamburg, Oldenburg, Heidelberg, Freiburg, Düsseldorf and Frankfurt are *not* city signals, any more than Hagen, Halle, Essen, Bonn, Kiel, Ulm, Münster or Lübeck are. `Berlin` is the one deliberate exception: it is a rare surname but carries the common case.

The same care is taken with English trading names, which are everywhere in Berlin: the vocabulary has `bakery` but not `baker`, `garden` but not `gardner`, and leaves out Cook, Taylor, Carpenter, Marshall, Chandler, Foster, Mason, Miller, Turner, Walker, Wright, Fisher, Hunter, Parker, Porter, Shepherd and Smith entirely — the trade word, never the name form.

### Why this scraper?

- **A real end-of-pagination signal** — Gelbe Seiten states the size of every result set (`204 Treffer`, `gesamtanzahlTreffer`) and how many further results it will serve. This actor stops on those numbers. It never assumes "a full page means there's another one" — and notably it ignores the site's `anzahlMehrTreffer` field, which still claims *10 more* on the very last batch of a search. No phantom pages, no wasted requests on the tail.
- **Complete addresses** — street, PLZ and city on every record, straight from the result list, plus the Stadtteil where the town has one. No detail crawl needed for an address.
- **E-mail and website at the base price** — no enrichment tier, no per-company surcharge, no residential-proxy blow-up.
- **Opening hours when you want them** — a real differentiator for German lead lists, one toggle away, at no extra price per record.
- **An empty search is an empty search — a broken one is an error** — a search with genuinely no hits succeeds cleanly with zero records and zero cost. But if Gelbe Seiten ever renames the fields this scraper reads, the run **fails loudly** instead of handing you an empty dataset with a green tick.
- **No silent failures** — a burned search is recorded as a named error in the run **SUMMARY** (including *which* status burned it — 403, 429 or a timeout) and never kills the other searches; a charge failure is never swallowed; and you are never billed for records you did not receive, because records are pushed before they are charged.
- **Fast and cheap by construction** — plain HTTP, no headless browser, one request at a time by default.

Also from worktech: [Hitta.se Scraper — Swedish Business Directory Leads](https://apify.com/worktech/hitta-scraper), [Eniro Scraper — Swedish Business Directory Leads](https://apify.com/worktech/eniro-scraper) and [Lean Shopify Scraper](https://apify.com/worktech/lean-shopify-scraper).

### FAQ

**Do I need an API key or a Gelbe Seiten account?**
No. You need a free Apify account; the actor needs no credentials of any kind, and it only reads pages that are public to any visitor.

**Can I export the leads to CSV or Excel?**
Yes. Every run's dataset exports to JSON, CSV, Excel, XML, HTML or RSS from the **Dataset** tab, or through the Apify API.

**How many results do I get per search?**
As many as Gelbe Seiten has, up to your **Max results per search** cap (default 100, `0` = unlimited). A Berlin plumber search has ~204; a nationwide plumber search has ~15,000.

**Wie finde ich alle Klempner in Berlin?**
Search term `Klempner`, location `Berlin`, and set **Max results per search** to `0` for the full list.

**Kann ich bundesweit suchen?**
Yes — leave **Locations** empty and the actor searches all of Germany for each term.

**Why do some records have no e-mail or website?**
Because the business did not publish one on Gelbe Seiten. Measured on the live captures: 94 % have a phone, 84 % a website, 61 % a Stadtteil, and e-mail swings from 34 % on a Berlin plumber search to 0 % on a small-town bakery search. Nothing is invented to fill the gap: a missing value is `null`.

**Why is a business I can see on the site missing from my results?**
Either your **Max results per search** cap cut it off, or its listing is nothing but a personal name and was withheld as a private individual — check `privatePersonEntriesDropped` in the run **SUMMARY**.

**Do I need a proxy?**
No. The default is no proxy at all. If the SUMMARY ever shows failed searches, switch **Proxy configuration** to Apify Proxy with the **RESIDENTIAL** group and country **DE**.

**Does it use a headless browser?**
No — plain HTTP. That is why it is fast and why the compute cost is negligible next to the per-record price.

**Is scraping Gelbe Seiten legal?**
This actor reads only publicly available business listings, at a polite request rate, and deliberately withholds private individuals. You are responsible for how you use the data — in the EU that means honouring the GDPR and the German UWG rules on unsolicited contact (in short: no cold e-mail or fax to businesses without a lawful basis; telephone contact to businesses requires at least presumed consent). Take your own legal advice for your use case.

### Limitations

- **Search results only.** Gelbe Seiten's `/branchenbuch/` SEO index (branch and city link trees) and individual profile URLs are not crawlable entry points — paste search URLs.
- **Phone numbers come in two forms.** `phone` is E.164 (`+49302816584`), matching the other country scrapers in this family so datasets union cleanly; `phoneRaw` is exactly what Gelbe Seiten printed (`030 2 81 65 84`). If a published number is not a recognisable German number, `phone` is `null` rather than guessed, and `phoneRaw` still carries the text.
- **Opening hours cost one extra request per business.** On a 1,000-record run that is 1,000 extra requests and roughly double the run time. The price per record does not change.
- **Ratings and review counts are not collected** — Gelbe Seiten shows them for only a small minority of listings (7 of 50 on the live Berlin capture), which is too thin to be a useful column.
- **Sole traders listed under a bare personal name are withheld by design.** See [Business-only by design](#business-only-by-design).
- **One search = one trade in one place.** Radius searches (`umkreis`) and the "related branches" expansion are deliberately off, so you get the trade you asked for rather than padding you are charged for.

# Actor input Schema

## `searchTerms` (type: `array`):

Trades or keywords to search on Gelbe Seiten, in German (e.g. Klempner, Elektriker, Steuerberater, Zahnarzt, Bäckerei). Each term is combined with every location below.

## `locations` (type: `array`):

German cities, towns or districts to search in (e.g. Berlin, München, Grünwald Kreis München). Leave empty to search all of Germany.

## `startUrls` (type: `array`):

Paste Gelbe Seiten search URLs directly, e.g. https://www.gelbeseiten.de/Suche/Klempner/Berlin — used instead of or in addition to search terms. Only search result pages are accepted; business profile pages, the /branchenbuch/ index and external person directories are rejected with a clear error.

## `maxResultsPerSearch` (type: `integer`):

Cap on business records per search term x location combination. 0 = no cap (scrapes every result page). Gelbe Seiten serves 50 results on the first page and 10 per page after that.

## `scrapeOpeningHours` (type: `boolean`):

Opening hours (Öffnungszeiten), coordinates and the company description live on each business's own profile page, so collecting them costs ONE EXTRA REQUEST PER BUSINESS and roughly doubles the run time. The price per record does not change. Leave off if you only need name, phone, address, e-mail and website.

## `requestDelayMs` (type: `integer`):

Politeness delay between paginated requests.

## `proxyConfiguration` (type: `object`):

No proxy is needed — gelbeseiten.de served every probe over plain HTTP from a non-German IP with no challenge, and running without a proxy is both proven and far cheaper. Only turn a proxy on if you see failed searches in the run SUMMARY, and then choose Apify Proxy with the RESIDENTIAL group and country DE. A country-targeted DATACENTER proxy fails with 'ProxyError: 407 Proxy Authentication Required' on accounts without that entitlement.

## Actor input object example

```json
{
  "searchTerms": [
    "Klempner"
  ],
  "locations": [
    "Berlin"
  ],
  "maxResultsPerSearch": 100,
  "scrapeOpeningHours": false,
  "requestDelayMs": 500,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `businessRecords` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "Klempner"
    ],
    "locations": [
        "Berlin"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("worktech/gelbeseiten-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchTerms": ["Klempner"],
    "locations": ["Berlin"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("worktech/gelbeseiten-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "Klempner"
  ],
  "locations": [
    "Berlin"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call worktech/gelbeseiten-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,worktech/gelbeseiten-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/5W3Iqyk86rr6JSIop/builds/KXaavQMbSCec9LUqa/openapi.json
