
最近总有人问我Python爬虫入门该怎么学尤其是那种“看完教程还是不知道从哪下手”的感觉我特别理解。我刚开始接触爬虫的时候也是对着文档折腾了很久写出来的脚本要么报错要么拿到一堆乱码后来踩过足够多的坑才慢慢理清一条相对顺畅的路径。今天这篇文章就围绕Python爬虫入门聊点实在的先想清楚哪些认知问题再讲环境和工具怎么选然后手写一个能跑通的小爬虫最后总结我实战中遇到的高频问题和排查思路。这篇文章适合两类人一是刚学完Python基础、想做点小项目练手的人二是想定期把某个公开网页上的信息整理成表格、但又不想手动复制粘贴的人。爬虫本身并不神秘它就是在模拟人在浏览器里的操作只是用代码来批量完成。你不需要成为编程高手只要会写基本的循环、字典、列表跟着下面的步骤走就足够完成入门级别的数据抓取任务了。1. 动笔写代码之前先把这三个认知问题想明白很多入门教程一上来就丢给你一个requests.get()示例看起来很简单实际上后续踩坑全是因为少想了几层。我建议先把下面三件事想清楚再开始写代码效果会好很多。1.1 爬虫不是想爬就爬边界和合规意识必须前置这句话听起来像是套话但真的有必要放在最前面。爬虫本质上是用程序访问某个服务器上的资源如果你的行为给对方服务器造成很大压力或者把不公开的数据扒下来做商业用途那问题性质就不一样了。入门阶段的正确姿势是只抓取明确公开的数据优先选择提供公开接口的站点并且把请求频率控制在很低的范围。我给自己定的一个简单标准是这个页面我肉眼能直接看到内容、不需要登录、也没有明确禁止抓取的信息那才适合用来做学习案例。对于那些需要登录、需要破解加密参数、页面里明显写了“未经许可禁止采集”的情况我建议直接绕开别碰。学习爬虫的本质是学习HTTP、HTML结构和数据处理不是学习怎么对抗某个平台的安全机制。练手素材那么多没必要给自己找麻烦。1.2 去目标站点之后先看一眼robots.txtrobots.txt是网站和爬虫之间的一种约定文件通常放在域名根目录下。它的作用是告诉自动访问程序的“哪些路径可以访问、哪些路径不建议访问”。在浏览器里输入“目标域名/robots.txt”就能打开比如你准备爬一个示例站点study.example.com那就可以去study.example.com/robots.txt看看。刚开始我根本不知道这个文件的存在后来有一次观察某个目标站点的返回内容发现它的服务器日志里明显标记了Disallow路径这才意识到自己之前其实是在“硬闯”。现在我的习惯是爬一个新的站点之前先花十秒钟访问这个文件里面写的Allow和Disallow规则就是最基本的礼貌边界。如果某个路径被明确Disallow了我就换个页面或者换一个站点练手。1.3 请求频率一定要克制别把自己写成攻击流量很多初学者看到数据在屏幕上滚动会有一种“停不下来”的冲动把循环里的sleep删掉一个挨一个地疯狂请求。这个操作非常危险轻则被服务器封掉IP重则给对方造成实质性的访问压力。你想想你自己正常浏览网页的时候也不可能一秒钟打开十几次页面爬虫也应该模拟这种人类节奏。我常用的做法是每次请求之间至少间隔1到3秒最好把间隔时间设置成一个随机范围这样既不会让目标服务器感觉到异常也不会把自己的学习过程变成一次压力测试。后文实战部分会给出具体写法。总之能把数据抓到只是第一步能让目标服务器不受影响地正常运行才是爬虫工程师的基本修养。2. 环境准备与工具选型别一上来就扑向框架Python爬虫入门阶段最容易犯的错误就是装了一堆看起来很厉害的库和框架最后连最基本的功能都没跑通。其实你真正需要的工具四个手指头就能数得过来。2.1 Python版本与虚拟环境理论上Python 3.8以上就能顺畅运行主流爬虫库我建议直接用最新的稳定版本比如Python 3.10或3.11没必要追求太激进的测试版。安装Python时记得勾选“Add Python to PATH”这能省掉不少环境变量配置的麻烦。虚拟环境是很多新人忽略的东西。它的作用相当于给每个项目建一个独立的厨房你在里面安装的库和版本不会影响系统里其他项目。我用的是Python自带的venv模块创建和激活都很简单# 创建虚拟环境 python -m venv venv # Windows下激活 venv\Scripts\activate # macOS / Linux下激活 source venv/bin/activate激活之后命令行提示符前面会出现一个(venv)之类的前缀你说代表当前已经在虚拟环境里了。之后安装的所有库都会装到这个独立环境项目搬家或者换机器时用requirements.txt就能一键复现依赖列表。2.2 发送请求requests库就够了Python官方标准库里其实自带urllib但它的API设计对新手不太友好编码处理、请求头设置、错误处理都很啰嗦。我平时教身边朋友入门爬虫首选requests库。它是目前Python生态里最流行的HTTP请求库代码读起来直观文档也丰富底层帮你处理了很多细节。安装方式很简单pip install requests一个最基本的请求写法长这样import requests url https://study.example.com/article/1 response requests.get(url) print(response.status_code) # 200说明正常 print(response.text) # 网页源代码request.get会帮我们完成TCP连接、发送HTTP请求、接收响应等一连串工作你不需要关心底层socket怎么操作。要相信对于入门阶段来说requests就是最稳的答案Scrapy这类框架可以等你做完整项目之后再学到时候你会更理解框架帮你省掉的到底是什么。2.3 解析页面从BeautifulSoup4开始拿到网页源代码之后数据还是混在一大堆HTML标签里的这时候需要做解析。解析方式主要有三种正则表达式、BeautifulSoup4、lxml配合XPath。我的建议是入门就学BeautifulSoup4它把复杂的HTML结构转换成Python对象你可以像查字典一样取标签、取属性心智负担最小。pip install beautifulsoup4配合它一起安装的还有解析器lxml速度更快容错也更好pip install lxml有一个坑我想提醒一下很多人安装了BeautifulSoup4之后忘了安装lxml然后解析时指定解析器就会报错。所以上面的两个库建议一起装。另外尽量在代码里显式指定解析器写成BeautifulSoup(html, lxml)不要偷懒省略否则可能出现不同环境行为不一致的问题。2.4 其他辅助工具存储和重试的搭配除了requests和BeautifulSoup4我还习惯在项目里多装几个工具库它们不是必须的但能减少很多手写代码的麻烦。pandas用来处理表格数据很方便尤其当你打算把结果保存成Excel文件时pandas的to_excel真的是一行代码搞定。openpyxl是pandas写入Excel时依赖的底层库如果只保存CSV的话用Python自带的csv模块就行。另外我强烈建议安装retrying库或者自己写一个简单的重试函数。网络请求是出了名的不稳定偶尔超时、偶尔返回5xx错误都很正常如果没有重试机制可能一个临时抖动就让整个爬虫中断。pip install pandas openpyxl retrying工具准备到这步就够了不需要再安装其他东西。下面进入核心环节。3. 一个完整的小爬虫从网页下载到数据落盘纸上谈兵聊再多不如写一个能跑通的例子。这个例子的目标是抓取一个公开示例站点的文章列表提取标题和发布时间最后保存成CSV文件。全程我会把每一步的思路、参数选择逻辑都讲清楚。3.1 先检查目标页面结构别一上来就写代码我习惯先把目标URL在浏览器里打开按F12进入开发者工具用元素选择器看一下标题区域到底在哪些标签下面。拿示例站点study.example.com来说假设它的文章列表页结构大致是这样div classlist-item h2 classtitlePython爬虫入门实战/h2 span classdate2025-01-12/span /div看到这种结构我心里就有数了可以用BeautifulSoup先找到所有class为list-item的div再在每个div里继续查找h2和span。这种“先看结构、再写选择器”的方式能避免大量瞎猜。我见过不少新手不看页面就直接写代码结果抓回来一堆空白然后开始怀疑自己的代码有问题其实只是选择器写错了。提示浏览器开发者工具里看到的HTML结构和requests拿到的源代码可能不完全一样。如果页面内容是通过JavaScript动态加载的requests拿到的HTML里根本找不到列表数据这个问题在本文第4章会专门讲。3.2 构建请求头让代码看起来像一个正常访问者很多服务器会识别requests默认的User-Agent然后返回403或者一个奇怪的验证页面。所谓User-Agent就是浏览器请求时附带的一个身份标识字段告诉服务器“我是谁”。requests默认的User-Agent是一长串Python标识服务器一眼就能看出来是脚本。解决办法很简单在请求头里设置一个常见的浏览器User-Agent。下面是我常用的写法import requests url https://study.example.com/articles headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } response requests.get(url, headersheaders, timeout10) print(response.status_code)这里的timeout参数也值得说一下它表示等待服务器响应的最长时间单位是秒。不给超时时间的话一旦目标服务器没有响应你的程序可能一直卡在那里整个爬虫就像死机一样。设成10秒遇到不响应的请求就能快速取消。头部字段中的User-Agent没必要每次自己手敲我一般是从浏览器复制一份真实的UA字符串粘进来默认情况下就能通过很多基础校验。3.3 解析HTML并提取目标数据在这一步把页面源代码交给BeautifulSoup然后写选择器取出标题和时间。代码是这样的from bs4 import BeautifulSoup soup BeautifulSoup(response.text, lxml) items soup.find_all(div, class_list-item) data [] for item in items: title_tag item.find(h2, class_title) date_tag item.find(span, class_date) if title_tag and date_tag: data.append({ title: title_tag.get_text(stripTrue), date: date_tag.get_text(stripTrue), })重点说一下get_text(stripTrue)这个方法。它会把标签里面的纯文本内容取出来同时去掉前后空格和换行。你可能会问为什么取文本的时候要这么小心因为我实际抓数据时发现HTML源码里经常有很多缩进和空白字符如果不处理存下来的数据会带着一堆没用的空格看着非常难受。如果页面结构稍微复杂一点比如一个标题可能嵌套在多个层级里直接用find可能不准这时可以改用select配合CSS选择器比如soup.select(.list-item .title)。我在实际使用中更偏好select因为CSS选择器的表达能力更强尤其在处理复杂嵌套结构时一行就能写完。3.4 翻页处理让爬虫学会遍历列表能够抓取单页之后下一个自然的需求是抓取多页。很多站点的分页URL是有规律的比如第一页是/articles?page1第二页是/articles?page2。这时候用循环遍历页面编号即可all_data [] for page in range(1, 6): # 抓前5页 url fhttps://study.example.com/articles?page{page} response requests.get(url, headersheaders, timeout10) soup BeautifulSoup(response.text, lxml) items soup.find_all(div, class_list-item) for item in items: title_tag item.find(h2, class_title) date_tag item.find(span, class_date) if title_tag and date_tag: all_data.append({ title: title_tag.get_text(stripTrue), date: date_tag.get_text(stripTrue), }) # 礼貌抓取每页之间随机等待一段时间 time.sleep(random.uniform(1, 3))这里用到的random和time模块都是Python标准库不需要额外安装。random.uniform(1, 3)会生成一个1到3秒之间的随机浮点数这样每两次请求之间的间隔不完全一样更接近人类的操作节奏。说实话“稍微随机一点”这个细节是我被服务器拒绝过一次之后才学到的教训。以前我用固定的sleep(1)对方日志里就能看到非常规律的请求模式后来改成随机间隔情况好了很多。3.5 保存数据CSV、Excel和数据库的取舍数据抓到之后保存格式怎么选我的建议是看数据量和后续用途。如果只是一次性的学习练习输出成CSV文件最方便Excel和主流数据分析工具都能直接打开。Python自带的csv模块就够用import csv with open(articles.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, date]) writer.writeheader() writer.writerows(all_data)这里特别注意两点。第一newline是为了避免Windows平台下CSV文件出现多余空行第二encodingutf-8-sig可以让CSV文件里的中文在Excel中正常显示不至于打开看到一堆乱码。这两个细节不处理的话文件确实也能生成但打开后的体验完全不一样。如果数据要存成Excel表格我一般用pandasimport pandas as pd df pd.DataFrame(all_data) df.to_excel(articles.xlsx, indexFalse)需要说明的是这个场景里你已经提前安装了pandas和openpyxl才能运行。SQLite则适合数据量较大、需要频繁查询的情况用Python内置的sqlite3模块就能操作不需要额外安装数据库服务。入门阶段我建议先掌握CSV和Excel两种方式就够了等真正做项目的时候再根据需求去学数据库。3.6 完整代码串一遍把上面的片段拼起来一个完整的入门爬虫就这样import csv import random import time import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } def fetch_page(page): url fhttps://study.example.com/articles?page{page} response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 状态码不是200时抛异常 return response.text def parse_articles(html): soup BeautifulSoup(html, lxml) result [] for item in soup.find_all(div, class_list-item): title_tag item.find(h2, class_title) date_tag item.find(span, class_date) if title_tag and date_tag: result.append({ title: title_tag.get_text(stripTrue), date: date_tag.get_text(stripTrue), }) return result all_data [] for page in range(1, 6): html fetch_page(page) all_data.extend(parse_articles(html)) time.sleep(random.uniform(1, 3)) with open(articles.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, date]) writer.writeheader() writer.writerows(all_data) print(f完成共保存 {len(all_data)} 条数据)这段代码看起来不长但它已经包含了发送请求、解析、循环翻页、延时、异常触发和数据落盘这些核心要素。入门阶段的爬虫本来就不需要多复杂能稳定把数据抓下来并保存好已经算是迈出了最关键的一步。后面学的所有东西本质上都是在这个框架上做替换和扩展。4. 入门阶段的高频问题与排查思路写爬虫最花时间的部分其实不是写代码而是排查问题。我把自己遇到过的高频问题整理成一个速查清单你在跑代码时遇到类似情况可以照着排查。4.1 乱码问题先确认页面编码再谈解析乱码是新手最容易遇到的情况。原因通常是requests按默认方式猜测了解码编码但和页面实际编码不一致。解决思路很简单# 方式一直接指定编码 response.encoding utf-8 # 方式二让requests自动推测 response.encoding response.apparent_encodingapparent_encoding会根据页面内容里的编码声明和字节特征自动判断绝大多数情况下都能得到正确结果。也可以用chardet模块来检测编码但requests其实内部已经集成了类似的检测能力所以先用上面的第二种方式就够了。还有极少数情况是页面编码本身声明错误这时候就得手动尝试gbk、gb2312、big5这些常见编码逐个试过去总能找到正确的。4.2 请求被拒绝403和429是最常见的信号看到403 Forbidden大概率是服务器识别出了非浏览器请求最常见原因就是User-Agent不对。先把请求头里的UA换成真实浏览器再试一次。看到429 Too Many Requests说明请求频率太高触发了限流这时候应该增加等待时间别硬刚。看到500或503通常是目标服务器本身出问题了跟你关系不大可以等几分钟再重试。下面是我常用的“重试三次失败就跳过”的基础封装def get_with_retry(url, headers, max_retries3, timeout10): for attempt in range(max_retries): try: response requests.get(url, headersheaders, timeouttimeout) response.raise_for_status() return response except Exception: if attempt max_retries - 1: raise time.sleep(2 * (attempt 1))这个函数里每次失败后的等待时间会逐步增加退避策略避免了失败时仍然高频重试的尴尬。如果你的爬虫仍然持续被拒绝可能不只是UA的问题那就得检查自己是不是请求太频繁或者目标站点出于反爬策略对特定路径做了防护。入门阶段我的建议非常直接换一个练手目标别在某个站点上纠缠太久学习过程中的性价比比什么都重要。4.3 页面里找不到数据动态加载和接口定位很多现代网站页面不是一次性在HTML里输出全部数据而是先加载一个空壳然后通过JavaScript异步请求数据接口再把内容渲染到页面上。requests拿到的HTML里自然就没有你要找的数据。此时可以在浏览器开发者工具的网络面板里把筛选器切换到XHR或者Fetch刷新页面观察哪些请求返回了JSON数据。你可能会看到某个接口的地址直接访问这个地址能返回结构化数据。这个思路不是破解什么加密协议只是利用公开的接口来获取数据非常正当。入门阶段遇到动态加载页面直接解析这种JSON接口往往比扣HTML更简单。拿到JSON之后用Python的json模块解析即可不需要BeautifulSoup出场因为JSON本身就是干净的结构化数据。至于更复杂的接口签名、加密参数我不建议现在研究那是另一个层次的话题。4.4 编码之外的其他解析报错当你用find_all选不到任何元素时不要急着改选择器先用print(response.text[:500])看一眼前500个字符确认你拿到的东西真的是HTML页面而不是一个登录跳转页、一个验证码页面或者一段报错信息。我见过太多人拿着错误的数据在那边猛调选择器完全是在浪费时间。如果HTML内容确实包含目标数据但选择器仍然匹配不到那就要仔细看标签是单标签还是双标签、属性是单引号还是双引号、class是不是动态变化的字符串。尤其class里面如果带空格比如classlist-item active使用find_all(div, class_list-item)依然可以匹配到因为它做的是包含匹配。要是遇到属性值带非常特殊的符号用CSS选择器会更稳妥。4.5 高频问题速查表现象可能原因优先排查方向乱码编码识别错误response.encoding 手动指定或使用 apparent_encoding403 Forbidden请求被识别为脚本检查User-Agent是否真实浏览器429 Too Many Requests请求频率过高增加延时降低并发找不到数据页面动态渲染打开开发者工具找XHR接口选择器匹配为空HTML结构和预期不一致打印原始HTML前500字符核对莫名超时网络波动或服务器慢设置timeout封装重试机制中文保存乱码文件编码不对CSV用utf-8-sigExcel确认openpyxl正常这张表是我把自己入门阶段踩过的坑整理出来的每个现象背后都对应一段抓狂的调试经历。你在实际开发里遇到的问题可能不会完全一样但排查思路是共通的先确认输入数据是否正常再确认解析过程最后再检查输出编码。按这个顺序做问题一般都能快速定位。5. 入门之后下一步往哪个方向走当你已经能稳定跑通上面的小爬虫说明基础环节没问题了。接下来我建议按你实际需求去扩展而不是漫无目的地刷教程。下面这几个方向是我自己按照性价比排序出来的你可以参考。5.1 从单页到多页把URL规律研究透很多站点的翻页有两种情况一种是URL里带页码参数像前面例子里的?page1这种最简单另一种是“加载更多”按钮通过点击触发接口请求背后往往是一个JSON接口每点一次发一个POST请求参数里带一个页码或者游标。解决办法就是在开发者工具里观察网络请求找到接口规律后用requests模拟注意POST请求可能需要构造表单数据或JSON数据。我一直觉得“观察接口规律”这件事才是爬虫的核心能力代码反而是最简单的部分。URL参数的命名、分页大小的限制、返回字段的含义这些都是你在开发者工具里能直接看到的信息认真观察几遍就能总结出规律。5.2 需要登录的站点用Session维持状态如果你真的需要抓取一个需要登录才能看的内容前提是你有合法账号且这个行为被对方允许这时候requests.Session就能派上用场。Session对象会在多个请求之间自动保持Cookie相当于你用一个浏览器开着登录状态连续访问多个页面都不用重新登录。它的使用方式很简单基本就是把requests.get换成session.get。需要注意模拟登录时通常要分析登录接口需要哪些参数有的会包含一个页面下发的token或者动态生成的加密字段。入门阶段我建议你只挑那些接口简单、逻辑清晰的站点练习把复杂加密留到后面再说。5.3 数据清洗与存储规范化抓取只是第一步数据质量往往才是真正头疼的部分。同样的标题可能有的带空格有的带全角符号日期字段可能横跨多种格式这些都需要清洗和规范化。我常用的方法是把数据都整理成字典列表然后用pandas批量处理比如去除重复行、统一日期格式、填充缺失值等等。这一步做得好后续分析才会顺利。存储方面CSV适合轻量级场景SQLite适合单机中等规模数据MySQL或PostgreSQL适合团队协作和在线服务。入门阶段先把前两者用好等真正有项目需求时再迁移不会有什么障碍。5.4 什么时候学Scrapy什么时候学异步很多人入门没多久就开始焦虑要不要学Scrapy我的意见是如果你每次抓取任务只需要很少的页面并发需求不高先用纯requests就完全够用。当你的需求变成“每天抓取几万个URL、需要自动重试、需要管道化存储”的时候再引入Scrapy。这个框架自带调度器、下载器、中间件和Item Pipeline结构非常清晰但它有一定的学习曲线过早接触可能会让你的注意力从数据问题转移到框架自身的概念上。异步HTTP库比如aiohttp和httpx也不是入门阶段的必备技能。异步的优势是能在同一时间发起大量请求但前面说过入门阶段的爬虫应该尽量保持低频率和礼貌访问异步带来的提速在这个阶段反而容易触发风控。先学会把同步流程写得稳定扎实之后遇到真实的高吞吐需求再学异步你会更容易理解它解决的问题到底是什么。5.5 维护和观察给你的爬虫加一点“仪表盘”随着爬虫越写越多你会发现维护成本开始体现出来。我给的实用建议是每条请求都打日志至少记录时间、URL和状态码。可以用Python自带的logging模块把日志同时输出到控制台和文件这样运行几天之后出了问题回看日志就知道是哪个URL在什么时候失败了。再加一个简单的计数变量统计成功和失败的比例这些“土办法”在项目早期比任何监控系统都直观。写在最后爬虫入门本质上是“控制节奏”的学问回头看我自己的爬虫入门经历最大的体会是爬虫考验的不是写代码的能力而是你能不能控制好自己的节奏。这里说的节奏有两层意思一是请求的速度要克制尊重目标服务器二是学习的节奏要克制不要一上来就追着最复杂的框架和反爬对抗去研究而是把基础链路老老实实跑通。我最后再分享一个特别实用的习惯每完成一个小爬虫就在项目根目录放一个README文件里面记录三件事——目标页面地址、采集字段、当时的延时策略。这个习惯帮了我大忙。因为过两个月你自己都会忘了当初是怎么抓的有记录之后想复用或者修改就很轻松。你可以试着给这个入门爬虫加一个README内容就写“2025年某周抓取某示例站点的文章标题和日期间隔1到3秒”这种真实记录积累多了你就会发现自己的成长速度比想象中快很多。