news 2026/10/17 5:05:28

Python爬虫入门:requests库核心用法与Scrapy衔接指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫入门:requests库核心用法与Scrapy衔接指南

1. 从 urllib 到 requests:为什么爬虫入门总绕不开这一课

如果你刚开始接触 Python 爬虫,大概率会经历这样一个过程:先听说有个叫 Scrapy 的框架很厉害,兴冲冲打开官方文档,结果发现文档里到处都在讲Request、Response、Selector,而你连最基础的 HTTP 请求都还没发明白。于是又回头去查“Python 怎么发请求”,搜出来的答案十有八九会指向两个东西——标准库urllib和第三方库requests。

这个顺序其实是对的。Scrapy 本质上是一个高度封装的异步爬虫框架,它把请求调度、去重、中间件、管道这些工程化问题都替你处理好了,但代价是你必须理解它内部那套基于Request和Response的抽象模型。而requests恰恰是理解这套模型最平滑的入口——它的 API 设计足够直观,直观到你几乎可以凭直觉写出一个可用的请求,同时它又足够完整,覆盖了真实爬虫场景里 90% 以上的需求。

我在带新人的时候经常说一句话:先把 requests 用熟,再去看 Scrapy,你会觉得框架的每一个设计都在帮你解决你曾经手动处理过的麻烦。反过来,如果跳过 requests 直接啃 Scrapy,你大概率会陷入“照着文档能跑,换个网站就懵”的状态,因为你不知道框架底层到底替你做了什么。

这篇笔记就围绕 requests 这个库展开,把它在爬虫场景下的核心用法、容易踩的坑、以及和 Scrapy 之间的衔接关系讲清楚。不管你是完全没写过爬虫的新手,还是已经用过 requests 但总觉得“会用但不懂”的开发者,应该都能从中找到一些之前忽略的细节。

2. requests 到底解决了 urllib 的哪些痛点

2.1 标准库 urllib 的原始面貌

Python 内置的urllib其实功能并不弱,它能发 GET、POST 请求,能处理 cookie,能设置 header。但问题在于它的 API 设计带着浓厚的“标准库风格”——层级深、命名长、参数分散。举个最直观的例子,用 urllib 发一个带参数的 GET 请求,代码大概长这样:

import urllib.request import urllib.parse params = urllib.parse.urlencode({'keyword': 'python', 'page': 1}) url = 'https://example.com/search?' + params req = urllib.request.Request(url) req.add_header('User-Agent', 'Mozilla/5.0') response = urllib.request.urlopen(req) content = response.read().decode('utf-8')

这段代码能跑,但读起来很累。参数要手动编码拼接,header 要单独构造 Request 对象再添加,响应内容要自己 decode。如果遇到 POST 请求、文件上传、会话保持,代码复杂度会迅速上升。

2.2 requests 的设计哲学:为人类而写

requests的作者 Kenneth Reitz 有一句很有名的话:“HTTP for Humans”。这个库从诞生之初就瞄准了一个目标——让发 HTTP 请求这件事变得像说话一样自然。同样的 GET 请求,用 requests 写出来是这样的:

import requests response = requests.get( 'https://example.com/search', params={'keyword': 'python', 'page': 1}, headers={'User-Agent': 'Mozilla/5.0'} ) content = response.text

参数用字典传,header 用字典传,响应直接.text拿到解码后的文本。没有多余的中间对象,没有手动编码,没有显式 decode。这种差异在简单场景下可能只是“少写几行”,但在复杂场景下——比如需要维持会话、处理重定向、上传文件、设置超时和重试——requests 的优势会被放大到非常明显。

2.3 一个容易被忽略的点:requests 并不是“另一个 urllib”

很多人以为 requests 是 urllib 的替代品,其实更准确的说法是:requests 建立在 urllib3 之上,而 urllib3 又封装了底层的 http.client。也就是说,requests 并没有抛弃标准库,它只是在上层做了一层极其友好的抽象。理解这一点很重要,因为它意味着当你遇到 requests 解决不了的问题时,你依然可以往下钻,去操作 urllib3 甚至 http.client 的层面。

对于爬虫学习来说,这个认知能帮你建立一条清晰的层次链:http.client→urllib3→requests→Scrapy。每一层都在解决上一层暴露出来的工程问题,而 Scrapy 站在最顶端,把并发、调度、去重、持久化这些爬虫特有的问题也一并接管了。

3. 请求发送的核心参数与实战配置

3.1 GET 与 POST:不只是方法名的区别

GET 和 POST 在 HTTP 协议层面的区别,很多文章都讲过——GET 参数放在 URL 里,POST 参数放在请求体里。但在爬虫实战中,真正需要关注的是目标网站期望你用哪种方式。

