ScreenshotNeo

BlogHow-to

How to Extract Job Listings, Details, and Salaries from Indeed

A careful workflow for recording Indeed listings, job details, and pay while distinguishing employer compensation from Indeed estimates.

By the ScreenshotNeo team1 October 20269 min read

Short answer: Use Indeed’s search and filters to find relevant roles, open each listing, and record the title, employer, location, duties, URL, date checked, and compensation exactly as displayed. Mark whether pay is employer-provided or an Indeed Estimate. Recheck the listing and employer before applying because listings, filters, and user-submitted information can change.

This guide treats “extract” as collecting information for your own job search. It does not provide instructions for high-volume scraping, bypassing access controls, or building a permanent commercial database of Indeed content. Indeed’s developer terms govern API use, scraping, copying, and rate limits; use an approved integration only when current documentation authorizes it.

1. Search Indeed and narrow the results

  1. Enter the role, skills, or title in Indeed’s keyword field.
  2. Enter a city, region, remote option, or postal code in the location field.
  3. Apply useful filters such as employment type, date posted, salary, remote status, or experience level.
  4. Open each promising result instead of relying only on the result-card summary.

Filters are useful for triage, but Indeed warns that they may not perfectly reflect the advertisement. Some filters can also be influenced by profile or activity. Treat a filter or badge as a search aid, not proof that the listing contains a particular condition.

2. Capture a consistent record for every listing

Use one row per listing. A spreadsheet is sufficient for a personal search. Record the following fields:

Field What to record Why it matters
Title The exact displayed job title Titles that look similar may describe different responsibilities.
Employer Company name as shown on the listing Lets you verify the employer and compare duplicate postings.
Location City, region, remote wording, and travel requirements Pay and duties depend heavily on location and work arrangement.
Job URL The listing URL Listings can change or disappear; preserve the source you checked.
Date checked Date and, if useful, time zone Creates a timestamp for later comparison.
Duties A concise summary or selected requirements Enables meaningful role comparisons.
Work type Full-time, part-time, contract, temporary, internship, or other wording Prevents comparing unlike jobs.
Schedule Shift, weekend, on-call, or flexible requirements when disclosed Schedule can materially change the value of an offer.
Benefits Benefits and variable compensation explicitly disclosed Do not infer benefits that are not stated.
Pay text The complete compensation wording Preserves ranges, qualifiers, currency, and pay period.
Pay source Employer-provided or Indeed Estimate These are different kinds of evidence.
Verification notes Company-page details, source site, and questions to ask Supports fraud checks and follow-up.

Copy compensation verbatim before creating any normalized comparison column. Preserve wording such as “from,” “up to,” “plus tips,” “bonus eligible,” hourly versus annual periods, and the displayed currency.

3. Understand salary and pay estimates

Indeed may show compensation in search results, a job description, or a company page. Employers can provide an exact amount, a range, a starting amount, or a maximum amount.

Employer-provided compensation

Label the value employer_provided when the listing presents it as compensation supplied by the employer. Store the original text and, separately, any numeric values you extract. A range such as “$80,000–$105,000 a year” must remain a range; do not replace it with the midpoint.

Indeed Estimates

When an employer has not supplied a salary, Indeed may display an estimate based on similar roles and locations. Indeed’s Terms of Service state that estimated salary is for informational purposes only. Estimates are approximate, can change, and may not reflect specialized skills, benefits, or regional demand. Confirm the actual compensation with the employer.

Use a separate field such as indeed_estimate. Never present an estimate as an employer promise or confirmed offer.

pay_displayed: "$32 - $41 an hour"
pay_currency: "USD"
pay_min: 32
pay_max: 41
pay_period: "hour"
pay_qualifier: "range"
pay_source: "employer_provided"
pay_notes: "Plus tips; wording copied from listing"

If the listing says “up to $120,000,” set the qualifier to up_to and leave the minimum empty. If it says “from $25 an hour,” set the qualifier to from. If no period or currency is shown, leave those fields unknown rather than guessing.

