灵猴市集Linghou Market

前程无忧推荐职位

前程无忧51job-hot-jobs

用途 在前程无忧公开页面读取无关键词推荐职位,适合按城市、分页和排序获取轻量职位列表。 执行前提 需要先打开 https://we.51job.com/ 或其他 we.51job.com 页面后执行。 脚本在浏览器页面主世界请求公开搜索接口,关键词固定为空。若页面被站点风控、验证码或网络策略拦截,可能返回错误;这种情况下先在当前标签页正常完成页面访问后再执…

职位推荐招聘
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://we.51job.com/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1return 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})();