

# How to Compare LinkedIn and Indeed Job Coverage

Compare LinkedIn and Indeed on one matched test before you choose a source. A matched test uses the same role, the same market, and the same posting window on both sources. A market is the place you choose to search. A posting window is the age range of listings you include. Compare the new useful openings, the fields your work needs, and the source failures. A larger raw file is not better coverage. A claim of wide reach is not a test.

People ask which job source gives the most complete data. That question shows a need to compare sources. It does not show a winner. It does not show market size. The public question is on [Reddit](https://www.reddit.com/r/automation/comments/1nuelm7/which_job_board_gives_the_most_comprehensive_data/).

An Actor is a hosted program that collects data. A Task is a saved input preset for an Actor. The Actor saves source rows. You do the comparison yourself. The Task does not compare sources for you. This method does not rank sources for every user. It does not score pay. It does not rank tools by cost.

A run is one collection with one input. A raw row is one saved listing before review. A source id is the listing number from that source. A useful listing matches your role, market, and window, and it has a known source id. A duplicate is the same listing saved more than once. A repost is the same opening published again. An opportunity is one opening after you group duplicates and reposts. Field coverage is how often a needed field is present in useful listings. Market coverage is which openings the test collected. Those are different facts.

## Take eight actions

1. Write the decision and the fields you need.

Write the choice you must make. For public job-market research, you often need four facts. You need the source name, the role, the place, and a usable description. Declare the role. Declare the market. Declare the language. Declare the posting window. Declare the date and time you collect rows. Declare the inclusion rule. The inclusion rule says which rows count as the role you want.

Pick two sources for the small test. Do not assume they list the same openings. One panel is one role, one market, one window, and one source. Use the same panel rules on both sources. If the role or window changes, the files answer different questions. You cannot compare them fairly.

The current [LinkedIn hiring-trends Task](https://apify.com/chronometrica/linkedin-jobs-scraper/examples/collect-linkedin-job-market-data-for-hiring-trends) is a broad sample. It is not your comparison panel. Its search words are three roles on separate lines: software engineer, data analyst, and product manager. Its startUrls list is empty. startUrls is an optional list of pages to open. saveOnlyUniqueItems is true. That setting keeps one copy of a repeated listing in the save. Replace the three roles with one role you declare. Add one matching location and one window. Leave startUrls empty. This route is a keyword search. Do not replace the search words with known job page addresses.

1. Set budgets and filters you can compare.

Write the search words, the page limit, and the row cap for each source. A cap is the most rows you let that run save. A cap limits what you can see. It is not the number of jobs in the market. If one source hits its cap and the other stops early, the raw counts are not equal evidence. The capped source may hold more matching jobs than you saved. Record the input that actually ran. Record why each run stopped.

Use the input names checked on 13 October 2026. A newer public page can change them. Read the current names before you rely on an old list.

The [LinkedIn Jobs Actor](https://apify.com/chronometrica/linkedin-jobs-scraper) public build 0.2.19 supports searchTerm, location, postedWithin, maxItems, maxPagesPerSearch, saveOnlyUniqueItems, and fetchJobDetails. It also supports the other filters in its current input list. fetchJobDetails true tells the run to try to save the full job text. A small LinkedIn example can use the role software engineer, one market, postedWithin any, maxItems 3, and maxPagesPerSearch 1. Also set saveOnlyUniqueItems true and fetchJobDetails true. The value any is only right when your declared window is the full range that control means.

The [Indeed Jobs Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper) matches Indeed Jobs public build 1.2.14. That build supports searchTerm, country, location, maxItems, maxPagesPerSearch, postedWithinDays, and saveOnlyUniqueItems. It also supports its current optional filters. Use one matching country, location, and role. Do not set a minimum salary above zero. A pay filter hides listings. That changes the coverage count. A small Indeed example can use maxItems 2, maxPagesPerSearch 1, and saveOnlyUniqueItems true.

The 3-row example and the 2-row example are not a matched pair of budgets. They only show that a tiny run is a valid input. For the comparison, set the same maxItems on both sources. Set the same maxPagesPerSearch on both sources. If one source cannot accept that number, write the difference.

LinkedIn uses postedWithin. Indeed uses postedWithinDays. These controls are not the same input. Choose the closest allowed value on each source for your declared window. If the windows still differ, write the mismatch. Do not call the samples equal.

Indeed has a country input. The checked LinkedIn inputs use location, not a separate country field. Set Indeed country and location to the same declared market you put in LinkedIn location. If the place controls still differ, write the difference.

This LinkedIn block is a small input shape. Replace the market token. Replace the role if you declared a different role. SET_YOUR_MARKET is a reminder, not a real place.

```json
{
  "searchTerm": "software engineer",
  "location": "SET_YOUR_MARKET",
  "postedWithin": "any",
  "maxItems": 3,
  "maxPagesPerSearch": 1,
  "saveOnlyUniqueItems": true,
  "fetchJobDetails": true,
  "startUrls": []
}
```

Take eight actions

This Indeed block is a small input shape. SET_THE_CLOSEST_ALLOWED_VALUE is a reminder, not a real Indeed setting. Put the closest allowed day value there. Do not add a minimum salary.

```json
{
  "searchTerm": "software engineer",
  "country": "SET_YOUR_COUNTRY",
  "location": "SET_YOUR_MARKET",
  "postedWithinDays": "SET_THE_CLOSEST_ALLOWED_VALUE",
  "maxItems": 2,
  "maxPagesPerSearch": 1,
  "saveOnlyUniqueItems": true
}
```

Take eight actions

Checked runs of these controls saved four kinds of values. They saved source listing ids that were not empty. They saved source page addresses. They saved collection times. They saved full descriptions. Those runs used different places and different windows. Both stopped at small row caps. That check shows the Actors can save those fields on those inputs. It is not a matched test. It is not a ranking. No real shared job pair was verified. No full coverage number was verified. Reuse these same input contracts for your own matched panel.

1. Collect each source, then sort the rows.

Sort every saved row into one group before you compare sources. Keep these groups apart.

- Useful rows.
- Rows outside the role, market, or window.
- Duplicates inside one saved file.
- Rows with no source id.
- Blocked or failed requests.
- Searches that hit the cap.

Account for each input you planned to run. A refused run can return zero rows. That zero is not proof that no jobs exist. Keep a failure separate from a true empty result. If you do not know a count, leave it unknown. Do not write zero in its place. A mixed zero can make a blocked source look empty.

1. Use the same rule on both samples.

Read the role, the place, and the window the same way for every row. Full description text can decide a hard case. A title alone can be too vague. descriptionStatus full means the full description was saved. It does not mean you invented a short-text label.

If a date is missing and your rule needs a date, leave that row unresolved. If the market is missing and your rule needs a market, leave that row unresolved. The market you sent does not prove every listing is in that place. The listing text can name another place.

Map columns on purpose. Each source uses its own field names. Keep those original names in the raw save. Fill the comparison columns from them. A URL is the page address of a listing.

| Comparison column | What you store |
| --- | --- |
| source and job id | Source name plus its own listing id |
| title | Job title text |
| companyName | Employer name |
| locationRaw | Place text as saved |
| jobUrl | Listing page address |
| descriptionText | Description words |
| descriptionStatus | Whether the description is full |
| scrapedAt | Time you collected the row |
| query | Search words you sent |
| searchLocation | Market you sent |
| run context | Input you ran and why it stopped |

Write the original source field next to each column. Do not rename the raw fields away. LinkedIn postedAt can be calculated from a relative time phrase. A relative time phrase says how long ago the posting appeared. That value is not exact proof of the publish time. Indeed time values keep the meaning that source states. postedWithinDays also keeps that source meaning. Optional fields can be missing. Listing ids can be missing. A row count does not prove you found every opening. A row count does not prove a job is genuine.

1. Compare reviewed opportunities, not raw id digits.

[Group duplicates and reposts](/en/articles/jobs-duplicates-reposts/) inside each source first. Then review possible matches across the two sources. Use those reviewed groups for the comparison. Do not count two saves of one opening as two openings.

A source id is unique only inside that source namespace. A namespace is that source's own set of ids. Never join a LinkedIn job id to an Indeed job id because the digits match. The same digits can belong to different listings.

Keep both source URLs for each pair. A source URL is the page address of that listing. Keep the LinkedIn URL and the Indeed URL. Do not replace one URL with the other.

Compare the employer. Compare the role. A role is the job the listing asks a person to do, not only the title line. Compare the location. Compare the available requisition. A requisition is the employer's opening number when the listing shows one. Compare the full wording. Compare the observation-time context. Observation time is when you saw the listing. The context is what that listing said at that time.

Confirm a pair only when these facts agree. Keep both URLs on each confirmed pair.

A title alone is not confirmation. An id alone is not confirmation. The same employer and a similar title still need this review. They are not an automatic match.

A weak pair stays uncertain. A contradictory pair stays uncertain. A weak pair has missing facts or only some of the same facts. A contradictory pair has facts that do not agree. Different requisitions do not agree. Wording that describes a different opening does not agree. Observation times that show two different openings do not agree. Keep uncertain pairs outside the confirmed overlap.

1. Build confirmed sets and count four groups.

Call the confirmed useful opportunities from the first source set A. Call the second source set B. Count four groups. The union is every confirmed opportunity in the collected test. The intersection is the overlap you confirmed. A-only opportunities appear in A and not in B. B-only opportunities appear in B and not in A. Report uncertain matches as a separate count. Do not force them into the intersection.

The union covers the collected test only. It is not every open role in that market. Do not use capture-recapture on these counts. Capture-recapture guesses a whole population from overlap in two samples. It needs random samples. It needs separate samples. These sources are not random samples. The same job can appear on both for reasons you did not measure. Report the four counts. Do not turn them into a total for the whole market.

1. Compare needed fields inside each useful sample.

Divide the count of [full descriptions](/en/articles/jobs-full-descriptions/) by the count of useful listings with a known source id. That share is field coverage for descriptions. Report missing source ids as their own count. Do not delete them and then speak as if every saved row was useful. A missing id can still be a real row. It is a weak row for matching.

Field coverage is not market coverage. A source can add few new opportunities and still matter. It matters when it supplies a field you require. Keep cost numbers only when you measured them. Keep effort numbers only when you measured them. Do not invent a cheapest source. Do not invent a best source.

1. Choose the mix for this decision only.

Choose from the new confirmed opportunities and the required fields. A source that adds little can still stay in the mix. Keep it when its rows supply a field you cannot get from the other source. If the collection failed, repeat the same declared panel. If the uncertainty is too large to decide, repeat the same panel. Do not repeat only to grow the row count. More rows do not repair a blocked run. More rows do not repair a fuzzy match. Do not call one source best for all roles and markets.

## Work one fictional matched test

The numbers in this section are fiction. They teach the arithmetic. They are not a product result. They are not the checked runs above. Source A and Source B are labels only. This fiction does not say which label is LinkedIn. This fiction does not say which label is Indeed. J1 through J8 are opportunity labels. They are not source ids.

In this fiction, both panels use the same role, market, and window. Source A saves 8 raw rows. Source B saves 7 raw rows. Review leaves 6 useful known-source listings in A. Review leaves 5 useful known-source listings in B. The drop accounts for irrelevant rows and duplicate rows together. This fiction does not split those two reasons. Do not invent the split. Missing source ids are not stated. Leave them unknown. Do not fill them with zero.

| Measure | Source A | Source B |
| --- | --- | --- |
| Raw rows | 8 | 7 |
| Useful listings with a known source id | 6 | 5 |
| Share of raw rows that are useful | 6/8 = 75 percent | 5/7 = 71.4 percent |
| Full descriptions on useful listings | 5 of 6 = 83.3 percent | 4 of 5 = 80 percent |
| Stop | Hit its cap | Ended before its cap |
| Unresolved matches | none in this fiction | none in this fiction |
| Missing source ids | not stated | not stated |
| Split of the unused raw rows | not stated | not stated |

The shares 75 percent and 71.4 percent use this fiction's rule. They are not accuracy scores for a source. Divide 6 by 8 to get 75 percent. Divide 5 by 7 to get 71.4 percent.

The reviewed sets are:

- A = {J1, J2, J3, J4, J5, J6}
- B = {J4, J5, J6, J7, J8}

| Opportunity | In A | In B | Group |
| --- | --- | --- | --- |
| J1 | yes | no | A-only |
| J2 | yes | no | A-only |
| J3 | yes | no | A-only |
| J4 | yes | yes | both |
| J5 | yes | yes | both |
| J6 | yes | yes | both |
| J7 | no | yes | B-only |
| J8 | no | yes | B-only |

The intersection is {J4, J5, J6}. The count is 3. The union is {J1, J2, J3, J4, J5, J6, J7, J8}. The count is 8. A-only is {J1, J2, J3}. The count is 3. B-only is {J7, J8}. The count is 2. Every included match in this fiction is resolved. No pair sits in an uncertain group.

Adding B to A adds 2 confirmed opportunities. Two shares describe that add. They answer different questions. Use the share that matches your question.

- New opportunities divided by A's six: 2/6 = 33.3 percent. Use this share when you ask how much B adds to A.
- B-only opportunities divided by the union of eight: 2/8 = 25 percent. Use this share when you ask what part of the collected union comes only from B.

Do not swap these shares. The first share uses A's useful set as the base. The second share uses the collected union as the base.

Source A has full descriptions on 5 of 6 useful listings. Divide 5 by 6 to get 83.3 percent. Source B has full descriptions on 4 of 5 useful listings. Divide 4 by 5 to get 80 percent. These are field shares. They do not measure the whole market.

In this fiction, A hit its cap. B ended before its cap. Neither set is a full count of open jobs. The A-only count can rise if a later run uses a higher cap and the same rules. The fictional choice can be A as the main source, plus B for the 2 added opportunities. That choice fits only this declared test. It is not a universal rank. Show the fields, the sets, the stops, and the unknown counts.

Publication dates are not given in this fiction. A blank date is not day zero. If your window rule needs a date, that row stays unresolved. This fiction does not have that unresolved row. A real uncertain pair needs its own count before you choose.

## Next action

Run one matched panel for your role, market, and window. Choose the mix from new confirmed jobs and the fields you need. Use the [LinkedIn Jobs Actor](https://apify.com/chronometrica/linkedin-jobs-scraper) and the [hiring-trends Task](https://apify.com/chronometrica/linkedin-jobs-scraper/examples/collect-linkedin-job-market-data-for-hiring-trends). Pair that run with the [Indeed Jobs Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper). Replace the three sample roles with one role. Keep startUrls empty. Map the two window controls, or write down any mismatch.

## Sources

- [LinkedIn Jobs Actor](https://apify.com/chronometrica/linkedin-jobs-scraper)
- [LinkedIn hiring-trends Task](https://apify.com/chronometrica/linkedin-jobs-scraper/examples/collect-linkedin-job-market-data-for-hiring-trends)
- [Indeed Jobs Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper)
- [Public question on which job source gives more complete data](https://www.reddit.com/r/automation/comments/1nuelm7/which_job_board_gives_the_most_comprehensive_data/)