4. Verify the employer and listing

  1. Open the employer details and company page available from the listing.
  2. Check whether the employer has a verifiable public presence and professional contact information.
  3. Look for the original company careers page or another source named in the listing.
  4. Compare the title, location, duties, and pay wording across sources.
  5. Ask the employer to confirm compensation, work location, schedule, and benefits before relying on the data.

Indeed aggregates listings from career sites, other job boards, and postings made directly on Indeed. A listing may therefore originate outside Indeed. Indeed also does not guarantee that user-submitted content is accurate, complete, legitimate, or continuously available.

Be cautious if a posting requests payment, promises an immediate offer without an interview, or advertises unusually high pay without credible duties. Validate the employer independently.

5. Use a spreadsheet or CSV for a personal comparison

A simple CSV keeps the process portable. Create indeed_jobs.csv with a header like this, then add one row per listing:

checked_at,title,employer,location,url,pay_displayed,pay_source,pay_period,work_type,notes
2026-10-01,Backend Engineer,Example Co,"Austin, TX","https://example.com/job", "$120,000-$145,000 a year",employer_provided,year,full-time,"Remote two days; copied from listing"

Python: validate and summarize your CSV

import csv
from collections import Counter
from pathlib import Path

path = Path("indeed_jobs.csv")
required = {
    "checked_at", "title", "employer", "location", "url",
    "pay_displayed", "pay_source", "pay_period"
}

with path.open(newline="", encoding="utf-8-sig") as f:
    rows = list(csv.DictReader(f))

if not rows:
    raise SystemExit("The CSV has no listing rows.")

missing = required - set(rows[0])
if missing:
    raise SystemExit(f"Missing columns: {', '.join(sorted(missing))}")

for number, row in enumerate(rows, start=2):
    for field in ("title", "employer", "url", "pay_displayed", "pay_source"):
        if not row[field].strip():
            print(f"Row {number}: missing {field}")
    if row["pay_source"] not in {"employer_provided", "indeed_estimate", "not_listed"}:
        print(f"Row {number}: review pay_source={row['pay_source']!r}")

print(f"Listings: {len(rows)}")
print("Pay sources:")
for source, count in Counter(r["pay_source"] for r in rows).items():
    print(f"  {source}: {count}")

This script validates your own notes. It does not request pages from Indeed or bypass its access controls.

Node.js: produce a JSON comparison file

import { readFile, writeFile } from "node:fs/promises";

const text = await readFile("indeed_jobs.csv", "utf8");
const lines = text.trim().split(/\r?\n/);
const headers = lines.shift().split(",");

// This compact parser expects values without commas inside quoted fields.
// For production CSV files, use a maintained CSV parser and preserve quotes.
const jobs = lines.map((line) => {
  const values = line.split(",");
  return Object.fromEntries(headers.map((header, i) => [header, values[i] ?? ""]));
});

await writeFile("indeed_jobs.json", JSON.stringify(jobs, null, 2));
console.log(`Wrote ${jobs.length} listings to indeed_jobs.json`);

If your notes contain commas inside quoted fields, use a CSV library rather than the compact example above. Keep the original pay text even after parsing numeric values.

6. Why a general-purpose Indeed scraper is the wrong default

The reviewed Indeed documentation does not establish a public, general-purpose job-seeker extraction API. Indeed’s developer agreement restricts scraping, permanent database copies, unapproved uses, and bypassing API limits, subject to documented integration needs and additional terms. Check the current agreement and API documentation before building any integration.

For a personal search, manual recording or a small, terms-compliant workflow is the safer interpretation. Do not automate around CAPTCHA or bot checks, rotate identities, defeat rate limits, or collect job-seeker or user content into a permanent database.

7. Or skip the browser setup

If your authorized workflow needs a clean visual record of a listing or company page, ScreenshotNeo provides a website screenshot API. It accepts one GET request and returns PNG, JPEG, WebP, or PDF. Before capture, it can accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Only clean shots are billed: bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers.

