关于爬虫:一个神器大幅提升爬虫爬取效率

31次阅读

共计 6529 个字符,预计需要花费 17 分钟才能阅读完成。

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

  • 网站比较复杂,会碰到很多反复申请。
  • 有时候爬虫意外中断了,但咱们没有保留爬取状态,再次运行就须要从新爬取。

还有诸如此类的问题。

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

那个别怎么做呢?

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

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

  • 写入到文件或者数据库可能是永久性的,如果我想管制缓存的无效工夫,那就还得有个过期工夫管制。
  • 这个缓存依据什么来判断?如果仅仅是 URL 自身够吗?还有 Request Method、Request Headers 呢,如果它们不一样了,那还要不要用缓存?
  • 如果咱们有好多我的项目,难道都没有一个通用的解决方案吗?

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

不过不必放心,明天给大家介绍一个神器,能够帮忙咱们统统解决如上的问题。

介绍

它就是 requests-cache,是 requests 库的一个扩大包,利用它咱们能够十分不便地实现申请的缓存,间接失去对应的爬取后果。

上面咱们来介绍下它的应用。

装置

装置非常简单,应用 pip3 即可:

pip3 install requests-cache

装置结束之后咱们来理解下它的根本用法。

根本用法

上面咱们首先来看一个根底实例:

import requests
import time

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)

这里咱们申请了一个网站,是 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 time

start = 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 记录,如图所示:

咱们能够能够看到,这个 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_cache

requests_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_cache

requests_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 甚至内存,但也须要对应的依赖库反对,具体能够参见下表:

Backend Class Alias Dependencies
SQLite SQLiteCache 'sqlite'
Redis RedisCache 'redis' redis-py
MongoDB MongoCache 'mongodb' pymongo
GridFS GridFSCache 'gridfs' pymongo
DynamoDB DynamoDbCache 'dynamodb' boto3
Filesystem FileCache 'filesystem'
Memory BaseCache 'memory'

比方应用 Redis 就能够改写如下:

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

Filter

当然,咱们有时候也想指定有些申请不缓存,比方只缓存 POST 申请,不缓存 GET 申请,那能够这样来配置:

import time
import requests
import requests_cache

requests_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_cache

requests_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。

Cache Headers

除了咱们自定义缓存,requests-cache 还反对解析 HTTP Request / Response Headers 并依据 Headers 的内容来缓存。

比如说,咱们晓得 HTTP 外面有个 Cache-Control 的 Request / Response Header,它能够指定浏览器要不要对本次申请进行缓存,那 requests-cache 怎么来反对呢?

实例如下:

import time
import requests
import requests_cache

requests_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-Control 为 no-store,这样的话,即便咱们申明了缓存那也不会失效。

当然 Response Headers 的解析也是反对的,咱们能够这样开启:

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

如果咱们配置了这个参数,那么 expire_after 的配置就会被笼罩而不会失效。

总结

好了,到当初为止,一些根本配置、过期工夫配置、后端配置、过滤器配置等根本常见的用法就介绍到这里啦!

如果你感觉文章还不错,欢送关注公众号:Python 编程学习圈 ,或是返回编程学习网,理解更多编程技术常识,还有大量干货学习材料能够支付!

正文完
 0