ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python爬虫学习框架:从请求到入库的完整路线与避坑指南

Python爬虫学习框架:从请求到入库的完整路线与避坑指南

简介:这份PDF面向零基础到进阶的Python爬虫学习者,系统梳理了从入门到实战的完整知识框架,帮助初学者解决「学什么、怎么学、在哪里学」的困惑。内容围绕爬虫三部曲展开:获取数据环节讲解urllib、requests、aiohttp等请求库及异步抓取思路;解析数据环节对比pyquery、BeautifulSoup、XPath与正则的适用场景;数据存储环节则涉及TXT、JSON、CSV及MySQL、MongoDB等方案,并延伸至代理IP、请求头伪装、Selenium动态渲染与分布式爬虫等进阶技巧,同时提醒Robots协议与合规边界。资源包共1个PDF文件,大小约2.1MB,便于随时查阅与打印。目前已有174人学习,适合希望用一份文档建立完整爬虫知识体系、少走弯路的开发者参考。

1. 一张爬虫学习地图:从请求到入库,到底该按什么顺序学

很多人学 Python 爬虫,第一反应是打开编辑器写requests.get(),跑通了就觉得自己会了,结果一遇到动态加载、登录校验、数据量上万就原地翻车。问题不在代码,在于没有一张完整的学习地图。这份《2023年最新Python爬虫的完整学习框架》把整个方向拆成了四块:请求库、解析库、存储方案、工程化框架。四块之间不是并列关系,而是递进关系——先能拿到网页,再能从网页里取出数据,再把数据存下来,最后用框架把整条链路工程化。轻量级爬虫和工程化爬虫的分界线,就在第三步到第四步之间。如果你只是偶尔抓几十条数据做分析,前三块够用;如果你要面对海量页面、复杂反爬、分布式调度,第四块绕不过去。这份笔记按这个顺序梳理,每一块都落到具体库、具体参数、具体易错点,适合正在系统学爬虫或者准备复习的人对照查漏补缺。

2. 请求库选型:urllib、requests、aiohttp 到底怎么选

2.1 同步请求的两条路:urllib 与 requests

Python 自带的urllib是内置的 HTTP 请求库,不需要额外安装,适合理解请求的底层结构。但它的 API 设计比较绕,构造一个带参数的 GET 请求要拼接 URL、创建 Request 对象、再调用urlopen,代码量大且可读性差。requests的口号是“为人类设计的 HTTP 库”,同样的请求用requests写出来简洁得多,而且自带 JSON 解析器,处理返回的 JSON 数据不需要额外调json.loads()。

import requests # 设置请求头,模拟真实浏览器访问 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Accept-Language": "zh-CN,zh;q=0.9", } # 带参数的 GET 请求,params 会自动拼接到 URL resp = requests.get( "https://example.com/search", params={"q": "python", "page": 1}, headers=headers, timeout=10, # 超时设置,避免无限等待 ) # 检查状态码,非 200 直接抛异常 resp.raise_for_status() # 自动解析 JSON 响应 data = resp.json() print(data)

这段代码里几个参数值得注意:headers中的User-Agent是最基本的反反爬手段,很多网站会直接拒绝没有 UA 的请求;timeout必须设,否则遇到慢响应会一直挂住;raise_for_status()帮你快速定位 4xx 和 5xx 错误。requests的文档和社区资源非常丰富,遇到问题基本都能搜到答案。

提示:建议先从urllib开始学,理解请求的构造过程,再切换到requests提升开发效率。直接上requests容易知其然不知其所以然。

2.2 异步请求:aiohttp 与性能差距

同步请求的瓶颈在于等待。每次请求都要等服务器响应回来才能发下一个,100 次请求按每次 0.3 秒算,至少 30 秒。异步请求可以在等待响应的间隙去发下一个请求,同样的 100 次请求能压缩到 3 秒左右。aiohttp是 Python 异步请求的主流库,配合async/await语法,代码结构比回调风格清晰很多。

import aiohttp import asyncio async def fetch(session, url): async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp: return await resp.text() async def main(urls): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] results = await asyncio.gather(*tasks) return results urls = [f"https://example.com/page/{i}" for i in range(100)] results = asyncio.run(main(urls))

ClientSession是复用的核心,不要在每次请求时都新建 session,否则连接池的优势就没了。asyncio.gather把所有任务并发提交,await等待全部完成。异步代码的调试比同步麻烦,出错时堆栈信息不够直观,建议先用少量 URL 验证逻辑再放大规模。

2.3 动态页面与自动化:Selenium 的适用边界

