ConstructionJobs.com Scraper — Full Board avatar

ConstructionJobs.com Scraper — Full Board

Pricing

from $2.00 / 1,000 jobs

Go to Apify Store
ConstructionJobs.com Scraper — Full Board

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

Piquno

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 days ago

Last modified

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 description field holds raw HTML with unescaped double quotes (<div style="margin-left:auto;">), which terminates the JSON string early.
  • PostalAddress carries 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.

ModeReachesUse for
Full board (sitemap)all 2,837 jobscomplete extracts, market analysis
Recent only (paged)newest ~714cheap 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

FieldFill
jobId, title, employerName, locationCity, datePosted100% across all test fixtures
jobFunction, educationLevel, experienceLevel, vacancieshigh
salaryMin / salaryMaxa minority — most construction postings withhold pay
description100%, 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

EventPrice
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 withSalaryOnly if 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.
  • experienceLevel is 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 install
npm 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.