ConstructionJobs.com Scraper — Full Board
Pricing
from $2.00 / 1,000 jobs
ConstructionJobs.com Scraper — Full Board
Scrape all 2,800+ jobs on ConstructionJobs.com, not the 714 the paged listing exposes. Salary, benefits, trade category and employer profile links. HTTP-only, no browser.
Pricing
from $2.00 / 1,000 jobs
Rating
0.0
(0)
Developer
Piquno
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Share
Scrape all 2,800+ live jobs on ConstructionJobs.com, the main US construction-trades board. Superintendents, estimators, project managers, skilled trades, equipment operators.
HTTP-only, no browser. 28 flat fields per job, ready for CSV or Excel.
$2.00 per 1,000 jobs. No charge for empty rows.
Why this one
Nothing else on the Store covers this site. Four searches for it returned only unrelated boards.
More importantly, the obvious way to scrape it is broken, and quietly so.
The paged listing hides 75% of the board
/jobs?page=N clamps at page 34 and then re-serves that same page forever. Verified: page 35 returned a byte-for-byte identical body to page 34, with all 22 URLs the same.
A page-loop scraper therefore collects roughly 714 of 2,837 jobs, then spins on duplicates without ever terminating — and reports success the whole time. This actor seeds from the sitemap instead and reaches the entire board. The paged listing is offered only as a deliberate "recent only" mode for cheap daily watches.
The site's structured data does not parse
Every job page carries a JSON-LD JobPosting block, and JSON.parse throws on 100% of them, for two independent reasons:
- The
descriptionfield holds raw HTML with unescaped double quotes (<div style="margin-left:auto;">), which terminates the JSON string early. PostalAddresscarries a trailing comma before its closing brace.
This actor repairs both and recovers the complete record, description included.
Its salary figure is wrong, so we don't use it
The JSON-LD baseSalary has been observed as minValue: 0, maxValue: 0 on postings that display a real range, and elsewhere as min == max with the wrong period. Salary is read from the page's own Salary block instead — scoped to the posting, because the page also renders a "Related Jobs" rail containing other jobs' pay. A body-wide match silently attaches a different job's salary, and there's a regression test for exactly that.
Input
{"seedMode": "sitemap","maxJobs": 500,"titleContains": "superintendent","locationState": "Texas"}
Defaults: maxJobs is 50, a quick sample that finishes in about 80 seconds. Each job is one request at roughly one per second, so raise it for a full extract and give the run enough time.
| Mode | Reaches | Use for |
|---|---|---|
| Full board (sitemap) | all 2,837 jobs | complete extracts, market analysis |
| Recent only (paged) | newest ~714 | cheap daily monitoring |
Each job is one request, so maxJobs is also your request count.
Monitoring the board
Set incremental: true with seedMode: "recent" and its own stateKey. Only postings not seen before are emitted, tagged changeType: "new".
Output
{"jobId": "14305","title": "Yard Truck Driver - Yard Jockey","normalizedJobTitle": "CDL Driver, Yard Associate","jobFunction": "Equipment & Fleet, Warehouse & Distribution","employmentType": "FULL_TIME","employerName": "48forty Solutions and Relogistics Services","employerProfileUrl": "https://constructionjobs.com/employer/48forty-solutions-and-relogistics-services","locationCity": "Fontana","locationState": "California","locationPostalCode": "92335","salaryMin": 30,"salaryMax": 30,"salaryPeriod": "hourly","educationLevel": "Any","experienceLevel": "N/A","vacancies": 1,"datePosted": "2026-07-09T17:02:26.000Z","validThrough": "2026-09-21T00:00:00.000Z","fieldsPopulated": 27}
employerSlug and employerProfileUrl let you join postings to the site's 16,589 employer profiles.
Field fill rates
| Field | Fill |
|---|---|
jobId, title, employerName, locationCity, datePosted | 100% across all test fixtures |
jobFunction, educationLevel, experienceLevel, vacancies | high |
salaryMin / salaryMax | a minority — most construction postings withhold pay |
description | 100%, recovered from the malformed JSON-LD |
Nothing is inferred. A posting whose own block reads "Salary Not Specified" returns null, not a number borrowed from elsewhere on the page.
Pricing
| Event | Price |
|---|---|
| Job | $0.002 ($2.00 / 1,000) |
| Actor start | $0.00005 |
A row is billed only if it has a job id or title, and it is billed before it is pushed — so you are never charged for a row you did not receive. Rows removed by your filters are never billed.
Limitations — read before you buy
- One request per job. There is no bulk endpoint; a full-board extract is ~2,800 requests. Budget the time.
- Salary is often absent. Use
withSalaryOnlyif you need it, and expect a much smaller result set. - Employer contact details are not available. Phone and email sit behind a login wall on employer profiles and are simply not in the HTML. This actor does not promise them.
experienceLevelis frequently "N/A" — that is what the employer entered, not a parsing failure.- Format risk. If the site fixes its JSON-LD, the repair path falls through to the standard parser automatically. If the structure changes more deeply, the actor logs and skips rather than emitting partial rows.
Running locally
npm installnpm test
Tests run against captured live pages in test/fixtures/ — no network required. They assert the things that actually break this scraper: that the sitemap yields 2,800+ jobs, that malformed JSON-LD repairs on every fixture, that a trailing-comma fix alone is not sufficient, and that salary is never borrowed from the Related Jobs rail.