一个神器,大幅提升爬虫爬取效率!

article/2025/10/4 5:35:24

这是「进击的Coder」的第 575 篇技术分享

作者:崔庆才

在做爬虫的时候,我们往往可能这些情况:

  • 网站比较复杂,会碰到很多重复请求。

  • 有时候爬虫意外中断了,但我们没有保存爬取状态,再次运行就需要重新爬取。

还有诸如此类的问题。

那怎么解决这些重复爬取的问题呢?大家很可能都想到了“缓存”,也就是说,爬取过一遍就直接跳过爬取。

那一般怎么做呢?

比如我写一个逻辑,把已经爬取过的 URL 保存到文件或者数据库里面,每次爬取之前检查一下是不是在列表或数据库里面就好了。

是的,这个思路没问题,但有没有想过这些问题:

  • 写入到文件或者数据库可能是永久性的,如果我想控制缓存的有效时间,那就还得有个过期时间控制。

  • 这个缓存根据什么来判断?如果仅仅是 URL 本身够吗?还有 Request Method、Request Headers 呢,如果它们不一样了,那还要不要用缓存?

  • 如果我们有好多项目,难道都没有一个通用的解决方案吗?

的确是些问题,实现起来确实要考虑很多问题。

不过不用担心,今天给大家介绍一个神器,可以帮助我们通通解决如上的问题。

介绍

它就是 requests-cache,是 requests 库的一个扩展包,利用它我们可以非常方便地实现请求的缓存,直接得到对应的爬取结果。

  • GitHub:https://github.com/reclosedev/requests-cache

  • PyPi:https://pypi.org/project/requests-cache/

  • 官方文档:https://requests-cache.readthedocs.io/en/stable/index.html

下面我们来介绍下它的使用。

安装

安装非常简单,使用 pip3 即可:

pip3 install requests-cache

安装完毕之后我们来了解下它的基本用法。

基本用法

下面我们首先来看一个基础实例:

import requests
import timestart = time.time()
session = requests.Session()
for i in range(10):session.get('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time', end - start)

这里我们请求了一个网站,是 http://httpbin.org/delay/1,这个网站模拟了一秒延迟,也就是请求之后它会在 1 秒之后才会返回响应。

这里请求了 10 次,那就至少得需要 10 秒才能完全运行完毕。

运行结果如下:

Finished 1 requests
Finished 2 requests
Finished 3 requests
Finished 4 requests
Finished 5 requests
Finished 6 requests
Finished 7 requests
Finished 8 requests
Finished 9 requests
Finished 10 requests
Cost time 13.17966604232788

可以看到,这里一共用了13 秒。

那如果我们用上 requests-cache 呢?结果会怎样?

代码改写如下:

import requests_cache
import timestart = time.time()
session = requests_cache.CachedSession('demo_cache')for i in range(10):session.get('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time', end - start)

这里我们声明了一个 CachedSession,将原本的 Session 对象进行了替换,还是请求了 10 次。

运行结果如下:

Finished 1 requests
Finished 2 requests
Finished 3 requests
Finished 4 requests
Finished 5 requests
Finished 6 requests
Finished 7 requests
Finished 8 requests
Finished 9 requests
Finished 10 requests
Cost time 1.6248838901519775

可以看到,一秒多就爬取完毕了!

发生了什么?

这时候我们可以发现,在本地生成了一个 demo_cache.sqlite 的数据库。

我们打开之后可以发现里面有个 responses 表,里面多了一个 key-value 记录,如图所示:

923f1b779155029618f34c75dd4026ca.png

我们可以可以看到,这个 key-value 记录中的 key 是一个 hash 值,value 是一个 Blob 对象,里面的内容就是 Response 的结果。

可以猜到,每次请求都会有一个对应的 key 生成,然后 requests-cache 把对应的结果存储到了 SQLite 数据库中了,后续的请求和第一次请求的 URL 是一样的,经过一些计算它们的 key 也都是一样的,所以后续 2-10 请求就立马返回了。

是的,利用这个机制,我们就可以跳过很多重复请求了,大大节省爬取时间。

Patch 写法

但是,刚才我们在写的时候把 requests 的 session 对象直接替换了。有没有别的写法呢?比如我不影响当前代码,只在代码前面加几行初始化代码就完成 requests-cache 的配置呢?

当然是可以的,代码如下:

