--- title: "03-jd_data_proc_api" created: 2026-04-02 tags: - 项目 aliases: - jd_data_proc_api --- # jd_data_proc_api.py ### `jd_data_proc_api.py` — 京东招聘直连 POST 接口拿职位 JSON,`generate_job_html` 把结构化数据拼成完整 HTML 落盘,无缝进入后续 ann_md → ann_model 解析流水线。 ## 代码 ```python import time import hashlib import os import requests from urllib.parse import urlencode import sys sys.path.append('../') import json from utils import ner_logger import re # 请求头配置,模拟浏览器访问京东招聘 headers = { "Accept": "*/*", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Language": "zh-CN,zh;q=0.9", "Cache-Control": "no-cache", "Connection": "keep-alive", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Origin": "https://zhaopin.jd.com", "Pragma": "no-cache", "Referer": "https://zhaopin.jd.com/web/job_info_list/3?isHunterFlag=false", "Sec-Ch-Ua": '"Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139"', "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": '"Windows"', "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36", "X-Requested-With": "XMLHttpRequest" } # 登录Cookie,保持会话状态 cookie_str = ('__jdu=17603403343142008747562; wlfstk_smdl=ub60hbvf0g2s8u5pyxjns2fb2l03gtvb; ' 'TrackID=1P4oDr4XPqwWzKwDWRu_4_UeOP6RnR4vGrsonKEeuEFZ89h3hL-1lsc5zllxYrBV6BhYxoo1nteMl5lLtIivBbVGDNM-dumEFbaafdxAD3Fo; ' 'thor=48A7AE1638066EA15412CB5251AEFC8107ABEA59BE4DD47C089393E91C56D975DD386AA83445D095271A06E91F6753A4EEBD41B4F7139D275E6095C88581B0A7D72022FB17DBF1F98F8E3C0D2B4042A28C10C72A2C585175B3F6BC568AC7F7C29A871F6356377A88EDE3B99DE048C52F959125F4DFD8E143FFE614221BAE4023630B1443AABDCA4052E0B2F67197B3E61C69EC748E0D5C3EB8B58C637AEEEF72; ' 'light_key=AASBKE7rOxgWQziEhC_QY6ya123kIBM_FU4iYdzu87Ek3y3AV9F3jFJOmjWGUk2f5PR2vrMW; ' 'pinId=C_LZUcdw8CuA_WidPC2E7w; pin=jd_VHpZKUJPelsF; unick=%E7%84%B6%E5%90%8E%E5%AE%87%E4%BD%A0%E7%9B%B8%E7%88%B1; ' 'ceshi3.com=000; _tp=CxbMgt%2F7gka5zyOGDzDRag%3D%3D; _pst=jd_VHpZKUJPelsF; ' 'zp.sso.tag=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJsb2dpbkJ5T2xkIiwiaWF0IjoxNzYwNTA3NTI5LCJleHAiOjE3NjMwOTk1Mjl9.xLvCNXREwucALBEzwF0jhkhtUbs5D1RKF-uXNY5jENMdUyROG-rMG2M4nW4ORfzN5Skia1IhotK2d0taowJv6w; ' 'unpl=JF8EAJtnNSttCkxXAx9SGxMRTQ5XW15fGx8LPTMMV1teHFwNGQcbGhR7XlVdWRRLFh9vYxRUWlNJVA4bAysSEHtdVV9fCUgRAWlgNWRaWEIZRElPKxEQe11Vbl0OTBYBamYHVlhaS1EGGgMSFBVNWVNdbQl7EANmVzVkXGhKZAQrSXUTXUtbU19fDUoVAWplBVFeWUpdAx4EHxUTe1xkXQ; ' '__jdv=29846306|lianmeng__10__www.google.com.hk|t_1003027376_|tuiguang|c6275a1007b242fa99ce8277f9934195|1760517069963; ' '3AB9D23F7A4B3CSS=jdd03LVXW7AQQE26DOABPQYAY3EB6DJSYADSWLWLTOHYDBJGHCUM25J27NOSGZVJ2J7V3QDYFLWKJ4NQRRH5PMSF2RI4F5YAAAAMZ437J7DIAAAAACHDPQLVTWZZ3RIX; ' 'shshshfpa=cefa655f-9611-dcc5-f04f-0d8be1ec8ad3-1756955929; shshshfpx=cefa655f-9611-dcc5-f04f-0d8be1ec8ad3-1756955929; ' 'flash=3_bhTmbWMtFMJfVp-BLo4k-ea4GVqbGnHBOtoZ5LkZEy6YPePdfLKfsIgUsHIJd1xFCCEEcUuxKBBoqPrxmykb3k7uaNtXC7Z1jn2gLb5BwoNiP6TV8Dp9mGTxtG9Dn9JG-hZAUa6jij9vCh1PYvsQN21NjlArUn1gSr356w_HCZY6PR5fsRJi; ' 'areaId=1; cn=0; shshshfpb=BApXSICP25fxADMtPZFYycUKGZPxaKjsJBhTUXgZp9xJ1MqY-pI62; ' 'ipLoc-djd=1-72-55653-0; 3AB9D23F7A4B3C9B=LVXW7AQQE26DOABPQYAY3EB6DJSYADSWLWLTOHYDBJGHCUM25J27NOSGZVJ2J7V3QDYFLWKJ4NQRRH5PMSF2RI4F5Y; ' 'sdtoken=AAbEsBpEIOVjqTAKCQtvQu17U36gG_rDiFgjEQvxDFInMeUOlnYGCFauvg-tNlP6uaZN4laBI8yNz85SBVKxd4YWIBo0ryEYCi80bbRO9W700UGvEMfO8Dk24zjLodef1Ya5g3YGMPtOnrnFTTD44HOTP4-ZHrpcRFdPVC-prb9u5Q; ' '__jda=176729966.17603403343142008747562.1760340334.1760516250.1760517070.4; ' '__jdb=176729966.3.17603403343142008747562|4.1760517070; __jdc=176729966; ' 'JSESSIONID=E058CC60933C75F58ADAE7FC146CD0DD.s1') headers["Cookie"] = cookie_str # 请求京东招聘接口,获取职位JSON数据 def get_jd_job_json(url, curPage): # 构造分页请求参数 payload_dict = { "pageIndex": curPage, "pageSize": 100, "workCityJson": "[]", "jobTypeJson": "[]", "jobSearch": "" } payload = urlencode(payload_dict) ner_logger.info(f"准备发送请求到 {url},页码: {curPage},请求体: {payload}") # 发送POST请求 with requests.Session() as s: resp = s.post(url, data=payload, headers=headers, timeout=15) ner_logger.info(f"收到响应,状态码: {resp.status_code},响应头: {dict(resp.headers)}") # 解析返回的JSON数据 try: if resp.status_code == 200: json_data = resp.json() ner_logger.info(f"成功解析JSON数据,数据结构: {type(json_data)},数据预览: {str(json_data)[:500]}") # 直接返回列表 if isinstance(json_data, list): data = json_data total = len(data) ner_logger.info(f"返回的是职位列表,数据量: {total}") return True, data, total # 包含 data 字段 elif 'data' in json_data: data = json_data.get('data', []) total = json_data.get('total', len(data)) ner_logger.info(f"返回的是包含data字段的结构,数据量: {len(data)},总数: {total}") return True, data, total # 包含 results 字段 elif 'results' in json_data: data = json_data.get('results', []) total = json_data.get('count', len(data)) ner_logger.info(f"返回的是包含results字段的结构,数据量: {len(data)},总数: {total}") return True, data, total # 其他格式 else: data = [json_data] if not isinstance(json_data, list) else json_data total = len(data) ner_logger.info(f"返回的是其他格式的数据,数据量: {total}") return True, data, total else: ner_logger.error(f"请求失败,状态码: {resp.status_code},响应内容: {resp.text}") return False, [], 0 except Exception as e: ner_logger.error(f"JSON解析错误: {str(e)},响应内容: {resp.text}") return False, [], 0 # 根据职位信息生成可视化HTML详情页 def generate_job_html(item, tmp_file): try: ner_logger.info(f"开始生成HTML文件,职位信息: {item}") # 提取职位关键字段 job_name = item.get('positionNameOpen', item.get('positionName', item.get('name', '职位名称'))) publish_time = item.get('formatPublishTime', item.get('publishTime', '')) work_city = item.get('workCity', '') job_type = item.get('jobType', '') department = item.get('positionDeptName', item.get('departmentName', '')) work_content = item.get('workContent', '暂无职位描述信息') qualification = item.get('qualification', '暂无职位要求信息') ner_logger.info(f"职位信息提取结果 - 名称: {job_name}, 发布时间: {publish_time}, 工作城市: {work_city}") # 拼接HTML内容 html_content = f""" {job_name}
{job_name}
发布时间: {publish_time} 工作地点: {work_city} 职位类型: {job_type}
所属部门: {department}
职位描述
{work_content}
职位要求
{qualification}
""" # 写入HTML文件 with open(tmp_file, "w", encoding="utf-8") as f: f.write(html_content) ner_logger.info(f"成功生成HTML文件: {tmp_file}") time.sleep(1) return True except Exception as e: ner_logger.error(f"生成HTML文件失败:{e}") return False # 将京东原始数据转为统一标准JSON def transform_job_json(item, job_type, channel, target_url, tmp_file, json_file): ner_logger.info(f"开始转换JSON数据,原始数据: {item}") # 字段映射 field_mapping = { "announcement_name": "positionNameOpen", "publish_time": "formatPublishTime", "hd_dept": "positionDeptName", "hd_loc": "workCity", "hd_job_num": "", "hd_job_category": "jobType" } # 适配不同返回格式 if "positionNameOpen" not in item: if "formatPublishTime" in item: field_mapping = { "announcement_name": "name", "publish_time": "formatPublishTime", "hd_dept": "departmentName", "hd_loc": "workCity", "hd_job_num": "", "hd_job_category": "jobTypeName" } elif "publishTime" in item: field_mapping = { "announcement_name": "name", "publish_time": "publishTime", "hd_dept": "departmentName", "hd_loc": "workCity", "hd_job_num": "", "hd_job_category": "jobTypeName" } # 固定公共字段 fixed_fields = { "link": target_url, "full_url": target_url, "last_url": target_url, "file_path": tmp_file, "parent_url": "https://zhaopin.jd.com/", "channel": channel, "job_type": job_type } target_json = {} # 字段映射赋值 for target_field, source_field in field_mapping.items(): if source_field: target_json[target_field] = item.get(source_field, "") else: target_json[target_field] = "" # 写入固定字段 target_json.update(fixed_fields) ner_logger.info(f"转换后的JSON数据: {target_json}") # 保存标准JSON with open(json_file, 'w', encoding='utf-8') as f: json.dump(target_json, f, ensure_ascii=False, indent=4) ner_logger.info(f"成功保存JSON文件: {json_file}") time.sleep(1) # 京东招聘主爬取逻辑 def api_proc_jd(spider_com, _key, com_info, k, url, _stat): ner_logger.info(f"开始处理京东招聘数据,参数 - _key: {_key}, k: {k}, url: {url}, _stat: {_stat}") # 未传入URL则使用默认接口 if not url or url == "": url = "https://zhaopin.jd.com/web/job/job_list" ner_logger.info(f"URL为空,使用默认URL: {url}") # 判断社招/校招 job_type = "shezhao" if k.startswith("shezhao"): job_type = "shezhao" ner_logger.info("设置job_type为shezhao") elif k.startswith("xiaozhao"): job_type = "xiaozhao" ner_logger.info("设置job_type为xiaozhao") # 获取临时文件目录 key_tmp_dir = spider_com.get_key_dir(_key) ner_logger.info(f"使用临时目录: {key_tmp_dir}") total_page = 50 # 循环翻页爬取 for curPage in range(1, 100): ner_logger.info(f"开始处理第 {curPage} 页") flag, json_data, totalcount = get_jd_job_json(url, curPage) if flag: ner_logger.info("jd json response: total_page=%s, data_count=%s", total_page, len(json_data)) if total_page == 0: total_page = int(totalcount / 100) + 1 ner_logger.info(f"计算总页数: {total_page} (总数据量: {totalcount})") # 保存列表页JSON _hash = hashlib.md5(url.encode("utf-8")).hexdigest() tmp_fname = f'{key_tmp_dir}/index_{_hash}_{curPage}.json' with open(tmp_fname, 'w', encoding='utf-8') as f: json.dump(json_data, f, ensure_ascii=False, indent=4) ner_logger.info(f"保存页面JSON数据到: {tmp_fname}") # 终止条件 if curPage >= total_page: ner_logger.info("已达到总页数,结束循环") break if curPage > 5 and _stat.get('method', '') != "cp_full": ner_logger.info("页数超过5页且method不为cp_full,结束循环") break # 遍历职位生成详情 for idx, item in enumerate(json_data): ner_logger.info(f"处理第 {curPage} 页中的第 {idx+1} 个项目") job_id = item.get("id", "") _fullurl = f"https://zhaopin.jd.com/web/job_info_list/3" # 生成文件路径 _hash = hashlib.md5((_fullurl + str(job_id)).encode("utf-8")).hexdigest() tmp_file = os.path.join(key_tmp_dir, f"detail_{_hash}.html") tmp_json_file = os.path.join(key_tmp_dir, f"detail_{_hash}.json") # 生成HTML html_result = generate_job_html(item, tmp_file) if not html_result: ner_logger.error(f"生成HTML文件失败,跳过当前项目: {item}") continue # 转换标准JSON transform_job_json(item, job_type, _key, _fullurl, tmp_file, tmp_json_file) time.sleep(1) else: ner_logger.error(f"获取第 {curPage} 页数据失败") if curPage == 1: ner_logger.error("第一页数据获取失败,终止执行") return False time.sleep(1) ner_logger.info("京东招聘数据处理完成") return True ``` --- **项目分区导航**:[[02-isoftstone_data_proc_api|isoftstone_data_proc_api]] ⬅️ | 03-jd_data_proc_api | ➡️ [[04-kingdee_data_proc_api|kingdee_data_proc_api]]