如何使用 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: 20 | 2 |
Experience: * min: 3 years | 3 |
2 years experience、2+ years of experience、1 to 5 Years experience | 2、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() 删除逗号。
示例数据应如何解读?
按上述规则,八条描述的提取结果为:
| id | yrs_experience | 依据 |
|---|---|---|
| 1 | NA | 提到了经验,但没有明确年限 |
| 2 | 5 | 阿拉伯语表达“不少于 5 年” |
| 3 | 2 | Min: 2 Max: 20 的下限 |
| 4 | 2 | 2 years experience |
| 5 | 3 | Experience: ... min: 3 years |
| 6 | 1 | 首次匹配到 1 year experience |
| 7 | 2 | 2+ years of experience |
| 8 | 1 | 1 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 的年限也需要区分是“优先条件”还是“硬性要求”。
备注:内容仅供参考。