import time
import requests
import requests_cacherequests_cache.install_cache('demo_cache')start = time.time()
session = requests.Session()
for i in range(10):session.get('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time', end - start)

这次我们直接调用了 requests-cache 库的 install_cache 方法就好了,其他的 requests 的 Session 照常使用即可。

我们再运行一遍:

Finished 1 requests
Finished 2 requests
Finished 3 requests
Finished 4 requests
Finished 5 requests
Finished 6 requests
Finished 7 requests
Finished 8 requests
Finished 9 requests
Finished 10 requests
Cost time 0.018644094467163086

这次比上次更快了,为什么呢?因为这次所有的请求都命中了 Cache,所以很快返回了结果。

后端配置

刚才我们知道了,requests-cache 默认使用了 SQLite 作为缓存对象,那这个能不能换啊?比如用文件,或者其他的数据库呢?

自然是可以的。

比如我们可以把后端换成本地文件,那可以这么做:

import time
import requests
import requests_cacherequests_cache.install_cache('demo_cache', backend='filesystem')start = time.time()
session = requests.Session()
for i in range(10):session.get('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time', end - start)

这里我们添加了一个 backend 参数,然后指定为 filesystem,这样运行之后本地就会生成一个 demo_cache 的文件夹用作缓存,如果不想用缓存的话把这个文件夹删了就好了。

当然我们还可以更改缓存文件夹的位置,比如:

requests_cache.install_cache('demo_cache', backend='filesystem', use_temp=True)

这里添加一个 use_temp 参数,缓存文件夹便会使用系统的临时目录,而不会在代码区创建缓存文件夹。

当然也可以这样:

requests_cache.install_cache('demo_cache', backend='filesystem', use_cache_dir=True)

这里添加一个 use_cache_dir 参数,缓存文件夹便会使用系统的专用缓存文件夹,而不会在代码区创建缓存文件夹。

另外除了文件系统,requests-cache 也支持其他的后端,比如 Redis、MongoDB、GridFS 甚至内存,但也需要对应的依赖库支持,具体可以参见下表:

BackendClassAliasDependencies
SQLiteSQLiteCache'sqlite'
RedisRedisCache'redis'redis-py
MongoDBMongoCache'mongodb'pymongo
GridFSGridFSCache'gridfs'pymongo
DynamoDBDynamoDbCache'dynamodb'boto3
FilesystemFileCache'filesystem'
MemoryBaseCache'memory'

比如使用 Redis 就可以改写如下:

backend = requests_cache.RedisCache(host='localhost', port=6379)
requests_cache.install_cache('demo_cache', backend=backend)

更多详细配置可以参考官方文档:https://requests-cache.readthedocs.io/en/stable/user_guide/backends.html#backends。

Filter

当然,我们有时候也想指定有些请求不缓存,比如只缓存 POST 请求,不缓存 GET 请求,那可以这样来配置:

import time
import requests
import requests_cacherequests_cache.install_cache('demo_cache2', allowable_methods=['POST'])start = time.time()
session = requests.Session()
for i in range(10):session.get('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time for get', end - start)
start = time.time()for i in range(10):session.post('http://httpbin.org/delay/1')print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time for post', end - start)

这里我们添加了一个 allowable_methods 指定了一个过滤器,只有 POST 请求会被缓存,GET 请求就不会。

看下运行结果:

Finished 1 requests
Finished 2 requests
Finished 3 requests
Finished 4 requests
Finished 5 requests
Finished 6 requests
Finished 7 requests
Finished 8 requests
Finished 9 requests
Finished 10 requests
Cost time for get 12.916549682617188
Finished 1 requests
Finished 2 requests
Finished 3 requests
Finished 4 requests
Finished 5 requests
Finished 6 requests
Finished 7 requests
Finished 8 requests
Finished 9 requests
Finished 10 requests
Cost time for post 1.2473630905151367

这时候就看到 GET 请求由于没有缓存,就花了 12 多秒才结束,而 POST 由于使用了缓存,一秒多就结束了。

另外我们还可以针对 Response Status Code 进行过滤,比如只有 200 会缓存,则可以这样写:

import time
import requests
import requests_cacherequests_cache.install_cache('demo_cache2', allowable_codes=(200,))

当然我们还可以匹配 URL,比如针对哪种 Pattern 的 URL 缓存多久,则可以这样写:

urls_expire_after = {'*.site_1.com': 30, 'site_2.com/static': -1}
requests_cache.install_cache('demo_cache2', urls_expire_after=urls_expire_after)

