使用Tampermonkey辅助招聘筛选简历

Tampermonkey 是一款免费的浏览器扩展和最为流行的用户脚本管理器,它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。
官方网站
文章只是为快速在网页扩展一些工具能力提供一些思路。

Tampermonkey 基本使用

Tampermonkey 内新建一个脚本,如下地方配置你自己需要匹配的网站信息

1
2
3
4
5
6
7
8
9
// ==UserScript==
// @name New Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @grant none
// ==/UserScript==

然后匹配成功后,会在访问对应网站时看到如下图所示的图标上显示数字,这时可以开始编写你的脚本了
如图

筛选简历基本思路

  • 为网页添加一个按钮,点击可以开启简历过滤
  • 对简历容器元素创建 MutationObserver,监听内部元素变更之后自动执行简历过滤
  • 提取过滤规则配置,比如薪资、工作年限、学历、目前状态等
  • 如果想要自动筛选可以通过触发翻页操作,如点击加载下一页

区间存在交集算法

如上的学历、目前状态可以通过包含关系实现,但是薪资和工作年薪会涉及到一个区间的匹配。
比如:

1
2
薪资区间设置为:[15, 20];
匹配人员薪资要求为:[18, 25];

如何判断两个区间之间是存在交集的呢,会有一个简单的算法

1
2
3
4
// 区间 [A,B] 和 [X,Y]
// 有交集的情况

X <= B && A <= Y

详细原文可查看:
http://world.std.com/~swmcd/steven/tech/interval.html

基本实现

代码实现逻辑如下:

graph LR
A[创建按钮] --> B[点击开始监听Dom Mutation]
B --> C[触发加载下一页数据]
C --> D[监听到Dom变更]
D --> E[过滤数据]
E -- 有下一页 --> C
E -- 无下一页 --> F
F[筛选结束]

基本点:不能改动到页面原本的执行逻辑,整个实现方案主要在dom的变更监听MutationObserver,以及过滤DOM节点获取你需要的信息来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
(function () {
const $ = window.$;

// 配置筛选简历信息
const config = {
autoScroll: true,
duration: 3000,
defaultPaddingTop: '600px',
rules: {
salary: [16, 25],
workYear: [3, 10],
degree: ['本科'],
applyStatus: ['离职-随时到岗', '在职-月内到岗'],
greeting: {
other: false,
self: '打招呼'
}
}
};

function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
}

// 插入css
function insertStyleCode(code) {
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = code;
document.getElementsByTagName('head').item(0).appendChild(style);
}

function getInnerWindow() {
const innerContentWindow = document.querySelector('.sync-container .frame-container').contentWindow;
const $innerHtml = $(innerContentWindow.document.documentElement);

// debugger
window.innerContentWindow = innerContentWindow;

return {
innerContentWindow,
$innerHtml
};
}

// 验证器,包含和交集两种类型
const validatior = {
includes(value, ruleValue) {
return ruleValue.includes(value);
},
intersection(value1, value2) {
// 原文: http://world.std.com/~swmcd/steven/tech/interval.html

const [x, y] = value1;
const [a, b] = value2;

// NaN
if ([x, y, a, b].some(item => Number.isNaN(item))) {
return false;
}

return x <= b && a <= y;
}
};

// 过滤薪水、工作年限、学历等信息
const filterRules = {
salary($el, ruleValue) {
// 薪水
const curValue = $el.find('.salary').text().split('-')
.map(item => parseInt(item, 10));

return validatior.intersection(curValue, ruleValue);
},
workYear($el, ruleValue) {
// 年限
const curValue = parseInt($el.find('.info-labels').children().eq(3).text(), 10);
return validatior.intersection([curValue, curValue], ruleValue);
},
degree($el, ruleValue) {
// 学历
const curValue = $el.find('.info-labels').children().eq(5).text();
return validatior.includes(curValue, ruleValue);
},
applyStatus($el, ruleValue) {
// 状态
const curValue = $el.find('.info-labels').children().eq(7).text();
return validatior.includes(curValue, ruleValue);
},
greeting($el, ruleValue) {
// 沟通过
const other = $el.find('.sider-op em').length; // 他人沟通过
const self = $el.find('.sider-op button').text(); // 自己沟通过
return !!other === ruleValue.other && self === ruleValue.self;
}
};

// 过滤数据,后滚动滚动条到底部触发下一页加载
function filterData($list) {
$list.each((i, v) => {
const isValid = Object.entries(config.rules).every(([key, value]) => filterRules[key].call(filterRules, $(v), value));

$(v)[isValid ? 'show' : 'hide']();
});

// 滚动浏览器到底部

if (config.autoScroll) {
sleep(config.duration).then(() => {
const { innerContentWindow } = getInnerWindow();

// 滚动条到底
$(innerContentWindow).scrollTop('999999');
});
}
}

function runObserverResume($btn) {
const { $innerHtml } = getInnerWindow();
const $wrap = $innerHtml.find('#recommend-list>.candidate-card>ul');

if ($wrap.length <= 0) return;

$btn.text('运行成功...');
sleep(3000).then(() => {
$btn.text('点击运行');
});

filterData($wrap.children('li'));


// 调整滚动条前 加个 paddingTop 避免没有滚动条无法滚动
sleep(config.duration - 500).then(() => {
// 避免没有滚动条无法触发滚动事件
$wrap.css({
paddingTop: config.defaultPaddingTop
});
});

// 避免按钮多次点击
if (!$wrap.data('observe')) {
$wrap.data('observe', 1);

// 避免反复监听
const mutationResumeObserver = new MutationObserver((mutationList, observer) => {
// 获得到每次更新的条数
const insertLength = mutationList.length;

filterData($wrap.children(`li:gt(${$wrap.children('li').length - insertLength - 1})`));
});

mutationResumeObserver.observe($wrap[0], {
childList: true,
subtree: false, // 后代
attributes: false
});
}
}

function setToolBar() {
const insertHtml = `<div class="xxFilterTools">
<div class="xxFilterTool_line">
<a class="js_xxFilterToolBtn toolBtn" data-type="run" href="javascript:;">点击运行</a>
</div>
<div class="xxFilterTool_line">
</div>
</div>`;

const cssCode = `.xxFilterTools{
position: absolute;
top: 70px;
left: 50%;
margin-left: 20px
}
.toolBtn {
display: flex;
align-items: center;
padding: 0 10px;
line-height: 32px;
color: #FFF;
margin-right: 1px;
background-color: #b5a9a9;
position: relative;
}
.myHide{
display: none !important;
}
`;

$('#header').append(insertHtml);
insertStyleCode(cssCode);

setTimeout(() => {
const $btn = $('.js_xxFilterToolBtn');
$btn.on('click', () => {
runObserverResume($btn);
});
});
}

function run() {
// 屏蔽错误
window.onerror = function (msg, url, lineNo, columnNo, error) {
debugger;
return true;
};

setToolBar();
}

run();
}());