
上周有个朋友找我说他跟着网上的 Python 网络爬虫教程抄了一段代码准备从一个公开网站上抓取文章列表结果先是编码乱码加了请求头后又开始超时最后好不容易拿到第一页数据却发现不会把它保存成表格。他问我是不是自己的 Python 环境没装对。我告诉他问题大概率不在环境而在很多人学爬虫时忽略掉的那条链路从请求、响应、解析到存储中间任何一环出了问题代码都会在某个看似莫名其妙的位置停下来。这篇文章想给零基础读者做一个更老实的梳理Python 网络爬虫到底该怎么学学哪些东西学到什么程度才能算入门以及从“入门”到“能稳定使用”之间还差哪些环节。我的核心判断是爬虫的真正门槛从来不是写第一段请求代码而是把一次偶然跑通变成一套稳定、可复现、可持续维护的流程。标题里说的“零基础到精通”拆开看其实就是下面这六步。1. 动手之前先想清楚爬虫到底在解决什么问题1.1 爬虫的本质是自动化“请求 解析 存储 策略”很多人第一次接触爬虫会觉得它很神秘。其实爬虫做的事情和人手动浏览网页几乎一样输入网址服务器返回页面然后你从页面里找到需要的信息抄下来。唯一区别是人手动操作太慢爬虫用代码把“请求、解析、保存”这三件重复动作变成了自动化。但自动化的前提是流程清晰。我建议初学者先记住一个四环节模型环节要解决的问题常见工具请求拿到服务器返回的响应requests、httpx解析从 HTML 或 JSON 中提取目标数据BeautifulSoup、lxml、json存储把结果落到文件或数据库csv、json、SQLite策略处理重试、频率、去重、异常自定义函数、Scrapy这个模型看起来很简单但它能帮你在报错时快速定位问题。比如请求超时问题在“请求”环节解析出来是空列表问题在“解析”环节数据能打印但存不进文件问题在“存储”环节。绝大多数新手卡住都是因为不知道自己在哪一环最后只能到处复制代码碰运气。1.2 为什么“零基础入门”最容易把学习路径走偏网络上太多零基础教程把爬虫包装成“一行代码抓数据”“复制就能跑”。这种宣传不是完全错但它会掩盖一个事实那些代码只在特定页面、特定时间、特定网络环境下能跑。一旦目标页面的结构调整或者请求被拒绝新手就会卡住。真正的入门不是会运行别人的代码而是能在代码报错时判断问题出在哪个环节。如果你不知道超时可能是网络问题、可能是目标服务器限制、也可能是请求头被拒绝那么你只能不断复制粘贴、碰运气。所以我的建议是先不要急着学分布式爬虫或所谓高级框架先花时间把请求、解析、存储、策略这四个环节分别跑通再考虑把它们组合起来。整个过程不需要两个月一两个周末就能走完但走完之后你对“爬虫”这个词的体感会和以前完全不同。2. 零基础从哪开始Python 基础不需要很多但需要扎实2.1 环境准备版本、虚拟环境和编辑器先安装 Python。Windows 和 macOS 用户到 Python 官网下载对应安装包安装时注意勾选“Add Python to PATH”否则命令行里找不到 python 命令。Linux 系统通常自带 Python但自带版本可能偏旧可以用包管理器安装需要的版本。装好后在终端运行python --version或python3 --version检查版本即可。一个很容易被忽略的步骤是创建虚拟环境python -m venv .venv激活后再通过pip安装项目依赖。不同项目用不同环境避免依赖冲突。这不是可选项长期开发你就会明白它有多重要。编辑器我建议 VS Code 或 PyCharm。VS Code 需要安装 Python 扩展并手动选择解释器路径PyCharm 社区版免费对调试更友好。实际落地时我见过太多人在系统 Python 环境里装了一堆包等到下一个项目需要不同版本的 requests 时整个环境乱成一团。虚拟环境能在起步阶段就帮你避开这个坑。2.2 入门爬虫前至少要掌握的 Python 知识点不需要学完全部 Python 语法但以下知识点必须能熟练写出来变量和基本数据类型字符串、整数、列表、字典字符串的常见操作切片、拼接、格式化流程控制if、for、while函数定义、返回值、默认参数异常处理try/except文件读写with open()写法模块导入import、from ... import ...列表和字典尤其重要因为解析 HTML 或 JSON 之后你面对的数据结构基本就是这两种。字典取值、列表遍历、条件判断如果写不顺后面会不断卡壳。可以这样检查自己是否准备好了不用查资料写一个小脚本读一个文本文件统计里面每个单词出现的次数再把结果按次数排序后写回另一个文件。这个过程覆盖了字符串处理、字典、循环、排序和文件读写正好是爬虫数据加工的最小模型。如果这个脚本能顺利写出来Python 基础够用了。2.3 不用第三方库先理解一次 HTTP 请求在安装 requests 和 BeautifulSoup 之前先用标准库urllib.request发一次请求。这能帮你理解“请求”到底在做什么。from urllib.request import Request, urlopen url https://example.com req Request(url, headers{User-Agent: Mozilla/5.0}) with urlopen(req, timeout10) as resp: print(状态码:, resp.status) data resp.read() print(内容长度:, len(data))这段代码会打印状态码 200 和响应体字节长度。你可能会问为什么一定要带User-Agent因为服务器在接收请求时会通过请求头判断对方是什么客户端。默认的Python-urllib/3.x有时会被服务器拒绝换成常见的浏览器标识相当于告诉服务器“我使用的是普通浏览器”。这不是欺骗而是遵守服务器识别客户端的常见惯例。此时如果你把data直接打印出来会发现是一堆字节或乱码。这就是后面编码问题的起点。把这一步走通再进入第三方库你会更容易理解它们为什么存在。3. 第一个能跑的爬虫请求、解析、落盘把每个环节拆开看3.1 最小实现requests 请求 BeautifulSoup 解析先把两个核心库装好pip install requests beautifulsoup4如果下载速度慢可以临时换用国内镜像源。然后写最基础的抓取代码import requests from bs4 import BeautifulSoup url https://example.com headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) title soup.title.get_text(stripTrue) if soup.title else No Title print(title)这里有几个关键点raise_for_status()状态码不是 200 时直接抛异常方便你尽早发现问题。resp.encoding resp.apparent_encoding用响应内容猜测编码解决一部分中文乱码问题。soup.title.get_text(stripTrue)提取title标签里的文本stripTrue会去掉首尾空白。第一次跑通后可以把 URL 换成一个你经常访问、且允许公开抓取的页面先抓一个标题或列表字段。选目标时先看站点是否允许爬虫最简单的方式是查看它的robots.txt和服务条款。3.2 最容易出错的三个参数请求头、超时和编码新手在请求阶段最容易踩三个坑请求头headers。除了User-Agent有时服务器还会要求Referer、Accept等字段。但不需要一上来就模仿各种复杂请求头先加User-Agent就够。真正的规则是服务器返回了什么提示你再根据提示补什么参数。盲目堆请求头只会掩盖真正的问题。超时timeout。很多爬虫卡住是因为请求一直没有结束。timeout10表示 10 秒内没有连接或读取响应就放弃。这个参数的意义不是省时间而是让异常能被捕获让整个流程不会卡死。没有超时脚本可能挂在一个无人知晓的地方特别浪费生命。编码。最常见的乱码场景是目标页面本身不是 UTF-8但 requests 默认用了错误编码去解码。遇到乱码先用resp.encoding resp.apparent_encoding试一次如果仍乱就手动指定页面meta charset里声明的编码。排查顺序也很固定先打印resp.status_code和resp.text前 500 个字符。不要急着写解析代码先确认你拿到的确实是 HTML再谈提取。注意先跑通一个成功样例再考虑循环。很多批量任务失败都是因为单次流程还没稳定就急着上量。3.3 结果保存CSV 和 JSON 怎么选抓下来的数据需要落盘。最常用的两种格式是 CSV 和 JSON。CSV 适合表格类数据尤其适合后续用 Excel 打开。示例写法import csv with open(titles.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([title, url]) writer.writerow([title, url])注意这里用了utf-8-sig。直接写utf-8用 Excel 打开可能出现乱码utf-8-sig会带上 BOM 头兼容性更好。这个小细节往往就是“零基础”和“能做小工具”之间的差距。JSON 适合嵌套结构也更方便代码读取。示例写法import json data {title: title, url: url} with open(result.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse是为了让中文以原文写入文件而不是变成一堆\u转义。这几个参数看着不起眼但能在你真正使用数据时省很多事。4. 从“跑通一次”到“稳定复用”工程化才是真门槛4.1 异常处理顺序先看输入再看网络再看解析我带着学生做爬虫时发现绝大多数报错都可以按同一个顺序排查先看输入再看网络再看解析。一个带重试机制的请求函数可以这样写import time import requests def fetch(url, retries3, timeout10): for attempt in range(retries): try: resp requests.get(url, timeouttimeout) resp.raise_for_status() return resp except requests.RequestException as e: print(f第 {attempt 1} 次请求失败: {e}) if attempt retries - 1: raise time.sleep(2 ** attempt)这里的2 ** attempt是简单的指数退避第一次失败后等 1 秒第二次等 2 秒第三次等 4 秒。为什么不能失败后立刻重试因为瞬时网络抖动不会立刻恢复隔一会儿再试成功率更高。但要注意重试只解决临时网络失败不解决代码逻辑错误。如果返回 404 或页面结构变化重试多少次都没意义。所以异常信息里一定要把状态码和失败 URL 打出来方便回看。4.2 控制频率新手最不该忽略的一步很多初学者写循环时会写成这样for page in range(1, 6): url fhttps://example.com/list/{page} # 请求、解析、保存然后过一会儿就被拒绝了。原因很简单请求频率太高服务端把它当作异常流量。正确做法是让每次请求之间留出间隔import time for page in range(1, 6): print(f抓取第 {page} 页) # 请求、解析、保存 time.sleep(1)间隔时间没有统一标准通常从 1 到 2 秒起步再结合目标网站的承受能力调整。关键不是追求最快而是不要让程序给服务器造成压力。如果需要更自然的节奏可以加入随机延迟比如time.sleep(random.uniform(1, 3))降低请求尖峰。控制频率不只是保护目标服务器也是保护你自己的任务流程。请求太快导致被拒绝后后续所有逻辑都会跟着乱掉。4.3 任务多了之后日志、去重、调度和 Scrapy 的引入时机当你开始抓 100 个页面、1000 个页面时控制台print就不够了。你需要日志哪条成功、哪条失败、失败原因是什么。Python 标准库logging可以先顶一阵不需要一上来就上 ELK 那套重型方案。去重也很重要。如果任务会重复执行你需要判断哪些 URL 已经抓过。最简单的做法是把已抓 URL 存到set或数据库等下次启动时加载进来。什么时候引入 Scrapy我的判断是当你的请求需要并发控制、需要中间件处理不同站点、需要管道把数据写入不同目标时再学 Scrapy 才顺手。如果只是抓几十个页面requests 加脚本完全够用。很多教程一上来就讲 Scrapy、分布式爬虫这会让零基础读者产生一种错觉爬虫很难必须上重型工具。其实不是。Scrapy 解决的核心问题是工程化复杂度而不是“能不能抓取”本身。先把手动流程走通再引入框架你会更容易理解框架为什么这样设计。5. 页面数据不直接出现在 HTML 里怎么办5.1 一个五步排查链路遇到目标页面先用五步判断数据到底在哪一层浏览器打开页面肉眼确认目标数据存在。右键查看网页源代码搜索目标数据的某个关键字。用 requests 请求该 URL打印响应文本再搜索同一个关键字。如果源代码和响应文本都没有数据大概率由 JavaScript 动态加载。打开浏览器开发者工具切到 Network 面板查看 XHR / Fetch 请求找返回 JSON 的接口。这个过程听起来简单但很多初学者会直接跳过第 3 步盲目写选择器然后在解析环节浪费大量时间。按顺序排查能快速定位问题在哪一层。如果找到了公开 API下一步不是用 Selenium 开浏览器而是直接请求这个接口。请求 JSON 通常比解析 HTML 简单得多import requests url https://jsonplaceholder.typicode.com/posts resp requests.get(url, timeout10) for item in resp.json(): print(item[title])这里用的是公共测试接口。实际项目里替换成你有权访问的接口即可。5.2 浏览器自动化工具是最后手段不是首选如果页面必须执行 JavaScript 才能渲染数据并且没有公开 API才考虑 Playwright 或 Selenium 这类浏览器自动化工具。这类工具的成本很高启动一个浏览器内核资源占用大需要等待页面渲染速度慢选择器稍微变化或网络波动脚本就容易失败。所以它应该被当作用尽简单手段之后的备选而不是默认选项。我见过有人用 Selenium 去抓一个只需要简单 JSON 接口就能获取数据的页面结果脚本跑三分钟就崩一次维护成本远高于收益。先做 5.1 的排查通常能帮你避开这种折腾。如果目标站点的条款明确禁止自动化访问那就不要继续。做技术的人不能只问“能不能实现”还要问“允不允许实现”。6. 学习爬虫之前先把边界和规则说清楚6.1 robots.txt、站点条款和服务压力robots.txt不是法律文件但很能说明站点态度。很多网站根目录下直接访问/robots.txt就能看到它对爬虫的约束。技术人应该有主动遵守的意识。更重要的判断标准是请求是否会造成影响。如果每秒钟发几十个请求即使网站没有明确禁止也会增加服务器压力。合理频率是爬虫的基本修养。抓到的数据怎么用也要想清楚。用于学习、分析公开信息是一类情况用于商业产品、涉及个人隐私是另一类情况。越界不是由代码决定的而是由动机和结果决定的。6.2 什么样的爬虫是“合理学习”什么样的会越界合理学习的范围可以这样