你好,我是悦创。
对于比较大型的爬虫来说,URL 管理的管理是个核心问题,管理不好,就可能重复下载,也可能遗漏下载。这里,我们设计一个 URL Pool 来管理 URL 。
这个 URL Pool 就是一个 生产者-消费者模式:
依葫芦画瓢,URLPool 就是这样的:
我们从网址池的使用目的出发来设计网址池的接口,它应该具有以下功能:
- 往池子里面添加 URL;
- 从池子里面取 URL 以下载;
- 池子内部要管理 URL 状态;
前面我提到 URL 的状态有以下 4 中:
- 已经下载成功
- 下载多次失败无需再下载
- 正在下载
- 下载失败要再次尝试
前两个是永久状态,也就是已经下载成功的不再下载,多次尝试后仍失败的也就不再下载,它们需要永久存储起来,以便爬虫重启后,这种永久状态记录不会消失,已经成功下载的 URL 不再被重复下载。永久存储的方法有很多种:
- 比如,直接写入文本文件,但它不利于查找某个 URL 是否已经存在文本中;
- 比如,直接写入 MySQL 等关系型数据库,它利用查找,但是速度又比较慢;
- 比如,使用
key-value
数据库,查找和速度都符合要求,是不错的选择!
我们这个 URL Pool 选用 redis 来作为 URL 状态的永久存储。
UrlDB 的实现
# -*- coding: utf-8 -*-
# @Time : 2021/5/17 2:41 下午
# @Author : AI悦创
# @FileName: UrlDB.py
# @Software: PyCharm
# @Blog :http://www.aiyc.top
# @公众号 :AI悦创
from redis import Redis
class UrlDB(object):
'''
Use redis to store URLs what have been done(succeed or faile)
'''
status_failure = b'0'
status_success = b'1'
def __init__(self, db_name):
self.name = db_name
self.db = Redis(host='localhost', port=6379, db=self.name)
def set_success(self, url):
"""
请求成功的 url
"""
if isinstance(url, str):
url = url.encode('utf8')
try:
self.db.set(url, self.status_success)
s = True
except:
s = False
return s
def set_failure(self, url):
"""
请求失败的 url
"""
if isinstance(url, str):
url = url.encode('utf8')
try:
self.db.set(url, self.status_failure)
s = True
except:
s = False
return s
def has(self, url):
"""
判断我们的 url 是否存在
"""
if isinstance(url, str):
url = url.encode('utf8')
try:
# attr = self.db.get(url)
attr = self.db.exists(url)
return attr
except:
pass
return False
UrlDB 将被 UrlPool 使用,主要有三个方法被使用:
has(url)
查看是否已经存在某 urlset_success(url)
存储 url 状态为成功set_failure(url)
存储 url 状态为失败
UrlPool 的实现
而正在下载和下载失败次数这两个 URL 的状态只需暂时保存在内容即可,我们把它们放到 UrlPool 这个类中进行管理。接着我们来实现网址池: