# DUB.de Scraper – German Businesses for Sale (`outlandish_mainsail/dub-business-scraper`) Actor

Scrape businesses for sale (Unternehmen kaufen) from DUB.de, the German business exchange: revenue, earnings, employees, region, sector, sale reason, share for sale and multi-year financial history as JSON/CSV/Excel.

- **URL**: https://apify.com/outlandish_mainsail/dub-business-scraper.md
- **Developed by:** [Andreas v](https://apify.com/outlandish_mainsail) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

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

## DUB.de Scraper – German businesses for sale (Unternehmen kaufen)

Scrape **businesses for sale from [DUB.de](https://www.dub.de/unternehmen-kaufen/)** (Deutsche Unternehmerbörse), one of Germany's largest marketplaces for company succession (*Unternehmensnachfolge*) and M\&A. Get one clean record per listing with **revenue (Umsatz), earnings (Ergebnis), margin, employees, region, sector, reason for sale, share for sale** and the **multi-year revenue/earnings history** shown on the Exposé page. Export as JSON, CSV, Excel or via API.

- ✅ Covers the whole public marketplace (~2,000 active listings, Germany + Austria)
- ✅ **Numbers as numbers**: "2.500.000 €" becomes `2500000`, plus a computed `ebitMarginPct`
- ✅ **Financial history** per listing (year, revenue, earnings) and seller forecasts when published
- ✅ **Filters**: keyword, sector, reason for sale (succession, distressed, insolvency …), share for sale, revenue and earnings ranges, region
- ✅ **Only new listings** mode for scheduled deal-flow alerts
- ✅ German labels plus English translations (`sectorEn`, `saleReasonEn`, `shareForSaleEn`)
- ✅ Fast mode (search results only) or full detail mode
- ✅ Lightweight and polite: plain HTTP, no browser, no proxy needed, respects `robots.txt`

### What data can you get?

| Field | Example | Notes |
|---|---|---|
| `listingId`, `url` | `"b98c3ea9-…"`, `…/expose/etablierter-anbieter-…/` | |
| `title`, `summary`, `description` | | `description` needs *Include full listing details* |
| `sector`, `sectorEn` | `"Einzelhandel"`, `"Retail"` | Details mode |
| `location`, `country` | `"Sachsen"`, `"Germany"` | German state, Austrian state or country |
| `revenue`, `revenueRaw` | `310700`, `"310.700 €"` | Latest yearly revenue, EUR |
| `earnings`, `earningsRaw` | `41500`, `"41.500 €"` | Latest yearly earnings, EUR (negative = loss) |
| `ebitMarginPct` | `13.4` | earnings ÷ revenue, computed |
| `employees`, `employeesRaw` | `3` | |
| `saleReason`, `saleReasonEn` | `"Altersnachfolge"`, `"Succession (retirement)"` | Details mode |
| `shareForSale`, `shareForSaleEn` | `"100%-Verkauf"`, `"100% sale"` | Details mode |
| `productsAndServices`, `customers`, `market`, `competition` | | Text sections when the seller filled them in |
| `financialHistory` | `[{"year":2024,"revenue":366900,"earnings":-18200}, …]` | Up to several years |
| `financialForecast`, `forecastComment` | | Seller's own projections, when published |
| `currency`, `detailsScraped`, `scrapedAt` | `"EUR"`, `true` | |

DUB does not publish an asking price or the seller's identity on public pages; buyers contact sellers anonymously through DUB. This Actor only collects what is publicly visible without login.

### How to use it

1. Click **Start** (or **Try for free**).
2. Set **Max listings** (0 = all) and optional filters.
3. Download the dataset (Overview or Financial history view) as JSON/CSV/Excel, or use the API.

#### Input examples

All retail and services businesses with at least €100k earnings:

```json
{ "maxItems": 200, "sectors": ["retail", "services"], "minEarnings": 100000, "includeListingsWithoutFinancials": false }
```

Succession deals in Bavaria, daily alert (use with an Apify **Schedule**):

```json
{ "maxItems": 0, "saleReasons": ["succession_planning"], "locations": ["Bayern"], "skipPreviouslyScraped": true }
```

Fast overview of the whole marketplace (search results only, no detail pages):

```json
{ "maxItems": 0, "includeDetails": false }
```

You can also paste a DUB.de search URL (with filters set in the browser) or specific listing URLs into **Specific URLs**.

#### Output example

```json
{
  "listingId": "b98c3ea9-3ad9-4b5f-953b-8005cade4e83",
  "url": "https://www.dub.de/de/unternehmen-kaufen/expose/etablierter-anbieter-fuer-reifenvertrieb-montage-und-reparatur-fuer-fahrzeuge/",
  "title": "Etablierter Anbieter für Reifenvertrieb, -montage und -reparatur für Fahrzeuge, Bau- und Landmaschinen",
  "sector": "Einzelhandel",
  "sectorEn": "Retail",
  "location": "Sachsen",
  "country": "Germany",
  "revenue": 310700,
  "earnings": 41500,
  "ebitMarginPct": 13.4,
  "employees": 3,
  "saleReason": "Persönliche Umorientierung",
  "saleReasonEn": "Personal reorientation",
  "shareForSale": "100%-Verkauf",
  "shareForSaleEn": "100% sale",
  "financialHistory": [
    { "year": 2022, "revenue": 407800, "earnings": -28800 },
    { "year": 2023, "revenue": 400000, "earnings": 1200 },
    { "year": 2024, "revenue": 366900, "earnings": -18200 },
    { "year": 2025, "revenue": 310700, "earnings": 41500 }
  ],
  "currency": "EUR",
  "detailsScraped": true
}
```

### Use cases

- **Search funds, acquisition entrepreneurs and family offices**: daily deal flow of German *Mittelstand* succession deals
- **M\&A advisors and brokers**: market maps, competitor activity, valuation and margin benchmarks by sector and region
- **Banks, lenders and accountants**: identify financing and advisory opportunities around succession
- **Researchers**: the German succession market (sale reasons, sectors, company sizes)

### Pricing

Pay per result: you pay only for listings saved to the dataset. Set a **maximum cost per run** in the run options and the Actor stops automatically when it is reached.

### Performance and cost

Search results come 12 per page. With full details, the Actor opens one extra page per listing, at a polite ~1 request per 1.5 s, so 100 listings take about 3 minutes. Fast mode (no details) is roughly 12× faster.

### Is it legal to scrape DUB.de?

This Actor reads only public pages that anyone can open without an account, respects the site's `robots.txt` (it never calls the disallowed `/api/` or `/app/` paths), and uses low request rates. Listings on DUB are anonymous company profiles; the Actor collects **no personal data** (no names, emails or phone numbers). You are responsible for using the data in line with DUB.de's terms, copyright/database rights and the GDPR in your jurisdiction. Do not republish the full listing texts as your own marketplace. If in doubt, ask a lawyer.

### FAQ

**Why are some revenue/earnings fields empty?** Sellers can hide them ("Keine Angabe"). Turn off *Include listings without revenue/earnings* to keep only listings with both.

**Does it include Austria/Switzerland?** It returns whatever DUB.de lists in its German marketplace, which includes some Austrian listings (`country: "Austria"`).

**Can I get the seller's contact details?** No. DUB only reveals them to registered buyers after an anonymous request, so they are not public.

**Something broke?** Open an issue on the Actor's Issues tab with the run link and we'll fix it quickly.

# Actor input Schema

## `maxItems` (type: `integer`):

Maximum number of listings to save (after filters). Listings are processed newest first. Set 0 for no limit (all ~2,000 listings).

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

Open each listing (Exposé) page to add sector, sale reason, share for sale, full description, products & services, customers, market, competition and the multi-year revenue/earnings history. Turn off for a fast run that only uses the search results (title, summary, region, revenue, earnings, employees).

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

DUB.de listing URLs (…/de/unternehmen-kaufen/expose/…) or a DUB.de 'Unternehmen kaufen' search URL with your filters already applied (copy it from the browser). Leave empty to use the filters below.

## `keyword` (type: `string`):

Free-text search, e.g. 'Hotel', 'Software', 'Bäckerei'.

## `sectors` (type: `array`):

Only listings in these sectors (DUB 'Branche').

## `saleReasons` (type: `array`):

Only listings with these transaction backgrounds (e.g. succession, distressed, insolvency).

## `shareTypes` (type: `array`):

100% sale, majority or minority stake.

## `minRevenue` (type: `integer`):

Minimum yearly revenue (Umsatz) in euros.

## `maxRevenue` (type: `integer`):

Maximum yearly revenue in euros.

## `minEarnings` (type: `integer`):

Minimum yearly earnings (Ergebnis) in euros. Applied on the scraped data.

## `maxEarnings` (type: `integer`):

Maximum yearly earnings in euros. Applied on the scraped data.

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

Keep only listings whose region contains any of these words, e.g. 'Bayern', 'Nordrhein', 'Österreich'. Case-insensitive.

## `includeListingsWithoutFinancials` (type: `boolean`):

Some sellers hide revenue or earnings ('Keine Angabe'). Turn off to keep only listings that show both.

## `skipPreviouslyScraped` (type: `boolean`):

Remember listing IDs across runs (named key-value store 'dub-business-scraper-seen' in your account) and skip listings saved in an earlier run. Ideal for scheduled deal-flow alerts.

## `seenListingsKey` (type: `string`):

Use a different key per saved task if you monitor several searches separately.

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

Not needed normally. Only use it if you get blocked.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep it low to be polite to the website.

## `maxRequestsPerMinute` (type: `integer`):

Upper bound on request rate.

## `delayBetweenRequestsSecs` (type: `number`):

Minimum pause between two page requests.

## Actor input object example

```json
{
  "maxItems": 50,
  "includeDetails": true,
  "includeListingsWithoutFinancials": true,
  "skipPreviouslyScraped": false,
  "seenListingsKey": "SEEN_IDS",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 2,
  "maxRequestsPerMinute": 40,
  "delayBetweenRequestsSecs": 1.5
}
```

# Actor output Schema

## `listings` (type: `string`):

All scraped business-for-sale listings in the default dataset.

# 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 = {
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("outlandish_mainsail/dub-business-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 = { "maxItems": 50 }

# Run the Actor and wait for it to finish
run = client.actor("outlandish_mainsail/dub-business-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 '{
  "maxItems": 50
}' |
apify call outlandish_mainsail/dub-business-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,outlandish_mainsail/dub-business-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/MmSWkgGctgbLZcy6G/builds/ie54RKhyVKtzjCGxW/openapi.json
