Local.ch $1π° Listings | URLs | Reviews Scraper
Pricing
from $1.00 / 1,000 results
Local.ch $1π° Listings | URLs | Reviews Scraper
From $1/1K. Pull structured company data from local.ch, including name, address, GPS, phone, email, website, hours, ratings, social links, photos, and categories. Search by category and location or use URLs. Supports Deutsch, English, FranΓ§ais, and Italiano.
Pricing
from $1.00 / 1,000 results
Rating
5.0
(1)
Developer
Abot API
Maintained by CommunityActor stats
2
Bookmarked
21
Total users
6
Monthly active users
3 days ago
Last modified
Categories
Share
local.ch Scraper: Swiss Business Listings, Contacts & Reviews
local.ch Scraper turns local.ch, the Swiss business directory, into structured data: names, full addresses, GPS coordinates, phone and email, websites, opening hours, ratings, social profiles, attribute tags and photos. Search by category and location (city, postal code, canton, or all of Switzerland), or paste local.ch search and profile links directly. Works in Deutsch, English, FranΓ§ais and Italiano, then export to JSON, CSV or Excel, or read the results through the API.
Why This Scraper?
- Two ways in, one filtered lane out. Search by category and location, or paste local.ch search/profile links. An optional review-only switch turns either path into one row per review comment instead of one row per company.
- Four languages, one dataset shape. Pages and category slugs resolve automatically in DE, EN, FR and IT, so
coiffeurin French andfriseurin German return the same normalized fields. - Detail-page enrichment by default. Search-result cards alone only carry name, address and category, so the actor visits every profile page to add phone, email, GPS, opening hours, social links, attributes and photos.
- Lead-gen filters that do not silently zero out. Keep only rated, premium-listed, or contactable businesses; the actor automatically turns on detail-page fetching whenever one of those filters needs data that only lives on the profile page.
- Built for recurring monitoring. Incremental mode classifies each row against the previous run for the same search, URL, or review scope, and a search whose pages cannot be read is reported in the run log with a clear warning.
- Full polymorphic contact data preserved. Every raw contact and attribute object from local.ch is kept alongside the flattened fields, so nothing upstream adds gets lost before the dataset catches up.
- Regional-fallback and postal-code guards. Two opt-in toggles catch local.ch quietly padding a sparse search with unrelated regions, so a narrow query does not quietly return noise.
Use Cases
- Lead generation and sales prospecting: pull phone, email and website for every business in a canton, filtered to premium-listed or contactable companies only.
- Local SEO and directory audits: check how a business's local.ch profile (categories, hours, photos, attributes) compares with its listings elsewhere.
- Market research and competitor mapping: sweep multiple categories across a canton or all of Switzerland to size a market.
- Reputation monitoring: collect review comments for a business or category and track sentiment over time.
- Recurring lead monitoring: schedule the actor with incremental mode to catch new, updated or removed businesses in a market segment.
Data You Get
Sample shape: values are illustrative placeholders, not from a live company.
| Field | Example |
|---|---|
id | "aaaaaaaaaaaaaaaaaaaaaa" |
name / subtitle | "Sample CafΓ© ZΓΌrich" / "Sample CafΓ© GmbH" |
language | "de" |
isPremium | true |
mainCategory / mainCategorySlug | "CafΓ©" / "cafe" |
categories / categorySlugs | ["CafΓ©", "Tea room", "Catering"] / ["cafe", "tea-room", "catering"] |
address | "Sample Street 1, 8001 ZΓΌrich" |
latitude / longitude | 47.0000 / 8.0000 |
phone / mobile | "+41440000000" / "+41760000000" |
email / website | "info@example.com" / "https://example.com/" |
facebook / instagram / linkedin | social profile URLs (when present on the profile) |
rating / ratingCount / reviewCount | 4.5 / 120 / 60 |
reviews | [{ "title": "Great service", "text": "...", "author": "Reviewer", "date": "2026-01-01", "rating": 5 }] (embedded, newest first, up to maxReviewsPerListing = 20 by default, when includeReviews=true) |
openingHours | "Mon: 09:00-18:00; Tue: 09:00-18:00; Wed: closed; ..." (always lists all 7 days) |
openingHoursStructured | [{ "day": "MONDAY", "status": "open", "byArrangement": false, "intervals": [...] }, ...] |
openingHoursByArrangement | false (listing-level flag, true when any day is "by arrangement") |
description | "Sample description paragraph appears here when fetchDetails=true." |
attributes | ["Takeaway", "Delivery", "Vegetarian options"] |
spokenLanguages / paymentMethods | ["Deutsch", "Englisch"] / ["Barzahlung", "TWINT", "Visa"] (labels in the page language, when the profile lists them) |
refusesAdvertising | false (listing output only; true when a contact carries local.ch's "no advertising" flag, null when the profile page could not be read) |
breadcrumb | ["Home", "Food", "CafΓ©"] |
logoUrl / imageUrls | photo and logo URLs from the profile gallery |
contactsRaw / attributeGroupsRaw | the full upstream polymorphic contacts and attribute-group objects, kept verbatim |
detailUrl / searchQuery / searchWhere | "https://www.local.ch/de/d/zurich/8001/cafe/sample-cafe-aaaaaaaaaaaaaaaaaaaaaa" / "cafe" / "zurich" |
Every row also carries street, zipCode, city, canton and country as separate fields alongside the combined address, plus entryType, schemaOrgType, favorited and professionalTitle (the last one is set only for individually-listed professionals). When reviewOnly is turned on, each row is a single review comment instead of a business listing: businessId, businessName, businessUrl, businessCity, businessZipCode, reviewId, reviewTitle, reviewText, reviewDate, reviewAuthor, reviewReply and reviewRating, one row per review of that business (newest first, up to maxReviewsPerListing per business).
The dataset tab offers the same rows through several views: Results (everything), Company overview (core listing fields), Contacts only (phone/email/website/socials), Review comments (reviewOnly rows), Incremental changes (the NEW/UPDATED/EXPIRED/REAPPEARED rows an incremental run classified), and Embedded reviews (one row per review comment unwound out of each listing's reviews array when includeReviews=true). Pick whichever view matches what you need; every view reads the same underlying dataset.
How to Use
- Pick a start: fill in Category (or Categories for a multi-query sweep) and Location under Option 1, or set Run mode to "Paste local.ch URLs" (
mode: url) and paste local.ch search or profile links under Option 2. Pasted links are only used when Run mode is "Paste local.ch URLs"; in the default Search mode theurlslist is ignored. - Leave Fetch detail pages on (the default) for full contact and hours data, or turn on Review-only output to collect review comments instead of business listings.
- Set any filters you need (rating, phone, email, website, premium-only) and cap the run with Maximum rows and Maximum pages per search.
- Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.
Search by category in one city:
{"mode": "search","category": "restaurant","where": "zurich","language": "de","maxPages": 5,"fetchDetails": true}
Paste local.ch URLs (search and profile pages mixed):
{"mode": "url","urls": ["https://www.local.ch/de/s/restaurant/zurich","https://www.local.ch/en/d/zurich/8001/tea-room/sample-name-aaaaaaaaaaaaaaaaaaaaaa"],"maxPages": 3,"fetchDetails": true}
Default URL list: in URL mode, leaving urls untouched (or omitting it in an API call) scrapes 12 distinct example URLs, one per genuinely different shape from the table below, sharing maxListings evenly between them. The input form is prefilled with those 12 plus two redirecting examples (the short /d/<id> link, which redirects to the full profile URL, and the bare local.ch host, which redirects to www.local.ch); those two are not in the untouched-default list because they would return the same rows as another example. Provide your own urls to replace it.
Every local.ch URL shape URL mode accepts:
| Shape | Example | Notes |
|---|---|---|
| Classic search, category + location | https://www.local.ch/de/s/restaurant/zurich | Walks result pages 20 at a time; local.ch 307-redirects this to a ?rid= tracking variant, followed automatically. |
| Classic search, category only | https://www.local.ch/de/s/pizza | Nationwide (no location segment). |
| Classic search, reversed segments | https://www.local.ch/en/s/zurich/pizza | local.ch resolves which segment is the place regardless of order, with the same result set either way. |
| Canonical SEO search (path form) | https://www.local.ch/de/q/zuerich/restaurant | local.ch's own share-link shape, /<lang>/q/<location>/<category>; note the reversed segment order vs. /s/. |
| Canonical SEO search (query form) | https://www.local.ch/de/q?what=restaurant&where=zuerich | Same search, ?what=/?where= instead of path segments. |
| Paginated / sorted search | https://www.local.ch/de/s/restaurant/zurich?page=2, ...?sorting=rating | A pasted ?page=N starts the walk at that page (partial scope). |
| Postal-code / canton-code location | https://www.local.ch/de/s/restaurant/8001, https://www.local.ch/de/s/restaurant/zh | Location segment may be a 4-digit postal code or a canton code. |
| Bare host | https://local.ch/de/s/restaurant/zurich | 301-redirects to www.local.ch, followed automatically. |
| Profile / detail page | https://www.local.ch/en/d/zurich/8001/tea-room/sample-name-aaaaaaaaaaaaaaaaaaaaaa | Scrapes that one business. |
| Short profile link | https://www.local.ch/de/d/aaaaaaaaaaaaaaaaaaaaaa | local.ch redirects it to the full profile URL, followed automatically. |
| Profile contact card (and its sibling links) | https://www.local.ch/de/entries/aaaaaaaaaaaaaaaaaaaaaa/vcard | The "add to address book" link on every profile page (also /opening_hours, /feedbacks, /qr). Converted to that business's profile, so you get the full row. |
Any search shape + ?sorting= | ...?sorting=rating | Honoured: accepts relevance, alphanum, distance, rating (case-insensitive); any other value is dropped with a log warning rather than sent through. distance lists the nearest businesses first from the URL's own coordinates=<lat>,<lng> (or the Sort by distance from input); with no point local.ch returns A-Z order, so the run logs a warning. |
Any search shape + ?filter_facet= / ?filter_tags= / ?filter_open= | ...?filter_facet=attr_cuisine:italian | Honoured: the filters local.ch shows next to its results (cuisine, diets, amenities, accessibility, open now, bookable, ...) are applied on every page of the crawl. You can also type them in the facetFilters input. |
Any search shape + bare ?facet= / ?tags= / ?open= | ...?facet=attr_diets:glutenfree | Not applied by local.ch (results unchanged); pasting one logs a loud warning instead of silently ignoring it. Use filter_facet= or the facetFilters input instead. |
| Category or city hub/index page | https://www.local.ch/de/kategorien/..., https://www.local.ch/de/staedte/... (+ /categories, /cities, /villes, /categorie, /citta in EN/FR/IT) | Not a search result. These are multi-level letter/category/city index pages that link out to many individual /q/<location>/<category> searches. Paste the /q/... link you actually want instead; the actor logs a warning and skips the URL rather than returning 0 rows silently. |
Review comments only, discovered from a category search:
{"mode": "search","reviewOnly": true,"category": "restaurant","where": "zurich","maxListings": 10}
Lead-gen filter: only premium-listed companies with both phone and email, in a postal code:
{"mode": "search","category": "garage","where": "8001","language": "de","matchQueryPostalCode": true,"premiumOnly": true,"withPhone": true,"withEmail": true,"maxListings": 500}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/local-ch-scraper").call(run_input={"mode": "search", "category": "restaurant", "where": "zurich"})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["name"], row["city"], row["phone"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/local-ch-scraper').call({ mode: 'search', category: 'restaurant', where: 'zurich' });const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted run: paste its run or dataset ID and the actor skips every row already collected there, so you do not pay twice. - Incremental mode (
incrementalMode) is for scheduled runs over the same search, URL or review scope. Each row is classifiedNEW,UPDATED(withchangedFields), orUNCHANGED(suppressed and not billed unlessemitUnchangedis on).EXPIREDrows are only produced after a run proves it scanned the whole tracked scope end to end, not when Maximum pages/rows capped it, Resume was used, or a pasted URL started mid-pagination (?page=2or higher), and only withemitExpiredon.REAPPEAREDcan only follow a row that was previously markedEXPIRED, so it needs that earlier complete-scan run withemitExpiredon before it can ever appear.stateKeynames or shares the stored state; leave it empty and the actor derives one automatically from the search/filter/review setup.categories/categorySlugs/mainCategory/mainCategorySlugnever count as a change on their own; local.ch's search-result cards return a different (but equally valid) category set on repeated fetches, so list any other fields you don't want treated as changes inignoreFieldsForChanges(checked against nested fields too, e.g. insidereviews). Ignored fields are still returned in the output. With incremental mode off, row shape is exactly as described above, with no extra fields.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each row, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify β Settings β Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you have not authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (build from category + location) or url (use the urls list). |
reviewOnly | boolean | false | When true, emit one row per review comment instead of listing rows. Works in both search and URL modes. |
category | string | restaurant | Single category slug or label. Used in search mode when queries is empty. |
queries | array | [] | Multiple categories to walk in search mode. Overrides category when set. |
where | string | zurich | Location: city slug, 4-digit postal code, 2-letter canton code, switzerland, or empty. |
locations | array | [] | Search mode only. Optional list of places (same forms as where). When set, every category is searched in every place listed here instead of just where. Leave empty to search one place as before. |
language | string | de | de, en, fr, or it. Affects URL slugs and content language in search mode; URL mode reads the language directly from each pasted link. |
urls | array | 12 distinct example URLs (classic, nationwide, /q/ path and query forms, ?page=, ?sorting=, full profile link, FR/EN variants, postal-code and canton-code locations, profile contact-card link) | local.ch URLs: search (any shape in the table above) or profile pages. Used in URL mode. With the field untouched (or an API call that omits it) URL mode now scrapes one example of each shape, with maxListings split evenly across them. With reviewOnly=true, profile URLs scrape reviews directly and search URLs discover profiles first. |
maxPages | integer | none (prefill: 0) | Optional bound on result pages walked per category/URL (20 results each). Leave empty or 0 to walk every result page. |
maxListings | integer | 20 | Total row cap (0 = unlimited). Multi-query inputs split the cap evenly across queries. |
fetchDetails | boolean | true | Visit each profile page for phone, email, full address, coordinates, opening hours, social links and attributes. |
includeReviews | boolean | false | Listing output only. Embed review comments (newest first) inside each search or URL mode row. Ignored when reviewOnly=true. |
maxReviewsPerListing | integer | 20 | Maximum reviews per business, up to 200. Listing output: reviews embedded per row, 0 = none. Review-only output: review rows per business, 0 = every review local.ch lists (the row cap still applies). A profile page shows only its 3 newest reviews, so asking for more makes one extra small request per business that has more. |
maxReviewRows | integer | 10 | Review-only output only. Default review-row cap when reviewOnly=true and maxListings is explicitly set to 0. |
maxConcurrency | integer | 6 | No longer user-configurable. Detail-fetch concurrency is fixed internally; kept in the schema only so saved task inputs keep validating. |
resumeFromRunId | string | none | Paste a previous run or dataset ID to skip rows it already collected and return only new ones. Continues ONE specific interrupted run; for recurring monitoring of the same scope, use incrementalMode instead. |
incrementalMode | boolean | false | Turn on for daily or recurring monitoring of the same search/URL/review scope. The first run returns everything as NEW; later runs return only the rows that changed. |
stateKey | string | none | Incremental mode only. Name a monitoring campaign to keep its state stable, or deliberately share it across runs. Leave empty to derive a key automatically from the search/filter/review setup. |
emitUnchanged | boolean | false | Incremental mode only. Also return (and bill) rows unchanged since the last run, marked UNCHANGED. |
emitExpired | boolean | false | Incremental mode only. Also return (and bill) rows tracked previously but no longer found, marked EXPIRED. Only produced after a run proves a complete scan (no cap hit, no resume, no pasted mid-pagination URL). |
ignoreFieldsForChanges | string[] | [] | Incremental mode only. Extra output field names to ignore when deciding whether a row is UPDATED (categories/categorySlugs/mainCategory/mainCategorySlug are always ignored). Matched at any nesting depth, so it also works for a field inside reviews, contactsRaw, attributeGroupsRaw or openingHoursStructured. |
ratedOnly | boolean | false | Keep only rows that have an average rating. |
minRating | integer | none | Keep only rows at or above this rating (0 to 5). |
withPhone | boolean | false | Keep only rows with a phone number. This data only exists on the profile page, so turning this on also enables Fetch detail pages, regardless of that toggle. |
withEmail | boolean | false | Keep only rows with an email address. Forces Fetch detail pages on for the same reason as withPhone. |
withWebsite | boolean | false | Keep only rows with a website. Forces Fetch detail pages on for the same reason as withPhone. |
premiumOnly | boolean | false | Keep only premium-listed companies. Forces Fetch detail pages on for the same reason as withPhone. |
matchQueryPostalCode | boolean | false | Search mode only. When the location is a postal code, keep only rows whose postal code exactly matches the queried one, dropping the rest before any detail fetch. Guards against local.ch padding a sparse page with other regions. |
skipRegionalFallback | boolean | false | Search mode only. When a query has zero exact matches, local.ch can silently fall back to a region-wide search and return unrelated rows. When on, the actor detects this and emits no output for that query. |
sort | string | relevance | The order local.ch returns results in (search mode, and pasted search URLs without their own sorting=). relevance is local.ch's default; alphanum, rating and distance each widen the result set slightly to include nearby or loosely related businesses. distance lists the nearest businesses to Sort by distance from first; the run fails if that point is empty. |
distanceFrom | string | none | Only used with sort=distance: the point to measure from, as latitude,longitude (e.g. 47.3769,8.5417 for Zurich main station). A pasted URL's own coordinates= value takes precedence. |
facetFilters | array | [] | The same filters local.ch shows next to its search results (cuisine, diets, amenities, accessibility, open now, bookable, ...), applied by local.ch itself. Type a plain name (italian, wifi, open now) or the exact filter value (attr_cuisine:italian). Filters from different groups must all match; filters from the same group match any of them. An unmatched plain name skips that search with an explanatory error rather than returning unfiltered results. |
excludeRefusesAdvertising | boolean | false | Listing output only. Drop businesses that marked a phone, mobile or fax number with local.ch's "no advertising" flag (output field refusesAdvertising), plus any business whose profile page could not be read. This data only exists on the profile page, so turning this on also enables Fetch detail pages. |
proxy | object | { useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"], apifyProxyCountry: "CH" } | Connection settings. If the residential tier is not available on your plan, the actor automatically falls back to a standard connection, then to a backup connection pool, so the run still completes. |
mcpConnectors | array | none | Optional: send a summary of each row to apps you authorized under Integrations. |
notionParentPageUrl | string | none | Notion connector only: page under which item pages are created. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"id": "aaaaaaaaaaaaaaaaaaaaaa","name": "Sample CafΓ© ZΓΌrich","subtitle": "Sample CafΓ© GmbH","language": "de","entryType": "COMPANY","schemaOrgType": "Cafe","isPremium": true,"favorited": false,"professionalTitle": null,"address": "Sample Street 1, 8001 ZΓΌrich","street": "Sample Street 1","zipCode": "8001","city": "ZΓΌrich","canton": "ZH","country": "CH","latitude": 47.0000,"longitude": 8.0000,"phone": "+41440000000","phoneAlt": null,"mobile": "+41760000000","fax": null,"email": "info@example.com","emailAlt": null,"website": "https://example.com/","whatsapp": "+41760000000","facebook": "https://www.facebook.com/example","instagram": "https://www.instagram.com/example","linkedin": "https://www.linkedin.com/company/example","twitter": null,"tiktok": null,"pinterest": null,"youtube": null,"xing": null,"foursquare": null,"mainCategory": "CafΓ©","mainCategorySlug": "cafe","categories": ["CafΓ©", "Tea room", "Catering"],"categorySlugs": ["cafe", "tea-room", "catering"],"rating": 4.5,"ratingCount": 120,"reviewCount": 60,"reviewsFetched": 0,"reviews": null,"openingHours": "Mon: 09:00-18:00; Tue: 09:00-18:00; Wed: closed; Thu: 09:00-18:00; Fri: 09:00-18:00; Sat: 10:00-17:00; Sun: closed","openingHoursStructured": [{ "day": "MONDAY", "status": "open", "byArrangement": false, "intervals": [{ "type": "OPEN", "start": "09:00", "end": "18:00" }] },{ "day": "WEDNESDAY", "status": "closed", "byArrangement": false, "intervals": [] }],"openingHoursByArrangement": false,"description": "Sample description paragraph appears here when fetchDetails=true.","descriptionHtml": "<p>Sample description paragraph appears here when fetchDetails=true.</p>","attributes": ["Takeaway", "Delivery", "Vegetarian options"],"spokenLanguages": ["Deutsch", "Englisch"],"paymentMethods": ["Barzahlung", "TWINT", "Visa"],"refusesAdvertising": false,"breadcrumb": ["Home", "Food", "CafΓ©"],"logoUrl": "https://images.services.local.ch/aaaa/logo.jpg","imageUrls": ["https://images.services.local.ch/aaaa/photo-1.jpg","https://images.services.local.ch/aaaa/photo-2.jpg"],"contactsRaw": [{ "__typename": "MobileContact", "value": "+41760000000", "displayValue": "076 000 00 00", "whatsapp": true },{ "__typename": "EmailContact", "value": "info@example.com", "displayValue": "info@example.com" },{ "__typename": "CustomerProvidedURLContact", "value": "https://example.com/", "displayValue": "example.com" },{ "__typename": "SocialMediaContact", "value": "https://www.facebook.com/example", "socialMediaProvider": "FACEBOOK" }],"additionalContactsRaw": null,"attributeGroupsRaw": [{ "groupName": "Services", "attributes": [{ "id": "takeaway", "name": "Takeaway" }] }],"detailUrl": "https://www.local.ch/de/d/zurich/8001/cafe/sample-cafe-zurich-aaaaaaaaaaaaaaaaaaaaaa","searchQuery": "cafe","searchWhere": "zurich"}
Plan Requirement
The default connection requests a residential-tier proxy scoped to Switzerland, which loaded every page shape in our checks. A datacenter connection also loads local.ch, but in our checks about half of datacenter exits were refused (the actor rotates and retries search pages, while profile pages may come back without detail fields), so residential is recommended. Large pages can stall on a shared standard connection. The actor observes whether that tier is actually granted for your account: if it is not (for example on an Apify free plan), it logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies." and continues on a backup connection; once that backup actually serves a page it logs "Served over the backup connection (the default connection was unavailable)." Some pages may still be unavailable on the backup. A run that could not load a single results page ends as a failed run with a status message instead of a silent empty success: "The proxy connection was refused for this account, so no page could be read..." when every connection was refused, "No results: residential proxies are unavailable on this run ... and the backup connection could not load the requested local.ch pages" on a free plan, or "Could not load any results page (the requests failed)..." otherwise. Only a run that loaded no page at all fails this way; if some pages load and others do not, the run completes with whatever was collected and logs a warning per failed page. local.ch error pages (HTTP 500, for example on the category and city index pages) are treated as a site error and are not retried. An explicit connection group or your own proxy URLs are always honored as given.
FAQ
How much does it cost?
You pay per company or review row returned, plus a separate detail-enrichment charge for rows where a profile page was actually fetched (once per business in review-only output). Reviews embedded in a company row (includeReviews) add no extra charge. The Pricing tab shows the current rates. Use Maximum rows to cap the cost of any run.
Is it legal to scrape local.ch?
This actor collects only publicly available business directory data. You are responsible for how you use it: follow local.ch's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Contact details come from the businesses' own listings; handle personal data such as phone numbers and emails in line with the privacy law that applies to you.
Can I track only new or changed businesses on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each row is then classified NEW, UPDATED, or UNCHANGED against the previous run for the same search, URL or review scope, and unchanged rows are suppressed and not billed unless you turn emitUnchanged on. EXPIRED (and the REAPPEARED classification that can follow it) only appear once a run has proven it scanned the whole tracked scope, so a capped or resumed run keeps the previous state untouched instead.
Why did my withPhone, withEmail, withWebsite or premiumOnly filter return nothing?
Search-result cards never carry phone, email, website or premium status; only the profile page does. If Fetch detail pages is off, the actor automatically turns it on whenever one of these filters is set, so the filter narrows real data instead of dropping every row. This is logged as a visible warning, not a silent change.
Why did a search return fewer rows than expected?
Two opt-in guards can legitimately shrink a run: Only listings matching the query postal code drops off-target cards before local.ch pads a sparse page with other regions, and Only listings from exact query matches skips a query entirely when local.ch itself silently falls back to a region-wide search. Both default off, so a normal run is unaffected unless you turn them on.
Why did my run return zero rows?
If local.ch cannot be reached for one search among several, or for a later page, the actor logs a warning for that search (for example "page 1: fetch failed, stopping") and moves on, so check the run log before concluding the search has no results. If no results page at all could be loaded, the run ends as failed with a status message explaining why (see Plan Requirement) rather than succeeding with zero rows. Run it again in a few minutes, or use Resume from run ID to continue an interrupted run without re-collecting or re-charging rows you already have.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
π Want more leads data?
Pair this actor with these related scrapers from the same team:
| π·οΈ Tutti.ch Scraper Scrape listings from tutti.ch across all 23 categories, including vehicles, property... | π Yellow Pages AU From $1/1K. Scrape business listings from Yellow Pages Australia by type and location... |
| π TrueLocal AU Directory Listings & Reviews Scraper Scrape TrueLocal.com.au business listings by keyword, location, or URL. Extract names... | π Herold.at Scraper From $0.80/1K. Scrape Herold.at business listings across Austria into clean JSON. Extract... |
| π Yellow Pages NZ From $0.8/1K. Scrapes business listings from Yellow.co.nz (New Zealand Yellow Pages)... | π BBB Scraper Scrape bbb.org (Better Business Bureau, USA & Canada) business profiles: BBB rating... |
π Browse all abotapi scrapers
π¬ Support
- π Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- π οΈ Need another site, extra fields or a private build? Email contact@abotapi.com.
- β Enjoying it? A quick review on the actor page helps other users find it.