有些网站的搜索接口,URL 里明明带着?keyword=xxx,看起来是 GET,但实际提交时却要求 POST。这种情况通常是因为前端用 JavaScript 拦截了表单提交,把参数重新打包成了 POST 请求。判断方法很简单:打开浏览器开发者工具的 Network 面板,看实际发出的请求方法是什么。

requests 里发送 POST 请求,参数用data传递:

response = requests.post( 'https://example.com/login', data={'username': 'user', 'password': 'pass'} )

如果目标接口期望的是 JSON 格式的请求体,则用json参数:

response = requests.post( 'https://example.com/api/search', json={'keyword': 'python', 'page': 1} )

json参数会自动把字典序列化为 JSON 字符串,并设置Content-Type: application/json。这个细节很多人会忽略,手动用data=json.dumps(...)然后忘记设置 header,导致服务端解析失败。

3.2 headers 的取舍:哪些必须带,哪些可以省

请求头是爬虫里最容易“用力过猛”的地方。新手常常从网上抄一大段 header,把 Accept、Accept-Language、Accept-Encoding、Connection、Cache-Control 全带上,结果反而因为某些字段不一致被识别出来。

我的经验是:优先只带 User-Agent,遇到问题再逐个补充。User-Agent 是最基础的标识,不带它很多网站会直接返回 403。但其他字段,比如 Accept-Language,只有在目标网站根据语言返回不同内容时才需要设置。

有一个字段值得特别注意:Referer。有些网站会检查请求来源,如果 Referer 为空或者不是站内地址,就会拒绝请求。这种情况在图片防盗链、API 接口鉴权里很常见。设置方法:

headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Referer': 'https://example.com/list' }

注意:不要伪造 Referer 去访问你无权访问的资源。这里讨论的 Referer 设置,仅用于正常访问公开页面时绕过不合理的防盗链限制。

3.3 超时设置:不设超时的爬虫等于埋雷

requests默认没有超时限制。这意味着如果目标服务器响应极慢或者根本不响应,你的程序会一直卡在那里。在单次请求的脚本里这可能只是让你多等一会儿,但在批量爬取或者 Scrapy 这样的框架里,一个卡住的请求会拖垮整个调度队列。

设置超时很简单:

response = requests.get(url, timeout=10)

这里的timeout是连接和读取的总超时时间。更精细的写法是传一个元组:

response = requests.get(url, timeout=(3, 10))

第一个数字是连接超时,第二个是读取超时。连接超时指建立 TCP 连接的时间,读取超时指服务器开始返回数据后,两次数据到达之间的最大间隔。分开设置的好处是,对于连接快但响应慢的服务器,你可以给读取阶段更长的容忍时间。

3.4 会话保持:Session 对象的正确用法

如果你需要先登录再访问其他页面,或者需要维持 cookie 状态,就必须用requests.Session()。Session 对象会自动在多次请求之间保持 cookie,就像浏览器一样。

session = requests.Session() session.post('https://example.com/login', data={'username': 'user', 'password': 'pass'}) response = session.get('https://example.com/profile')

这里有一个常见的误区:很多人以为 Session 会自动处理所有事情,包括 JavaScript 生成的 cookie。实际上,Session 只能保持服务端通过 Set-Cookie 下发的 cookie,对于前端 JS 动态生成的 token,它无能为力。遇到这种情况,要么分析 JS 逻辑手动构造,要么用自动化浏览器工具。

4. 响应处理:从原始字节到可用数据

4.1 response.text 与 response.content 的选择

response.text返回的是解码后的字符串,response.content返回的是原始字节。什么时候用哪个?

如果你确定响应内容是文本(HTML、JSON、XML),用.text更方便。但.text的解码依赖 requests 对编码的猜测,这个猜测基于 HTTP header 里的Content-Type字段。如果服务端没有正确设置编码,或者编码声明在 HTML meta 标签里而不是 header 里,.text就可能出现乱码。

这时候有两个解决方案。一是手动指定编码:

response.encoding = 'utf-8' content = response.text

二是直接用.content拿到字节,自己 decode:

content = response.content.decode('utf-8')

对于二进制内容(图片、视频、PDF),必须用.content,因为.text会尝试解码,可能损坏数据。

4.2 JSON 响应的处理

现代网站大量使用 JSON 接口。requests 内置了.json()方法,直接把响应体解析为 Python 字典或列表:

data = response.json() items = data['results']

但要注意,.json()在解析失败时会抛出json.JSONDecodeError。如果目标接口偶尔返回 HTML 错误页而不是 JSON,程序就会崩溃。稳妥的做法是加一层判断:

try: data = response.json() except ValueError: print('响应不是合法 JSON:', response.text[:200])

4.3 状态码检查与异常处理

