

# 如何统计职位描述中的技能术语

当技能术语出现在相关语境中时，把每条完整职位描述对该术语计一次。对该技能的这一用法，该条职位只计一次。同一条职位里词语重复出现，不会再增加一条职位。原始词数是另一个数字。把原始词数和职位条数分开。

相关语境是指术语周围的词语表明，这段文字是在谈该项技能。一个偶然出现的字母不是相关语境。Actor 是托管的数据采集程序。Task 是已保存的输入预设。Actor 采集源数据行。你来标注技能术语。

## 1. 先界定样本组

在标注任何技能之前，先写下样本组规则。样本组是同一职位、同一市场、同一日期规则下的一组职位。把日期规则写在样本组说明里。日期规则是你接受的时间窗口。

在编码之前，先写明你要报告的数字。你可以报告有多少条职位出现了某项技能用法。你可以报告有多少家雇主有该用法。你可以报告这些词语出现了多少次。这是三个不同的数字。不要把它们混在一起。

份额是一个计数除以它下面的那个数。分母就是那个数。你要在知道哪些职位有全文之后再设定分母。在写明份额之前，不要编码术语。

对于过去的月份，使用你已经保存的描述快照。Actor 无法采集 2020 年或 2021 年的描述。一次新运行只能开启一个新样本组。它不能重建旧样本组。

## 2. 采集并保存源数据行

使用 [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper) Actor。公开版本 1.2.14 接受这些字段：searchTerm、country、location、maxItems、maxPagesPerSearch、postedWithinDays、sortBy 和 saveOnlyUniqueItems。已保存的 [Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper) 已于 2026 年 10 月 13 日核对。Task 只是起始输入。它不会替你完成这次计数。

已保存的 Task 输入是：

```json
{
  "country": "us",
  "searchTerm": "software engineer",
  "maxItems": 100,
  "postedWithinDays": "7",
  "sortBy": "date",
  "maxPagesPerSearch": 2,
  "saveOnlyUniqueItems": true
}
```

2. 采集并保存源数据行

此 Task 把 country 设为 us，把搜索词设为 software engineer。它不设置城市。只有当你的市场小于国家时，才添加 location。当你研究其他对象时，替换职位和市场。一次运行只保留一个职位和一个市场。

上限是 maxItems 限制。此 Task 把上限设为 100 行，并最多读取 2 个搜索页。这个较宽的上限不是第一次文本核对。第一次核对时，只保留一个职位和一个市场。把 maxItems 设为 2。把 maxPagesPerSearch 设为 1。第一次核对的输入是：

```json
{
  "country": "us",
  "searchTerm": "software engineer",
  "maxItems": 2,
  "postedWithinDays": "7",
  "sortBy": "date",
  "maxPagesPerSearch": 1,
  "saveOnlyUniqueItems": true
}
```

2. 采集并保存源数据行

在此版本上，一次设有上限的运行返回了两行。每一行都有来源职位 ID 和来源 URL。每一行都有完整描述文本。该运行达到了 maxItems。从你自己的输出中复制 ID 和 URL。不要键入新的 ID。这次运行表明文本采集可用。它不是技能结果。它也不是下面的虚构样本组。

