PAYATHON 2026

如何使用 R 从招聘描述长文本中提取工作经验年限?

技术老齐

直接从 description 中匹配包含数字的完整工作经验表达,再将捕获的数字转为 numeric。原函数只保留了 years 这个单词,丢掉了前面的数字,所以后续提取会得到 NA。

1 to 5 years 包含两个数值,需要先确定取值规则。这里按区间下限取值,2+ years 取 2,没有识别到明确年限时保留 NA_real_。

为什么原代码会得到 NA?

以这段文本为例:

2 years experience in IT companies

分词后,2 和 years 是两个独立元素。原函数中的这段代码:

if (str_detect(letter, tag)) {
  experience = split_string[i]
}

只会保存 "years"。随后运行:

str_match("years", "[0-9,\\.]+")

就匹配不到数字,结果为 NA。

原逻辑还有几处限制:

  • tag = "years" 匹配不到单数 year,也匹配不到阿拉伯语 سنوات。
  • Years of Experience 后面的 Min: 2 Max: 20 需要结合字段上下文处理,只检查相邻单词不够。
  • 多次命中时,循环会不断覆盖结果,最终只保留最后一次命中的单词。
  • " |\n" 没有完整处理 \r\n,结果中可能残留 \r。

按展示的 tag = "years",函数不会返回 "سنوات\r"。所列输出与这段代码并不完全对应,需要核对实际运行时的 tag、数据对象和执行顺序。

从完整描述中提取年限

代码使用的数据对象是 df;如果实际对象叫 job_posts,替换对象名即可。规则覆盖示例中的四类写法:

写法提取值
Years of Experience Min: 2 Max: 202
Experience: * min: 3 years3
2 years experience、2+ years of experience、1 to 5 Years experience2、2、1
خبرة لا تقل عن 5 سنوات5
library(dplyr)
library(stringr)

# 支持整数和以小数点表示的小数
num <- "[0-9]+(?:\\.[0-9]+)?"

patterns <- c(
  # Years of Experience Min: 2 Max: 20
  paste0(
    "\\byears?\\s+of\\s+experience\\s*:?\\s*",
    "min\\s*:\\s*(", num, ")\\b"
  ),

  # Experience: * min: 3 years
  paste0(
    "\\bexperience\\s*:\\s*\\*?\\s*",
    "min\\s*:\\s*(", num, ")\\s*years?\\b"
  ),

  # 1 year experience
  # 2+ years of experience
  # 1 to 5 years experience / 1-5 years experience
  paste0(
    "\\b(", num, ")",
    "(?:\\s*(?:to|[-–—])\\s*", num, ")?",
    "\\s*\\+?\\s*years?\\b\\s*",
    "(?:['’]\\s*)?(?:of\\s+)?(?:work\\s+)?experience\\b"
  ),

  # 示例中的阿拉伯语:工作经验不少于 5 年
  paste0(
    "خبرة\\s+لا\\s+تقل\\s+عن\\s+(",
    num,
    ")\\s+سنوات\\b"
  )
)

extract_years <- function(x) {
  # 将换行、回车和连续空白统一为空格;NA 仍然保留
  x <- str_squish(as.character(x))

  candidates <- lapply(patterns, function(p) {
    m <- str_match(x, regex(p, ignore_case = TRUE))
    as.numeric(m[, 2])
  })

  # 按 patterns 的顺序,为每行选取第一个非缺失值
  Reduce(dplyr::coalesce, candidates)
}

df <- df %>%
  mutate(yrs_experience = extract_years(description))

df %>%
  select(id, yrs_experience)

str_match() 一次处理整个字符向量,无须逐行循环。它返回的矩阵中,第一列是完整匹配,第二列是第一个捕获组。每条规则都把要提取的年限放在第一个捕获组中,因此可以用 m[, 2] 取出数字,再用 as.numeric() 转换。

未匹配到的内容会得到数值型缺失值,不需要先填入空格,也不需要再用 gsub() 删除逗号。

示例数据应如何解读?

按上述规则,八条描述的提取结果为:

idyrs_experience依据
1NA提到了经验,但没有明确年限
25阿拉伯语表达“不少于 5 年”
32Min: 2 Max: 20 的下限
422 years experience
53Experience: ... min: 3 years
61首次匹配到 1 year experience
722+ years of experience
811 to 5 Years experience 的下限

第 6 条提供了两条可选资格路径:

High school diploma or GED; 1 year experience ...
OR
2-year degree ...; no work experience required.

代码会提取到 1,也不会把 2-year degree 中的 2 当成经验年限。不过,另一条路径明确允许无工作经验,因此 1 不能代表该岗位对所有申请人的统一最低要求。

如果字段要表示“所有可选路径中的最低经验要求”,就需要结合学历条件解析。直接把所有出现 no work experience required 的广告设为 0,也可能丢失条件信息。

使用时需要注意什么?

这些规则针对示例中的表达方式,无法覆盖所有招聘广告。遇到 three years、阿拉伯数字字符 ٥、3 years of relevant experience 或“5 年总经验,其中 2 年管理经验”等写法时,需要增加规则或明确字段含义。

同一条描述可能包含多个经验要求。当前代码按规则顺序取第一个非缺失结果,每条规则只取首次匹配。如果需要保留所有要求,可以使用 str_match_all(),同时保存匹配原文、经验类别和上下限,再决定如何汇总。

NA 表示当前规则没有识别到明确年限,不能直接等同于“要求 0 年经验”。标有 Preferred 的年限也需要区分是“优先条件”还是“硬性要求”。

备注:内容仅供参考。