简介这是一份面向Python爬虫初学者与数据采集爱好者的实战型学习资料围绕“爬取招聘网站职位信息并保存为CSV文件”这一典型场景展开以猎聘网为例帮助读者掌握从网页请求到数据落盘的完整流程。资源包内共1个docx文档压缩后约18KB内容以图文讲解与代码示例为主涵盖Parsel解析、Requests请求、GET参数构造、请求头设置、CSS选择器提取、字典组织数据、csv模块写入及异常处理等关键知识点并附有完整可运行代码。文档还涉及robots协议与合规采集的注意事项便于读者在练习中建立规范意识。目前已有4903人学习下载适合希望快速上手爬虫、理解数据采集与存储链路的学习者参考也可作为课程作业或小型数据项目的起步模板。1. 招聘数据采集从搜索页到 CSV 的完整落地路径很多人第一次写爬虫都是拿招聘网站练手——页面结构清晰、字段规整、翻页逻辑简单跑通一次就能拿到一份像样的数据表。但真到动手时坑也不少请求参数对不上、CSS 选择器写错、CSV 中文乱码、翻页翻到一半被封。这篇笔记拆的就是一个能直接跑起来的 Python 招聘信息采集项目以某招聘平台搜索页为入口用 requests 发 GET 请求、parsel 做 CSS 解析、csv 模块落盘最终产出一份包含标题、城市、薪资、经验、学历、职位标签、公司名称、公司标签、详情页链接的表格文件。适合刚学完 Python 基础语法、想找一个完整小项目练手的开发者也适合需要批量整理岗位数据做分析的人。代码不长但每一行都对应一个真实环节改几个参数就能换成别的岗位或城市。2. 请求构造与页面解析GET 参数、请求头与 CSS 选择器2.1 抓包定位真实请求Network 面板怎么用打开目标招聘网站搜索“python”右键“检查”进入开发者工具切到 Network 面板筛选 Doc 类型刷新页面。列表里会出现若干条请求其中一条就是搜索结果的 HTML 文档。点开它看 Headers 里的 Request URL 和 Query String Parameters这些就是后面要拼进代码的东西。这一步的关键是确认请求方式。招聘搜索页通常是 GET参数直接挂在 URL 问号后面。如果误用 POST服务器返回的可能是空列表或错误页而代码不会报错只是解析不到数据——这是新手最容易翻车的地方。提示抓包时先清空 Network 记录再刷新避免在大量静态资源请求里翻找。筛选 Doc 能直接过滤掉图片、JS、CSS。2.2 构造请求三件套url、data、headers确认请求方式后把 URL、查询参数、请求头分别写进代码。查询参数就是抓包时看到的 Payload请求头至少带上 User-Agent否则部分站点会直接拒绝非浏览器请求。import requests # 搜索页地址不带问号后面的参数 url https://www.liepin.com/zhaopin/ # 查询参数对应抓包时 Query String Parameters 里的键值对 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 } # 发 GET 请求params 会自动把 data 拼到 url 后面 response requests.get(urlurl, headersheaders, paramsdata)key是搜索关键词换成“java”“数据分析”都能复用。workYearCode控制工作年限筛选0表示不限。ckId是页面会话标识如果请求返回 403 或空内容优先检查这个值是否过期——重新抓一次包替换即可。requests.get()的params参数会自动做 URL 编码不需要手动拼?keypython...。response.text拿到的是解码后的 HTML 字符串编码由 requests 根据响应头推断多数情况没问题。2.3 parsel 解析从大标签到字段提取拿到 HTML 后用 parsel 的 Selector 包一层然后按 CSS 选择器逐层往下取。思路是先定位到每一条职位卡片再在卡片内部取字段。import parsel selector parsel.Selector(response.text) # 先取所有职位卡片这是一个列表 lis selector.css(.left-list-box .job-list-item) for li in lis: # 在单个卡片内部继续用 css 取字段 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()::text取文本::attr(title)取属性值.get()返回第一个匹配结果.getall()返回全部匹配的列表。tag_list里前两项固定是经验和学历剩下的用,.join()拼成字符串避免 CSV 里出现嵌套列表。这里有个细节tag_list长度不固定直接写tag_list[0]在数据缺失时会抛 IndexError。加上长度判断后即使某条职位没有标签程序也不会中断。2.4 数据落盘csv.DictWriter 的用法与编码处理字段取完后用字典把一条数据装起来再通过csv.DictWriter写入文件。用追加模式打开方便多次运行累积数据。import csv # modea 追加写入newline 防止 Windows 下多空行 f open(招聘.csv, modea, encodingutf-8, newline) css_write csv.DictWriter(f, fieldnames[ 标题, 城市, 薪资, 经验要求, 学历要求, 职位标签, 公司名称, 公司标签, 详情页, ]) # 如果文件是新建的先写表头已有内容则跳过 import os if os.path.getsize(招聘.csv) 0: css_write.writeheader() dit { 标题: title, 城市: city, 薪资: money, 经验要求: exp, 学历要求: edu, 职位标签: tag, 公司名称: company_name, 公司标签: company_tag, 详情页: href, } css_write.writerow(dit) print(title, city, money, exp, edu, tag, company_name, company_tag, href)fieldnames的顺序决定了 CSV 列的顺序字典的键必须和它一一对应否则会报 ValueError。encodingutf-8保证中文正常写入但用 Excel 直接打开可能乱码——这是 Excel 默认按 GBK 解码导致的不是文件本身的问题。解决办法是用记事本打开再另存为 ANSI或者用 pandas 读取。newline在 Windows 上必须加否则每写一行会多出一个空行。writeheader()只在文件为空时调用避免多次运行时重复插入表头。3. 翻页、异常与合规让采集脚本稳定跑完3.1 翻页逻辑循环构造页码参数单页数据通常只有二三十条要拿更多就得翻页。招聘网站的翻页一般通过 URL 参数控制比如page或curPage。在抓包时翻到第二页观察 URL 变化找到页码参数名。all_data [] for page in range(0, 5): # 抓前 5 页 data[curPage] page # 参数名以实际抓包为准 response requests.get(urlurl, headersheaders, paramsdata) selector parsel.Selector(response.text) lis selector.css(.left-list-box .job-list-item) for li in lis: # ... 提取字段逻辑同上 ... all_data.append(dit) print(f第 {page1} 页抓取完成累计 {len(all_data)} 条)把请求和解析包进循环每页抓完打印进度。页码从 0 开始还是从 1 开始取决于站点设计抓包确认最稳妥。循环里加time.sleep(1)能降低请求频率减少被限制的概率。3.2 异常处理网络抖动与字段缺失实际跑的时候网络超时、连接重置、返回内容为空都可能发生。不加处理的话一次超时就会让整个脚本崩掉前面抓的数据也白费。import time for page in range(0, 5): try: data[curPage] page response requests.get(urlurl, headersheaders, paramsdata, timeout10) response.raise_for_status() # 非 200 状态码直接抛异常 except requests.RequestException as e: print(f第 {page1} 页请求失败{e}跳过) continue selector parsel.Selector(response.text) lis selector.css(.left-list-box .job-list-item) if not lis: print(f第 {page1} 页未解析到数据可能被限制或页面结构变化) continue for li in lis: try: # ... 字段提取 ... css_write.writerow(dit) except Exception as e: print(f单条数据写入失败{e}) continue time.sleep(1)timeout10防止请求无限等待raise_for_status()把 4xx/5xx 转成异常。单条数据提取失败时用continue跳过不影响后续条目。这样即使中间有几条脏数据整体任务仍能跑完。3.3 合规采集频率控制与 robots.txt采集前先看目标站点的robots.txt确认允许抓取的路径。控制请求频率单线程加sleep是最简单的方式一般 1 到 2 秒一次不会对服务器造成压力。不要用多线程高并发去压招聘网站既容易被封 IP也不符合基本的采集礼仪。采集到的数据仅用于个人学习或分析不要批量倒卖或用于商业竞争。详情页链接可以保留但不要进一步抓取用户隐私信息。4. 避坑与排查请求、解析、写入三个环节的常见问题4.1 返回内容为空或不是目标页面现象response.text里没有职位卡片或者返回的是验证页面。原因请求头缺少 User-Agent或者ckId等会话参数过期或者请求频率过高被临时限制。解决补全 User-Agent重新抓包替换ckId降低请求频率并加sleep。如果持续返回验证页换一个时间段再试。4.2 CSS 选择器取不到值现象li.css(...).get()返回 NoneCSV 里对应列为空。原因页面结构改版类名或层级变了或者选择器写得太具体某个中间层不存在。解决在浏览器开发者工具里用document.querySelector()验证选择器确认能选中元素后再写进代码。选择器尽量用稳定的类名避免依赖nth-child这种位置相关的写法。4.3 CSV 中文乱码现象用 Excel 打开 CSV中文显示为乱码。原因Excel 默认按系统编码GBK读取而文件是 UTF-8。解决写入时用encodingutf-8-sig带 BOM 的 UTF-8 能让 Excel 正确识别。或者用 pandas 读取后再导出。4.4 多次运行数据重复现象每次运行脚本CSV 里都会追加相同的数据。原因追加模式modea不会清空文件重复运行就会重复写入。解决在写入前根据详情页链接去重或者每次运行前手动清空文件。简单做法是用modew覆盖写入但会丢失历史数据按需选择。4.5 翻页到后面返回重复内容现象翻到第 3 页以后抓到的数据和第 1 页一样。原因页码参数名写错或者站点对未登录用户限制只返回前几页。解决抓包确认页码参数名和起始值检查是否需要在请求头里带 Cookie。如果站点限制未登录访问考虑登录后再采集但要注意账号安全。5. 二次开发从单关键词到多维度数据管道的改造思路跑通单关键词采集后最自然的改造方向是把它变成一个可配置的小型数据管道。我一般会从三个地方下手参数外置、存储升级、字段扩展。参数外置把关键词、城市、页数、输出文件名抽到配置文件或命令行参数里避免每次改代码。用argparse几行就能搞定import argparse parser argparse.ArgumentParser() parser.add_argument(--key, defaultpython, help搜索关键词) parser.add_argument(--pages, typeint, default5, help抓取页数) parser.add_argument(--out, default招聘.csv, help输出文件名) args parser.parse_args() data[key] args.key这样运行时python spider.py --key java --pages 10 --out java岗位.csv就能换目标不用动源码。存储升级CSV 适合小规模数据几百条以内用 Excel 打开很方便。但如果要抓多个关键词、多个城市数据量上去后 CSV 的查询和去重会很吃力。换成 SQLite 是成本最低的升级路径Python 内置sqlite3模块不需要额外安装数据库服务。import sqlite3 conn sqlite3.connect(jobs.db) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS jobs ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, city TEXT, salary TEXT, exp TEXT, edu TEXT, tags TEXT, company TEXT, company_tags TEXT, href TEXT UNIQUE ) ) cur.execute( INSERT OR IGNORE INTO jobs (title, city, salary, exp, edu, tags, company, company_tags, href) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , (title, city, money, exp, edu, tag, company_name, company_tag, href)) conn.commit()href加 UNIQUE 约束后INSERT OR IGNORE自动去重多次运行不会产生重复记录。后续想按城市统计岗位数量、按薪资排序一条 SQL 就能出结果比在 CSV 里手动筛选高效得多。字段扩展当前脚本只抓了列表页可见的字段。如果要做更细的分析比如岗位职责里出现频率最高的技术词就需要进入详情页抓正文。改造方式是在列表页拿到href后再发一次请求解析详情页把职责描述、任职要求等字段补进字典。注意详情页请求也要加sleep并且处理好相对链接转绝对链接的问题。验证方法改完之后不要直接跑全量先用--pages 1抓一页检查 CSV 或数据库里的字段是否完整、中文是否正常、链接能否打开。确认无误再放开页数。我自己的习惯是每次改完解析逻辑都先拿一页数据做回归对比字段数量和内容有没有异常。从那以后我每次改爬虫都强制先跑一页验证再放开全量这个习惯帮我省了很多次回滚的麻烦。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?