简介这份资源面向具备Python基础、希望入门网络爬虫的开发者与数据分析初学者以猎聘网为例完整演示如何抓取招聘岗位信息并落地为CSV表格。内容围绕Requests发送GET请求、Parsel结合CSS选择器解析页面、请求头伪装、字典组织字段、csv模块追加写入等核心环节展开并涉及异常处理与robots协议等合规要点帮助读者打通从请求到结构化存储的完整链路。资源包共1个docx文档约18KB以图文笔记形式记录关键代码与解析思路便于对照练习。目前已有4903人学习下载适合想快速掌握爬虫取数流程、积累实战案例的读者参考也可作为数据采集入门练习的模板。1. 从一份招聘 CSV 说起这个爬虫项目到底能干什么前阵子帮朋友整理岗位数据他手动复制了三天招聘信息Excel 里贴得乱七八糟薪资、城市、经验要求全混在一列。我接手后写了个不到 80 行的 Python 脚本跑一遍就拿到一份带表头的 CSV字段拆得干干净净。这个项目做的就是这件事用 requests 发 GET 请求抓招聘列表页parsel 做 CSS 解析csv 模块落盘。适合刚学完 Python 基础、想找一个完整爬虫练手的人也适合做岗位分析、薪资调研的开发者直接改参数复用。它不涉及登录、不碰详情页翻页核心链路短但把请求构造、选择器提取、字典写入 CSV 这三个爬虫基本功全串了一遍拿来当第一个真实项目正合适。2. 请求构造与页面解析从 URL 到职位列表的完整链路2.1 先搞清楚请求怎么发GET 参数与请求头打开目标招聘站搜索岗位右键检查进 Network 面板筛选 Doc 类型请求刷新后能看到列表页的请求。重点看两处Request URL 和 Payload。Payload 里就是 URL 问号后面的查询参数比如搜索关键词、工作年限、来源标识。请求方式是 GET代码里也必须用 GET用 POST 会直接拿不到列表数据。import requests url https://www.liepin.com/zhaopin/ # 这些参数对应浏览器 Payload 里的键值对 data { inputFrom: www_index, workYearCode: 0, key: python, # 想换岗位就改这里 scene: input, ckId: kfpt1emhvkrshc9o4h2xquxd74pfihmb, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.46 } response requests.get(urlurl, headersheaders, paramsdata)paramsdata会让 requests 自动把字典拼成?inputFrom...keypython这种形式不用自己手拼字符串。headers里最关键的是User-Agent不带它很多站点会返回空页面或直接拒绝。ckId这类参数是站点生成的会话标识不同时间访问可能变化如果请求返回的 HTML 里没有职位节点优先检查这个值是否过期。2.2 用 parsel 定位列表容器拿到response.text后交给 parsel 的 Selector先选中包裹所有职位的大容器再在循环里逐个提取字段。这样做的好处是选择器作用域被限制在单个职位卡片内不会串到别的卡片上。import parsel selector parsel.Selector(response.text) # 先定位到每一个职位卡片 lis selector.css(.left-list-box .job-list-item) print(f本次共抓到 {len(lis)} 个职位).left-list-box .job-list-item是后代选择器意思是左侧列表容器下所有职位项。如果打印出来是 0说明页面结构变了或者请求没拿到真实数据先别急着改提取逻辑回头确认请求那一步。2.3 逐字段提取标题、城市、薪资、标签每个职位卡片内部字段位置不同有的在属性里有的在文本节点里。标题和详情页链接藏在attr里城市、薪资、公司名是纯文本标签是一组文本需要getall()拿列表。for li in lis: title li.css(.job-title-box div:nth-child(1)::attr(title)).get() city li.css(.job-dq-box .ellipsis-1::text).get() money li.css(.job-detail-header-box .job-salary::text).get() tag_list li.css(.job-labels-box .labels-tag::text).getall() exp tag_list[0] if len(tag_list) 0 else edu tag_list[1] if len(tag_list) 1 else tag ,.join(tag_list[2:]) if len(tag_list) 2 else company_name li.css(.company-name::text).get() company_list li.css(.company-tags-box span::text).getall() company_tag ,.join(company_list) href li.css(.job-detail-box a:nth-child(1)::attr(href)).get()::attr(title)取元素属性值::text取文本内容.get()返回第一个匹配结果.getall()返回全部匹配的列表。标签列表里前两项固定是经验和学历剩下的用,.join()拼成字符串避免 CSV 里出现嵌套列表。这里加了长度判断防止某个职位标签缺失导致IndexError直接中断整个循环。2.4 字典组装与 CSV 写入提取完字段后装进字典键名就是 CSV 的表头。用csv.DictWriter写入字段顺序由fieldnames决定和字典键名一一对应。import csv f open(招聘.csv, modea, encodingutf-8, newline) csv_write csv.DictWriter(f, fieldnames[ 标题, 城市, 薪资, 经验要求, 学历要求, 职位标签, 公司名称, 公司标签, 详情页, ]) dit { 标题: title, 城市: city, 薪资: money, 经验要求: exp, 学历要求: edu, 职位标签: tag, 公司名称: company_name, 公司标签: company_tag, 详情页: href, } csv_write.writerow(dit) print(title, city, money, exp, edu, tag, company_name, company_tag, href)modea是追加模式多次运行不会覆盖之前的数据。encodingutf-8保证中文正常写入newline防止 Windows 下每行多出空行。writerow(dit)按fieldnames顺序把字典值写成一行。如果 CSV 打开后表头重复出现说明每次运行都写了表头把writerow换成writeheader()只在文件不存在时调用一次即可。3. 避坑与排查请求失败、字段错位、编码乱码怎么查3.1 返回的 HTML 里没有职位节点现象len(lis)打印为 0但浏览器里明明能看到职位列表。原因通常是请求头不完整或ckId过期站点返回了验证页或空壳页。解决先把response.text存成debug.html用浏览器打开看实际返回内容。如果是验证页补全Cookie或Referer头如果只是ckId过期重新从浏览器 Network 面板复制最新值。3.2 标签列表长度不足导致索引报错现象循环跑到某一条时抛IndexError: list index out of range。原因部分职位没有经验或学历标签tag_list长度小于 2。解决不要直接写tag_list[0]用条件判断兜底如exp tag_list[0] if len(tag_list) 0 else 。这是最容易被忽略的翻车点因为前几条数据正常跑到中间才崩。3.3 CSV 中文乱码现象Excel 打开 CSV 显示乱码但记事本能正常看。原因Excel 默认按 GBK 解析而文件是 UTF-8。解决写入时用encodingutf-8-sig它会自动加 BOM 头Excel 就能正确识别。如果数据要进数据库或做后续处理保持utf-8即可别加 BOM。3.4 详情页链接是相对路径现象CSV 里详情页列只有/job/12345.shtml这种半截地址。原因页面里href本身就是相对路径。解决拼接域名前缀href https://www.liepin.com href或者用urllib.parse.urljoin处理。不拼的话后续想批量请求详情页会直接失败。3.5 频繁请求被限流现象跑几次后请求返回 403 或空数据。原因同一 IP 短时间请求过多。解决在循环里加time.sleep(1)到time.sleep(3)的随机延迟别用固定值。另外把User-Agent换成更常见的浏览器标识不要用脚本默认的python-requests。提示调试阶段先把response.text落盘比反复改选择器高效得多。选择器写错和请求没拿到数据现象都是空列表但排查方向完全不同。4. 二次开发从单页脚本到可复用采集工具4.1 把硬编码参数抽成配置原始代码里 URL、关键词、请求头全写在顶层改一个岗位要翻好几处。我一般会抽一个config.py或直接用命令行参数import argparse parser argparse.ArgumentParser() parser.add_argument(--keyword, defaultpython, help搜索岗位关键词) parser.add_argument(--output, default招聘.csv, help输出文件名) args parser.parse_args() data[key] args.keyword f open(args.output, modea, encodingutf-8-sig, newline)这样跑不同岗位只需python spider.py --keyword java --output java岗位.csv不用改代码。参数化之后批量采集多个关键词就是外面再套一层循环的事。4.2 加异常处理和重试网络请求天然不稳定单次失败不该让整个脚本挂掉。给请求加try-except和重试import time def fetch(url, headers, params, retry3): for i in range(retry): try: resp requests.get(urlurl, headersheaders, paramsparams, timeout10) if resp.status_code 200: return resp except requests.RequestException as e: print(f第 {i1} 次请求失败: {e}) time.sleep(2) return Nonetimeout10防止请求卡死retry3给三次机会每次间隔 2 秒。返回None时上层跳过本次采集继续下一条保证整体不中断。4.3 去重与增量采集追加模式跑多次会产生重复行。简单做法是在写入前用集合记录已见过的标题加公司名组合seen set() key (title, company_name) if key not in seen: seen.add(key) csv_write.writerow(dit)如果要跨次运行去重就在启动时先读一遍已有 CSV把标题和公司名加载进seen集合。数据量不大时这样够用上万条再考虑上 SQLite。4.4 字段扩展与数据清洗薪资字段常带「·13薪」这类后缀城市可能带「·」分隔的区域名。二次开发时可以在写入前做一轮清洗原始值清洗后处理方式15-25k·13薪15-25k按·分割取第一段北京·朝阳区北京按·分割取第一段经验不限不限字符串替换清洗逻辑放在字典组装之前用split和replace就能覆盖大部分情况。别在提取阶段做提取阶段保持原样方便回溯。4.5 验证改造是否生效改完代码后别直接跑全量先用--keyword test跑一次检查三件事CSV 表头是否正确、字段有没有错位、中文是否正常。确认无误再换真实关键词。我习惯在脚本里加一行print(f写入 {len(lis)} 条)跑完一眼就能看出数量对不对。从那以后我每次改完爬虫都强制走一遍「小样本验证 → 检查 CSV → 再跑全量」的流程省得跑了几千条才发现字段错位。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?