# Panorama Firm Scraper (`automation-lab/panorama-firm-scraper`) Actor

Extract Polish company listings, contacts, categories, ratings, NIP numbers, opening hours, and coordinates from Panorama Firm.

- **URL**: https://apify.com/automation-lab/panorama-firm-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.16 / 1,000 item extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Panorama Firm Scraper

Export Polish company listings from [Panorama Firm](https://panoramafirm.pl/) as structured lead and market-research data. Search by business keyword and location, follow a real Panorama Firm category/search URL, or extract one company profile directly.

The Actor returns company identity, category, address, phone, email, website, rating, review count, NIP, opening hours, coordinates, and the source profile URL when those values are publicly available.

### What can you do with this Panorama Firm scraper?

- Build local prospect lists for a Polish city or district.
- Export a business category for territory and competitor analysis.
- Enrich known Panorama Firm profile URLs for CRM import.
- Schedule the same query and compare datasets over time.
- Feed normalized company records into Sheets, Airtable, a warehouse, or an outreach-review workflow.

The Actor resolves Panorama Firm's public search route, paginates server-rendered result pages, deduplicates company profiles, and can enrich every result from its profile page. It starts with direct HTTP requests and, when Panorama Firm returns an automated verification page, retries through the default Apify Proxy. It does not require a browser.

### Who is it for?

**Sales and partnerships teams** can find Polish companies in a target category and region before reviewing leads for outreach.

**Market researchers** can compare local supplier density, contact availability, ratings, and category coverage.

**Data and RevOps teams** can schedule consistent exports and connect the default dataset to downstream systems.

**Agency operators** can collect source-linked company records for territory planning while retaining an audit trail back to Panorama Firm.

### Why use this Actor?

- **Two discovery paths:** use a keyword/location pair or canonical Panorama Firm URLs.
- **Optional deep enrichment:** choose fast list-only exports or profile-level NIP, hours, and coordinates.
- **Typed output:** nullable fields are represented consistently instead of hidden or replaced with placeholders.
- **Bounded crawling:** the Actor honors `maxItems`, deduplicates profile URLs, limits profile concurrency, and retries only transient failures.
- **Integration ready:** primary results always go to the run's default dataset.

### What data is extracted?

| Field | Meaning |
| --- | --- |
| `companyId` | Panorama Firm company identifier when exposed |
| `tradeId` | Panorama Firm category/trade identifier when exposed |
| `name` | Company or professional name |
| `category` | Listed Panorama Firm category |
| `address` | Public postal address as shown by the source |
| `phone` | Public phone number; formatting follows the source/profile |
| `email` | Public company email, or `null` |
| `website` | Public company website normalized to an absolute URL |
| `rating` | Average Panorama Firm rating, or `null` |
| `reviewCount` | Number of displayed opinions, or `null` |
| `nip` | Polish tax identifier from the company profile, or `null` |
| `latitude`, `longitude` | Public profile coordinates, or `null` |
| `openingHours` | Array of weekday/open/close values from profile structured data |
| `profileUrl` | Canonical Panorama Firm company profile URL |
| `searchUrl` | Listing page that produced the record, or `null` for direct profiles |
| `scrapedAt` | ISO timestamp for this extraction |

Missing source values remain `null` or an empty `openingHours` array. A missing field is not guessed.

### How to get started

1. Open the Actor input form.
2. Enter a Polish business keyword such as `hydraulik` or `dentysta`.
3. Optionally enter a city, district, or region such as `Warszawa`.
4. Choose the maximum number of companies.
5. Keep **Enrich company profiles** enabled when you need NIP, coordinates, hours, and fuller contacts.
6. Run the Actor and open **Polish business leads** in the default dataset.
7. Export the results as JSON, CSV, Excel, XML, or connect an integration.

You can omit the keyword when supplying at least one supported Panorama Firm URL.

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | string | `hydraulik` in the prefill | Business category, service, or company name |
| `location` | string | `Warszawa` in the prefill | Optional Polish locality or region |
| `startUrls` | array | `[]` | Canonical Panorama Firm search, category, or profile URLs |
| `maxItems` | integer | `50` | Maximum unique companies saved, from 1 to 10,000 |
| `includeProfiles` | boolean | `true` | Fetch each company profile for deeper fields |
| `maxConcurrency` | integer | `5` | Simultaneous profile requests, from 1 to 10 |

At least one non-empty `query` or one `startUrls` entry is required. URLs from other domains are rejected.

### Input examples

Search Warsaw plumbers with profile enrichment:

```json
{
  "query": "hydraulik",
  "location": "Warszawa",
  "maxItems": 50,
  "includeProfiles": true,
  "maxConcurrency": 5
}
```

Extract one known profile:

```json
{
  "startUrls": [
    {
      "url": "https://panoramafirm.pl/mazowieckie,,warszawa,ursus,drzymały,8_41/artem_frolov-shicoy_fhm.html"
    }
  ],
  "maxItems": 1,
  "includeProfiles": true
}
```

For a faster contact export from result cards, set `includeProfiles` to `false`.

### Output example

A current enriched record has this shape:

```json
{
  "companyId": "122220311",
  "tradeId": "3660",
  "name": "Artem Frolov",
  "category": "Hydraulicy",
  "address": "ul. Drzymały 8/41, 02-495 Warszawa Ursus woj: mazowieckie",
  "phone": "883489839",
  "email": "tomfroloff87@gmail.com",
  "website": null,
  "rating": null,
  "reviewCount": 0,
  "nip": "5223369897",
  "latitude": 52.19012,
  "longitude": 20.88729,
  "openingHours": [
    { "dayOfWeek": "Monday", "opens": "8:00", "closes": "17:00" }
  ],
  "profileUrl": "https://panoramafirm.pl/mazowieckie,,warszawa,ursus,drzymały,8_41/artem_frolov-shicoy_fhm.html",
  "searchUrl": "https://panoramafirm.pl/hydraulik/mazowieckie,,warszawa",
  "scrapedAt": "2026-07-29T05:17:00.752Z"
}
```

Public directory details can change. Treat the record as an extraction example, not a guarantee that every field remains present.

### How much does it cost to extract Polish company leads?

This Actor uses pay-per-event pricing: a **$0.005 Actor start** event plus one **company record** event per saved company. There is no separate charge for profile enrichment.

| Apify tier | Price per company record |
| --- | ---: |
| Free | $0.00414 |
| Bronze | $0.00360 |
| Silver | $0.002808 |
| Gold | $0.00216 |
| Platinum | $0.00144 |
| Diamond | $0.001008 |

Examples before any Apify plan/platform adjustments:

- 10 records on Free: about **$0.0464** including the start event.
- 100 records on Bronze: about **$0.365** including the start event.
- 1,000 records on Silver: about **$2.813** including the start event.

Charges are based on records actually saved. Empty results do not emit company-record events.

### List-only versus profile enrichment

With `includeProfiles: false`, the Actor uses fields exposed on result cards. This is the fastest option for names, categories, addresses, contacts, ratings, and URLs.

With `includeProfiles: true`, the Actor visits each profile and can add NIP, coordinates, opening hours, and more complete contact values. Profile enrichment increases runtime but does not use a separate billing event.

For scheduled exports, start with 20–50 records, inspect coverage, and then raise `maxItems`.

### Pagination, limits, and deduplication

The Actor follows the source's canonical next-page link until it reaches `maxItems` or no further page exists. It stops scheduling accepted records when the limit is reached.

Duplicate company profile URLs are saved once per run, including when query results and explicit URLs overlap. Multiple start URLs share the same global `maxItems` limit.

Panorama Firm can canonicalize a user query to a related category form. The resolved canonical URL is stored in `searchUrl` so you can audit source scope.

### Scheduling and change monitoring

Apify schedules can run the same input daily, weekly, or monthly. To monitor a local market:

1. Save a Task with a stable keyword, location, and item limit.
2. Create a schedule for that Task.
3. Send each run dataset to your database or storage integration.
4. Compare by `companyId` or `profileUrl`.
5. Review new, removed, or changed contacts before using them operationally.

The Actor returns snapshots. It does not maintain history, diff datasets, or send alerts by itself.

### Export and integrations

The default dataset works with Apify's CSV, JSON, Excel, XML, and RSS exports. Common workflows include:

- Google Sheets for a reviewed local lead list.
- Airtable for enrichment and assignment.
- Make or Zapier for dataset-item automation.
- Webhooks for post-run loading into a warehouse.
- Apify API clients for programmatic collection.

When processing personal or contact data, configure downstream retention and access controls appropriate to your purpose.

### Run through the API with cURL

Replace `YOUR_TOKEN` with an Apify API token:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~panorama-firm-scraper/runs?token=YOUR_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "dentysta",
    "location": "Kraków",
    "maxItems": 20,
    "includeProfiles": true
  }'
