To scrape Impressum data in Python without building a custom crawler, use Datablist's Impressum Scraper API with the Requests library. You supply company website URLs, the API finds each website's Impressum page, and it extracts structured company and contact details from that page.

For this example, I use two established German brands: clothing manufacturer Trigema and outdoor brand VAUDE. A small runnable Python example performs the lookup, then keeps the extracted details and source Impressum URL with each supplier record.

📌 Summary For Those In a Rush

Use Datablist's Impressum Scraper API to turn German business websites into structured company and contact data. The Python example submits two websites, waits for the background task, retrieves every result page, and keeps the discovered legal-page URL with each record.

  • Input: German business website URLs.
  • Output: Company and contact details plus the source Impressum link.

💡 Prefer a no-code solution? Use Datablist's Impressum Scraper to extract these company details without writing code.

Set up your API key and Python environment

Create a Datablist account and then create a Personal API Key. REST API access is available on the Growth plan. Here is a sample key format:

text
auth0|exampleuser:exampleaccount:EXAMPLE_TOKEN_NOT_VALID:1

Export your own key as DATABLIST_API_KEY:

shell
export DATABLIST_API_KEY="your-personal-api-key"

For this Python example, I use the requests package. Create a file named scrape_impressum.py and install the package:

shell
python -m pip install requests

Each submitted website uses 10 credits. The two-website example below uses 20 credits when both inputs are processed.

Build the scrape_impressums() function

Paste this complete script into scrape_impressum.py:

python
import json
import os
import time
import uuid
import requests
def scrape_impressums(suppliers, api_key):
"""Submit one task, wait for it to finish, and return every result."""
tasks_url = "https://api.datablist.com/v1/enrichments/tasks"
headers = {"X-API-Key": api_key}
inputs = [
{
"inputId": supplier["id"],
"data": {"websiteUrl": supplier["website_url"]},
}
for supplier in suppliers
]
payload = {
"enrichmentId": "impressum-scraper",
"uniqueId": str(uuid.uuid4()),
"settings": {},
"inputs": inputs,
}
response = requests.post(tasks_url, headers=headers, json=payload)
response.raise_for_status()
task = response.json()
task_id = task["@id"]
task_url = f"{tasks_url}/{task_id}"
while task["status"] not in ("completed", "failed", "stopped"):
time.sleep(3)
response = requests.get(task_url, headers=headers)
response.raise_for_status()
task = response.json()
if task["status"] != "completed":
raise RuntimeError(
f"Task {task_id} {task['status']}: {task.get('error')}"
)
results = []
results_url = f"{task_url}/results"
params = {"limit": 100}
while True:
response = requests.get(results_url, headers=headers, params=params)
response.raise_for_status()
page = response.json()
results.extend(page["results"])
next_cursor = page["nextCursor"]
if next_cursor is None:
break
params["cursor"] = next_cursor
return results
if __name__ == "__main__":
api_key = os.environ.get("DATABLIST_API_KEY")
if not api_key:
raise RuntimeError("Set DATABLIST_API_KEY before running this script.")
suppliers = [
{
"id": "supplier-trigema",
"label": "Trigema",
"website_url": "https://www.trigema.de",
},
{
"id": "supplier-vaude",
"label": "VAUDE",
"website_url": "https://www.vaude.com",
},
]
impressum_results = scrape_impressums(suppliers, api_key)
for result in impressum_results:
print(json.dumps(result, ensure_ascii=False))

Run the complete script with:

shell
python scrape_impressum.py

The function follows the API in three steps. It submits the website inputs, waits for the task to finish, and then retrieves every result page.

Submit the website inputs

The script turns each supplier into an API input like this:

python
inputs = [
{
"inputId": supplier["id"],
"data": {"websiteUrl": supplier["website_url"]},
}
for supplier in suppliers
]

inputId is your own stable identifier. data.websiteUrl is the company homepage that the scraper will inspect. Use the business's own website rather than a directory or social profile. The payload selects the public impressum-scraper enrichment and uses an empty settings object because this API does not require public settings.

The POST request creates a background task. Datablist accepts the request and returns a task object whose @id identifies that run. The function uses that ID to build the status and result URLs.

A shortened creation response looks like this:

