
Relevant context means the words around the term show that the text is about that skill. A stray letter is not relevant context. An Actor is a hosted data collection program. A Task is a saved input preset. The Actor collects source rows. You mark the skill terms.
1. Define the panel first
Write the panel rules before you mark any skill. A panel is the group of listings for one role, one market, and one date rule. Write the date rule in the panel note. A date rule is the time window you accept.
Name the number you will report before you code. You can report how many listings have a skill use. You can report how many employers have that use. You can report how many times the words appear. These are three different numbers. Do not mix them.
A share is one count divided by a bottom number. The denominator is that bottom number. You will set it after you know which listings have full text. Do not code terms before you name the share.
For an old month, use description snapshots you already saved. The Actor cannot collect 2020 or 2021 descriptions. A new run can start a new panel only. It cannot rebuild an old panel.
2. Collect and save the source rows
Use the Indeed Jobs Scraper Actor. Public build 1.2.14 takes these fields: searchTerm, country, location, maxItems, maxPagesPerSearch, postedWithinDays, sortBy, and saveOnlyUniqueItems. The saved Task was checked on 13 October 2026. A Task is only a starting input. It does not do this count for you.
The saved Task input is:
{
"country": "us",
"searchTerm": "software engineer",
"maxItems": 100,
"postedWithinDays": "7",
"sortBy": "date",
"maxPagesPerSearch": 2,
"saveOnlyUniqueItems": true
}This Task sets the country to us and the search term to software engineer. It does not set a city. Add location only when your market is smaller than the country. Replace the role and the market when you study something else. Keep one role and one market in the run.
The cap is the maxItems limit. This Task sets the cap at 100 rows and reads up to 2 search pages. That broad cap is not the first text check. For the first check, keep one role and one market. Set maxItems to 2. Set maxPagesPerSearch to 1. A first-check input is:
{
"country": "us",
"searchTerm": "software engineer",
"maxItems": 2,
"postedWithinDays": "7",
"sortBy": "date",
"maxPagesPerSearch": 1,
"saveOnlyUniqueItems": true
}A capped run on this build returned two rows. Each row had a source job ID and a source URL. Each row had full description text. The run reached maxItems. Copy the IDs and URLs from your own output. Do not type new IDs. This run shows that text collection works. It is not a skill result. It is not the fictional panel below.
The saved Task sets saveOnlyUniqueItems to true. That setting can reduce repeats when the Actor collects rows. It does not finish a duplicate review. A repost is the same opening posted again. Before you code, remove reviewed duplicate rows and reviewed repost rows. Use the duplicate and repost guide for that review. Keep the removed count in your notes.
Each source row is one collected record. A listing is a row you keep for the count. Store jobId as text. Do not let a spreadsheet turn jobId into a number.
Use these output fields. A run counter is one number for the whole run. It is not a listing row.
| Output name | Kind | How to use it |
|---|---|---|
| jobId | Row field | Store it as text. |
| title | Row field | Keep it. Do not treat the title as the full requirement. |
| companyName | Row field | Keep it for the employer count. |
| locationRaw | Row field | Keep the source location text. |
| descriptionText | Row field | Code skills from this text. |
| descriptionStatus | Row field | Use full or missing only. |
| jobUrl | Row field | Keep the source URL. |
| query | Row field | Keep the search text for the row. |
| searchLocation | Row field | Keep the search location for the row. |
| scrapedAt | Row field | This is the collection time. |
| seniority | Optional row field | It can be missing. It is not the full requirement. |
| employmentType | Optional row field | It can be missing. |
| salaryRaw | Optional row field | It can be missing. Missing is not a salary of zero. |
| postedAt | Optional row field | It can be missing. Do not invent a posting month. |
| postedAtRaw | Optional row field | It can be missing. |
| rowsExcludedByCompany | Run counter | Keep this count by itself. |
| rowsExcludedByDate | Run counter | Keep this count by itself. |
| rowsExcludedBySalary | Run counter | Keep this count by itself. |
descriptionStatus is full when descriptionText is there. It is missing when the text is not there. Do not add a snippet status. Full text here is the text the source gave. It is not a promise of the employer's whole document.
scrapedAt is the time the Actor collected the row. It is not the month the employer posted the job. If postedAt is missing, leave the posting month unknown.
rowsExcludedByCompany is the run count of rows left out for a company reason. rowsExcludedByDate is the run count of rows left out for a date reason. rowsExcludedBySalary is the run count of rows left out for a salary reason. Do not add these three counts into one total. Do not turn the counters into listing rows. This control gives each count. None of the three is omitted. rowsExcludedByCompany is 0. rowsExcludedByDate is 0. rowsExcludedBySalary is 0. Do not write zero for an omitted counter. These three counters are present, so each zero is the count. The run map lists each field and its value. A field that this control does not give is unknown. A future field that is absent is unknown. The current two-row capped Indeed control has these output values: requestedMaxItems: 2, candidateItemsSeen: 100, jobsSaved: 2, pagesParsed: 1, blockedPages: 0, failedRequests: 0, stopReason: "max_items_reached", rowsExcludedByCompany: 0, rowsExcludedByDate: 0, rowsExcludedBySalary: 0. This run is not unknown. Future absent fields remain unknown.
Save the input that ran, the raw rows, and the output. Write the maxItems you set and the number of rows returned. If the two numbers are equal, the run reached the cap. A run at the cap is not every matching job. If the run returns no rows, write that the run was empty. An empty run does not prove that no jobs exist. If the run fails, save the failure note. Do not fill a failed run with skill zeros.
Keep a row with missing descriptionText outside the coded set. Add 1 to the gap count for each such row. A gap is not a listing where the skill is absent.
The Actor gives current public rows. It does not give old description text. It does not give employer edit times. It does not give applicant status. It does not give a learning plan. It does not label skill terms. You label them with the rules below.
3. Write the skill list before you match
Write a skill list before you search the text. The skill key is the one name you store for a skill. An alias is another way to write that same skill. A token is a whole word or a standalone symbol. It is not a piece of a longer word. An exclusion is a match you refuse.
Ignore letter case when you match SQL, Structured Query Language, and the other keys below. Let SQL and Structured Query Language share one key when the words mean that language. Keep Java separate from JavaScript. Match R only as a whole skill token in relevant text. Do not match the letter R inside another word.
A version label, such as a language version, needs a written rule. A product name needs a written rule. Decide the rule before you code. Do not guess while you read.
This skill list is for the fictional panel in this article. It is not a list supplied by the Actor. Your own panel needs your own keys.
| Skill key | Aliases allowed | Token rule | Exclusions |
|---|---|---|---|
| SQL | SQL; Structured Query Language | The two aliases share this key when the text means the language. | Do not use this key for a different phrase. |
| Python | Python | Match the whole word. | Do not code a not-required phrase as required. |
| R | R | Match a whole token in relevant skill text. | Do not match the letter inside a word. |
| Java | Java | Match the whole word. | Do not match JavaScript. |
| JavaScript | JavaScript | Match the whole word. | Do not count Java as JavaScript. |
This list has no version rule and no product-name rule. The fictional texts have no version and no product name. If your text has either, add the rule to the list first.
4. Code each passage
Find the passage that contains the skill name. Then read the words around it. A class is the kind of mention. Use these classes:
- Required means the text says the applicant must have the skill.
- Preferred means the text asks for the skill but does not say the applicant must have it.
- Context means the text says the work uses the skill, but it does not require it from the applicant.
- Explicitly not required means the text says the applicant does not need the skill.
- Unclear means you cannot tell the class from the words.
A lexical mention means the skill name appears in the text. Keep the lexical mention even when the class is not required. Do not drop the sentence.
The fictional panel later contains the words "no Python experience required." Those words are a lexical mention of Python. The class is explicitly not required. The class is not required.
The same panel contains the words "we use R for reports." Those words are a lexical mention of R. The class is context. Do not mark R required unless the text also says the applicant must have R.
The letter r inside "reports" is not the skill R. The letter r inside "required" is not the skill R.
If one listing has two classes for one skill, mark each class. If the same class repeats in one listing, mark that class once.
5. Build the listing matrix
A term-by-listing matrix is a table with one row for each coded listing. Each skill class has its own column. Put 1 when the full text has that class. Put 0 when you read the full text and that class is absent. A 0 means you read the text. It does not mean the text was missing.
Repeated mentions of one key in one listing still make one listing mark in that class. Listing presence means that 1. Keep a raw occurrence count in a separate table when you need it. A raw occurrence is one appearance of the key. Do not add raw occurrences into the listing column.
A listing can have 1 for preferred and 0 for required. A listing can have 1 for explicitly not required and 0 for required. A listing can have 1 for context and 0 for required. Do not merge those columns.
Do not put a missing description in the matrix as a row of zeros. Keep it in the gap count.
6. Set the denominator
The denominator is the number of distinct full-text listings in the same panel. Distinct means you count the same listing once. Count only listings that remain after the duplicate review. Count only listings with descriptionStatus full.
The listing share equals the class count divided by the denominator. Write the share as a count out of the denominator. Report the gap count beside it. Report the uncertain-ID count beside it. An uncertain ID is blank, changed, or in conflict with another row. Keep an uncertain ID out of the denominator.
Report coding uncertainty too. Coding uncertainty means reviewers do not agree on the class. Keep a disputed listing out of the required count until the record states the agreed class. Do not turn a dispute into a quiet zero.
Employer breadth is the number of different employers with that class. Divide that employer count by the number of different employers in the denominator. One employer can write many listings. A high listing share can still be a narrow employer share. Report both shares.
These shares describe your panel only. They are not national skill demand. They are not a measure of what applicants can do.
7. Compare panels with the same rules
Compare two months only when the panel rules match. Keep the same role, market, query, date window, cap, and skill-list version. A skill-list version is the name you give to one saved skill list.
If you change an alias or a class rule, the share can change while the job text stays the same. That change is a coding change. Report it before you read the new share.
If the cap, query, market, or date window changes, the sample changed. Missing source text is also a coverage change. Report the sample change before you read the share change. Do not treat a coding change as a sample change.
A search sample does not show the cause of a change. It does not show that the whole market grew. A change in a skill share is not a change in the number of listings. For listing counts over time, use the hiring-trend guide.
8. Save the record
Save the skill list and its version name. Save each passage you coded, with the listing ID, the skill key, and the class. Save the matrix. Save the denominator record.
The denominator record should include:
- The role, market, query, date window, and cap.
- The input that ran and the Actor build.
- The row count returned, and whether the run reached maxItems.
- Any failure note.
- rowsExcludedByCompany, rowsExcludedByDate, and rowsExcludedBySalary, or unknown for each omitted counter.
- The duplicate rows you removed.
- The gap count.
- The uncertain-ID count.
- The coding-uncertainty count.
- The denominator.
- The employer count in that denominator.
A second person should be able to start from the saved passage and reach the same matrix cell. If they cannot, the record is not complete.
Stop at the term count. A repeated duty can guide a later learning choice. That choice is not this count. This count is also not a personal readiness score.
Count a fictional panel
All five descriptions in this section are fiction. They are not Actor results. They are not a real skill finding. J1 through J5 are labels. They are not real source IDs. The skill list above is the rule for this panel.
| Label | Employer | descriptionText | descriptionStatus |
|---|---|---|---|
| J1 | A | SQL required. SQL reports run daily. Python preferred. | full |
| J2 | A | Structured Query Language required; no Python experience required. | full |
| J3 | B | JavaScript required; we use R for reports. | full |
| J4 | C | Java required; R preferred. | full |
| J5 | D | `""` | missing |
These labels have no scrapedAt values and no postedAt values. Do not assign a posting month to them.
J1 and J2 use employer A. Their texts differ. This fiction keeps both listings. It does not call J2 a repost of J1. If a real review removed J2 as a repost, the denominator would change. This fiction does not make that call.
J5 has a known label and no full text. J5 stays out of the matrix. The gap count is 1. The uncertain-ID count is 0 because the label is known and the text is the gap. Do not invent text for J5.
The passage record is:
| Listing | Passage | Skill key | Class |
|---|---|---|---|
| J1 | SQL required. | SQL | required |
| J1 | SQL reports run daily. | SQL | context |
| J1 | Python preferred. | Python | preferred |
| J2 | Structured Query Language required | SQL | required |
| J2 | no Python experience required | Python | explicitly not required |
| J3 | JavaScript required | JavaScript | required |
| J3 | we use R for reports | R | context |
| J4 | Java required | Java | required |
| J4 | R preferred | R | preferred |
| J5 | No full text | none | Not coded |
An occurrence is one time the skill text appears. Presence is one listing mark for one class. Mark each class once for a listing. A second sentence with the same key and the same class adds one raw occurrence. It does not add a second presence. A second sentence with the same key and a different class gets its own presence. Do not drop that class because the key also has required text.
The first SQL sentence in J1 is required. It is one required occurrence. The required presence for J1 is 1. The sentence "SQL reports run daily." is context. It is one context occurrence. The context presence for J1 is 1. J2 counts as SQL because Structured Query Language is an allowed alias. The words mean the language, and the sentence says it is required. That alias is one required occurrence. The required presence for J2 is 1.
The matrix uses 1 and 0 only for the four full texts. J5 is not a row here.
| Listing | Employer | SQL required | SQL context | Python required | Python preferred | Python explicitly not required | R required | R context | R preferred | Java required | JavaScript required |
|---|---|---|---|---|---|---|---|---|---|---|---|
| J1 | A | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| J2 | A | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| J3 | B | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
| J4 | C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
| Listing count | `""` | 2 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 1 |
Each 0 in J1 through J4 means the full text was read and that class was absent. No coded passage is unclear, so unclear is 0 of 4. That zero follows a reading. It is not the J5 gap. No class is in dispute, so the coding-uncertainty count is 0.
The raw counts stay in a separate table.
| Listing | Skill key | Class | Raw occurrences |
|---|---|---|---|
| J1 | SQL | required | 1 |
| J1 | SQL | context | 1 |
| J2 | SQL | required | 1 |
| J2 | SQL | context | 0 |
| J1 | Python | preferred | 1 |
| J2 | Python | explicitly not required | 1 |
| J2 | Python | required | 0 |
| J3 | R | context | 1 |
| J4 | R | preferred | 1 |
| J3 | R | required | 0 |
| J4 | R | required | 0 |
| J3 | Java | required | 0 |
| J4 | Java | required | 1 |
| J3 | JavaScript | required | 1 |
| J4 | JavaScript | required | 0 |
Four valid full-text listings form the denominator. The listings are J1, J2, J3, and J4. The employers in the denominator are A, B, and C. That employer total is 3. Employer D is outside it because J5 has no full text.
J1 has 2 raw SQL occurrences. The required class has 1. The context class has 1. J2 has 1 raw SQL occurrence, and that class is required. The raw count for SQL required is 1 plus 1, which equals 2. The raw count for SQL context is 1. 2 plus 1 equals 3 raw SQL occurrences. The total lexical SQL count stays 3. The raw required total is 2. The required listing count is also 2. The same number does not make the two measures one measure. The context occurrence is outside the required raw total. The SQL required listing count is 2. The SQL required share equals 2 divided by 4. It does not equal 3 divided by 4. It does not equal 2 divided by 5. The extra raw mention is context. It is not an extra listing. J5 does not enter the bottom number.
The SQL context listing count is 1. The SQL context share equals 1 divided by 4. Do not add the 1 context listing to the 2 required listings. J1 would count twice. The classes stay separate.
Both SQL required listings belong to employer A. The SQL required employer share equals 1 divided by 3. The one SQL context listing belongs to employer A. The SQL context employer share equals 1 divided by 3. Employer A also wrote 2 of the 4 listings. Report that breadth beside the listing share. In this fiction, one employer can move the SQL share.
Python text appears in J1 and J2. That is two lexical mentions, and both stay on the record. Python preferred equals 1 divided by 4. Python explicitly not required equals 1 divided by 4. Python required equals 0 divided by 4. Do not add the preferred mention to the not-required mention and call the sum required. The not-required phrase remains a lexical mention.
R text appears in J3 and J4. R context equals 1 divided by 4. R preferred equals 1 divided by 4. R required equals 0 divided by 4. Lexical R presence equals 2 divided by 4. Do not report those two listings as required.
Java required is J4 only. That share equals 1 divided by 4. JavaScript required is J3 only. That share equals 1 divided by 4. The Java count in J3 is 0 because JavaScript is a different key.
The reported shares are:
| Class | Listings | Listing share | Employers | Employer share |
|---|---|---|---|---|
| SQL required | J1, J2 | 2 of 4 | A | 1 of 3 |
| SQL context | J1 | 1 of 4 | A | 1 of 3 |
| Python required | none | 0 of 4 | none | 0 of 3 |
| Python preferred | J1 | 1 of 4 | A | 1 of 3 |
| Python explicitly not required | J2 | 1 of 4 | A | 1 of 3 |
| R required | none | 0 of 4 | none | 0 of 3 |
| R context | J3 | 1 of 4 | B | 1 of 3 |
| R preferred | J4 | 1 of 4 | C | 1 of 3 |
| Java required | J4 | 1 of 4 | C | 1 of 3 |
| JavaScript required | J3 | 1 of 4 | B | 1 of 3 |
A word counter is a tool that finds a letter sequence and adds one. Two word-counter results fail this fictional rule. Use them as rule tests. They are not a score for a software product.
A word counter marks Java when it sees JavaScript. It would mark J3 as Java. That mark fails the token rule. Java stays 1 of 4, from J4 only.
A word counter treats "no Python experience required" as Python demand or as Python required. That mark fails the class rule. Python required stays 0 of 4. The J2 sentence stays explicitly not required.
A search for the letters SQL also misses J2. The alias rule still marks J2. The SQL required count stays 2 of 4.
Sources & further reading
By Chronometrica · Updated
Back to top ↑

