匹配范围
*://we.51job.com/*51job-hot-jobs用途 在前程无忧公开页面读取无关键词推荐职位,适合按城市、分页和排序获取轻量职位列表。 执行前提 需要先打开 https://we.51job.com/ 或其他 we.51job.com 页面后执行。 脚本在浏览器页面主世界请求公开搜索接口,关键词固定为空。若页面被站点风控、验证码或网络策略拦截,可能返回错误;这种情况下先在当前标签页正常完成页面访问后再执…
*://we.51job.com/*dom.read1return await (async () => {2 const input = readParams();3 const page = readInteger(input.page, 1, 1, 100, "page");4 const limit = readInteger(input.limit, 10, 1, 50, "limit");5 const sortType = readSortType(input.sortType == null ? input.sort : input.sortType);6 const areaInfo = resolveArea(input);7 8 const data = await fetchJobSearch({9 keyword: "",10 areaCode: areaInfo.areaCode,11 page,12 limit,13 sortType,14 });15 16 const body = data.resultbody || data.resultBody || data.data || {};17 const jobBlock = body.job || body.jobs || body.searchResult || {};18 const items = firstArray([19 jobBlock.items,20 jobBlock.jobList,21 jobBlock.list,22 body.items,23 body.jobList,24 body.list,25 ]);26 const total = readTotal(jobBlock, body, items.length);27 const jobs = items.slice(0, limit).map(normalizeJob);28 29 return {30 area: areaInfo.area,31 areaCode: areaInfo.areaCode,32 page,33 limit,34 sortType,35 total,36 count: jobs.length,37 jobs,38 };39 40 function readParams() {41 const value = typeof params === "undefined" ? {} : params;42 if (value == null) {43 return {};44 }45 if (typeof value !== "object" || Array.isArray(value)) {46 throw new Error("params must be an object.");47 }48 return value;49 }50 51 function fetchJobSearch(options) {52 const query = new URLSearchParams({53 api_key: "51job",54 timestamp: String(Date.now()),55 keyword: options.keyword,56 searchType: "2",57 function: "",58 industry: "",59 jobArea: options.areaCode,60 jobArea2: "",61 landmark: "",62 metro: "",63 salary: "",64 workYear: "",65 degree: "",66 companyType: "",67 companySize: "",68 jobType: "",69 issueDate: "",70 sortType: options.sortType,71 pageNum: String(options.page),72 pageSize: String(options.limit),73 source: "1",74 accountId: "",75 pageCode: "sou|sou|soulb",76 });77 78 const url = "https://we.51job.com/api/job/search-pc?" + query.toString();79 return fetch(url, {80 method: "GET",81 credentials: "include",82 headers: {83 accept: "application/json, text/plain, */*",84 },85 }).then(async (response) => {86 const text = await response.text();87 if (!response.ok) {88 throw new Error("Request failed with HTTP " + response.status + ".");89 }90 91 let json;92 try {93 json = JSON.parse(text);94 } catch (error) {95 throw new Error("The site returned a non-JSON response. Open a normal page and try again.");96 }97 98 if (json && json.status != null && String(json.status) !== "1") {99 const message = toText(json.message || json.msg || json.error) || "The search API returned an error.";100 throw new Error(message);101 }102 return json;103 });104 }105 106 function resolveArea(value) {107 const area = toText(value.area == null ? "全国" : value.area) || "全国";108 const explicitCode = toText(value.areaCode == null ? value.jobArea : value.areaCode);109 if (isAreaCode(explicitCode)) {110 return { area, areaCode: explicitCode };111 }112 if (isAreaCode(area)) {113 return { area, areaCode: area };114 }115 116 const areaCodes = {117 全国: "000000",118 北京: "010000",119 上海: "020000",120 广州: "030200",121 深圳: "040000",122 天津: "050000",123 重庆: "060000",124 南京: "070200",125 苏州: "070300",126 杭州: "080200",127 宁波: "080300",128 成都: "090200",129 厦门: "110300",130 青岛: "120300",131 合肥: "150200",132 郑州: "170200",133 武汉: "180200",134 长沙: "190200",135 西安: "200200",136 };137 if (!areaCodes[area]) {138 throw new Error("Unsupported area. Use areaCode with the site's six-digit area code.");139 }140 return { area, areaCode: areaCodes[area] };141 }142 143 function isAreaCode(value) {144 return /^\d{6}(,\d{6})*$/.test(value);145 }146 147 function readSortType(value) {148 const text = toText(value == null ? "0" : value).toLowerCase();149 const map = {150 "0": "0",151 default: "0",152 relevance: "0",153 related: "0",154 "1": "1",155 latest: "1",156 newest: "1",157 date: "1",158 };159 if (!map[text]) {160 throw new Error("sortType must be 0/default/relevance or 1/latest/date.");161 }162 return map[text];163 }164 165 function normalizeJob(item) {166 const jobId = firstText(item, ["jobid", "jobId", "jobID", "job_id", "id"]);167 const detailUrl = firstText(item, ["jobHref", "jobUrl", "jobURL", "url", "href"]);168 return {169 jobId,170 jobName: firstText(item, ["jobName", "jobname", "name", "title"]),171 companyName: firstText(item, ["companyName", "companyname", "coname", "company"]),172 city: firstText(item, ["jobAreaString", "jobArea", "workArea", "workarea_text", "cityString", "city"]),173 salary: firstText(item, ["provideSalaryString", "providesalary_text", "salary", "salaryText"]),174 workYear: firstText(item, ["workYearString", "workyearString", "workyear", "workYear"]),175 degree: firstText(item, ["degreeString", "degreefrom", "degree", "education"]),176 companyType: firstText(item, ["companyTypeString", "companytype_text", "companyType"]),177 companySize: firstText(item, ["companySizeString", "companysize_text", "companySize"]),178 industry: firstText(item, ["companyIndustryType1Str", "industryType1Str", "industry", "companyIndustry"]),179 welfareTags: firstArray([item.jobTags, item.attribute_text, item.welfareTags, item.welfare]).map(toText).filter(Boolean),180 publishTime: firstText(item, ["issuedate", "issueDate", "publishTime", "releaseTime"]),181 updateTime: firstText(item, ["updateDateTime", "updatedate", "updateTime", "updatetime"]),182 detailUrl,183 };184 }185 186 function firstArray(values) {187 for (const value of values) {188 if (Array.isArray(value)) {189 return value;190 }191 }192 return [];193 }194 195 function firstText(item, keys) {196 for (const key of keys) {197 if (item && item[key] != null && item[key] !== "") {198 return toText(item[key]);199 }200 }201 return "";202 }203 204 function readTotal(jobBlock, body, fallback) {205 const candidates = [206 jobBlock.totalcount,207 jobBlock.totalCount,208 jobBlock.total,209 body.totalcount,210 body.totalCount,211 body.total,212 ];213 for (const value of candidates) {214 const number = Number(value);215 if (Number.isFinite(number) && number >= 0) {216 return number;217 }218 }219 return fallback;220 }221 222 function readInteger(value, defaultValue, min, max, name) {223 const number = value == null || value === "" ? defaultValue : Number(value);224 if (!Number.isInteger(number) || number < min || number > max) {225 throw new Error(name + " must be an integer between " + min + " and " + max + ".");226 }227 return number;228 }229 230 function toText(value) {231 if (value == null) {232 return "";233 }234 if (Array.isArray(value)) {235 return value.map(toText).filter(Boolean).join(" ");236 }237 return String(value).replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();238 }239})();