requests不会因为 HTTP 状态码是 4xx 或 5xx 就抛出异常,它只是把状态码放在response.status_code里。这意味着如果你不主动检查,可能会把 404 页面的内容当成正常数据解析。

有两种处理方式。一是手动检查:

if response.status_code == 200: # 正常处理 else: # 记录日志或重试

二是用raise_for_status():

response.raise_for_status()

这行代码会在状态码不是 2xx 时抛出HTTPError异常。配合 try-except 使用,可以统一处理各种网络异常:

try: response = requests.get(url, timeout=10) response.raise_for_status() except requests.exceptions.Timeout: print('请求超时') except requests.exceptions.HTTPError as e: print('HTTP 错误:', e) except requests.exceptions.RequestException as e: print('其他请求异常:', e)

RequestException是所有 requests 异常的基类,放在最后兜底。

5. 那些文档里不会写的踩坑经验

5.1 编码问题的完整排查链路

编码乱码是爬虫新手遇到的第一大坑。我见过太多人对着response.text里的乱码发呆,不知道从哪下手。这里给一条完整的排查链路。

第一步,看response.encoding是什么。requests 会根据 header 推断,如果 header 没写,默认是 ISO-8859-1。

第二步,看response.apparent_encoding。这是 requests 用 chardet 库猜测的编码,通常比 header 更准,但计算有开销。

第三步,如果两者都不对,去 HTML 源码里找<meta charset="xxx">,手动设置response.encoding = 'xxx'。

第四步,如果还是乱码,用.content拿字节,尝试用常见编码(utf-8、gbk、gb2312)逐个 decode,看哪个不出错。

for enc in ['utf-8', 'gbk', 'gb2312', 'latin-1']: try: text = response.content.decode(enc) print(f'{enc} 解码成功') break except UnicodeDecodeError: continue

这条链路走下来,99% 的编码问题都能定位。

5.2 重定向与历史记录

requests 默认会自动跟随重定向。这通常是好事,但有时候你需要知道最终请求到了哪个 URL,或者需要阻止自动重定向。

查看最终 URL 用response.url,查看重定向历史用response.history。阻止自动重定向则设置allow_redirects=False:

response = requests.get(url, allow_redirects=False) print(response.status_code) # 可能是 301 或 302 print(response.headers['Location']) # 重定向目标

这个技巧在分析短链接、登录跳转逻辑时特别有用。

5.3 代理设置的正确姿势

在某些网络环境下,你可能需要通过代理访问目标网站。requests 支持 HTTP 和 SOCKS 代理:

proxies = { 'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080' } response = requests.get(url, proxies=proxies)

注意:代理的使用必须遵守目标网站的服务条款和相关法律法规,仅用于合法的网络调试和访问公开资源。

5.4 并发请求的朴素实现与局限

requests 是同步库,一个请求发出去要等响应回来才能发下一个。对于需要爬取大量页面的场景,这个速度是不可接受的。朴素的加速方案是用多线程:

from concurrent.futures import ThreadPoolExecutor def fetch(url): return requests.get(url, timeout=10).text urls = ['https://example.com/page/{}'.format(i) for i in range(1, 21)] with ThreadPoolExecutor(max_workers=5) as executor: results = list(executor.map(fetch, urls))

这段代码能把 20 个请求的耗时从“20 倍单次耗时”压缩到“大约 4 倍单次耗时”。但多线程方案有它的局限:线程数量不好控制,太多会拖垮目标服务器也拖垮自己;异常处理变得复杂;cookie 和 session 的共享需要额外小心。

这正是 Scrapy 存在的意义之一。Scrapy 基于 Twisted 实现异步 IO,用单线程事件循环处理并发请求,不需要手动管理线程池,同时内置了下载延迟、自动限速、重试机制这些工程化功能。当你发现 requests 加多线程的代码越来越难维护时,就是时候转向 Scrapy 了。

6. 从 requests 到 Scrapy 的思维转换

6.1 请求对象的抽象升级

在 requests 里,一个请求就是一次函数调用,响应就是返回值。在 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.item::attr(href)').getall(): yield scrapy.Request(link, callback=self.parse_detail) def parse_detail(self, response): yield { 'title': response.css('h1::text').get(), 'content': response.css('div.content::text').get() }

这段代码里,scrapy.Request对象被 yield 出去后,Scrapy 的调度器会接管它,负责发送请求、处理重试、调用回调。你不再需要关心“怎么发请求”,只需要关心“请求什么”和“拿到响应后做什么”。

6.2 选择器:从 BeautifulSoup 到 parsel

requests 本身不提供 HTML 解析功能,通常搭配 BeautifulSoup 或 lxml 使用。Scrapy 内置了 parsel 库,支持 CSS 和 XPath 两种选择器,语法和 BeautifulSoup 有相似之处但更简洁。