这样的话,site_1.com 的内容就会缓存 30 秒,site_2.com/static 的内容就永远不会过期。

当然,我们也可以自定义 Filter,具体可以参见:https://requests-cache.readthedocs.io/en/stable/user_guide/filtering.html#custom-cache-filtering。

Cache Headers

除了我们自定义缓存,requests-cache 还支持解析 HTTP Request / Response Headers 并根据 Headers 的内容来缓存。

比如说,我们知道 HTTP 里面有个 Cache-Control 的 Request / Response Header,它可以指定浏览器要不要对本次请求进行缓存,那 requests-cache 怎么来支持呢?

实例如下:

import time
import requests
import requests_cacherequests_cache.install_cache('demo_cache3')start = time.time()
session = requests.Session()
for i in range(10):session.get('http://httpbin.org/delay/1',headers={'Cache-Control': 'no-store'})print(f'Finished {i + 1} requests')
end = time.time()
print('Cost time for get', end - start)
start = time.time()

这里我们在 Request Headers 里面加上了 Cache-Controlno-store,这样的话,即使我们声明了缓存那也不会生效。

当然 Response Headers 的解析也是支持的,我们可以这样开启:

requests_cache.install_cache('demo_cache3', cache_control=True)

如果我们配置了这个参数,那么 expire_after 的配置就会被覆盖而不会生效。

更多的用法可以参见:https://requests-cache.readthedocs.io/en/stable/user_guide/headers.html#cache-headers。

总结

好了,到现在为止,一些基本配置、过期时间配置、后端配置、过滤器配置等基本常见的用法就介绍到这里啦,更多详细的用法大家可以参考官方文档:https://requests-cache.readthedocs.io/en/stable/user_guide.html。

希望对大家有帮助。

85091d11f88f60781570e9a0b92d081e.png

End

崔庆才的新书《Python3网络爬虫开发实战(第二版)》已经正式上市了!书中详细介绍了零基础用 Python 开发爬虫的各方面知识,同时相比第一版新增了 JavaScript 逆向、Android 逆向、异步爬虫、深度学习、Kubernetes 相关内容,‍同时本书已经获得 Python 之父 Guido 的推荐,目前本书正在七折促销中!

内容介绍:《Python3网络爬虫开发实战(第二版)》内容介绍

b5f47d57851aeecb3059f07471f9fbc3.png

扫码购买

d3bbaf648362db98f05d4fc82000c74c.png

好文和朋友一起看~


http://chatgpt.dhexx.cn/article/6uRG07IT.shtml

相关文章

python爬虫基本功(一)--初识python爬虫与爬虫学习路径

大家好,这里是Kaiser。👏👏今天想给大家介绍下python爬虫的知识,我将与大家一起去揭开python爬虫的神秘面纱,字不多敲,让我们进入今天的主题:初识python爬虫。 文章目录 1️⃣什么是爬虫2️⃣爬…

神箭手 爬虫操作(1)

今天有一个任务,是要将微博用户的昵称从数据库导入到神箭手中。 以前一直是手动操作,现在有一个需求是要直接导入到设置中。 数据库使用的是mysql,数据库连接使用的是JDBCtemplate,使用spring构建,还要用到神箭手的S…

如何在神箭手云爬虫上写爬虫

摘要 上一篇博客跟大家详细介绍了如何写出《黄焖鸡米饭是怎么火起来的》这样的数据分析类的文章,相信很多人都对数据来源也就是如何爬取到黄焖鸡米饭商家信息很感兴趣。那么今天我就跟大家具体讲一讲怎么使用神箭手云爬虫写爬虫,以上篇博客的…

神箭手云爬虫平台 如何在1小时内编写简单爬虫

既然你来到了这里,想必你肯定已经知道了神箭手云爬虫平台是干什么的,目的也是非常的明确。 那么接下来的过程中,我将给你演示如何在最快时间内编写一个简单的爬虫,每一个属性的讲解,将会让你一路顺风。 demo项目GitHu…

神箭手云爬虫工具

title: 神箭手云爬虫 categories: 神箭手 tags:爬虫 分为: 入口页,帮助页,内容页 入口页: scanUrl 爬虫网页的入口 帮助页:helperurl 一帮包含大量的内容页(列表),多数情况下事业…

JS求绝对值