有些页面内容不是直接写在 HTML 里的,而是通过 JavaScript 异步加载后再渲染出来的。这时候requests拿到的源码里没有你要的数据,需要浏览器抓包分析真实请求,或者用Selenium驱动真实浏览器渲染页面后再提取。Selenium的优势是能模拟点击、滚动、输入等交互操作,适合处理登录、翻页、懒加载等场景。代价是速度慢、资源占用高,不适合大规模抓取。

注意:能用抓包分析真实 API 解决的,就不要上 Selenium。Selenium 是最后的手段,不是首选方案。

3. 解析库对比:正则、BeautifulSoup、PyQuery、XPath 各自的主场

3.1 四种解析方式的定位差异

拿到网页源码之后,下一步是从 HTML 或 JSON 中提取目标数据。原文提到了四种主流方式:正则表达式、BeautifulSoup、PyQuery、XPath。它们不是互相替代的关系,而是各有适用场景。

解析方式核心优势适用场景学习曲线
正则 re速度最快,不依赖 HTML 结构提取格式固定的文本片段较高,正则语法容易写错
BeautifulSoup容错性强,自动修正不规范 HTML结构混乱的页面低,API 直观
PyQueryjQuery 语法,CSS 选择器前端基础好的人低,有 jQuery 经验几乎零成本
XPath函数丰富,支持复杂条件嵌套深、需要精确定位中等,语法需要记

原文的建议是:前端基础扎实的用 PyQuery 最方便,BeautifulSoup 也不错,正则速度最快但写起来麻烦。这个判断是合理的。实际项目中,常见组合是requests + BeautifulSoup做快速原型,Scrapy + XPath做工程化抓取。

3.2 BeautifulSoup 的解析器选择

BeautifulSoup 本身不解析 HTML,它依赖外部解析器。Python 标准库自带html.parser,速度一般但不需要额外安装。lxml解析器速度更快、容错性更好,是推荐选择。安装lxml后,初始化时指定解析器即可。

from bs4 import BeautifulSoup html = """ <html><body> <div class="item"> <h2 class="title">示例标题</h2> <span class="price">¥99.00</span> </div> </body></html> """ soup = BeautifulSoup(html, "lxml") # 按标签和 class 定位 title = soup.find("h2", class_="title").text price = soup.find("span", class_="price").text # 按 CSS 选择器定位,返回列表 items = soup.select("div.item") for item in items: print(item.select_one(".title").text)

find返回第一个匹配元素,find_all返回列表,select支持 CSS 选择器语法。.text提取标签内的纯文本。注意class_带下划线是因为class是 Python 关键字。

3.3 XPath 的核心语法与高频函数

XPath 用路径表达式在 XML 和 HTML 文档中定位节点,功能比 CSS 选择器更强。Scrapy 的response.xpath()返回的是选择器列表,需要配合extract()或get()取出字符串。

# 假设 response 是 Scrapy 的 Response 对象 # 提取所有 class 为 title 的 h2 文本 titles = response.xpath('//h2[@class="title"]/text()').getall() # 提取 href 属性 links = response.xpath('//a/@href').getall() # 使用 contains 做模糊匹配 items = response.xpath('//div[contains(@class, "item")]') # 使用 position 取第一个 first = response.xpath('//ul/li[1]/text()').get()

//表示任意层级,/表示直接子节点,@取属性,text()取文本内容。contains()用于 class 名有多个值的情况,position()用于取指定位置的元素。XPath 内置超过 100 个函数,覆盖字符串处理、数值比较、日期时间等,遇到复杂提取需求时先查函数列表,往往有现成方案。

提示:解析库的选择没有绝对优劣,关键看页面结构。结构规整用 CSS 选择器,结构混乱用 BeautifulSoup 的容错,需要复杂条件用 XPath,格式固定的文本片段用正则。

4. 数据存储:从 TXT 到 Redis 的选型逻辑

4.1 小数据量:文件存储够用

数据量在几千条以内时,直接写文件是最省事的方案。TXT 适合纯文本,CSV 适合表格型数据,JSON 适合嵌套结构。Python 标准库的csv和json模块足够应付。

import csv import json # 写入 CSV with open("data.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["title", "price"]) writer.writeheader() writer.writerow({"title": "示例商品", "price": "99.00"}) # 写入 JSON Lines,每行一个独立 JSON 对象 with open("data.jsonl", "w", encoding="utf-8") as f: for item in [{"title": "A", "price": "10"}, {"title": "B", "price": "20"}]: f.write(json.dumps(item, ensure_ascii=False) + "\n")

JSON Lines 格式比单个大 JSON 数组更适合爬虫场景,因为可以逐行追加写入,不需要一次性加载全部数据到内存。