已保存的 Task 把 saveOnlyUniqueItems 设为 true。当 Actor 采集数据行时，该设置可以减少重复。它并不完成重复项复核。再次发布是同一空缺再次发布。编码之前，去掉已复核的重复行和已复核的再次发布行。使用[重复项与再次发布指南](https://chronometrica.com/zh-Hans/articles/%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E8%81%8C%E4%BD%8D%E5%88%97%E8%A1%A8-%E5%BD%92%E7%BB%84%E5%8F%AF%E8%83%BD%E7%9A%84%E9%87%8D%E6%96%B0%E5%8F%91%E5%B8%83/)做该复核。把去掉的数量记在你的笔记里。

每一条源数据行是一条已采集的记录。职位条目是你为计数而保留的一行。把 jobId 存为文本。不要让电子表格把 jobId 变成数字。

使用这些输出字段。运行计数器是整个运行的一个数字。它不是职位行。

| 输出名称 | 种类 | 用法 |
| --- | --- | --- |
| jobId | 行字段 | 将其存为文本。 |
| title | 行字段 | 保留它。不要把标题当作完整要求。 |
| companyName | 行字段 | 为雇主计数而保留它。 |
| locationRaw | 行字段 | 保留来源中的地点文本。 |
| descriptionText | 行字段 | 根据这段文本编码技能。 |
| descriptionStatus | 行字段 | 只使用 full 或 missing。 |
| jobUrl | 行字段 | 保留来源 URL。 |
| query | 行字段 | 保留该行的搜索文本。 |
| searchLocation | 行字段 | 保留该行的搜索地点。 |
| scrapedAt | 行字段 | 这是采集时间。 |
| seniority | 可选行字段 | 它可以缺失。它不是完整要求。 |
| employmentType | 可选行字段 | 它可以缺失。 |
| salaryRaw | 可选行字段 | 它可以缺失。缺失不是薪资为零。 |
| postedAt | 可选行字段 | 它可以缺失。不要编造发布月份。 |
| postedAtRaw | 可选行字段 | 它可以缺失。 |
| rowsExcludedByCompany | 运行计数器 | 单独保留这个计数。 |
| rowsExcludedByDate | 运行计数器 | 单独保留这个计数。 |
| rowsExcludedBySalary | 运行计数器 | 单独保留这个计数。 |

当 descriptionText 存在时，descriptionStatus 为 full。当文本不存在时，它为 missing。不要增加 snippet 状态。这里的全文是来源给出的文本。它并不承诺这是雇主的整份文件。

scrapedAt 是 Actor 采集该行的时间。它不是雇主发布该职位的月份。如果 postedAt 缺失，就把发布月份留为未知。

rowsExcludedByCompany 是因公司原因而被排除的行的运行计数。rowsExcludedByDate 是因日期原因而被排除的行的运行计数。rowsExcludedBySalary 是因薪资原因而被排除的行的运行计数。不要把这三个计数加总成一个总数。不要把计数器变成职位行。本次对照给出了每一个计数。三者都没有被省略。rowsExcludedByCompany 为 0。rowsExcludedByDate 为 0。rowsExcludedBySalary 为 0。不要为被省略的计数器写零。这三个计数器都存在，所以每一个零都是计数。运行映射列出每个字段及其值。本次对照没有给出的字段是未知。将来不存在的字段是未知。当前这次两行、设有上限的 Indeed 对照具有这些输出值：requestedMaxItems: 2, candidateItemsSeen: 100, jobsSaved: 2, pagesParsed: 1, blockedPages: 0, failedRequests: 0, stopReason: "max_items_reached", rowsExcludedByCompany: 0, rowsExcludedByDate: 0, rowsExcludedBySalary: 0。这次运行不是未知。将来缺失的字段仍然未知。

保存实际运行的输入、原始行和输出。写下你设置的 maxItems，以及返回的行数。如果两个数字相等，这次运行达到了上限。达到上限的运行并不是每一个匹配职位。如果运行没有返回任何行，就写下这次运行是空的。空运行并不能证明不存在职位。如果运行失败，保存失败记录。不要用技能零去填一次失败的运行。

把 descriptionText 缺失的行留在已编码集合之外。每有这样一行，就把缺口计数加 1。缺口不是技能不存在的职位。

Actor 给出当前的公开数据行。它不给出旧的描述文本。它不给出雇主的编辑时间。它不给出申请人状态。它不给出学习计划。它不标注技能术语。你用下面的规则来标注它们。

## 3. 匹配之前先写技能清单

在搜索文本之前，先写一份技能清单。技能键是你为一项技能存储的那一个名称。别名是书写同一项技能的另一种方式。标记是一个完整的词，或一个独立的符号。它不是更长词语中的一段。排除项是你拒绝接受的一次匹配。

匹配 SQL、Structured Query Language 以及下面的其他键时，忽略字母大小写。当这些词语指该语言时，让 SQL 和 Structured Query Language 共用一个键。把 Java 与 JavaScript 分开。只在相关文本中把 R 作为完整的技能标记来匹配。不要匹配另一个词内部的字母 R。

版本标签，例如语言版本，需要一条写明的规则。产品名称需要一条写明的规则。编码之前先决定规则。阅读时不要猜测。

这份技能清单用于本文中的虚构样本组。它不是 Actor 提供的清单。你自己的样本组需要你自己的键。

| 技能键 | 允许的别名 | 标记规则 | 排除项 |
| --- | --- | --- | --- |
| SQL | SQL; Structured Query Language | 当文本指该语言时，两个别名共用此键。 | 不要把此键用于不同的短语。 |
| Python | Python | 匹配整个词。 | 不要把非必需短语编码为必需。 |
| R | R | 在相关技能文本中匹配完整标记。 | 不要匹配词内部的字母。 |
| Java | Java | 匹配整个词。 | 不要匹配 JavaScript。 |
| JavaScript | JavaScript | 匹配整个词。 | 不要把 Java 计为 JavaScript。 |

这份清单没有版本规则，也没有产品名称规则。虚构文本没有版本，也没有产品名称。如果你的文本有其中之一，先把规则加到清单里。

## 4. 为每一段文字编码

找到包含技能名称的那段文字。然后阅读它周围的词语。类别是提及的种类。使用这些类别：

- 必需是指文本说申请人必须具备该技能。
- 优先是指文本要求该技能，但没有说申请人必须具备它。
- 语境是指文本说工作使用该技能，但并不要求申请人具备它。
- 明确不要求是指文本说申请人不需要该技能。
- 无法判定是指你无法从词语判断类别。

词形提及是指技能名称出现在文本中。即使类别不是必需，也保留该词形提及。不要丢弃该句。

后面的虚构样本组包含词语“no Python experience required.”。这些词语是对 Python 的词形提及。类别是明确不要求。该类别不是必需。

同一样本组包含词语“we use R for reports.”。这些词语是对 R 的词形提及。类别是语境。除非文本还说申请人必须具备 R，否则不要把 R 标为必需。

“reports”内部的字母 r 不是技能 R。“required”内部的字母 r 不是技能 R。

如果一条职位对一项技能有两个类别，就把每个类别都标上。如果同一类别在一条职位中重复，该类别只标一次。

## 5. 建立职位矩阵

术语×职位矩阵是一张表，每个已编码职位占一行。每个技能类别有自己的列。当全文有该类别时填 1。当你读过全文且该类别不存在时填 0。0 表示你读过文本。它不表示文本缺失。

一条职位里，同一个键的重复提及，在该类别中仍然只形成一次职位标记。职位出现就是这个 1。当你需要原始出现次数时，把它放在另一张表里。一次原始出现是该键出现一次。不要把原始出现次数加进职位列。

一条职位可以在优先上为 1，在必需上为 0。一条职位可以在明确不要求上为 1，在必需上为 0。一条职位可以在语境上为 1，在必需上为 0。不要合并这些列。

不要把缺失的描述作为一行零放进矩阵。把它留在缺口计数里。

## 6. 设定分母

分母是同一样本组中、彼此不同的全文职位的数量。彼此不同是指同一条职位只计一次。只计算重复项复核之后仍然保留的职位。只计算 descriptionStatus 为 full 的职位。

职位份额等于类别计数除以分母。把份额写成计数占分母的多少。在旁边报告缺口计数。在旁边报告不确定 ID 计数。不确定 ID 是空白、已更改，或与另一行冲突。把不确定 ID 留在分母之外。

也要报告编码不确定性。编码不确定性是指复核人在类别上不一致。在记录写明已商定的类别之前，把有争议的职位留在必需计数之外。不要把争议悄悄变成零。

雇主覆盖面是具有该类别的不同雇主数量。用该雇主计数除以分母中的不同雇主数量。一家雇主可以写许多条职位。职位份额可以很高，同时雇主份额可以很小。两个份额都要报告。

这些份额只描述你的样本组。它们不是全国技能需求。它们也不是对申请人能做什么的度量。

## 7. 用相同规则比较样本组

只有当样本组规则一致时，才比较两个月份。保持相同的职位、市场、查询、日期窗口、上限和技能清单版本。技能清单版本是你给一份已保存技能清单起的名称。

如果你更改别名或类别规则，职位文本不变时，份额仍可能改变。那种改变是编码改变。在阅读新份额之前，先报告它。

如果上限、查询、市场或日期窗口改变，样本就改变了。来源文本缺失也是覆盖范围改变。在阅读份额变化之前，先报告样本变化。不要把编码改变当作样本改变。

搜索样本并不显示变化的原因。它并不显示整个市场增长了。技能份额的变化不是职位条数的变化。关于一段时间内的职位条数，使用[招聘趋势指南](https://chronometrica.com/zh-Hans/articles/%E7%94%A8%E9%87%8D%E5%A4%8D%E8%81%8C%E4%BD%8D%E5%BF%AB%E7%85%A7%E8%A1%A1%E9%87%8F%E6%8B%9B%E8%81%98%E8%B6%8B%E5%8A%BF/)。

## 8. 保存记录

保存技能清单及其版本名称。保存你编码的每一段文字，以及职位 ID、技能键和类别。保存矩阵。保存分母记录。

分母记录应包括：

- 职位、市场、查询、日期窗口和上限。
- 实际运行的输入和 Actor 版本。
- 返回的行数，以及运行是否达到 maxItems。
- 任何失败记录。
- rowsExcludedByCompany、rowsExcludedByDate 和 rowsExcludedBySalary；对每个被省略的计数器则写未知。
- 你去掉的重复行。
- 缺口计数。
- 不确定 ID 计数。
- 编码不确定性计数。
- 分母。
- 该分母中的雇主数量。

第二个人应当能够从已保存的段落出发，到达同一个矩阵单元格。如果不能，记录就不完整。

停在术语计数。重复出现的职责可以指导以后的[学习选择](/zh-Hans/articles/%E8%81%8C%E4%BD%8D%E6%8F%8F%E8%BF%B0%E8%BD%AC%E6%88%90%E6%8A%80%E8%83%BD%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/)。那个选择不是这次计数。这次计数也不是个人准备程度分数。

## 计数一个虚构样本组

本节中的五条描述都是虚构。它们不是 Actor 的结果。它们不是真实的技能发现。J1 到 J5 是标签。它们不是真实的来源 ID。上面的技能清单是此样本组的规则。

| 标签 | 雇主 | 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 |

这些标签没有 scrapedAt 值，也没有 postedAt 值。不要给它们指定发布月份。

J1 和 J2 使用雇主 A。它们的文本不同。这个虚构保留两条职位。它不把 J2 称为 J1 的再次发布。如果一次真实复核把 J2 作为再次发布去掉，分母会改变。这个虚构不做那个判定。

J5 有已知标签，但没有全文。J5 留在矩阵之外。缺口计数为 1。不确定 ID 计数为 0，因为标签已知，而文本才是缺口。不要为 J5 编造文本。

段落记录是：

| 职位 | 段落 | 技能键 | 类别 |
| --- | --- | --- | --- |
| J1 | SQL required. | SQL | 必需 |
| J1 | SQL reports run daily. | SQL | 语境 |
| J1 | Python preferred. | Python | 优先 |
| J2 | Structured Query Language required | SQL | 必需 |
| J2 | no Python experience required | Python | 明确不要求 |
| J3 | JavaScript required | JavaScript | 必需 |
| J3 | we use R for reports | R | 语境 |
| J4 | Java required | Java | 必需 |
| J4 | R preferred | R | 优先 |
| J5 | 无全文 | 无 | 未编码 |

一次出现是技能文本出现一次。出现标记是一个类别上的一次职位标记。一条职位的每个类别只标记一次。第二句具有相同的键和相同的类别时，增加一次原始出现。它不增加第二次出现标记。第二句具有相同的键和不同的类别时，得到自己的出现标记。不要因为该键也有必需文本就丢掉那个类别。

J1 中第一句 SQL 是必需。它是一次必需出现。J1 的必需出现标记为 1。句子“SQL reports run daily.”是语境。它是一次语境出现。J1 的语境出现标记为 1。J2 计为 SQL，因为 Structured Query Language 是允许的别名。这些词语指该语言，而且该句说它是必需的。该别名是一次必需出现。J2 的必需出现标记为 1。

矩阵只对四条全文使用 1 和 0。J5 在这里不是一行。

| 职位 | 雇主 | SQL 必需 | SQL 语境 | Python 必需 | Python 优先 | Python 明确不要求 | R 必需 | R 语境 | R 优先 | Java 必需 | JavaScript 必需 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 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 |
| 职位计数 | `""` | 2 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 1 |

J1 到 J4 中的每一个 0 都表示全文已读，且该类别不存在。没有已编码段落是无法判定，所以无法判定是 4 条中的 0。这个零来自一次阅读。它不是 J5 的缺口。没有类别处于争议中，所以编码不确定性计数为 0。

原始计数留在另一张表中。

| 职位 | 技能键 | 类别 | 原始出现次数 |
| --- | --- | --- | --- |
| J1 | SQL | 必需 | 1 |
| J1 | SQL | 语境 | 1 |
| J2 | SQL | 必需 | 1 |
| J2 | SQL | 语境 | 0 |
| J1 | Python | 优先 | 1 |
| J2 | Python | 明确不要求 | 1 |
| J2 | Python | 必需 | 0 |
| J3 | R | 语境 | 1 |
| J4 | R | 优先 | 1 |
| J3 | R | 必需 | 0 |
| J4 | R | 必需 | 0 |
| J3 | Java | 必需 | 0 |
| J4 | Java | 必需 | 1 |
| J3 | JavaScript | 必需 | 1 |
| J4 | JavaScript | 必需 | 0 |

四条有效的全文职位构成分母。这些职位是 J1、J2、J3 和 J4。分母中的雇主是 A、B 和 C。该雇主总数为 3。雇主 D 在其外，因为 J5 没有全文。

J1 有 2 次原始 SQL 出现。必需类别有 1 次。语境类别有 1 次。J2 有 1 次原始 SQL 出现，且该类别是必需。SQL 必需的原始计数是 1 加 1，等于 2。SQL 语境的原始计数是 1。2 加 1 等于 3 次原始 SQL 出现。SQL 的词形总计数仍为 3。必需的原始总数为 2。必需的职位计数也是 2。同一个数字并不使这两种度量变成同一种度量。语境出现在必需的原始总数之外。SQL 必需的职位计数是 2。SQL 必需份额等于 2 除以 4。它不等于 3 除以 4。它不等于 2 除以 5。多出来的那次原始提及是语境。它不是额外的一条职位。J5 不进入分母。

SQL 语境的职位计数是 1。SQL 语境份额等于 1 除以 4。不要把这 1 条语境职位加到 2 条必需职位上。J1 会被计两次。类别保持分开。

两条 SQL 必需职位都属于雇主 A。SQL 必需的雇主份额等于 1 除以 3。那一条 SQL 语境职位属于雇主 A。SQL 语境的雇主份额等于 1 除以 3。雇主 A 还写了 4 条职位中的 2 条。把该覆盖面报告在职位份额旁边。在这个虚构中，一家雇主就可以推动 SQL 份额。

Python 文本出现在 J1 和 J2 中。这是两次词形提及，两者都留在记录中。Python 优先等于 1 除以 4。Python 明确不要求等于 1 除以 4。Python 必需等于 0 除以 4。不要把优先提及与不要求提及相加，再把和称为必需。不要求短语仍然是一次词形提及。

R 文本出现在 J3 和 J4 中。R 语境等于 1 除以 4。R 优先等于 1 除以 4。R 必需等于 0 除以 4。R 的词形出现等于 2 除以 4。不要把那两条职位报告为必需。

Java 必需只有 J4。该份额等于 1 除以 4。JavaScript 必需只有 J3。该份额等于 1 除以 4。J3 中的 Java 计数为 0，因为 JavaScript 是不同的键。

所报告的份额是：

| 类别 | 职位 | 职位份额 | 雇主 | 雇主份额 |
| --- | --- | --- | --- | --- |
| SQL 必需 | J1, J2 | 4 中的 2 | A | 3 中的 1 |
| SQL 语境 | J1 | 4 中的 1 | A | 3 中的 1 |
| Python 必需 | 无 | 4 中的 0 | 无 | 3 中的 0 |
| Python 优先 | J1 | 4 中的 1 | A | 3 中的 1 |
| Python 明确不要求 | J2 | 4 中的 1 | A | 3 中的 1 |
| R 必需 | 无 | 4 中的 0 | 无 | 3 中的 0 |
| R 语境 | J3 | 4 中的 1 | B | 3 中的 1 |
| R 优先 | J4 | 4 中的 1 | C | 3 中的 1 |
| Java 必需 | J4 | 4 中的 1 | C | 3 中的 1 |
| JavaScript 必需 | J3 | 4 中的 1 | B | 3 中的 1 |

词计数器是一种工具，它找到一个字母序列并加一。两个词计数器结果不符合这条虚构规则。把它们用作规则测试。它们不是某个软件产品的分数。

词计数器在看到 JavaScript 时会标记 Java。它会把 J3 标为 Java。该标记不符合标记规则。Java 仍是 4 条中的 1，且只来自 J4。

词计数器把“no Python experience required”当作 Python 需求，或当作 Python 必需。该标记不符合类别规则。Python 必需仍是 4 条中的 0。J2 的那一句仍是明确不要求。

搜索字母 SQL 也会漏掉 J2。别名规则仍然标记 J2。SQL 必需计数仍是 4 条中的 2。

## 下一步

用你的别名规则，以及你对明确不要求的规则，编码一条完整描述。在报告频率之前核对分母。用 [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper) Actor 和这个已保存的 [Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper) 采集当前数据行。

## 来源和延伸阅读

- [Indeed Jobs Scraper](https://apify.com/chronometrica/indeed-jobs-scraper)
- [Indeed Jobs Scraper Task](https://apify.com/chronometrica/indeed-jobs-scraper/examples/indeed-jobs-scraper)
- [重复项与再次发布匹配](https://chronometrica.com/zh-Hans/articles/%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E8%81%8C%E4%BD%8D%E5%88%97%E8%A1%A8-%E5%BD%92%E7%BB%84%E5%8F%AF%E8%83%BD%E7%9A%84%E9%87%8D%E6%96%B0%E5%8F%91%E5%B8%83/)
- [职位条数趋势](https://chronometrica.com/zh-Hans/articles/%E7%94%A8%E9%87%8D%E5%A4%8D%E8%81%8C%E4%BD%8D%E5%BF%AB%E7%85%A7%E8%A1%A1%E9%87%8F%E6%8B%9B%E8%81%98%E8%B6%8B%E5%8A%BF/)
- [关于所采集 Indeed 职位的公开问题](https://www.reddit.com/r/webscraping/comments/p3110c/what_type_of_analysis_to_do_on_indeed_scraped_data/)