微信小程序开发交流qq群 173683895 承接微信小程序开发。扫码加微信。 需求,一个变量,需要判断它的值为负数时不能小于-20,正数也不能小于20,这个时候就需要用到求绝对值了。 Math.abs(num) > 求绝对值 使用示例&#xf…

javascript:求绝对值最小的数

问题来源:http://androidguy.blog.51cto.com/974126/1129543 有一个已经排序的数组(升序),数组中可能有正数、负数或0,求数组中元素的绝对值最小的数,要求,不能用顺序比较的方法(复杂…

力扣 -----最小绝对值(JavaScript解法)

一、题目描述 二、示例 三、解题思路 先进行一个排序,然后使用双指针,依次进行比较,将比较的值进行存储,最小值改变的话,数组置空,最小值改变,如果和最小值相等的话直接push。我刚开始写的排序…

python3 绝对值_python3对序列求绝对值

原博文 2018-11-08 11:27 − http://www.cnblogs.com/itdyb/p/5731804.html 一开始我是这样写的,据说这样写python2是可以的: myList [-1,2,-3,4,-5,6]absList map(abs, myList) &... 0 1936 相关推荐 2019-12-23 10:33 − ## Python3使用sud…

【前端】JavaScript基础(二)

目录 一、内置对象 Math对象 Data日期 数组对象 字符串对象 字符串的不可变 根据字符返回位置 根据位置返回字符(重点) 字符串基本方法(重点) 字符串的替换与转换 二、简单类型和复杂类型 简单类型传参 复杂类型传参 一、内…

LeetCode 任意子数组和绝对值的最大值(JavaScript)

这道题目,我是通过看题解,然后通过自己思考才写出来的,第一次写博客,如果有不好之处,请谅解。 写这个题目之前,我们先了解一下前缀和,通过下面例子,或许你就明白了什么是前缀和了。…

常用JavaScript控制结构语句,绝对值,累加,阶乘,循环

计算一个数的绝对值 function abs(x) {if (x > 0) {return x;} else {return -x;} } console.log(abs(10) abs(-10)); //>true计算数组元素之和 let arr [1, 3, 5];function sum(array) {let sum 0;for (let x of array) { //循环数组把每个元素赋给xsum x;}return…

02-Node.js基础(一)

目录 一、什么是Node.js二、Node.js在软件架构中的地位三、Node.js 可以做什么(作用)浏览器端的 JavaScriptNode端的JavaScript 四、Node.js APIsWeb端APINode.js端APIWeb端JS与Node端JS对比 五、安装 Node.js第一步:在官网下载 Node.js 安装…

vue取绝对值

先放效果&#xff1a; 代码 <template><el-input v-model"num" placeholder"请输入数字" /><el-button type"primary" click"changeNum">取绝对值</el-button> </template> import { ref } from &quo…

关于JSZIP压缩图片打包下载的一些用法

功能介绍 前端在处理一些批量文件或者图片时候&#xff0c;通过使用异步上传&#xff0c;减少表单一次性提交的的数据量。并将这些图片或者文件根据用户自定义压缩、分类保存到本地。是一个用于压缩文件和解压的JavaScript库。 官网地址 https://stuk.github.io/jszip/docume…

jsZip将多个文件压缩成一个压缩包

在项目开发中&#xff0c;搭档大佬要做一个断点续传的功能&#xff0c;让我帮忙研究一下前端将多个文件压缩成一个压缩包的方法&#xff0c;所以就有了这篇文章。 我的demo是写在vue中的&#xff0c;所以首先要有一个vue环境&#xff0c;才能进行接下来的步骤。那么下面我们就…

前端vue使用jszip压缩文件

一.引入element的上传文件组件 <el-form-item label"文件"><el-uploadclass"upload-demo"dragaction"https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"multiple:before-upload"beforeUpload"><el-icon …

vue3中使用jszip压缩文件

1、安装依赖 npm install jszip npm install file-saver --save 2、使用 <template><el-card class"mb15"><template #header><span>jszip</span></template><!-- 二维码容器 --><div id"qrCodeBox">&…

docxtemplater、pizzip、jszip-utils、file-saver 前端实现导出word

一、准备工作 1、插件安装&#xff1a; npm i docxtemplater pizzip jszip-utils file-saver -S2、安装完成&#xff1a; 3、导出的模板文件。 我这里是将其命名word-export.docx,并将其放在public/docxs目录下&#xff0c;如图所示&#xff1a; 二、实现代码 页面使用&…