// ==UserScript==
// @name BOOS岗位下载器
// @namespace bossXiaZaiGongZuo
// @version 1.0.0
// @author TING软件科技
// @description 【2025】【长期维护】一键批量抓取并导出BOSS直聘职位信息,让求职数据分析变得更简单!反馈问题或建议请加QQ群:685904930
// @license AGPL v3
// @icon https://u2233.vip/favicon.ico
// @match https://www.zhipin.com/web/geek/jobs*
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.global.prod.js
// @require data:application/javascript,%3Bwindow.Vue%20%3D%20Vue%3B
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/index.full.min.js
// @connect wan.baidu.com
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @noframes
// ==/UserScript==
(t=>{if(typeof GM_addStyle=="function"){GM_addStyle(t);return}const e=document.createElement("style");e.textContent=t,document.head.append(e)})(" #AppBtn[data-v-6221d9fc]{position:fixed;bottom:100px;right:100px}#tingApp[data-v-5ec82447]{position:fixed;z-index:99999} ");
(function (vue, ElementPlus) {
'use strict';
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$1 = {
__name: "AppBtn",
setup(__props) {
return (_ctx, _cache) => {
return vue.openBlock(), vue.createBlock(vue.unref(ElementPlus.ElButton), {
type: "primary",
id: "AppBtn",
size: "large"
}, {
default: vue.withCtx(() => [
vue.renderSlot(_ctx.$slots, "default", {}, void 0, true)
]),
_: 3
});
};
}
};
const AppBtn = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-6221d9fc"]]);
const _hoisted_1 = { style: { "margin-bottom": "20px" } };
const _hoisted_2 = { key: 0 };
const _hoisted_3 = { style: { "display": "flex", "flex-wrap": "wrap", "gap": "4px" } };
const _hoisted_4 = { style: { "display": "flex", "flex-wrap": "wrap", "gap": "4px" } };
const _hoisted_5 = { style: { "margin-top": "20px", "display": "flex", "justify-content": "space-between", "align-items": "center" } };
const _hoisted_6 = { key: 1 };
const _sfc_main = {
__name: "App",
setup(__props) {
let drawer = vue.ref(false);
const jobs = vue.ref([]);
const currentPage = vue.ref(1);
const pageSize = vue.ref(50);
const downloading = vue.ref(false);
const autoDetect = vue.ref(false);
let autoDetectTimer = null;
const totalPages = vue.computed(() => {
return Math.ceil(jobs.value.length / pageSize.value);
});
const paginatedJobs = vue.computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return jobs.value.slice(start, end);
});
const toggleAutoDetect = () => {
autoDetect.value = !autoDetect.value;
if (autoDetect.value) {
ElementPlus.ElMessage.success("已开启自动爬取岗位功能");
getJobs();
autoDetectTimer = setInterval(() => {
getJobs();
}, 3e3);
} else {
stopInterval();
ElementPlus.ElMessage.info("已暂停爬取岗位功能");
}
};
const stopInterval = () => {
if (autoDetectTimer) {
clearInterval(autoDetectTimer);
autoDetectTimer = null;
}
};
const getJobs = () => {
try {
const jobContainer = document.querySelector("#wrap > div.page-jobs-main");
if (!jobContainer) {
if (autoDetect.value) {
console.log("未找到岗位列表容器");
} else {
ElementPlus.ElMessage.error("未找到岗位列表容器,请确保在BOSS直聘岗位搜索页面使用");
}
return;
}
const vueInstance = jobContainer.__vue__;
if (!vueInstance || !vueInstance.jobList) {
if (autoDetect.value) {
console.log("无法爬取岗位数据");
} else {
ElementPlus.ElMessage.error("无法爬取岗位数据,请刷新页面后重试");
}
return;
}
const rawJobs = vueInstance.jobList || [];
console.log("原始岗位数据:", rawJobs);
const formattedJobs = rawJobs.map((job) => {
return {
jobId: job.jobId,
encryptJobId: job.encryptJobId,
jobName: job.jobName,
brandName: job.brandName,
salaryDesc: job.salaryDesc,
jobLabels: job.jobLabels || [],
skills: job.skills || [],
areaDistrict: job.areaDistrict,
jobExperience: job.jobExperience,
jobDegree: job.jobDegree,
cityName: job.cityName,
brandLogo: job.brandLogo,
brandStageName: job.brandStageName,
brandIndustry: job.brandIndustry,
brandScaleName: job.brandScaleName,
bossName: job.bossName,
bossTitle: job.bossTitle,
bossAvatar: job.bossAvatar,
goldHunter: job.goldHunter,
jobUrl: `https://www.zhipin.com/job_detail/${job.encryptJobId}.html`,
securityId: job.securityId,
lid: job.lid
};
});
const uniqueJobs = [];
const jobIds = /* @__PURE__ */ new Set();
formattedJobs.forEach((job) => {
if (!jobIds.has(job.encryptJobId)) {
jobIds.add(job.encryptJobId);
uniqueJobs.push(job);
}
});
if (jobs.value.length === uniqueJobs.length) {
ElementPlus.ElNotification({
title: "当前页面没有数据了~",
type: "success",
duration: 5e3
});
autoDetect.value = false;
stopInterval();
return;
}
jobs.value = uniqueJobs;
if (autoDetect.value) {
console.log(`自动检测: 爬取到 ${uniqueJobs.length} 个岗位信息`);
window.scrollTo({
top: document.body.scrollHeight,
behavior: "smooth"
});
} else {
ElementPlus.ElNotification({
title: "爬取成功",
message: `成功爬取到 ${uniqueJobs.length} 个岗位信息`,
type: "success"
});
}
} catch (error) {
console.error("爬取岗位信息失败:", error);
if (!autoDetect.value) {
ElementPlus.ElMessage.error("爬取岗位信息失败: " + error.message);
}
}
};
const downloadAll = async () => {
if (jobs.value.length === 0) {
ElementPlus.ElMessage.warning("没有岗位信息可下载");
return;
}
downloading.value = true;
try {
exportToCSV(jobs.value);
ElementPlus.ElNotification({
title: "下载成功",
message: `成功下载 ${jobs.value.length} 个岗位信息`,
type: "success"
});
} catch (error) {
console.error("下载失败:", error);
ElementPlus.ElMessage.error("下载失败: " + error.message);
} finally {
downloading.value = false;
}
};
const exportToCSV = (jobsData) => {
const headers = [
"职位名称",
"公司名称",
"薪资",
"城市",
"地区",
"经验要求",
"学历要求",
"技能要求",
"标签",
"公司行业",
"公司规模",
"HR姓名",
"HR职位",
"职位链接"
];
const chunkSize = 1e3;
const chunks = [];
for (let i = 0; i < jobsData.length; i += chunkSize) {
chunks.push(jobsData.slice(i, i + chunkSize));
}
const csvChunks = chunks.map((chunk) => {
return chunk.map(
(job) => [
`"${escapeCSVField(job.jobName || "")}"`,
`"${escapeCSVField(job.brandName || "")}"`,
`"${escapeCSVField(job.salaryDesc || "")}"`,
`"${escapeCSVField(job.cityName || "")}"`,
`"${escapeCSVField(job.areaDistrict || "")}"`,
`"${escapeCSVField(job.jobExperience || "")}"`,
`"${escapeCSVField(job.jobDegree || "")}"`,
`"${escapeCSVField((job.skills || []).join("|"))}"`,
`"${escapeCSVField((job.jobLabels || []).join("|"))}"`,
`"${escapeCSVField(job.brandIndustry || "")}"`,
`"${escapeCSVField(job.brandScaleName || "")}"`,
`"${escapeCSVField(job.bossName || "")}"`,
`"${escapeCSVField(job.bossTitle || "")}"`,
`"${escapeCSVField(job.jobUrl || "")}"`
].join(",")
);
});
const csvContent = [headers.join(","), ...csvChunks.flat()].join("\n");
const blob = new Blob(["\uFEFF" + csvContent], {
type: "text/csv;charset=utf-8;"
});
const url = URL.createObjectURL(blob);
const link2 = document.createElement("a");
link2.setAttribute("href", url);
link2.setAttribute(
"download",
`BOSS岗位信息_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`
);
link2.style.visibility = "hidden";
document.body.appendChild(link2);
link2.click();
document.body.removeChild(link2);
};
const escapeCSVField = (field) => {
if (typeof field !== "string") return field;
return field.replace(/"/g, '""');
};
vue.onUnmounted(() => {
if (autoDetectTimer) {
clearInterval(autoDetectTimer);
}
});
return (_ctx, _cache) => {
const _component_el_button = vue.resolveComponent("el-button");
const _component_el_alert = vue.resolveComponent("el-alert");
const _component_el_table_column = vue.resolveComponent("el-table-column");
const _component_el_link = vue.resolveComponent("el-link");
const _component_el_tag = vue.resolveComponent("el-tag");
const _component_el_table = vue.resolveComponent("el-table");
const _component_el_pagination = vue.resolveComponent("el-pagination");
const _component_el_drawer = vue.resolveComponent("el-drawer");
return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
vue.createVNode(AppBtn, {
onClick: _cache[0] || (_cache[0] = ($event) => vue.isRef(drawer) ? drawer.value = !vue.unref(drawer) : drawer = !vue.unref(drawer))
}, {
default: vue.withCtx(() => _cache[3] || (_cache[3] = [
vue.createTextVNode("打 开 插 件", -1)
])),
_: 1,
__: [3]
}),
vue.createVNode(_component_el_drawer, {
modelValue: vue.unref(drawer),
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => vue.isRef(drawer) ? drawer.value = $event : drawer = $event),
title: "BOSS岗位信息下载工具",
size: "80%"
}, {
default: vue.withCtx(() => [
vue.createElementVNode("div", _hoisted_1, [
vue.createVNode(_component_el_button, {
onClick: toggleAutoDetect,
type: autoDetect.value ? "danger" : "primary"
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(autoDetect.value ? "暂停爬取" : "爬取【当前页】岗位"), 1)
]),
_: 1
}, 8, ["type"]),
vue.createVNode(_component_el_button, {
onClick: downloadAll,
type: "success",
loading: downloading.value,
disabled: jobs.value.length === 0,
style: { "margin-left": "20px" }
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(downloading.value ? "下载中..." : `下载全部岗位 (${jobs.value.length}个)`), 1)
]),
_: 1
}, 8, ["loading", "disabled"])
]),
vue.createVNode(_component_el_alert, {
title: "免责声明",
type: "error",
description: "本插件仅用于学习和技术研究目的,不得用于任何商业用途。使用本插件产生的任何后果均由使用者自行承担,开发者不承担任何责任。",
"show-icon": "",
style: { "margin-bottom": "20px" },
closable: ""
}),
jobs.value.length > 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2, [
vue.createVNode(_component_el_alert, {
title: `共爬取到 ${jobs.value.length} 个岗位信息,当前显示第 ${currentPage.value} 页,共 ${totalPages.value} 页`,
type: "success",
"show-icon": "",
style: { "margin-bottom": "20px" }
}, null, 8, ["title"]),
vue.createVNode(_component_el_table, {
data: paginatedJobs.value,
height: "500",
style: { "width": "100%" },
border: ""
}, {
default: vue.withCtx(() => [
vue.createVNode(_component_el_table_column, {
type: "index",
label: "#",
width: "60"
}, {
default: vue.withCtx((scope) => [
vue.createTextVNode(vue.toDisplayString((currentPage.value - 1) * pageSize.value + scope.$index + 1), 1)
]),
_: 1
}),
vue.createVNode(_component_el_table_column, {
prop: "jobName",
label: "职位名称",
"min-width": "150"
}, {
default: vue.withCtx((scope) => [
vue.createVNode(_component_el_link, {
href: scope.row.jobUrl,
target: "_blank",
type: "primary"
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(scope.row.jobName), 1)
]),
_: 2
}, 1032, ["href"])
]),
_: 1
}),
vue.createVNode(_component_el_table_column, {
prop: "brandName",
label: "公司名称",
"min-width": "120"
}),
vue.createVNode(_component_el_table_column, {
prop: "salaryDesc",
label: "薪资",
width: "100"
}),
vue.createVNode(_component_el_table_column, {
prop: "skills",
label: "技能要求",
"min-width": "200"
}, {
default: vue.withCtx((scope) => [
vue.createElementVNode("div", _hoisted_3, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(scope.row.skills, (skill) => {
return vue.openBlock(), vue.createBlock(_component_el_tag, {
key: skill,
size: "small"
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(skill), 1)
]),
_: 2
}, 1024);
}), 128))
])
]),
_: 1
}),
vue.createVNode(_component_el_table_column, {
prop: "jobLabels",
label: "标签",
"min-width": "150"
}, {
default: vue.withCtx((scope) => [
vue.createElementVNode("div", _hoisted_4, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(scope.row.jobLabels, (tag) => {
return vue.openBlock(), vue.createBlock(_component_el_tag, {
key: tag,
size: "small"
}, {
default: vue.withCtx(() => [
vue.createTextVNode(vue.toDisplayString(tag), 1)
]),
_: 2
}, 1024);
}), 128))
])
]),
_: 1
}),
vue.createVNode(_component_el_table_column, {
prop: "areaDistrict",
label: "地区",
width: "100"
}),
vue.createVNode(_component_el_table_column, {
prop: "jobExperience",
label: "经验要求",
width: "100"
}),
vue.createVNode(_component_el_table_column, {
prop: "jobDegree",
label: "学历要求",
width: "100"
})
]),
_: 1
}, 8, ["data"]),
vue.createElementVNode("div", _hoisted_5, [
vue.createVNode(_component_el_pagination, {
"current-page": currentPage.value,
"onUpdate:currentPage": _cache[1] || (_cache[1] = ($event) => currentPage.value = $event),
"page-size": pageSize.value,
total: jobs.value.length,
layout: "prev, pager, next, jumper",
background: "",
style: { "margin": "0 auto" }
}, null, 8, ["current-page", "page-size", "total"])
])
])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_6, [
vue.createVNode(_component_el_alert, {
title: "暂无岗位信息,点击上方按钮开始爬取岗位信息",
type: "info",
"show-icon": ""
})
]))
]),
_: 1
}, 8, ["modelValue"])
], 64);
};
}
};
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5ec82447"]]);
var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
window.GM_xmlhttpRequest = _GM_xmlhttpRequest;
window.GM_setValue = _GM_setValue;
window.GM_getValue = _GM_getValue;
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.jsdelivr.net/npm/[email protected]/dist/index.css";
document.head.appendChild(link);
const appContainer = document.createElement("div");
const appId = `tingApp`;
appContainer.id = appId;
document.body.appendChild(appContainer);
vue.createApp(App).use(ElementPlus).mount(appContainer);
})(Vue, ElementPlus);