@reconcrap/boss-recruit-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/bin/boss-recruit-mcp.js +2 -0
- package/config/screening-config.example.json +7 -0
- package/package.json +42 -0
- package/skills/boss-recruit-pipeline/README.md +20 -0
- package/skills/boss-recruit-pipeline/SKILL.md +83 -0
- package/src/adapters.js +352 -0
- package/src/cli.js +112 -0
- package/src/index.js +210 -0
- package/src/parser.js +221 -0
- package/src/pipeline.js +215 -0
- package/src/test-parser.js +79 -0
- package/vendor/boss-screen-cli/boss-screen-cli.cjs +1646 -0
- package/vendor/boss-screen-cli/favorite-calibration.json +9 -0
- package/vendor/boss-search-cli/src/boss-searcher.js +667 -0
- package/vendor/boss-search-cli/src/chrome-connector.js +79 -0
- package/vendor/boss-search-cli/src/cli.js +132 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
|
|
2
|
+
import CDP from 'chrome-remote-interface';
|
|
3
|
+
|
|
4
|
+
export class BossSearcher {
|
|
5
|
+
constructor(port = 9222) {
|
|
6
|
+
this.port = port;
|
|
7
|
+
this.client = null;
|
|
8
|
+
this.Runtime = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async connect() {
|
|
12
|
+
try {
|
|
13
|
+
console.log('正在连接Boss直聘 (端口: ' + this.port + ')...');
|
|
14
|
+
this.client = await CDP({ port: this.port });
|
|
15
|
+
const { Runtime } = this.client;
|
|
16
|
+
this.Runtime = Runtime;
|
|
17
|
+
await Runtime.enable();
|
|
18
|
+
console.log('✅ 连接成功');
|
|
19
|
+
return true;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('❌ 连接失败:', error.message);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async evaluate(expression) {
|
|
27
|
+
const result = await this.Runtime.evaluate({
|
|
28
|
+
expression,
|
|
29
|
+
returnByValue: true,
|
|
30
|
+
awaitPromise: true
|
|
31
|
+
});
|
|
32
|
+
if (result.exceptionDetails) {
|
|
33
|
+
throw new Error(result.exceptionDetails.exception.description);
|
|
34
|
+
}
|
|
35
|
+
return result.result.value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async executeInIframe(expression) {
|
|
39
|
+
return this.evaluate(expression);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async sleep(ms) {
|
|
43
|
+
return new Promise(function(resolve) {
|
|
44
|
+
setTimeout(resolve, ms);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async refreshIframe() {
|
|
49
|
+
console.log('🔄 刷新iframe...');
|
|
50
|
+
try {
|
|
51
|
+
await this.evaluate(
|
|
52
|
+
"(function() {" +
|
|
53
|
+
" const iframe = document.querySelector('iframe');" +
|
|
54
|
+
" if (iframe && iframe.contentWindow) {" +
|
|
55
|
+
" iframe.contentWindow.location.reload();" +
|
|
56
|
+
" return { refreshed: true };" +
|
|
57
|
+
" }" +
|
|
58
|
+
" return { error: 'no iframe' };" +
|
|
59
|
+
"})()"
|
|
60
|
+
);
|
|
61
|
+
await this.sleep(4000);
|
|
62
|
+
console.log('✅ iframe已刷新');
|
|
63
|
+
return { success: true };
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(' 刷新iframe时出错:', e.message);
|
|
66
|
+
return { error: e.message };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async setKeywords(keywords) {
|
|
71
|
+
console.log('🔍 设置搜索关键词: ' + keywords);
|
|
72
|
+
try {
|
|
73
|
+
const safeKeywords = keywords.replace(/'/g, "\\'");
|
|
74
|
+
console.log(' [DEBUG] 开始设置关键词...');
|
|
75
|
+
const result = await this.evaluate(
|
|
76
|
+
'(function() {' +
|
|
77
|
+
' const iframe = document.querySelector("iframe");' +
|
|
78
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
79
|
+
' const doc = iframe.contentWindow.document;' +
|
|
80
|
+
' const input = doc.querySelector("input.search-input");' +
|
|
81
|
+
' if (input) {' +
|
|
82
|
+
' console.log("[DEBUG] 找到输入框,当前值:", input.value);' +
|
|
83
|
+
' input.focus();' +
|
|
84
|
+
' input.value = \'' + safeKeywords + '\';' +
|
|
85
|
+
' const event = new Event("input", { bubbles: true });' +
|
|
86
|
+
' input.dispatchEvent(event);' +
|
|
87
|
+
' console.log("[DEBUG] 已设置值:", input.value);' +
|
|
88
|
+
' ' +
|
|
89
|
+
' const dropdown = doc.querySelector(".search-result-C");' +
|
|
90
|
+
' const dropdownVisible = dropdown && dropdown.offsetParent !== null;' +
|
|
91
|
+
' console.log("[DEBUG] 下拉提示是否可见:", dropdownVisible);' +
|
|
92
|
+
' if (dropdownVisible) {' +
|
|
93
|
+
' const items = dropdown.querySelectorAll(".search-result-item");' +
|
|
94
|
+
' console.log("[DEBUG] 下拉选项数量:", items.length);' +
|
|
95
|
+
' }' +
|
|
96
|
+
' return { success: true, value: input.value, dropdownVisible: dropdownVisible };' +
|
|
97
|
+
' }' +
|
|
98
|
+
' return { error: "input not found" };' +
|
|
99
|
+
'})()'
|
|
100
|
+
);
|
|
101
|
+
console.log(' [DEBUG] setKeywords 结果:', JSON.stringify(result));
|
|
102
|
+
return result;
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.log(' 设置关键词时出错:', e.message);
|
|
105
|
+
return { error: e.message };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async setDegree(degree) {
|
|
110
|
+
console.log('🎓 设置学历要求: ' + degree);
|
|
111
|
+
const degreeMap = {
|
|
112
|
+
'不限': 0,
|
|
113
|
+
'本科': 1,
|
|
114
|
+
'本科及以上': 1,
|
|
115
|
+
'硕士': 2,
|
|
116
|
+
'硕士及以上': 2,
|
|
117
|
+
'博士': 3
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const idx = degreeMap[degree] || 0;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result = await this.evaluate(
|
|
124
|
+
'(function() {' +
|
|
125
|
+
' const iframe = document.querySelector("iframe");' +
|
|
126
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
127
|
+
' const doc = iframe.contentWindow.document;' +
|
|
128
|
+
' const items = doc.querySelectorAll(".degree-list-C .degree-item");' +
|
|
129
|
+
' if (items && items[' + idx + ']) {' +
|
|
130
|
+
' items[' + idx + '].click();' +
|
|
131
|
+
' return { success: true, selected: items[' + idx + '].textContent ? items[' + idx + '].textContent.trim() : "" };' +
|
|
132
|
+
' }' +
|
|
133
|
+
' return { error: "items not found" };' +
|
|
134
|
+
'})()'
|
|
135
|
+
);
|
|
136
|
+
await this.sleep(300);
|
|
137
|
+
return result;
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.log(' 设置学历时出错:', e.message);
|
|
140
|
+
return { error: e.message };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async setSchoolRequirements(schools) {
|
|
145
|
+
console.log('🏫 设置院校要求: ' + schools.join(', '));
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
for (const school of schools) {
|
|
149
|
+
const safeSchool = school.replace(/'/g, "\\'");
|
|
150
|
+
const result = await this.evaluate(
|
|
151
|
+
'(function() {' +
|
|
152
|
+
' const iframe = document.querySelector("iframe");' +
|
|
153
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
154
|
+
' const doc = iframe.contentWindow.document;' +
|
|
155
|
+
' const schoolItems = doc.querySelectorAll(".school-item");' +
|
|
156
|
+
' const targetTexts = ["统招本科", "双一流院校", "211院校", "985院校", "留学生", "QS 100", "QS 500"];' +
|
|
157
|
+
' const targetIdx = targetTexts.indexOf("' + safeSchool + '");' +
|
|
158
|
+
' if (targetIdx >= 0 && schoolItems[targetIdx]) {' +
|
|
159
|
+
' const label = schoolItems[targetIdx].querySelector("label.checkbox");' +
|
|
160
|
+
' if (label) {' +
|
|
161
|
+
' label.click();' +
|
|
162
|
+
' const checkbox = schoolItems[targetIdx].querySelector(".checkbox-input");' +
|
|
163
|
+
' return { success: true, school: "' + safeSchool + '", checked: checkbox ? checkbox.checked : false };' +
|
|
164
|
+
' }' +
|
|
165
|
+
' }' +
|
|
166
|
+
' return { error: "school item not found" };' +
|
|
167
|
+
'})()'
|
|
168
|
+
);
|
|
169
|
+
console.log(' ' + safeSchool + ': ' + (result && result.success ? '✅' : '❌'));
|
|
170
|
+
await this.sleep(300);
|
|
171
|
+
}
|
|
172
|
+
return { success: true };
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.log(' 设置院校要求时出错:', e.message);
|
|
175
|
+
return { error: e.message };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async setJobTitle(jobTitle = '不限职位') {
|
|
180
|
+
console.log('💼 设置职位: ' + jobTitle);
|
|
181
|
+
try {
|
|
182
|
+
const result = await this.evaluate(
|
|
183
|
+
"(function() {" +
|
|
184
|
+
" const iframe = document.querySelector('iframe');" +
|
|
185
|
+
" if (!iframe || !iframe.contentWindow) return { error: 'no iframe' };" +
|
|
186
|
+
" const doc = iframe.contentWindow.document;" +
|
|
187
|
+
" const container = doc.querySelector('.search-job-list-C');" +
|
|
188
|
+
" if (!container) return { error: 'search-job-list-C not found' };" +
|
|
189
|
+
" const firstOption = container.querySelector('li[ka=\"search_select_job\"]');" +
|
|
190
|
+
" if (firstOption) {" +
|
|
191
|
+
" firstOption.click();" +
|
|
192
|
+
" return { success: true, selected: firstOption.textContent ? firstOption.textContent.trim() : '' };" +
|
|
193
|
+
" }" +
|
|
194
|
+
" return { error: 'job option not found' };" +
|
|
195
|
+
"})()"
|
|
196
|
+
);
|
|
197
|
+
await this.sleep(300);
|
|
198
|
+
return result;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.log(' 设置职位时出错:', e.message);
|
|
201
|
+
return { error: e.message };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async _click(x, y) {
|
|
206
|
+
const { Input } = this.client;
|
|
207
|
+
await Input.dispatchMouseEvent({
|
|
208
|
+
type: 'mousePressed',
|
|
209
|
+
x, y,
|
|
210
|
+
button: 'left',
|
|
211
|
+
clickCount: 1
|
|
212
|
+
});
|
|
213
|
+
await this.sleep(50);
|
|
214
|
+
await Input.dispatchMouseEvent({
|
|
215
|
+
type: 'mouseReleased',
|
|
216
|
+
x, y,
|
|
217
|
+
button: 'left',
|
|
218
|
+
clickCount: 1
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async _hover(x, y) {
|
|
223
|
+
const { Input } = this.client;
|
|
224
|
+
await Input.dispatchMouseEvent({
|
|
225
|
+
type: 'mouseMoved',
|
|
226
|
+
x, y
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async _getElementPos(selector) {
|
|
231
|
+
return await this.evaluate(
|
|
232
|
+
"(function() {" +
|
|
233
|
+
" const iframe = document.querySelector('iframe');" +
|
|
234
|
+
" if (!iframe) return null;" +
|
|
235
|
+
" const doc = iframe.contentDocument;" +
|
|
236
|
+
" const el = doc.querySelector('" + selector + "');" +
|
|
237
|
+
" if (!el) return null;" +
|
|
238
|
+
" const rect = el.getBoundingClientRect();" +
|
|
239
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
240
|
+
" return {" +
|
|
241
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
242
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
243
|
+
" };" +
|
|
244
|
+
"})()"
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async _getElementPosByText(containerSelector, text) {
|
|
249
|
+
const safeText = text.replace(/'/g, "\\'");
|
|
250
|
+
return await this.evaluate(
|
|
251
|
+
"(function() {" +
|
|
252
|
+
" const iframe = document.querySelector('iframe');" +
|
|
253
|
+
" if (!iframe) return null;" +
|
|
254
|
+
" const doc = iframe.contentDocument;" +
|
|
255
|
+
" const container = doc.querySelector('" + containerSelector + "');" +
|
|
256
|
+
" if (!container) return null;" +
|
|
257
|
+
" const items = container.querySelectorAll('li');" +
|
|
258
|
+
" for (let i = 0; i < items.length; i++) {" +
|
|
259
|
+
" const t = items[i].textContent ? items[i].textContent.trim() : '';" +
|
|
260
|
+
" if (t.startsWith('" + safeText + "') || t === '" + safeText + "') {" +
|
|
261
|
+
" const rect = items[i].getBoundingClientRect();" +
|
|
262
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
263
|
+
" return {" +
|
|
264
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
265
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
266
|
+
" };" +
|
|
267
|
+
" }" +
|
|
268
|
+
" }" +
|
|
269
|
+
" return null;" +
|
|
270
|
+
"})()"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async getCurrentCity() {
|
|
275
|
+
const result = await this.evaluate(
|
|
276
|
+
"(function() {" +
|
|
277
|
+
" const iframe = document.querySelector('iframe');" +
|
|
278
|
+
" if (!iframe) return null;" +
|
|
279
|
+
" const doc = iframe.contentDocument;" +
|
|
280
|
+
" const selectors = [" +
|
|
281
|
+
" '.city-wrap .city-text'," +
|
|
282
|
+
" '.city-wrap .city'," +
|
|
283
|
+
" '.city-wrap .current-city'," +
|
|
284
|
+
" '.city-wrap .selected-city'," +
|
|
285
|
+
" '.city-wrap span.city-text'," +
|
|
286
|
+
" '.city-wrap span'," +
|
|
287
|
+
" '.search-city-kw .city-text'," +
|
|
288
|
+
" '.search-city-kw input'," +
|
|
289
|
+
" '.city-text'" +
|
|
290
|
+
" ];" +
|
|
291
|
+
" for (let i = 0; i < selectors.length; i++) {" +
|
|
292
|
+
" const el = doc.querySelector(selectors[i]);" +
|
|
293
|
+
" if (el && el.textContent) {" +
|
|
294
|
+
" const text = el.textContent.trim();" +
|
|
295
|
+
" if (text && text.length > 0 && text.length < 20) {" +
|
|
296
|
+
" return text;" +
|
|
297
|
+
" }" +
|
|
298
|
+
" }" +
|
|
299
|
+
" }" +
|
|
300
|
+
" const cityWrap = doc.querySelector('.city-wrap');" +
|
|
301
|
+
" if (cityWrap) {" +
|
|
302
|
+
" const allText = cityWrap.textContent ? cityWrap.textContent.trim() : '';" +
|
|
303
|
+
" const parts = allText.split(/\\s+/);" +
|
|
304
|
+
" for (let i = 0; i < parts.length; i++) {" +
|
|
305
|
+
" if (parts[i].length > 0 && parts[i].length < 10) {" +
|
|
306
|
+
" return parts[i];" +
|
|
307
|
+
" }" +
|
|
308
|
+
" }" +
|
|
309
|
+
" }" +
|
|
310
|
+
" return null;" +
|
|
311
|
+
"})()"
|
|
312
|
+
);
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async setCity(city) {
|
|
317
|
+
console.log('📍 设置城市: ' + city);
|
|
318
|
+
const safeCity = city.replace(/'/g, "\\'");
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
console.log(' 1. 获取城市输入框位置...');
|
|
322
|
+
const inputPos = await this.evaluate(
|
|
323
|
+
"(function() {" +
|
|
324
|
+
" const iframe = document.querySelector('iframe');" +
|
|
325
|
+
" if (!iframe) return null;" +
|
|
326
|
+
" const doc = iframe.contentDocument;" +
|
|
327
|
+
" const input = doc.querySelector('.city-wrap .search-city-kw input');" +
|
|
328
|
+
" if (!input) return null;" +
|
|
329
|
+
" const rect = input.getBoundingClientRect();" +
|
|
330
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
331
|
+
" return {" +
|
|
332
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
333
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
334
|
+
" };" +
|
|
335
|
+
"})()"
|
|
336
|
+
);
|
|
337
|
+
if (!inputPos) {
|
|
338
|
+
console.log(' ❌ 找不到城市输入框');
|
|
339
|
+
return { error: 'input not found' };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log(' 2. 点击输入框...');
|
|
343
|
+
await this._hover(inputPos.x, inputPos.y);
|
|
344
|
+
await this.sleep(100);
|
|
345
|
+
await this._click(inputPos.x, inputPos.y);
|
|
346
|
+
await this.sleep(300);
|
|
347
|
+
|
|
348
|
+
console.log(' 3. 输入城市名称: ' + city);
|
|
349
|
+
await this.evaluate(
|
|
350
|
+
"(function() {" +
|
|
351
|
+
" const iframe = document.querySelector('iframe');" +
|
|
352
|
+
" if (!iframe) return;" +
|
|
353
|
+
" const doc = iframe.contentDocument;" +
|
|
354
|
+
" const input = doc.querySelector('.city-wrap .search-city-kw input');" +
|
|
355
|
+
" if (input) {" +
|
|
356
|
+
" input.value = '" + safeCity + "';" +
|
|
357
|
+
" input.dispatchEvent(new Event('input', { bubbles: true }));" +
|
|
358
|
+
" }" +
|
|
359
|
+
"})()"
|
|
360
|
+
);
|
|
361
|
+
await this.sleep(800);
|
|
362
|
+
|
|
363
|
+
console.log(' 4. 检查搜索结果下拉...');
|
|
364
|
+
const searchResult = await this.evaluate(
|
|
365
|
+
"(function() {" +
|
|
366
|
+
" const iframe = document.querySelector('iframe');" +
|
|
367
|
+
" if (!iframe) return { error: 'no iframe' };" +
|
|
368
|
+
" const doc = iframe.contentDocument;" +
|
|
369
|
+
" const cityBox = doc.querySelector('.city-box');" +
|
|
370
|
+
" if (!cityBox) return { notFound: true };" +
|
|
371
|
+
" const searchResultC = cityBox.querySelector('.search-result-C');" +
|
|
372
|
+
" if (!searchResultC) return { notFound: true };" +
|
|
373
|
+
" const items = searchResultC.querySelectorAll('.search-result-item');" +
|
|
374
|
+
" if (items.length === 0) return { notFound: true };" +
|
|
375
|
+
" const firstText = items[0].textContent ? items[0].textContent.trim() : '';" +
|
|
376
|
+
" if (firstText.includes('暂无结果') || firstText.includes('无结果')) {" +
|
|
377
|
+
" return { needFallback: true };" +
|
|
378
|
+
" }" +
|
|
379
|
+
" return {" +
|
|
380
|
+
" found: true," +
|
|
381
|
+
" items: Array.from(items).slice(0, 5).map(function(i) {" +
|
|
382
|
+
" return { text: i.textContent ? i.textContent.trim() : '' };" +
|
|
383
|
+
" })" +
|
|
384
|
+
" };" +
|
|
385
|
+
"})()"
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
if (searchResult && searchResult.found) {
|
|
389
|
+
console.log(' 5. 找到下拉选项:', searchResult.items.map(function(i) { return i.text; }).join(', '));
|
|
390
|
+
|
|
391
|
+
const targetItem = searchResult.items.find(function(item) { return item.text === city; });
|
|
392
|
+
if (targetItem) {
|
|
393
|
+
console.log(' 6. 点击目标城市: ' + city);
|
|
394
|
+
await this.evaluate(
|
|
395
|
+
"(function() {" +
|
|
396
|
+
" const iframe = document.querySelector('iframe');" +
|
|
397
|
+
" if (!iframe) return;" +
|
|
398
|
+
" const doc = iframe.contentDocument;" +
|
|
399
|
+
" const cityBox = doc.querySelector('.city-box');" +
|
|
400
|
+
" if (cityBox) {" +
|
|
401
|
+
" const searchResultC = cityBox.querySelector('.search-result-C');" +
|
|
402
|
+
" if (searchResultC) {" +
|
|
403
|
+
" const items = searchResultC.querySelectorAll('.search-result-item');" +
|
|
404
|
+
" for (let i = 0; i < items.length; i++) {" +
|
|
405
|
+
" const text = items[i].textContent ? items[i].textContent.trim() : '';" +
|
|
406
|
+
" if (text === '" + safeCity + "') {" +
|
|
407
|
+
" items[i].click();" +
|
|
408
|
+
" return;" +
|
|
409
|
+
" }" +
|
|
410
|
+
" }" +
|
|
411
|
+
" }" +
|
|
412
|
+
" }" +
|
|
413
|
+
"})()"
|
|
414
|
+
);
|
|
415
|
+
await this.sleep(500);
|
|
416
|
+
|
|
417
|
+
const currentCity = await this.getCurrentCity();
|
|
418
|
+
if (currentCity === city) {
|
|
419
|
+
console.log(' ✅ 城市已选择: ' + city);
|
|
420
|
+
return { success: true, city: city };
|
|
421
|
+
} else {
|
|
422
|
+
console.log(' ⚠️ 验证失败,当前城市: ' + currentCity);
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
console.log(' ⚠️ 下拉中没有目标城市,启用fallback...');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (searchResult && searchResult.needFallback) {
|
|
430
|
+
console.log(' 5. 没有搜索结果,启用fallback...');
|
|
431
|
+
} else {
|
|
432
|
+
console.log(' 5. 下拉未出现,启用fallback...');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
for (let retry = 0; retry < 3; retry++) {
|
|
436
|
+
console.log(' 6. 清空输入框...');
|
|
437
|
+
await this.evaluate(
|
|
438
|
+
"(function() {" +
|
|
439
|
+
" const iframe = document.querySelector('iframe');" +
|
|
440
|
+
" if (!iframe) return;" +
|
|
441
|
+
" const doc = iframe.contentDocument;" +
|
|
442
|
+
" const input = doc.querySelector('.city-wrap .search-city-kw input');" +
|
|
443
|
+
" if (input) {" +
|
|
444
|
+
" input.value = '';" +
|
|
445
|
+
" input.dispatchEvent(new Event('input', { bubbles: true }));" +
|
|
446
|
+
" }" +
|
|
447
|
+
"})()"
|
|
448
|
+
);
|
|
449
|
+
await this.sleep(500);
|
|
450
|
+
|
|
451
|
+
console.log(' 7. 点击空div触发省份下拉...');
|
|
452
|
+
const emptyDivPos = await this.evaluate(
|
|
453
|
+
"(function() {" +
|
|
454
|
+
" const iframe = document.querySelector('iframe');" +
|
|
455
|
+
" if (!iframe) return null;" +
|
|
456
|
+
" const doc = iframe.contentDocument;" +
|
|
457
|
+
" const searchCityKw = doc.querySelector('.search-city-kw');" +
|
|
458
|
+
" if (!searchCityKw) return null;" +
|
|
459
|
+
" const divs = searchCityKw.querySelectorAll('div');" +
|
|
460
|
+
" for (let i = 0; i < divs.length; i++) {" +
|
|
461
|
+
" const div = divs[i];" +
|
|
462
|
+
" if (!div.className && div.innerHTML.trim() === '') {" +
|
|
463
|
+
" const rect = div.getBoundingClientRect();" +
|
|
464
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
465
|
+
" return {" +
|
|
466
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
467
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
468
|
+
" };" +
|
|
469
|
+
" }" +
|
|
470
|
+
" }" +
|
|
471
|
+
" return null;" +
|
|
472
|
+
"})()"
|
|
473
|
+
);
|
|
474
|
+
if (emptyDivPos) {
|
|
475
|
+
await this._hover(emptyDivPos.x, emptyDivPos.y);
|
|
476
|
+
await this.sleep(100);
|
|
477
|
+
await this._click(emptyDivPos.x, emptyDivPos.y);
|
|
478
|
+
await this.sleep(800);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log(' 8. Hover "热门"...');
|
|
482
|
+
const hotPos = await this.evaluate(
|
|
483
|
+
"(function() {" +
|
|
484
|
+
" const iframe = document.querySelector('iframe');" +
|
|
485
|
+
" if (!iframe) return null;" +
|
|
486
|
+
" const doc = iframe.contentDocument;" +
|
|
487
|
+
" const dropdownProvince = doc.querySelector('.dropdown-province');" +
|
|
488
|
+
" if (!dropdownProvince) return null;" +
|
|
489
|
+
" const items = dropdownProvince.querySelectorAll('li');" +
|
|
490
|
+
" for (let i = 0; i < items.length; i++) {" +
|
|
491
|
+
" const text = items[i].textContent ? items[i].textContent.trim() : '';" +
|
|
492
|
+
" if (text.startsWith('热门')) {" +
|
|
493
|
+
" const rect = items[i].getBoundingClientRect();" +
|
|
494
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
495
|
+
" return {" +
|
|
496
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
497
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
498
|
+
" };" +
|
|
499
|
+
" }" +
|
|
500
|
+
" }" +
|
|
501
|
+
" return null;" +
|
|
502
|
+
"})()"
|
|
503
|
+
);
|
|
504
|
+
if (hotPos) {
|
|
505
|
+
await this._hover(hotPos.x, hotPos.y);
|
|
506
|
+
await this.sleep(800);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
console.log(' 9. 点击"全国"...');
|
|
510
|
+
const nationwidePos = await this.evaluate(
|
|
511
|
+
"(function() {" +
|
|
512
|
+
" const iframe = document.querySelector('iframe');" +
|
|
513
|
+
" if (!iframe) return null;" +
|
|
514
|
+
" const doc = iframe.contentDocument;" +
|
|
515
|
+
" const dropdownCity = doc.querySelector('.dropdown-city');" +
|
|
516
|
+
" if (!dropdownCity) return null;" +
|
|
517
|
+
" const items = dropdownCity.querySelectorAll('li');" +
|
|
518
|
+
" for (let i = 0; i < items.length; i++) {" +
|
|
519
|
+
" const text = items[i].textContent ? items[i].textContent.trim() : '';" +
|
|
520
|
+
" if (text === '全国') {" +
|
|
521
|
+
" const rect = items[i].getBoundingClientRect();" +
|
|
522
|
+
" const iframeRect = iframe.getBoundingClientRect();" +
|
|
523
|
+
" return {" +
|
|
524
|
+
" x: rect.left + iframeRect.left + rect.width / 2," +
|
|
525
|
+
" y: rect.top + iframeRect.top + rect.height / 2" +
|
|
526
|
+
" };" +
|
|
527
|
+
" }" +
|
|
528
|
+
" }" +
|
|
529
|
+
" return null;" +
|
|
530
|
+
"})()"
|
|
531
|
+
);
|
|
532
|
+
if (nationwidePos) {
|
|
533
|
+
await this._click(nationwidePos.x, nationwidePos.y);
|
|
534
|
+
await this.sleep(800);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const currentCity = await this.getCurrentCity();
|
|
538
|
+
if (currentCity === '全国') {
|
|
539
|
+
console.log(' ✅ 已选择: 全国 (fallback)');
|
|
540
|
+
return { success: true, city: '全国' };
|
|
541
|
+
} else {
|
|
542
|
+
console.log(' ⚠️ 第' + (retry + 1) + '次尝试失败,当前城市: ' + currentCity);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
console.log(' ❌ fallback失败,无法选择全国');
|
|
547
|
+
return { error: 'fallback failed' };
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.log(' 设置城市时出错:', e.message);
|
|
550
|
+
return { error: e.message };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async clickSearch() {
|
|
555
|
+
console.log('🚀 执行搜索...');
|
|
556
|
+
try {
|
|
557
|
+
console.log(' [DEBUG] 搜索前检查下拉状态...');
|
|
558
|
+
const beforeSearch = await this.evaluate(
|
|
559
|
+
'(function() {' +
|
|
560
|
+
' const iframe = document.querySelector("iframe");' +
|
|
561
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
562
|
+
' const doc = iframe.contentWindow.document;' +
|
|
563
|
+
' const dropdown = doc.querySelector(".search-result-C");' +
|
|
564
|
+
' const dropdownVisible = dropdown && dropdown.offsetParent !== null;' +
|
|
565
|
+
' const input = doc.querySelector("input.search-input");' +
|
|
566
|
+
' return {' +
|
|
567
|
+
' dropdownVisible: dropdownVisible,' +
|
|
568
|
+
' inputValue: input ? input.value : null' +
|
|
569
|
+
' };' +
|
|
570
|
+
'})()'
|
|
571
|
+
);
|
|
572
|
+
console.log(' [DEBUG] 搜索前状态:', JSON.stringify(beforeSearch));
|
|
573
|
+
|
|
574
|
+
const result = await this.evaluate(
|
|
575
|
+
'(function() {' +
|
|
576
|
+
' const iframe = document.querySelector("iframe");' +
|
|
577
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
578
|
+
' const doc = iframe.contentWindow.document;' +
|
|
579
|
+
' const searchIcon = doc.querySelector(".icon-search");' +
|
|
580
|
+
' if (searchIcon) {' +
|
|
581
|
+
' searchIcon.click();' +
|
|
582
|
+
' return { success: true, method: "icon" };' +
|
|
583
|
+
' }' +
|
|
584
|
+
' return { error: "search icon not found" };' +
|
|
585
|
+
'})()'
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
console.log(' [DEBUG] 搜索点击完成,等待1500ms...');
|
|
589
|
+
await this.sleep(1500);
|
|
590
|
+
|
|
591
|
+
console.log(' [DEBUG] 搜索后检查下拉状态...');
|
|
592
|
+
const afterSearch = await this.evaluate(
|
|
593
|
+
'(function() {' +
|
|
594
|
+
' const iframe = document.querySelector("iframe");' +
|
|
595
|
+
' if (!iframe || !iframe.contentWindow) return { error: "no iframe" };' +
|
|
596
|
+
' const doc = iframe.contentWindow.document;' +
|
|
597
|
+
' const dropdown = doc.querySelector(".search-result-C");' +
|
|
598
|
+
' const dropdownVisible = dropdown && dropdown.offsetParent !== null;' +
|
|
599
|
+
' const input = doc.querySelector("input.search-input");' +
|
|
600
|
+
' let dropdownInfo = { visible: dropdownVisible };' +
|
|
601
|
+
' if (dropdownVisible && dropdown) {' +
|
|
602
|
+
' const items = dropdown.querySelectorAll(".search-result-item");' +
|
|
603
|
+
' dropdownInfo.itemCount = items.length;' +
|
|
604
|
+
' }' +
|
|
605
|
+
' return {' +
|
|
606
|
+
' dropdownInfo: dropdownInfo,' +
|
|
607
|
+
' inputValue: input ? input.value : null' +
|
|
608
|
+
' };' +
|
|
609
|
+
'})()'
|
|
610
|
+
);
|
|
611
|
+
console.log(' [DEBUG] 搜索后状态:', JSON.stringify(afterSearch));
|
|
612
|
+
|
|
613
|
+
console.log('✅ 搜索已执行');
|
|
614
|
+
return result;
|
|
615
|
+
} catch (e) {
|
|
616
|
+
console.log(' 执行搜索时出错:', e.message);
|
|
617
|
+
return { error: e.message };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async getResults() {
|
|
622
|
+
console.log('📋 获取搜索结果...');
|
|
623
|
+
try {
|
|
624
|
+
const results = await this.evaluate(
|
|
625
|
+
'(function() {' +
|
|
626
|
+
' const iframe = document.querySelector("iframe");' +
|
|
627
|
+
' if (!iframe || !iframe.contentWindow) return [];' +
|
|
628
|
+
' const doc = iframe.contentWindow.document;' +
|
|
629
|
+
' const cards = doc.querySelectorAll(".geek-info-card");' +
|
|
630
|
+
' const list = [];' +
|
|
631
|
+
' cards.forEach(function(card, idx) {' +
|
|
632
|
+
' const nameEl = card.querySelector(".name");' +
|
|
633
|
+
' const infoEl = card.querySelector(".info");' +
|
|
634
|
+
' const expectEl = card.querySelector(".expect-salary");' +
|
|
635
|
+
' const name = nameEl ? nameEl.textContent.trim() : "";' +
|
|
636
|
+
' const info = infoEl ? infoEl.textContent.trim() : "";' +
|
|
637
|
+
' const expect = expectEl ? expectEl.textContent.trim() : "";' +
|
|
638
|
+
' list.push({' +
|
|
639
|
+
' index: idx + 1,' +
|
|
640
|
+
' name: name,' +
|
|
641
|
+
' info: info,' +
|
|
642
|
+
' expect: expect' +
|
|
643
|
+
' });' +
|
|
644
|
+
' });' +
|
|
645
|
+
' return list;' +
|
|
646
|
+
'})()'
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
console.log('✅ 找到 ' + results.length + ' 个候选人');
|
|
650
|
+
results.slice(0, 10).forEach(function(r) {
|
|
651
|
+
console.log(' ' + r.index + '. ' + r.name + ' - ' + r.expect + ' - ' + (r.info ? r.info.substring(0, 30) : ''));
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
return results;
|
|
655
|
+
} catch (e) {
|
|
656
|
+
console.log(' 获取结果时出错:', e.message);
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async disconnect() {
|
|
662
|
+
if (this.client) {
|
|
663
|
+
await this.client.close();
|
|
664
|
+
console.log('已断开连接');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|