如果你已经熟悉 BeautifulSoup 的find和find_all,迁移到 parsel 的 CSS 选择器不会太困难。核心差异在于 parsel 的选择器返回的是 Selector 对象列表,需要用.get()或.getall()提取文本:

# BeautifulSoup soup.find('h1').text # parsel response.css('h1::text').get()

6.3 中间件与管道:工程化的开始

Scrapy 真正区别于 requests 的地方在于中间件和管道。下载中间件可以在请求发出前和响应返回后插入自定义逻辑,比如随机切换 User-Agent、添加代理、处理异常重试。管道则负责处理爬取到的数据,比如去重、清洗、写入数据库。

这些机制在 requests 里都需要手动实现,而在 Scrapy 里只需要按照约定编写类和方法。这也是为什么我说:requests 让你学会发请求,Scrapy 让你学会做工程。

7. 我个人的一些使用习惯和建议

用了这么多年 requests,有几个习惯我觉得值得分享。

第一,永远设置超时。不管是写脚本还是写正式项目,timeout参数是必须的。我一般设(5, 15),连接 5 秒,读取 15 秒。这个值可以根据目标网站的响应速度调整,但绝不能留空。

第二,用 Session 而不是裸 requests。即使不需要保持登录状态,Session 也能复用 TCP 连接,减少握手开销。对于需要发多个请求到同一个域名的场景,Session 能明显提升速度。

第三,异常处理要分层。超时、HTTP 错误、连接错误分开捕获,分别记录不同的日志。这样出问题的时候能快速定位是网络问题还是目标网站问题。

第四,不要迷信“万能 header”。网上流传的那些几十行的 header 模板,很多字段是浏览器特有的,带上反而容易暴露。从最简配置开始,遇到问题再加,这是更稳妥的策略。

第五,学会看开发者工具。requests 能帮你发请求,但发什么请求、带什么参数、用什么方法,这些信息要从浏览器的 Network 面板里找。把“右键复制为 cURL”这个功能用熟,能省下大量手动分析的时间。

最后说一句关于学习路径的体会。我见过不少人纠结“到底先学 requests 还是先学 Scrapy”,其实这个问题没有标准答案。但如果你问我,我会说:先用 requests 手动爬三五个网站,把编码、cookie、重定向、超时这些坑都踩一遍,然后再去看 Scrapy。到那个时候,你会发现 Scrapy 文档里那些曾经看不懂的设计,突然都变得理所当然了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:04:44

15.6寸工控平板一体机:产线部署选型与应用场景全解析

在产线边蹲过几年的人都懂一个道理&#xff1a;设备真正常态化用到哪台&#xff0c;不是看谁的参数表最漂亮&#xff0c;而是看谁开机三年之后还不闹脾气。工控平板一体机这几年被越来越多的车间搬上位&#xff0c;核心原因是它在“可靠”和“易用”之间找到了一个平衡点。今天…

作者头像 李华
网站建设 2026/10/17 5:02:54

SQL与DDL实战:从建表、改表到删表的完整指南

做为一个常年跟数据库打交道的人&#xff0c;我见过太多初学者把SQL当成“写查询语句的工具”&#xff0c;一上来就死磕SELECT的各种花式写法&#xff0c;却连表是怎么建出来的都说不清楚。SQL概念和DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff…

作者头像 李华
网站建设 2026/10/17 5:02:51

金融服务系统开发实战:业务场景、账务一致性与合规安全

1. 从"financial-services"这个标题里能读出什么"financial-services"这个词看起来简单&#xff0c;甚至有点泛&#xff0c;但它其实是一个典型的行业统称&#xff0c;覆盖的范围远比字面意思要宽。我第一次接触这个方向的时候&#xff0c;以为就是银行、保…

作者头像 李华
网站建设 2026/10/17 5:02:10

2025年AI编程助手选型指南:用TaoToken统一Key实测5款工具接入体验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 5:02:03

中小企业现金流预测模型:Python本地化实现与预警机制

我无法基于当前输入生成符合要求的博文。原因如下&#xff1a;输入中仅提供了项目标题"financial-services"&#xff0c;未提供任何实质性的项目正文、关键词列表或摘要描述&#xff1b;所谓“相关热搜词”和“最新网络热词”部分为空&#xff0c;未给出具体词汇&…

作者头像 李华
网站建设 2026/10/17 5:01:55

GraspMAS多智能体系统:零样本语言驱动的机器人抓取新范式

1. 语言指令驱动抓取检测&#xff1a;为什么零样本是一个真问题这几年机器人抓取领域有个很明显的变化&#xff1a;大家已经不满足于"看到物体就抓"&#xff0c;而是希望机器人能听懂人话&#xff0c;比如"帮我拿那个红色杯子里面的勺子""把桌上最左边…

作者头像 李华