# Gelbe Seiten Scraper: German Business Leads, Emails & Impressum (`steadyscrape/gelbe-seiten-scraper`) Actor

Scrape Gelbe Seiten (gelbeseiten.de), Germany's yellow pages: business name, address, phone, email, website, rating and categories by trade and city; optionally each business page (opening hours, services, fax) and its website's Impressum (owners, HRB number, VAT ID). No login.

- **URL**: https://apify.com/steadyscrape/gelbe-seiten-scraper.md
- **Developed by:** [Steady Scrape](https://apify.com/steadyscrape) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 businesses

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Gelbe Seiten Scraper — German business leads with emails, opening hours and Impressum data

Scrape **Gelbe Seiten** (gelbeseiten.de), Germany's yellow pages, by trade and place: every business as one clean row
with **name, address, district, phone (also in E.164), email, website, rating, review count, category and Gelbe Seiten
partner level**. Two optional steps make it a lead list you can act on:

- **Business page** (`includeDetails`): the email where the list shows none, every category, services, description,
  opening hours per day, fax, payment methods, coordinates, social profiles and when the entry last changed.
- **Impressum of the business's website** (`includeImprint`): the **legal name, legal form, managing directors or
  owners, register court and HRB number, VAT ID, tax number**, emails and phone, read from the legal notice every
  German commercial website must publish. That is the decision maker's name, which a directory listing does not give.

Search `Zahnarzt` in `Berlin`, `Steuerberater` in `München`, `Elektriker` in `Hamburg` — or paste results and business
links straight from gelbeseiten.de. You pay per business delivered; searches without results, links that give nothing
and websites without an Impressum are free rows that say why.

### Who it is for

- **B2B sales and lead generation in Germany**: local businesses by trade and city with phone, email and — with the
  Impressum — the owner or Geschäftsführer, the register entry and the VAT ID.
- **Agencies selling websites, SEO or ads** to local businesses: rating, review count, partner level (who already pays
  Gelbe Seiten for advertising) and whether a business has a website at all.
- **Market research**: count and map the dentists, roofers or tax advisers of a city (Gelbe Seiten's own count per
  search is in every search row; coordinates come with the business page).
- **CRM enrichment and scheduled refreshes**: run the same searches weekly and compare.

### How it works

1. **Searches** open the site's own results list for a trade in a place (`gelbeseiten.de/branchen/zahnarzt/berlin`),
   the pages listed in its sitemaps. The first 50 businesses come with the page; the rest through the list's own
   "Mehr anzeigen" request, 10 at a time, until your maximum or the end of the list. Loose place names are followed to
   the site's own (`Berlin Mitte` → `Berlin Bezirk Mitte`, `Frankfurt` → `Frankfurt am Main`).
2. **Trades the site has no list page for** (`Friseure`, `Heizung`, `Kfz Werkstatt`) are answered by Gelbe Seiten only on
   its search page, which its robots.txt closes to automated readers. The Actor does not read it: the search becomes a
   free row with look-alike trades the site does have (`friseur`, `heizung & sanitär`, `autowerkstatt`).
3. **Business pages** are read from the site's schema.org data and the page's own blocks.
4. **Impressums** are read with the same reader as our [Impressum Scraper](https://apify.com/steadyscrape/impressum-scraper):
   the legal-notice link is found on the business's homepage, and German formats are read (`Amtsgericht
   Charlottenburg, HRB 269744 B`, `USt-IdNr. DE450479811`, `info(at)firma.de`, Cloudflare-hidden emails). A website that
   is really a Facebook page, a directory or a booking profile is not read: its Impressum would name the platform.

A business that two searches find (a district inside its city) is delivered and charged once per run. Measured on
2026-10-22: 10 of 10 Berlin dentists of the prefill had a phone, 10 an email and 10 an Impressum naming the practice.

### Input example

```json
{
    "searchTerms": ["Zahnarzt", "Kieferorthopäde"],
    "locations": ["Berlin", "Potsdam"],
    "startUrls": ["https://www.gelbeseiten.de/gsbiz/5f06ccb6-068f-4b36-91a4-63113f066676"],
    "maxResultsPerSearch": 100,
    "includeDetails": true,
    "includeImprint": true
}
```

Every trade is searched in every place. Use `searchTerms` + `locations`, `startUrls`, or both.

### Output example

A business with details and Impressum (shortened lists):

```json
{
    "rowType": "business",
    "id": "e31b78fd-dfd7-4aa3-b2af-19d42058ba63",
    "url": "https://www.gelbeseiten.de/gsbiz/e31b78fd-dfd7-4aa3-b2af-19d42058ba63",
    "name": "Fiedler Andreas Dr. und Fiedler Julia",
    "category": "Zahnärzte",
    "partnerLevel": "Silber Partner",
    "address": "Reichenhaller Str. 63, 14199 Berlin",
    "street": "Reichenhaller Str. 63",
    "postalCode": "14199",
    "city": "Berlin",
    "district": "Schmargendorf",
    "phone": "030 8 23 96 97",
    "phoneInternational": "+49308239697",
    "email": "praxis.dr.fiedler@t-online.de",
    "website": "https://www.zahnarzt-fiedler-berlin.de",
    "rating": null,
    "reviewCount": null,
    "distanceKm": 7.3,
    "snippet": "Wir, die Zahnärzte Dr. Andreas Fiedler und Julia Fiedler bieten in unserer Zahnarztpraxis in Berlin-...",
    "searchTerm": "Zahnarzt",
    "location": "Berlin",
    "searchUrl": "https://www.gelbeseiten.de/branchen/zahnarzt/berlin",
    "position": 9,
    "details": {
        "categories": ["Zahnärzte"],
        "services": ["Zahnärztin", "Zahnärzte/innen", "Zahnreinigung"],
        "description": "Wir, die Zahnärzte Dr. Andreas Fiedler und Julia Fiedler bieten in unserer Zahnarztpraxis in Berlin-Wilmersdorf (Schmargendorf) moderne Behandlungsmethoden, …",
        "openingHours": [{ "day": "Montag", "hours": "Ruhetag" }, { "day": "Dienstag", "hours": "Ruhetag" }],
        "latitude": 52.477478,
        "longitude": 13.291053,
        "fax": null,
        "paymentMethods": [],
        "socials": { "linkedin": null, "xing": null, "facebook": null, "instagram": null, "youtube": null, "tiktok": null, "x": null },
        "logoUrl": "https://ies.v4all.de/0122/GS/0001/5/2575/70082575_310x190.png",
        "lastUpdated": "2026-08-13"
    },
    "imprint": {
        "status": "found",
        "message": null,
        "imprintUrl": "https://www.zahnarzt-fiedler-berlin.de/impressum",
        "companyName": "Dr. Andreas Fiedler und Julia Fiedler",
        "legalForm": null,
        "managingDirectors": ["Dr. Andreas Fiedler", "Julia Fiedler"],
        "registerType": null,
        "registerNumber": null,
        "registerCourt": null,
        "vatId": null,
        "taxNumber": null,
        "address": "Reichenhaller Straße 63, 14199 Berlin",
        "countryCode": "DE",
        "emails": ["praxis.dr.fiedler@t-online.de", "info@zaek-berlin.de", "kontakt@kzv-berlin.de"],
        "phone": "030 8239697",
        "fax": "(030) 34808 - 240",
        "socials": { "linkedin": null, "xing": null, "facebook": null, "instagram": null, "youtube": null, "tiktok": null, "x": null }
    },
    "scrapedAt": "2026-10-22T12:00:00.000Z"
}
```

#### Business fields (`rowType: business`, charged)

| field | what it holds |
|---|---|
| `id`, `url` | Gelbe Seiten's id of the entry and its page. |
| `name`, `category`, `partnerLevel` | The entry's name, its main trade, and the advertising package it buys from Gelbe Seiten (`Premium Partner`, `Top Partner`, `Silber Partner`…), `null` for a free entry. |
| `address`, `street`, `postalCode`, `city`, `district` | The address on one line and split; `district` is the city part (`Neukölln`, `Schmargendorf`). |
| `phone`, `phoneInternational` | As listed, and in E.164 (`+49308239697`) for dialers and CRMs. |
| `email` | The business's email: from the list when it shows one (its Gelbe Seiten chat contact), else from the business page. |
| `website` | The website the entry links (also a Facebook page or a directory profile, when that is what it links). |
| `rating`, `reviewCount` | Gelbe Seiten's star rating (0–5) and number of reviews. |
| `distanceKm`, `snippet` | Distance from the centre of the place searched, and the entry's teaser text, as the list shows them. |
| `searchTerm`, `location`, `searchUrl`, `position` | Which search found it and where it stood in the list; `null` for a business link. |
| `details` | With `includeDetails` (else `null`): `categories`, `services`, `description`, `openingHours` (`[{ day, hours }]`), `latitude`, `longitude`, `fax`, `paymentMethods`, `socials`, `logoUrl`, `lastUpdated`. |
| `imprint` | With `includeImprint` (else `null`): `status` (`found`, `noWebsite`, `notOwnWebsite`, `unreachable`, `blocked`, `noImprint`, `noCompanyData`), `message` saying why when not found, `imprintUrl`, `companyName`, `legalForm`, `managingDirectors`, `registerType`, `registerNumber`, `registerCourt`, `vatId`, `taxNumber`, `address`, `countryCode`, `emails`, `phone`, `fax`, `socials`. |

A field the site does not state is `null` (or an empty list).

#### Free rows

- `rowType: search` — one per trade and place: `status` (`ok`, `not_listed`, `no_results`, `blocked`,
  `not_searched`, `unreadable`), the trade and place as the site names them (`resolvedTerm`, `resolvedLocation`),
  Gelbe Seiten's own count (`totalListed`), businesses delivered, duplicates skipped, `suggestedTerms` for a trade the
  site has no list page for, and a plain `reason`.
- `rowType: link` — a link that gave no business: `invalid` (not a gelbeseiten.de list or business page), `not_found`
  (the entry was removed), `blocked`, `not_read` (spending limit), `unreadable`.

The run's `SUMMARY` record counts everything.

### Pricing

| event | price | when |
|---|---|---|
| `business` | US$0.001 | a business as the results list shows it |
| `business-with-details` | US$0.002 | instead of `business`, when its page was read (`includeDetails`, and every business link) |
| `imprint` | US$0.003 | on top, when its website's Impressum named the company (`includeImprint`) |

1,000 businesses cost US$1; with their pages US$2; with pages and Impressums at most US$5. Branches of a chain that
link one website pay its Impressum once. Free rows cost nothing, and a business page that cannot be read leaves the
row at the `business` price. Set a spending limit on the run and the Actor stops before reading anything it would not
pay for.

### Limits

- Searches use the site's list pages (`/branchen/<trade>/<place>`), which exist for the trades and places in its
  sitemaps: about 900 trades in the big cities, fewer in small towns. Postcodes and free-text searches are answered by
  the site only on its search page, which its robots.txt closes to automated readers: they are free `not_listed` rows.
- One Gelbe Seiten request every 0.6 s: about a minute for 1,000 businesses from the list (10 per request after the first 50), about 10 minutes with their
  pages, plus the websites for Impressums (read 8 at a time, different hosts).
- If Gelbe Seiten refuses (a firewall answer or a bot check), the Actor retries twice from another IP, never solves a
  challenge, and then stops asking: the rest are free `blocked` rows and the run still ends successfully.
- Impressums drawn with JavaScript or shown as an image are `noCompanyData`. Regulated professions (dentists,
  lawyers, tax advisers) must name their chamber in the Impressum, so the chamber's email or fax can appear after the
  business's own.
- The data is what each business publishes on Gelbe Seiten and on its website. It can be outdated; check the official
  register before relying on it for a contract.

### Is it legal?

This Actor reads only public pages — Gelbe Seiten's results lists and business pages, which the site lists in its own
sitemaps, and the Impressum every German commercial website must publish (§ 5 DDG) — at a visitor's pace, follows the
site's robots.txt, never signs in and never bypasses any protection. **Using the data is your responsibility.** In
particular:

- **Cold email and calls**: in Germany, § 7 UWG forbids advertising emails without prior consent, also to businesses,
  and cold calls to businesses need at least their presumed consent.
- **GDPR**: names, direct emails and phones of sole traders and managing directors are personal data. You need a legal
  basis (for example legitimate interest, with a balancing test), must inform the people you contact, honour
  objections, and keep the data only as long as needed.

This Actor is **not affiliated with, endorsed or sponsored by Gelbe Seiten, Sellwerk or the Gelbe Seiten publishers**,
nor by any website whose Impressum it reads. Read more in Apify's blog post on
[the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/).

### Feedback

A trade or place that gives a wrong row, or a field you need? Open an issue with the search or the link: every fix is
tested against the real page.

# Actor input Schema

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

What to search, one per line, as Gelbe Seiten names the trade: <code>Zahnarzt</code>, <code>Steuerberater</code>, <code>Elektriker</code>, <code>Friseur</code>, <code>Dachdecker</code>, <code>Restaurant</code>, <code>Rechtsanwalt</code>. Each trade is searched in every place. A trade the site answers only on its search page (e.g. <code>Friseure</code>) is a free row that suggests the names it has list pages for (<code>friseur</code>). Up to 50.

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

Where to search, one per line: a town or city (<code>Berlin</code>, <code>München</code>, <code>Frankfurt am Main</code>, <code>Ahrensburg</code>) or a district as the site writes it (<code>Berlin Bezirk Mitte</code>, <code>Köln Stadtteil Ehrenfeld</code>). Loose spellings such as <code>Berlin Mitte</code> or <code>Frankfurt</code> are followed to the site's own name. Up to 200.

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

Links copied from gelbeseiten.de, one per line: a results list (<code>https://www.gelbeseiten.de/branchen/zahnarzt/berlin</code>) is walked like a search; a business page (<code>https://www.gelbeseiten.de/gsbiz/…</code>) is read on its own and charged as a business with details. Search pages (<code>/suche/…</code>) are closed to automated readers by the site's robots.txt and are free rows. Up to 5,000.

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

Stop each trade in each place (and each results link) after this many businesses. Berlin lists 1,343 dentists; the site shows 50 at first and 10 more per request after that. The run's spending limit is respected too.

## `includeDetails` (type: `boolean`):

Also read every business's own Gelbe Seiten page: email where the list shows none (about 8 in 10 dentists have one), every category, services, description, opening hours per day, fax, payment methods, coordinates, social profiles and the date the entry last changed. Charged as <code>business-with-details</code> instead of <code>business</code>.

## `includeImprint` (type: `boolean`):

Also read the Impressum (legal notice) of each business's own website: the legal name, legal form, managing directors or owners, register court and HRB number, VAT ID, tax number, emails and phone. Charged as <code>imprint</code> (on top of the business) only when the Impressum names the company; no website, a Facebook or directory page, or a site that does not answer is free and says why in <code>imprint.status</code>.

## Actor input object example

```json
{
  "searchTerms": [
    "Zahnarzt"
  ],
  "locations": [
    "Berlin"
  ],
  "maxResultsPerSearch": 10,
  "includeDetails": true,
  "includeImprint": true
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (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": [
        "Zahnarzt"
    ],
    "locations": [
        "Berlin"
    ],
    "maxResultsPerSearch": 10,
    "includeDetails": true,
    "includeImprint": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/gelbe-seiten-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": ["Zahnarzt"],
    "locations": ["Berlin"],
    "maxResultsPerSearch": 10,
    "includeDetails": True,
    "includeImprint": True,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/gelbe-seiten-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": [
    "Zahnarzt"
  ],
  "locations": [
    "Berlin"
  ],
  "maxResultsPerSearch": 10,
  "includeDetails": true,
  "includeImprint": true
}' |
apify call steadyscrape/gelbe-seiten-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyscrape/gelbe-seiten-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/caYl9VW4MmonqrizN/builds/FL1aWXaYTjyaJEdPD/openapi.json