json
{
"@id": "task-id",
"enrichmentId": "impressum-scraper",
"status": "queued",
"links": {
"self": "https://api.datablist.com/v1/enrichments/tasks/task-id",
"results": "https://api.datablist.com/v1/enrichments/tasks/task-id/results"
}
}

queued means the request was accepted, not that the result is ready. The script carries the returned @id into the next two steps, so you do not have to copy task URLs by hand. It also calls raise_for_status() before reading each response. An authentication or validation error therefore stops at the HTTP request that failed instead of being mistaken for a completed lookup.

Wait for the background task

Datablist processes the task in the background. The example checks its status every three seconds until it reaches completed, failed, or stopped. Requests is a blocking Python library, so your local script waits during these calls even though the API task itself runs in the background.

If the task finishes with failed or stopped, the function raises an error that includes the task ID and terminal status. Keep that ID when investigating the existing task rather than immediately submitting the same paid work again.

Retrieve every result page

Once the task is complete, the script requests up to 100 results at a time. The pagination rule is simple: fetch the next page until nextCursor is null. When the cursor contains a value, the script sends it with the next request.

Do not join the returned records to your application by list position. Results can arrive in a different order from the submitted inputs. Use inputId as the join key.

How the API finds an Impressum page

The homepage is the starting point, not the page the API expects to extract. The Impressum Scraper uses AI to inspect internal links on the supplied host and identify an Impressum or an equivalent legal or contact page. This matters because the link may sit under labels such as Rechtliches, Kontakt, or Legal instead of appearing at /impressum.

If AI does not find a suitable internal link, the scraper tries the common /impressum and /imprint paths on the same host. After it reaches a source page, AI extracts the legal-company, address, contact, registration, VAT, and source fields into one result. It does not use a search engine or move to an external directory.

Impressum Scraper follows a German website's legal link or common path before extracting structured company details and the source Impressum URL
Impressum Scraper follows a German website's legal link or common path before extracting structured company details and the source Impressum URL

The two results show that path variation in practice. Trigema returned /kundenservice/rechtliches/impressum/, while VAUDE returned /de/de/impressum.html. Keep impressumUrl with each result because it records the exact source page behind the extracted fields.

Read the Trigema and VAUDE results

Here is a compact excerpt from the two example results.

📘 Example email addresses: Emails are masked with ** below. The API returns the full addresses.

json
[
{
"inputId": "supplier-trigema",
"status": "success",
"outputs": {
"firstName": "Wolfgang",
"lastName": "Grupp",
"companyName": "Trigema W. Grupp KG",
"postalAddress": "Josef-Mayer-Str. 31-35, D-72393 Burladingen",
"emailAddress": "b**@trigema.de",
"registrationNumber": "HRA 740365",
"vatNumber": "DE144846566",
"impressumUrl": "https://www.trigema.de/kundenservice/rechtliches/impressum/",
"agentConfidenceScore": 90
},
"creditsUsed": "10",
"error": null
},
{
"inputId": "supplier-vaude",
"status": "success",
"outputs": {
"firstName": "Antje",
"lastName": "von Dewitz",
"companyName": "VAUDE Sport GmbH & Co. KG",
"postalAddress": "Vaude Straße 2, 88069 Tettnang",
"emailAddress": "s**@service.vaude.com",
"registrationNumber": "HRA 631071",
"vatNumber": "DE203894755",
"impressumUrl": "https://www.vaude.com/de/de/impressum.html",
"agentConfidenceScore": 95
},
"creditsUsed": "10",
"error": null
}
]

Start with the names. Trigema is the supplier label in the Python input, while Trigema W. Grupp KG is the legal company name extracted from the Impressum. The same distinction appears for VAUDE: the input label is VAUDE, and the returned company is VAUDE Sport GmbH & Co. KG. Keeping both values lets you retain the recognizable name used by your team while adding the legal entity shown on the company's own website. This is especially useful when comparing a supplier record with an invoice, contract, or another company dataset that may use the longer legal name.

The returned fields answer different research questions:

  • firstName and lastName contain the managing-director or founder name extracted from the page when found. Here they are Wolfgang Grupp and Antje von Dewitz.
  • postalAddress gives the business address extracted from the legal page, while the email and phone fields provide the contact details published there.
  • registrationNumber and vatNumber preserve the identifiers printed on the page. For Trigema, those values are HRA 740365 and DE144846566; for VAUDE, they are HRA 631071 and DE203894755. They give you concrete values to compare with the identifiers already stored in your supplier data.
  • impressumUrl identifies the page used for extraction. It is separate from the submitted homepage because one records the business you started with and the other records the source of the legal-company details.

