Dice.com Scraper - Tech Jobs, Salaries, Skills & Employers
Pricing
from $1.20 / 1,000 job results
Dice.com Scraper - Tech Jobs, Salaries, Skills & Employers
Scrape tech job listings from Dice.com by keyword, filters, or URL. Extract job titles, companies, recruiter types, salaries, locations, skills, posted dates, apply links or emails, and full job descriptions. Auto-paginates with pay-per-result pricing and no subscription required.
Pricing
from $1.20 / 1,000 job results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
14 hours ago
Last modified
Categories
Share
Dice.com Scraper: Tech Job Listings with Salaries, Skills & Apply Contacts
Dice.com Scraper turns Dice.com into clean, structured tech job data. Search by keyword, location and filters, or paste any Dice.com search or job URL, and get back title, company, recruiter or direct-hire type, parsed salary, location, workplace type, skills, posted date, an apply URL or email, and (optionally) the full job description with a structured address. Export straight to JSON, CSV or Excel, pull it through the API, or pipe results into the apps you already use.
Why This Scraper?
- Dual input. Build a search from keywords, location and filters, or paste Dice.com URLs directly, both search pages and single job pages.
- Full filter set. Posted-date window, employment type, workplace type (remote/hybrid/on-site), employer type (recruiter vs direct hire), Easy Apply, visa-sponsor willingness, and sort by relevance or date.
- Superset of fields. Structured salary (min, max, currency, period), a skills list, an apply URL or apply email, a company profile link, and a structured street address, on top of every core listing field.
- Forward pagination. Walks the result set on its own, stopping naturally once a page repeats or empties, or once your listing cap is reached.
- Optional detail enrichment. Turn on richer per-job data (full description, skills, apply contact, structured address) only when you need it.
- Built for schedules. Incremental mode returns only new and changed listings on recurring runs, with clear, source-checked rules for when a listing can be marked gone and later reappear.
- Export anywhere. Optionally pipe results into Notion, Linear, Airtable or Apify itself, on top of the usual JSON, CSV or Excel download.
Use Cases
- Recruiters and staffing teams: source active tech openings by skill, location and visa-sponsor willingness.
- Tech job boards and aggregators: mirror Dice.com listings into your own search or alert product.
- Compensation and market research: track posted salary ranges across roles, regions and employment types over time.
- Career sites and newsletters: build a "new today" feed from a saved search using incremental mode.
- Workforce planning teams: monitor recruiter-type or visa-sponsorship-willing postings for a given role or region.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id | 3fa41c9e7d2b4a11 |
guid | 3fa41c9e-7d2b-4a11-9c3d-8b6f1e2a4d7c |
url | https://www.dice.com/job-detail/3fa41c9e-7d2b-4a11-9c3d-8b6f1e2a4d7c |
title | Senior Backend Engineer |
company | Sample Company |
companyProfileUrl | https://www.dice.com/company-profile/9c3d8b6f-1e2a-4d7c-aaaa-111122223333 |
city / state / country | Denver / Colorado / USA |
locationDisplay | Denver, Colorado, USA |
workplaceTypes | ["Hybrid"] |
remote | false |
salary | USD 110,000.00 - 140,000.00 per year |
salaryMin / salaryMax | 110000 / 140000 |
salaryCurrency / salaryPeriod | USD / year |
employmentType | Full-time |
employerType | Recruiter |
easyApply | true |
willingToSponsor | false |
skills | ["Go", "Kubernetes", "PostgreSQL"] |
postedDate / modifiedDate | 2026-02-10T00:00:00Z / 2026-02-12T00:00:00Z |
validThrough | 2026-03-12T00:00:00Z |
applyType | APPLY_TO_EMAIL |
applyUrl / applyEmail | https://careers.example.com/apply/4821 / jobs@example.com |
description | Full HTML job description appears here when Fetch detail pages is on. |
companyReviews | null |
Dice is a job board and does not expose company reviews or star ratings, so companyReviews is always null; company identity still comes through company, companyProfileUrl, and companyLogo. Each record also carries companyLogo and companyProfileId (raw identifiers behind companyProfileUrl), region, street and postalCode (structured address, detail mode only), positionId and score (Dice's own listing id and the search's live relevance rank, which changes on every fetch and is therefore ignored by change detection, see "Resume and recurring updates"), descriptionText (a plain-text copy of description), emails (every email address found on the detail page, including the apply address), workFromHomeAvailability, applicantLocationRequirements, searchQuery/searchLocation/page (which keyword, location and result page produced the row), sourceUrl, and scrapedAt. With incremental mode on, every record additionally carries changeType, changedFields, firstSeenAt and lastSeenAt, described below. validThrough is Dice's own stated expiration date for the posting and is only populated when detail is fetched; a change to it does trigger UPDATED like any other field, but it is never used to decide that a listing is gone (EXPIRED), which this actor's own change tracking only ever infers from whether a scan still finds the listing, not from this date.
How to Use
- Pick a mode:
search(build from keywords, location and filters) orurl(paste Dice.com search or job URLs). - Fill in the fields for that mode, and turn on Fetch detail pages if you want the full description, skills and apply contact.
- Set Max listings (and, optionally, Max pages) to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Basic keyword search:
{"mode": "search","queries": ["python developer"],"countryCode": "US","maxListings": 20}
Search with location and filters:
{"mode": "search","queries": ["engineer"],"location": "Austin, TX, USA","workplaceType": "Remote","postedDate": "SEVEN","sortBy": "date","maxListings": 50}
Search with detail enrichment (full description, skills, apply contact):
{"mode": "search","queries": ["python developer"],"fetchDetails": true,"maxListings": 50}
URL mode (paste a Dice.com search URL):
{"mode": "url","urls": ["https://www.dice.com/jobs?q=java&countryCode=US"],"maxListings": 100}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/dice-com-scraper").call(run_input={"mode": "search", "queries": ["python developer"], "maxListings": 20})for job in client.dataset(run["defaultDatasetId"]).iterate_items():print(job["title"], job["company"], job["salary"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/dice-com-scraper').call({ mode: 'search', queries: ['python developer'], maxListings: 20 });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
There are two different things here, pick the one that matches what you're doing:
| Need | Use |
|---|---|
| A crawl stopped and should continue | resumeFromRunId |
| 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) reads the dedup key (guid) of every listing already saved by a prior run or dataset and skips those listings, so this run only appends what it has not already collected. It works cleanly for a pasted list of individual job URLs: each one is checked against the saved set independently, regardless of order. For a search, or a pasted search URL, be aware that the walk still starts at page 1 (or at the page number already in a pasted URL) on every run, and a page that comes back with zero listings beyond the already-saved set is treated as "the end of results." If a prior run already collected that same page, a plain resume of the same search can stop immediately after refetching page 1, without reaching further, newer listings deeper in the result set. To keep extending a large search-mode pull page by page, paste the search URL in URL mode with a page number that picks up after where the previous run stopped, rather than resuming the bare keyword search. An automatic same-run checkpoint separately reloads what this run has already saved after a platform migration or restart mid-run, with no input needed, so nothing already collected is re-pushed or re-billed; this is unrelated to resumeFromRunId, but it shares the same page-1-looks-like-the-end limitation described above, so a migrated search run can also stop earlier than it would have otherwise. A run also fails fast, before any scraping, if resumeFromRunId points to a run or dataset your account cannot read, or if it is combined with incrementalMode on a search that already has saved incremental state.
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 NEW and UPDATED listings by default, plus REAPPEARED for any listing a previous run had already marked gone. A listing is only ever marked gone at all, and can therefore only ever later show up as REAPPEARED, once some earlier run had emitExpired switched on and completed a full, uncapped scan of the tracked search without finding it; a run that never turns emitExpired on never marks anything gone, so REAPPEARED cannot occur under that history. Duplicates and truly unchanged listings are suppressed by default (and not charged, including the detail-enrichment surcharge). Turn on emitUnchanged or emitExpired only when you also want those rows returned, and billed for. State is isolated automatically per mode/keyword/location/filter/URL and fetchDetails setting; set stateKey to name or deliberately share a monitoring campaign. Raising maxPages, maxListings, or maxNotifyListings never starts a new baseline, those are run-size caps, not part of the tracked search.
Scheduled-run example, same search input ({ "mode": "search", "queries": ["python developer"], "incrementalMode": true }) run daily: on day 1, the first run ever for this search, every listing comes back with "changeType": "NEW". On day 2, with the schedule firing again on the identical input and emitExpired left off, listings whose title, salary, company, description or other tracked field changed come back as "changeType": "UPDATED" with changedFields listing what changed; brand-new postings come back as "changeType": "NEW"; listings that are still there, unchanged, are not returned at all unless emitUnchanged is on. REAPPEARED and EXPIRED never appear on this schedule, because emitExpired was never turned on, so no listing was ever marked gone in the first place.
What counts as "changed"
Change detection compares every real listing field. Two fields are deliberately excluded because they are not real listing data:
scrapedAt, a per-run timestamp stamped fresh on every push. Comparing it would make every re-scrape look "changed."score, the search's live relevance rank. Measured live: fetching the same search page twice a few seconds apart changedscoreon every common listing while every other field stayed identical. It moves with the query and the rest of the index, not with the listing itself.
Everything else, including title, company, salary/salaryMin/salaryMax, description, skills, employmentType, postedDate, modifiedDate, validThrough, easyApply, and the location fields, is real data and does trigger UPDATED when it changes.
Cap semantics under incremental mode
maxListings (and each search's share of it) counts listings scanned, not listings emitted. Outside incremental mode the two numbers are always equal, since nothing is suppressed. Under incremental mode, with most listings UNCHANGED, counting emitted rows instead would make a quiet run walk deeper and deeper pages trying to "backfill" the cap with rows it would just suppress again, so cost would never drop even when nothing changed. Counting scanned listings means a quiet incremental run costs the same, in pages walked, as the equivalent full run, and stays there.
emitExpired only fires after a complete scan of the tracked search, never on a run capped by maxListings, a resumeFromRunId run, or a run that only fetched individual job detail URLs (those never walk a catalogue page, so there is no way to tell "gone" apart from "not reached"). Turning emitExpired on is also what marks a listing gone at all, so it is the only way a later run's REAPPEARED classification can ever fire for that listing. A partial scan leaves previously-tracked listings' state untouched instead of guessing.
Send results into your apps (MCP connectors)
Optionally push each result into the tools you already use, through Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape; the Apify dataset itself is never changed. Authorize a connector once under Apify, Settings, Integrations, then select it in the "Pipe results into your apps" input field. For Notion, also set the Notion parent page. The connector receives a condensed, human-readable summary per item (a title plus its key fields flattened to plain text); the complete record always stays in the Apify dataset. Leave the field empty to skip. Supported connectors include Notion, Linear, Airtable, and Apify.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search builds a query from the fields below; url reads the URLs list. |
queries | array | prefill: ["python developer"] | Free-text keywords. One scrape per keyword. Leave empty to browse without a keyword filter. |
location | string | prefill: (empty) | City + state or country. Leave empty to search nationwide. |
radius | integer | 0 | Distance around the location to include (0 to 500). 0 uses the site default. Ignored when Location is empty. |
radiusUnit | string | mi | mi or km. |
countryCode | string | US | Two-letter country code for the job market. |
postedDate | string | any | any, ONE, THREE, or SEVEN (days). |
employmentType | string | any | FULLTIME, PARTTIME, CONTRACTS, THIRD_PARTY. |
workplaceType | string | any | Remote, Hybrid, On-Site. |
employerType | string | any | Recruiter or Direct Hire. |
easyApply | boolean | false | Only jobs with one-click Easy Apply. |
willingToSponsor | boolean | false | Only jobs whose employer indicates willingness to sponsor a visa. |
sortBy | string | relevance | relevance (site default) or date (newest first). |
urls | array | prefill: sample search URL | One or more Dice.com URLs (search pages or single job pages). Filter fields above are ignored in this mode. |
fetchDetails | boolean | false | Also fetch each job's detail page for the full HTML description, skills, structured address, apply URL/email, valid-through date, and structured salary. A pasted single job URL (URL mode) always fetches and is billed for its detail page regardless of this toggle, since reading that one job is only possible by fetching it. |
maxPages | integer | 0 | Safety bound on result pages per search (roughly 20 listings per page). 0 = unlimited: the walk stops once a page comes back empty or repeats listings already collected, or sooner once Max listings is hit. |
maxListings | integer | 20 | The sole soft cap on the run, across all searches. 0 = unlimited (bounded only by the natural stop on Max pages). |
proxy | object | prefill: Apify Proxy | Connection routing. |
mcpConnectors | array | (none) | Optional MCP connectors to also receive a summary of each result. |
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. |
resumeFromRunId | string | (none) | Optional prior run ID or dataset ID. Listings already saved there are loaded before scraping starts and skipped, so this run only appends new listings. See "Resume and recurring updates" above. |
incrementalMode | boolean | 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 and recurring updates" above. |
stateKey | string | (none) | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. Auto-derived from the search/URL and filter settings when left empty. |
emitUnchanged | boolean | false | Incremental mode only. Also return listings unchanged since the last run, marked UNCHANGED. Adds and bills extra rows you already have. |
emitExpired | boolean | false | Incremental mode only. Also return listings from a previous run no longer found, marked EXPIRED, once a run has fully scanned the search (not capped, not a resume, not detail-URLs-only). This is also what marks a listing gone at all, which is what later allows a REAPPEARED classification. Adds and bills extra synthetic rows. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"id": "3fa41c9e7d2b4a11","guid": "3fa41c9e-7d2b-4a11-9c3d-8b6f1e2a4d7c","url": "https://www.dice.com/job-detail/3fa41c9e-7d2b-4a11-9c3d-8b6f1e2a4d7c","title": "Senior Backend Engineer","company": "Sample Company","companyProfileUrl": "https://www.dice.com/company-profile/9c3d8b6f-1e2a-4d7c-aaaa-111122223333","city": "Denver","state": "Colorado","country": "USA","locationDisplay": "Denver, Colorado, USA","workplaceTypes": ["Hybrid"],"remote": false,"salary": "USD 110,000.00 - 140,000.00 per year","salaryMin": 110000,"salaryMax": 140000,"salaryCurrency": "USD","salaryPeriod": "year","employmentType": "Full-time","employerType": "Recruiter","easyApply": true,"willingToSponsor": false,"skills": ["Go", "Kubernetes", "PostgreSQL"],"positionId": "R-048213","score": 812.4,"postedDate": "2026-02-10T00:00:00Z","modifiedDate": "2026-02-12T00:00:00Z","validThrough": "2026-03-12T00:00:00Z","applyType": "APPLY_TO_EMAIL","applyUrl": null,"applyEmail": "jobs@example.com","emails": ["jobs@example.com"],"summary": "Sample summary text appears here.","description": "<p>Full HTML description appears here when Fetch detail pages is on.</p>","companyReviews": null,"scrapedAt": "2026-02-12T08:00:00.000Z"}
With incrementalMode on, every record additionally carries changeType (NEW/UPDATED/UNCHANGED/REAPPEARED/EXPIRED), changedFields (array, populated only for UPDATED), firstSeenAt, and lastSeenAt, see "Resume and recurring updates" above.
Plan Requirement
The default connection setting works on any Apify plan, and the run automatically retries with a different connection when one is refused. For large or frequent runs, the residential proxy group gives more headroom; pick it under Connection.
FAQ
How much does it cost?
You pay per job listing returned, a smaller surcharge for each listing where detail enrichment actually fetched data, and a small per-run start fee. A pasted single job URL (URL mode) is billed for its detail page every time it succeeds, regardless of the Fetch detail pages toggle, because reading that one job requires fetching it. The Pricing tab shows the current rates. Use Max listings to cap the cost of any run.
Is it legal to scrape Dice.com?
This actor collects only publicly available job listing data. You are responsible for how you use it: follow Dice's terms and the laws that apply to you, and get legal advice before any commercial redistribution, especially since some listings carry a recruiter's personal contact details.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and changed listings for that saved search, and unchanged ones are not billed unless Emit unchanged is on.
Why did Resume return almost nothing on my second run of the same search?
Resume continues one specific interrupted or previous crawl by skipping listings a prior run already saved; it is not a way to poll a search for "what's new" (use Incremental mode for that). It works best for a pasted list of individual job URLs, checked one by one. For a plain search, Resume restarts at page 1 every time and stops as soon as a page returns nothing beyond what was already saved, so if the prior run already covered that page, this run can stop immediately instead of walking further. To keep extending a large search-mode pull, paste the search URL in URL mode with a page number that picks up where the previous run left off.
Why did my run finish with an empty dataset instead of failing?
If every request to Dice.com is refused during a run, the actor logs a warning and still finishes successfully with whatever it collected, which can be zero listings, rather than stopping with an error; "nothing matched your search" and "the site refused every request" can both look like a short, completed run. Check the run log for a warning naming which case happened. A run does stop with an error before any scraping if the connection is set to use no proxy at all (turning off Apify Proxy without supplying your own proxy URLs); an unrecognized proxy group instead falls back to the default connection and the run continues.
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 jobs data?
Pair this actor with these related scrapers from the same team:
| ๐ผ ZipRecruiter Scraper From $1/1K. Scrape ZipRecruiter jobs with titles, companies, salaries, locations... | ๐ผ NIJobs Scraper Scrape NIJobs.com listings across Northern Ireland by keyword, location, salary, posted... |
| ๐ผ Monster Jobs Scraper Scrape Monster.com job listings from search and direct job URLs. Extract full... | ๐ผ s1jobs Scraper Scrape jobs from s1jobs.com across Scotland and the UK. Search by keyword, location, or... |
| ๐ผ HH.ru Jobs Scraper From $1/1K. Scrape HH.ru job listings with 50+ structured fields. Search by filters or... | ๐ผ HiJobs.net Scraper Scrape hijobs.net job listings via the official mobile API: title, employer, salary... |
๐ Browse all abotapi scrapers
๐ฌ Support & custom scrapers
- ๐ 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 abotapi@proton.me or message Telegram @abotapi.
- โญ Enjoying it? A quick review on the actor page helps other users find it.