4.2 中等数据量:MySQL 与 MongoDB 的分工

数据量上万之后,文件存储的查询和去重变得困难,需要上数据库。MySQL 是关系型数据库的代表,适合结构化程度高、字段固定的数据,成熟度高、生态完善。MongoDB 是文档型数据库,适合非结构化数据,比如评论正文、图片链接、嵌套的 JSON 结构。原文提到 MongoDB 可以方便地存储非结构化数据,并且可以通过PyMongo在 Python 中操作。

from pymongo import MongoClient client = MongoClient("mongodb://localhost:27017/") db = client["spider_db"] collection = db["items"] # 插入单条 collection.insert_one({"title": "示例", "price": 99.0, "tags": ["新品", "热卖"]}) # 批量插入 collection.insert_many([ {"title": "商品A", "price": 10.0}, {"title": "商品B", "price": 20.0}, ]) # 按条件查询 results = collection.find({"price": {"$gt": 15}}) for doc in results: print(doc["title"])

MongoDB 的$gt、$lt、$in等操作符支持灵活的查询条件。insert_many比逐条插入快得多,批量操作是提升写入效率的关键。

4.3 大规模与分布式:Redis 的角色

Redis 是内存数据库,数据全部存在内存中,访问速度极快。在爬虫场景中,Redis 主要承担两个角色:一是作为分布式爬虫的请求队列,多个爬虫实例从同一个 Redis 队列中取 URL,实现协同工作;二是作为去重集合,利用 Redis 的 Set 结构记录已抓取的 URL,避免重复请求。原文提到 Redis 支持 hash、set、list 等丰富的数据结构,一般应用于分布式爬虫的数据存储。

import redis r = redis.Redis(host="localhost", port=6379, db=0) # 用 Set 做 URL 去重 url = "https://example.com/page/1" if r.sadd("visited_urls", url): print("新 URL,继续抓取") else: print("已抓取过,跳过") # 用 List 做请求队列 r.lpush("url_queue", "https://example.com/page/2") next_url = r.rpop("url_queue")

sadd返回 1 表示元素是新加入的,返回 0 表示已存在。这个特性天然适合去重。lpush和rpop组合可以实现先进先出的队列。

注意:数据库知识在爬虫中的要求并不高,核心就是入库和查询。不需要系统学完数据库再开始爬虫,用到什么学什么即可。

5. 工程化框架:Scrapy、PySpider、Nutch 的定位与选择

5.1 Scrapy:最接近爬虫本质的框架

Scrapy 是 GitHub 上 star 数最高的 Python 爬虫框架,核心优势在于灵活的定制化爬取和模块化的架构。它把爬虫拆成 Spider、Item Pipeline、Downloader Middleware、Scheduler 等组件,每个组件都可以独立替换和扩展。学会 Scrapy 之后,基本具备爬虫工程师的思维。

import scrapy class ExampleSpider(scrapy.Spider): name = "example" start_urls = ["https://example.com/list"] def parse(self, response): # 提取列表页中的详情页链接 for link in response.css("a.detail::attr(href)").getall(): yield response.follow(link, callback=self.parse_detail) # 翻页 next_page = response.css("a.next::attr(href)").get() if next_page: yield response.follow(next_page, callback=self.parse) def parse_detail(self, response): yield { "title": response.css("h1::text").get(), "price": response.css("span.price::text").get(), }

start_urls定义入口页面,parse是默认的回调函数。response.follow会自动处理相对 URL 和域名拼接。yield返回的字典会进入 Item Pipeline,可以在 Pipeline 中做清洗、去重、入库。Scrapy 的异步下载器基于 Twisted,并发性能远超手写的同步爬虫。

5.2 PySpider 与 Nutch 的差异化定位

PySpider 的特点是提供了 Web UI,可以在浏览器界面上编写脚本、调度任务、实时查看抓取结果。后端使用常用数据库存储结果。它的功能完整度更像一个产品而不是框架,适合需要可视化管理和快速上手的场景。Nutch 是 Apache 顶级项目,天生为搜索引擎设计,支持分布式抓取,有 Hadoop 支持,可以进行多机分布抓取、存储和索引。它提供了插件框架,方便扩展各种网页内容的解析和数据采集功能。

原文的建议是先从 Scrapy 学起,再去接触 PySpider,最后了解 Nutch。这个顺序是合理的:Scrapy 帮你建立爬虫的工程化思维,PySpider 让你看到产品化的可能性,Nutch 则是搜索引擎级别的解决方案,学习成本最高。

框架核心定位学习优先级适用场景
Scrapy灵活定制化爬取最先学通用爬虫工程化
PySpider产品级 WebUI其次需要可视化调度
Nutch搜索引擎解决方案最后分布式大规模抓取