```

Read dataset items using the `defaultDatasetId` returned by the run.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/panorama-firm-scraper').call({
  query: 'hydraulik',
  location: 'Warszawa',
  maxItems: 50,
  includeProfiles: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/panorama-firm-scraper').call(run_input={
    'query': 'dentysta',
    'location': 'Kraków',
    'maxItems': 20,
    'includeProfiles': True,
})

items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/panorama-firm-scraper"
```

For **Claude Desktop**, add this JSON under `mcpServers` in the desktop configuration. **Cursor** users can place the same server entry in `.cursor/mcp.json`, and **VS Code** users can add it to their workspace MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/panorama-firm-scraper"
    }
  }
}
```

Example prompts:

- “Find 20 dentists in Kraków on Panorama Firm and return contacts and ratings.”
- “Extract this Panorama Firm profile and summarize its NIP, opening hours, and contact channels.”
- “Create a JSON dataset of Warsaw plumbers for a territory-coverage review.”

### Reliability and failure behavior

The Actor retries network resets, HTTP 429 responses, and temporary server errors with bounded exponential backoff. It does not repeatedly retry invalid input, unsupported domains, stable client errors, or unrecognizable pages.

A changed upstream page shape causes a failed run rather than a misleading successful empty dataset. Naturally empty source searches can complete with zero records.

Direct requests are preferred. If Panorama Firm returns an automated verification page, the Actor retries through the default Apify Proxy. It does not use a residential proxy or browser fallback. Challenge recovery therefore depends on default Apify Proxy access being available to the run; if that fallback is unavailable or still receives a verification page, the run fails rather than returning misleading empty data.

### Tips and limitations

- Search in Polish for the closest Panorama Firm category matching.
- Use a precise locality to reduce broad national results.
- Set `includeProfiles: false` when list-card fields are sufficient.
- Keep concurrency conservative for larger enriched exports.
- Phone, email, website, NIP, ratings, and hours are nullable because companies choose what to publish.
- The Actor extracts company listings and profile facts; it does not scrape full review text.
- It does not bypass login, CAPTCHA, or access controls.
- It does not verify whether a business is active or whether contact details are current.

### Responsible use and legality

Panorama Firm pages contain publicly visible business information, but public availability does not remove your legal responsibilities. Use the Actor only for lawful purposes and follow applicable website terms, database rights, privacy rules, marketing laws, and data-retention requirements.

Avoid unsolicited or discriminatory use. Review contact records before outreach, honor opt-outs, minimize collected data, and provide required notices where applicable. You are responsible for your input, schedule, exports, and downstream processing.

### Troubleshooting

#### Why did my query return fewer companies than expected?

The source can map keywords to a specific category or return naturally sparse local results. Inspect `searchUrl`, try the Polish category term shown on Panorama Firm, broaden the location, or increase `maxItems`.

#### Why are NIP, coordinates, or opening hours empty?

Confirm `includeProfiles` is `true`. Even with enrichment, fields remain empty when the public company profile does not expose them.

#### Why was a start URL rejected?

Only HTTPS URLs on `panoramafirm.pl` are accepted. Use a canonical source search/category/profile URL, not a redirector or another domain.

#### Why did the run fail instead of returning an empty dataset?

The Actor fails when the upstream response is blocked, malformed, or no longer resembles a supported listing/profile page. This prevents silent data corruption. Retry later if Panorama Firm had a temporary outage; otherwise share the failed run with support.

### FAQ

#### Can I scrape one company only?

Yes. Add its Panorama Firm profile to `startUrls` and set `maxItems` to `1`.

#### Can I combine a query and start URLs?

Yes. The query is resolved first, then explicit URLs are processed until the shared item limit is reached. Duplicate profile URLs are skipped.

#### Does the Actor expose Panorama Firm opinie?

It exports the displayed average `rating` and `reviewCount` when present. It does not currently export individual review bodies.

#### Can I schedule recurring exports?

Yes. Save the input as an Apify Task and attach a schedule. Store each run's dataset externally if you need longitudinal comparisons.

#### Does it use proxies?

It uses direct requests first. If Panorama Firm responds with an automated verification page, the Actor retries through the default Apify Proxy. It does not use residential proxies or a browser fallback, and the recovery path requires default Apify Proxy access.

### Related Actors

For Central European directory research beyond Poland, see the [Firmy.cz Business Directory Scraper](https://apify.com/automation-lab/firmy-cz-scraper). Use each source-specific Actor separately and normalize records downstream by your own business keys.

# Actor input Schema

## `query` (type: `string`):

Business category, service, or company name in Polish, for example hydraulik or dentysta.

## `location` (type: `string`):

Optional Polish city, district, or region used with the business keyword.

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

Optional canonical panoramafirm.pl search, category, or company profile URLs. You can combine these with a keyword search.

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

Stop after saving this many unique company records across all inputs.

## `includeProfiles` (type: `boolean`):

Visit each company profile to add NIP, coordinates, opening hours, and complete contact fields. Disable for faster list-only exports.

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

Maximum simultaneous company profile requests. Conservative values reduce source load.

## Actor input object example

```json
{
  "query": "hydraulik",
  "location": "Warszawa",
  "startUrls": [],
  "maxItems": 20,
  "includeProfiles": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset view containing normalized Panorama Firm company records.

# 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 = {
    "query": "hydraulik",
    "location": "Warszawa",
    "startUrls": [],
    "maxItems": 20,
    "includeProfiles": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/panorama-firm-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 = {
    "query": "hydraulik",
    "location": "Warszawa",
    "startUrls": [],
    "maxItems": 20,
    "includeProfiles": True,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/panorama-firm-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "query": "hydraulik",
  "location": "Warszawa",
  "startUrls": [],
  "maxItems": 20,
  "includeProfiles": true
}' |
apify call automation-lab/panorama-firm-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/panorama-firm-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HDpOdVcQAI0rkWwiM/builds/2rgcaZSEQlnQhRLl9/openapi.json