Both records have status: success and used 10 credits. The agentConfidenceScore is a 0 to 100 signal that helps you prioritize review. It is not an accuracy percentage. Output fields can still be empty when a value is absent from the page or cannot be extracted, so keep the item status and open the returned Impressum URL when a detail matters to your next step.

The documented output can also include gender, phone number, and other contact details. See the Impressum Scraper API documentation for the current schema.

Turn the output into supplier research records

The direct API call already works. Now put it in the context of a supplier research task. Supplier discovery or onboarding may give you a stable ID, the short name your team uses, and a homepage. The Impressum result adds the legal company behind that website, its published identifiers and contact details, and the page where those values appeared.

The following snippet indexes the results by inputId and builds local company-research records. Add it after the scrape_impressums() call in the complete script:

python
results_by_id = {
result["inputId"]: result for result in impressum_results
}
company_research = []
for supplier in suppliers:
result = results_by_id[supplier["id"]]
company_research.append(
{
"supplier_id": supplier["id"],
"supplier_label": supplier["label"],
"website_url": supplier["website_url"],
"status": result["status"],
"outputs": result.get("outputs", {}),
"credits_used": result["creditsUsed"],
"error": result.get("error"),
}
)
print(json.dumps(company_research, indent=2, ensure_ascii=False))

For Trigema, the record preserves supplier-trigema, the Trigema display label, and https://www.trigema.de. The nested outputs object adds Trigema W. Grupp KG, its address and identifiers, Wolfgang Grupp's returned name, and the source Impressum URL. The VAUDE record follows the same chain from the VAUDE label and homepage to VAUDE Sport GmbH & Co. KG and its own source page. You can now compare or group records by legal company without losing the original supplier identity that triggered the research.

The record keeps the returned details, successful API status, 10-credit usage, and public error value together with the supplier ID, label, and website.

The important merge rule is the dictionary lookup by supplier ID. It still connects the right result when the API returns VAUDE before Trigema. The snippet also keeps unsuccessful item results in company_research because it does not filter by status. A no_result or error item remains available with its ID and error instead of disappearing from your review queue.

This code prepares local research records. You can serialize the list or pass it to your own downstream code without adding another API call.

Estimate the API cost

The Impressum Scraper uses a fixed 10 credits per submitted website. The two example websites used 20 credits in total. At the same rate, 1,000 websites use 10,000 credits.

The Growth plan provides REST API access and includes subscription credits. Optional top-ups add more credits when you need them. Plan access, included credits, and top-up purchases are separate parts of the cost, so check the current Datablist pricing before planning a larger run.

Loading pricing…

Handle common problems without repeating paid work

The script stops before making a request

If DATABLIST_API_KEY is missing, the script raises Set DATABLIST_API_KEY before running this script. Export the variable in the same terminal and run the file again:

shell
export DATABLIST_API_KEY="your-personal-api-key"
python scrape_impressum.py

The API rejects the request

response.raise_for_status() stops on an HTTP error. Check that you supplied a valid Personal API Key and that your account has REST API access. This differs from the missing-variable case: an invalid value reaches the API and receives an HTTP response.

The task fails or stops

The raised error contains the task ID and status. Inspect that existing task, its results, and its credit fields before creating another task. A failed or stopped task can retain results and credit usage, so repeating it immediately can duplicate paid work.

One result is incomplete

A completed task can contain item-level no_result, error, or incomplete outputs. Keep the record's inputId, status, impressumUrl when available, and public error. Check that the input is the company's own homepage or root domain, then inspect the site before deciding whether another lookup is useful.

The task documentation explains statuses, result pagination, task history, limits, and retention in more detail.

Next steps

You now have a runnable Python Requests integration that submits German company websites, waits for the Impressum Scraper task, retrieves every result page, and keeps structured company details with their source URLs in local supplier research records.

Replace the two sample suppliers with your own stable IDs and website URLs. Then review each returned impressumUrl before using the extracted fields in your downstream process. You can download the maintained synchronous Requests example or switch to the repository's async HTTPX example when you need non-blocking concurrency.