5.3 从轻量级到工程化的能力跃迁

轻量级爬虫和工程化爬虫的分界线不在于代码量,而在于是否具备可维护、可扩展、可监控的能力。轻量级爬虫是脚本,工程化爬虫是系统。Scrapy 的中间件机制让你可以统一处理请求头、代理、重试、异常;Pipeline 机制让你可以统一处理数据清洗、去重、入库;信号机制让你可以监控爬虫的运行状态。这些能力在脚本里需要自己实现,在框架里是开箱即用的。

提示:不要为了用框架而用框架。如果目标只是抓几百条数据,requests + BeautifulSoup更快。框架的价值在规模化和可维护性上。

6. 易漏点与易错点:那些跑通之后才暴露的问题

6.1 请求头缺失导致 403

易漏点:只写了requests.get(url),没有设置User-Agent。为什么漏:本地测试时有些网站不校验 UA,跑通了就以为没问题,换一个网站就 403。怎么补:养成习惯,每次请求都带上完整的请求头,至少包含User-Agent、Accept、Accept-Language。可以从浏览器开发者工具里复制真实请求头。

6.2 编码问题导致乱码

易漏点:resp.text返回的文本出现乱码。为什么漏:requests会根据响应头猜测编码,但有些网站声明错误或缺失编码信息。怎么补:手动指定编码,resp.encoding = "utf-8"或resp.encoding = resp.apparent_encoding。BeautifulSoup 初始化时也可以指定from_encoding参数。

6.3 动态加载内容抓不到

易漏点:requests拿到的 HTML 里没有目标数据。为什么漏:数据是通过 JavaScript 异步加载的,不在初始 HTML 中。怎么补:打开浏览器开发者工具的 Network 面板,筛选 XHR/Fetch 请求,找到真实的数据接口,直接请求接口。如果接口有加密参数,再考虑 Selenium。

6.4 数据库连接未关闭

易漏点:爬虫跑完后数据库连接没有释放。为什么漏:脚本运行时间短,问题不明显;长时间运行后连接数耗尽。怎么补:使用上下文管理器或try/finally确保连接关闭。MongoDB 的MongoClient内部有连接池,通常不需要手动关闭,但 MySQL 连接必须显式关闭。

6.5 忽略 Robots 协议

易漏点:没有检查目标网站的robots.txt。为什么漏:觉得“我只是抓一点点,没关系”。怎么补:爬取前先访问https://目标网站/robots.txt,查看Disallow规则。Robots 协议是爬虫的规矩,遵守它既是法律要求也是道德底线。原文明确提到“无规矩不成方圆,Robots 协议就是爬虫中的规矩”。

7. 复习清单:从请求到入库的查漏补缺路线

复习爬虫知识,最怕的是东看一眼西看一眼,没有主线。我自己的习惯是按“请求 → 解析 → 存储 → 框架”四步走一遍,每一步问自己三个问题:这个环节的核心库是什么?关键参数有哪些?最容易翻车的地方在哪?下面这张清单可以直接拿来对照。

复习模块核心检查项常见翻车点
请求urllib/requests/aiohttp 的适用场景没设 timeout、没设 UA
解析BeautifulSoup/PyQuery/XPath/正则的选择编码错误、选择器写错
存储文件/MySQL/MongoDB/Redis 的选型连接未关闭、批量插入未用
框架Scrapy 的 Spider/Pipeline/Middleware中间件顺序、异步调试
合规Robots 协议、请求频率忽略 Disallow、请求过密

具体操作上,我建议拿一个真实的目标页面,从零走一遍完整流程:先用requests拿到 HTML,确认状态码和编码;再用 BeautifulSoup 或 XPath 提取目标字段,打印出来核对;然后写入 CSV 或 MongoDB,确认数据完整;最后如果数据量大,用 Scrapy 重写一遍,对比代码量和运行效率。这一套走下来,哪个环节薄弱一目了然。

还有一个容易被忽略的复习点:异步请求的调试。aiohttp的报错信息不如同步请求直观,建议在fetch函数里加日志,记录每个请求的开始和结束时间,方便定位是哪个 URL 出了问题。Scrapy 的日志系统更完善,但中间件的执行顺序需要特别注意,process_request是正序执行,process_response是逆序执行,搞反了会导致请求头没加上或者响应被提前拦截。

从那以后我每次复习爬虫,都强制自己从请求到入库完整走一遍,不跳步。哪怕是最简单的页面,也把四个环节都过一遍,因为翻车往往发生在你觉得“这个我肯定会”的地方。希望这份清单帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进