Finn.no $0.8π° URL | Keyword | Deep Search Scraper
Pricing
from $0.80 / 1,000 results
Finn.no $0.8π° URL | Keyword | Deep Search Scraper
From $0.8/1K. Scrape active Finn.no ads via search builder or URL. Returns 30+ fields per ad, including full HTML description, employer details, deadline, contacts, languages, sector, industries, keywords, and apply URL.
Pricing
from $0.80 / 1,000 results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
16 days ago
Last modified
Categories
Share
Finn.no Jobs Scraper
Scrape every active job ad on Finn.no, Norway's largest classifieds portal, in two clicks. Two input modes (Search builder, URL paste), 30+ structured fields per ad including the full HTML description, employer details, deadline, contact persons, working languages, sector, industries, keywords, and the apply URL. Works on every Apify plan with no extra setup.
Why This Scraper?
- 30+ fields per ad, not just the SERP card: sector, industries, contact persons, keywords, apply URL, and the full rendered description.
- Two input modes that both work for individual ads or full searches: build by keyword, location, employment type, industry, occupation, sector, working language, and posted-since; or paste any finn.no/job URL (including a single /job/ad/
- Works on every plan, including Free. Apify Proxy is recommended for reliability; for higher volume or Norway-pinned routing set
groups: ["RESIDENTIAL"], country: "NO". - Per-search budget cap so multi-keyword runs return a fair share from each search; client-side de-duplication across pages and searches via finnkode.
- ~50 ads per SERP page, 2 to 5 ads per second sustained, low memory (256 MB default).
- Skip sponsored placements ("Betalt plassering") with a single checkbox if you only want organic results.
- Daily/recurring change monitoring: turn on Incremental mode to get only NEW, UPDATED, and REAPPEARED listings on every scheduled run, or resume one specific interrupted crawl with
resumeFromRunId.
Data You Get
Sample shape: values are illustrative placeholders, not from a live ad.
| Field | Example |
|---|---|
id | "460000000" |
idInt | 460000000 |
url | "https://www.finn.no/job/ad/460000000" |
title | "Sample Job Title" |
subtitle | "Sample Subtitle Headline" |
employer | "Sample Employer AS" |
employerUrl | "http://www.example.com/" |
employerHomepage | "http://www.example.com/" |
employerLogo | "https://images.finncdn.no/dynamic/default/0000/0/0/000000_0000000000.png" |
streetAddress | "Sample Street 1" |
postalCode | "0000" |
city | "Oslo" |
addressCountry | "NO" |
locationDisplay | "Oslo" |
locationLink | "https://www.finn.no/job/search?location=1.20001.20061" |
latitude | 59.9000 |
longitude | 10.7000 |
datePosted | "2026-01-01" |
datePostedEpochMs | 1735689600000 |
relativePosted | "2 dager siden" |
applicationDeadline | "2026-02-01" |
applicationDeadlineEpochMs | 1738368000000 |
applicationDeadlineRaw | "01.02.2026" |
lastModified | "2026-01-01T09:00:00.000Z" |
lastModifiedEpochMs | 1735722000000 |
employmentForm | "Fast" |
employmentType | ["FULL_TIME"] |
employmentTypeRequested | "fulltime" |
sector | "Privat" |
industries | ["Sample Industry A", "Sample Industry B"] |
jobFunction | "Sample Function" |
internalJobTitle | "Sample Internal Title" |
workingLanguages | ["Norsk", "Engelsk"] |
contactPersons | [{ "name": "Jane Doe", "phones": ["+4700000000"], "emails": ["jane@example.com"] }] |
keywords | ["sample", "keyword", "tags"] |
applyUrl | "https://example.com/apply/000" |
descriptionHtml | "<p>Full HTML ad description goes here.</p>" |
descriptionText | "Full ad description as plain text goes here." |
breadcrumb | ["FINN", "Jobb", "SΓΈk", "Oslo"] |
sponsored | false |
scrapedAt | "2026-01-01T00:00:00.000Z" |
changeType | "NEW" (incremental mode only β see "Resume & recurring updates" below) |
changedFields | ["title"] (incremental mode, UPDATED rows only) |
firstSeenAt | "2026-01-01T00:00:00.000Z" (incremental mode only) |
lastSeenAt | "2026-01-02T00:00:00.000Z" (incremental mode only) |
How to Use
1) Search by keyword + location
{"mode": "search","queries": ["developer"],"location": "1.20001.20061","employmentType": "fulltime","maxPages": 5,"maxListings": 200,"fetchDetails": true}
2) Search multiple keywords with industry filter
{"mode": "search","queries": ["sykepleier", "lege", "fysioterapeut"],"employmentType": "fulltime","industry": "9","publishedSince": "7","sortBy": "published_desc","maxPages": 3,"maxListings": 150}
3) Search English-language management roles
{"mode": "search","queries": ["product manager"],"employmentType": "management","workLanguage": "english","sector": "private","skipSponsored": true,"maxPages": 2}
4) URL mode (paste any Finn.no URL, including a single ad)
{"mode": "url","urls": ["https://www.finn.no/job/search?q=developer&extent=3947&location=1.20001.20061","https://www.finn.no/job/search?extent=3942&location=0.22030","https://www.finn.no/job/ad/460000000"],"skipSponsored": true,"maxPages": 5,"maxListings": 250,"fetchDetails": true}
The search-builder fields (queries, location, employmentType, industry, occupation, sector, workLanguage, publishedSince, sortBy) are ignored in URL mode, because the pasted URL already carries its own filters. skipSponsored is not: it is applied after fetch, so it drops "Betalt plassering" cards from a pasted URL's results too.
5) Daily monitoring: only what changed since yesterday
{"mode": "search","queries": ["developer"],"location": "1.20001.20061","incrementalMode": true}
First scheduled run returns everything as NEW. Every later run returns only NEW, UPDATED, and REAPPEARED listings β see "Resume & recurring updates" below.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | enum | search | search (use builder fields) or url (paste URLs). |
queries | array of strings | ["developer"] | Search mode only, ignored in URL mode. One search per keyword. Empty = browse without keyword filter. |
location | string | empty | Search mode only, ignored in URL mode. Finn.no hierarchical location code (e.g. 1.20001.20061 for Oslo, 1.20001.20015 for Vestland). Pick a filter on finn.no/job and copy from ?location=. |
employmentType | enum | fulltime | Search mode only. In URL mode the employment type is read back out of the pasted URL instead. any / fulltime (Heltid, extent=3947) / parttime (Deltid, extent=3942) / management (Lederstillinger, manager_role=6702). |
industry | string | empty | Search mode only, ignored in URL mode. Finn.no industry code from ?industry=. |
occupation | string | empty | Search mode only, ignored in URL mode. Finn.no occupation/job-function code from ?occupation=. |
sector | enum | any | Search mode only, ignored in URL mode. any / private / public. |
workLanguage | enum | any | Search mode only, ignored in URL mode. any / norwegian / english / swedish / danish / other. |
publishedSince | enum | any | Search mode only, ignored in URL mode. any / 1 / 3 / 7 / 14 / 30 (days). |
sortBy | enum | default | Search mode only. A pasted URL keeps whatever ?sort= it already carries. default (relevance), published_desc (newest), deadline_asc (closest deadline). |
skipSponsored | bool | false | Applies in both search mode and URL mode. Drop "Betalt plassering" cards (paid placements) after fetch, including from the results of a pasted finn.no/job URL. |
urls | array of strings | example URL | URL mode only. Each URL is paged forward independently. The search-builder fields above are ignored in this mode; skipSponsored still applies. |
maxPages | int | (empty) | Max SERP pages per search (or per pasted URL). Leave empty (or 0) to walk every result page until Finn.no has no more. |
maxListings | int | 20 | Cap across all searches. 0 = unlimited (still bounded by maxPages). |
fetchDetails | bool | true | Fetch full ad details: description, deadline, sector, industries, contact persons, keywords, apply URL. |
resumeFromRunId | string | (empty) | A previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by id) are skipped, so this run only saves the new ones. For recurring daily monitoring of the same search, use incrementalMode instead β see "Resume & recurring updates" below. |
incrementalMode | bool | false | Daily/recurring monitoring of this same search. First run returns everything as NEW; later runs return only NEW/UPDATED/REAPPEARED by default. See "Resume & recurring updates" below. |
stateKey | string | (empty) | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. Auto-derived from your search/URL and fetchDetails settings when left empty. |
emitUnchanged | bool | false | Incremental mode only. Also return listings unchanged since the last run, marked UNCHANGED. Adds and bills extra rows you already have. |
emitExpired | bool | false | Incremental mode only. Also return listings from a previous run no longer found, marked EXPIRED, once a run has fully scanned the tracked search (not capped, not a resume). Adds and bills extra synthetic rows. |
proxy | object | Apify Proxy | Works on every plan, including Free. For higher volume or Norway-pinned routing, set groups: ["RESIDENTIAL"], country: "NO". |
Resume & recurring updates
There are two different things here β pick the one that matches what you're doing:
| Need | Use |
|---|---|
| A crawl stopped and should continue | resumeFromRunId / automatic checkpoint recovery |
| Run the same search every day and receive only changes | incrementalMode |
| Keep separate daily campaigns for similar searches | distinct stateKey values |
| Run a normal full snapshot | leave both off |
Resume (resumeFromRunId) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips listings already collected there, returning only the remaining new ones. A checkpoint is also saved automatically as the run progresses β if the run is interrupted by an Apify platform migration or you click Resurrect on a failed run, it picks back up from where it left off in the same run, without re-saving already-collected listings. No input is needed for this; it's automatic.
Incremental mode (incrementalMode) is for a schedule (for example, daily): the actor remembers the previous run of the same search by itself, so you never paste a run ID. The first run returns everything as NEW. Later runs return only NEW, UPDATED, and REAPPEARED listings by default β duplicates and unchanged listings are suppressed (and not billed). Turn on emitUnchanged or emitExpired only when you also want those rows returned (and billed for). State is isolated per search/URL setup and per fetchDetails setting β turning fetchDetails on or off starts a fresh baseline rather than diffing detail fields against a card-only snapshot. Set stateKey to name or deliberately share a monitoring campaign.
Some fields are excluded from change detection because they always drift on their own and would otherwise mark every listing UPDATED on every run: scrapedAt (the run timestamp) and relativePosted (a relative "X days ago" string that changes with the clock, not the listing). Both fields are still returned on every row as usual β they are simply ignored when deciding NEW/UPDATED/UNCHANGED.
Scheduled-run example β same search, run daily:
Day 1 (first run ever for this search):
{ "mode": "search", "queries": ["developer"], "location": "1.20001.20061", "incrementalMode": true }
β every listing comes back with "changeType": "NEW".
Day 2 (the schedule fires again, identical input):
{ "mode": "search", "queries": ["developer"], "location": "1.20001.20061", "incrementalMode": true }
β listings that vanished and reappeared come back as "changeType": "REAPPEARED", listings whose title/deadline/etc. changed come back as "changeType": "UPDATED" with changedFields listing what changed, brand-new postings come back as "changeType": "NEW" β and listings that are still there, unchanged, are not returned at all (suppressed, not billed) unless emitUnchanged is on.
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 record β 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 haven't 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.
Output Example
Sample shape: values are illustrative placeholders, not from a live ad.
{"id": "460000000","idInt": 460000000,"url": "https://www.finn.no/job/ad/460000000","title": "Sample Job Title","subtitle": "Sample Subtitle Headline","employer": "Sample Employer AS","employerUrl": "http://www.example.com/","employerHomepage": "http://www.example.com/","employerLogo": "https://images.finncdn.no/dynamic/default/0000/0/0/000000_0000000000.png","streetAddress": "Sample Street 1","postalCode": "0000","city": "Oslo","addressCountry": "NO","locationDisplay": "Oslo","locationLink": "https://www.finn.no/job/search?location=1.20001.20061","latitude": 59.9,"longitude": 10.7,"datePosted": "2026-01-01","datePostedEpochMs": 1735689600000,"relativePosted": "2 dager siden","applicationDeadline": "2026-02-01","applicationDeadlineEpochMs": 1738368000000,"applicationDeadlineRaw": "01.02.2026","lastModified": "2026-01-01T09:00:00.000Z","lastModifiedEpochMs": 1735722000000,"employmentForm": "Fast","employmentType": ["FULL_TIME"],"employmentTypeRequested": "fulltime","sector": "Privat","industries": ["Sample Industry A", "Sample Industry B"],"jobFunction": "Sample Function","internalJobTitle": "Sample Internal Title","workingLanguages": ["Norsk", "Engelsk"],"contactPersons": [{ "name": "Jane Doe", "phones": ["+4700000000"], "emails": ["jane@example.com"] }],"keywords": ["sample", "keyword", "tags"],"applyUrl": "https://example.com/apply/000","descriptionHtml": "<p>Full HTML ad description goes here.</p>","descriptionText": "Full ad description as plain text goes here.","breadcrumb": ["FINN", "Jobb", "SΓΈk", "Oslo"],"sponsored": false,"scrapedAt": "2026-01-01T00:00:00.000Z"}
Plan Requirement
This scraper works on every Apify plan, including Free, with the default useApifyProxy: true config. For larger runs, or if you see throttling, switch the proxy field to { useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"], apifyProxyCountry: "NO" } (Apify Starter or higher). You can also paste your own proxy URLs in the proxy field.