Use it only for pages you are authorized to access and in line with Indeed’s current terms. See the ScreenshotNeo API documentation for all options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.indeed.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://www.indeed.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.indeed.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

You can also capture one element by CSS selector, use full-page capture with lazy images loaded, select a device or viewport, set a wait condition, provide headers or cookies when authorized, block selected resources, cache with a chosen TTL, create PDFs, submit asynchronous jobs with signed webhooks, capture up to 100 URLs per bulk call, and inspect usage through the usage API. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

There is a free tier of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan. Create a free ScreenshotNeo account.

8. Troubleshooting

Problem Likely cause Fix
The filter shows a salary, but the listing does not. Filters can be imperfect or based on profile/activity signals. Open the listing and record only the pay wording actually shown there.
A salary has no currency or period. The source omitted a necessary qualifier. Leave the field unknown and ask the employer; do not infer annual or hourly pay.
The listing disappeared. Listings are mutable and may expire or be removed. Keep the URL and date checked, then look for the employer’s original posting.
An estimate looks higher or lower than expected. Estimates use similar roles and locations and may miss specialization, benefits, or regional demand. Label it as an Indeed Estimate and confirm compensation with the employer.
Two rows appear to be the same job. The same role may be syndicated from multiple sources. Compare employer, location, duties, URL, and date; retain source URLs in notes.
The CSV summary is wrong. Quoted commas or inconsistent headers broke parsing. Use a standards-compliant CSV parser and keep the raw export unchanged.
A ScreenshotNeo response is not an image. The page may have failed, timed out, shown a bot check, or returned another verdict. Inspect X-Page-Verdict and X-Billed, then adjust waits, headers, cookies, or the authorized target.

9. Performance, reliability, and cost considerations

  • Manual reliability: Record the date checked and preserve original wording because listings and pay can change.
  • Comparison quality: Normalize only after preserving raw text. Compare amount, pay period, location, duties, experience, work type, schedule, benefits, and whether the value is posted or estimated.
  • Small personal datasets: A spreadsheet or CSV is usually easier to audit than an automated collector.
  • Screenshot costs: ScreenshotNeo bills only clean shots; failed loads, bot checks, blank pages, timeouts, and cache hits are not billed. Choose a cache TTL when repeated captures are authorized.
  • Latency: For dynamic pages, wait for a selector, a delay, or network idle. Full-page captures may take longer when lazy images must load.
  • Scale: Bulk capture supports up to 100 URLs per call, while asynchronous jobs and signed webhooks help separate long captures from your request process.
  • Access: Do not use screenshots or automation to evade Indeed restrictions. Authorization and current terms remain the controlling requirements.

10. A final checklist

  • Search by role and location.
  • Apply filters, then verify each listing directly.
  • Record title, employer, location, duties, URL, work type, schedule, benefits, and date checked.
  • Copy pay exactly, including currency, range, period, and qualifiers.
  • Label employer-provided pay separately from an Indeed Estimate.
  • Verify the employer and original source before applying.
  • Ask the employer to confirm compensation and job details.
  • Keep the workflow personal and terms-compliant.

FAQ

Can I treat an Indeed Estimate as the offered salary?

No. It is an approximate informational estimate and should be confirmed with the employer.

Should I convert hourly pay to an annual salary?

Keep the displayed period as the primary value. If you calculate an annual equivalent for planning, label the assumptions and retain the original hourly wording.

Does Indeed provide a public job-seeker scraping API?

The reviewed material does not establish one. Follow the current developer agreement and documentation for any authorized integration.

What should I do when a listing has no pay?

Record not_listed, preserve the absence of pay in your notes, and ask the employer during the hiring process.

Can I save screenshots of listings?

Only when you are authorized to access and retain the page, and in compliance with Indeed’s current terms. A screenshot does not replace recording the source URL and date.