您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
【2025】【长期维护】一键批量抓取并导出BOSS直聘职位信息,让求职数据分析变得更简单!反馈问题或建议请加QQ群:685904930
// ==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);