返回顶部
首页 > 资讯 > 精选 >如何实现网址池URL Pool
  • 486
分享到

如何实现网址池URL Pool

2023-06-02 06:06:22 486人浏览 八月长安
摘要

今天就跟大家聊聊有关如何实现网址池URL Pool,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。对于比较大型的爬虫来说,URL管理的管理是个核心问题,管理不好,就可能重复下载,也可能

今天就跟大家聊聊有关如何实现网址池URL Pool,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

对于比较大型的爬虫来说,URL管理的管理是个核心问题,管理不好,就可能重复下载,也可能遗漏下载。这里,我们设计一个URL池来管理URL。
这个URL池就是一个生产者 - 消费者模式:

如何实现网址池URL Pool

生产者 - 消费者流程图

依葫芦画瓢,URLPool就是这样的

如何实现网址池URL Pool

设计的网络爬虫URLPool

我们从网址池的使用目的出发来设计网址池的接口,它应该具有以下功能:

  • 往池子里面添加URL;

  • 从池子里面取URL以下载;

  • 池子内部要管理URL状态;

前面我提到的网址的状态有以下4中:

  • 已经下载成功

  • 下载多次失败无需再下载

  • 正在下载

  • 下载失败要再次尝试

前两个是永久状态,也就是已经下载成功的不再下载,多次尝试后仍失败的也就不再下载,它们需要永久存储起来,以便爬虫重启后,这种永久状态记录不会消失,已经成功下载的网址不再被重复下载永久存储的方法有很多种:

比如,直接写入文本文件,但它不利于查找某个URL是否已经存在文本中;
比如,直接写入的MySQL关系型数据库,它利用查找,但是速度又比较慢,
比如,使用键值数据库,查找和速度都符合要求,是不错的选择!

我们这个URL池选用LevelDB来作为URL状态的永久存储.LevelDB是谷歌开源的一个键值数据库,速度非常快,同时自动压缩数据。我们用它先来实现一个UrlDB作为永久存储数据库。

UrlDB的实现

import leveldbclass UrlDB:    '''Use LevelDB 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 + '.urldb'        self.db = leveldb.LevelDB(self.name)    def load_from_db(self, status):        urls = []        for url, _status in self.db.RangeIter():            if status == _status:                urls.append(url)        return urls    def set_success(self, url):        if isinstance(url, str):            url = url.encode('utf8')        try:            self.db.Put(url, self.state_success)            s = True        except:            s = False        return s    def set_failure(self, url):        if isinstance(url, str):            url = url.encode('utf8')        try:            self.db.Put(url, self.status_failure)            s = True        except:            s = False        return s    def has(self, url):        if isinstance(url, str):            url = url.encode('utf8')        try:            attr = self.db.Get(url)            return attr        except:            pass        return False

UrlDB将被UrlPool使用,主要有三个方法被使用:

  • has(url)查看是否已经存在某url

  • set_success(url)存储url状态为成功

  • set_failure(url)存储url状态为失败

UrlPool的实现

而正在下载和下载失败次数这两个URL的状态只需暂时保存在内容即可,我们把它们放到UrlPool这个类中进行管理接着我们来实现网址池:

#Author: veelionimport pickleimport leveldbimport timeimport urllib.parse as urlparseclass UrlPool:    '''URL Pool for crawler to manage URLs    '''    def __init__(self, pool_name):        self.name = pool_name        self.db = UrlDB(pool_name)        self.pool = {}  # host: set([urls]), 记录待下载URL        self.pending = {}  # url: pended_time, 记录已被pend但还未被更新状态(正在下载)的URL        self.failure = {}  # url: times, 记录失败的URL的次数        self.failure_threshold = 3        self.pending_threshold = 60  # pending的最大时间,过期要重新下载        self.in_mem_count = 0        self.max_hosts = ['', 0]  # [host: url_count] 目前pool中url最多的host及其url数量        self.hub_pool = {}  # {url: last_query_time}        self.hub_refresh_span = 0        self.load_cache()    def load_cache(self,):        path = self.name + '.pkl'        try:            with open(path, 'rb') as f:                self.pool = pickle.load(f)            cc = [len(v) for k, v in self.pool]            print('saved pool loaded! urls:', sum(cc))        except:            pass    def set_hubs(self, urls, hub_refresh_span):        self.hub_refresh_span = hub_refresh_span        self.hub_pool = {}        for url in urls:            self.hub_pool[url] = 0    def set_status(self, url, status_code):        if url in self.pending:            self.pending.pop(url)        if status_code == 200:            self.db.set_success(url)            return        if status_code == 404:            self.db.set_failure(url)            return        if url in self.failure:            self.failure[url] += 1            if self.failure[url] > self.failure_threshold:                self.db.set_failure(url)                self.failure.pop(url)            else:                self.add(url)        else:            self.failure[url] = 1    def push_to_pool(self, url):        host = urlparse.urlparse(url).netloc        if not host or '.' not in host:            print('try to push_to_pool with bad url:', url, ', len of ur:', len(url))            return False        if host in self.pool:            if url in self.pool[host]:                return True            self.pool[host].add(url)            if len(self.pool[host]) > self.max_hosts[1]:                self.max_hosts[1] = len(self.pool[host])                self.max_hosts[0] = host        else:            self.pool[host] = set([url])        self.in_mem_count += 1        return True    def add(self, url, always):        if always:            return self.push_to_pool(url)        pended_time = self.pending.get(url, 0)        if time.time() - pended_time < self.pending_threshold:            print('being downloading:', url)            return        if self.db.has(url):            return        if pended_time:            self.pending.pop(url)        return self.push_to_pool(url)    def addmany(self, urls, always=False):        if isinstance(urls, str):            print('urls is a str !!!!', urls)            self.add(urls, always)        else:            for url in urls:                self.add(url, always)    def pop(self, count, hubpercent=50):        print('\n\tmax of host:', self.max_hosts)        # 取出的url有两种类型:hub=1, 普通=2        url_attr_url = 0        url_attr_hub = 1        # 1\. 首先取出hub,保证获取hub里面的最新url.        hubs = {}        hub_count = count * hubpercent // 100        for hub in self.hub_pool:            span = time.time() - self.hub_pool[hub]            if span < self.hub_refresh_span:                continue            hubs[hub] = url_attr_hub  # 1 means hub-url            self.hub_pool[hub] = time.time()            if len(hubs) >= hub_count:                break        # 2\. 再取出普通url        # 如果某个host有太多url,则每次可以取出3(delta)个它的url        if self.max_hosts[1] * 10 > self.in_mem_count:            delta = 3            print('\tset delta:', delta, ', max of host:', self.max_hosts)        else:            delta = 1        left_count = count - len(hubs)        urls = {}        for host in self.pool:            if not self.pool[host]:                # empty_host.append(host)                continue            if self.max_hosts[0] == host:                while delta > 0:                    url = self.pool[host].pop()                    self.max_hosts[1] -= 1                    if not self.pool[host]:                        break                    delta -= 1            else:                url = self.pool[host].pop()            urls[url] = url_attr_url            self.pending[url] = time.time()            if len(urls) >= left_count:                break        self.in_mem_count -= len(urls)        print('To pop:%s, hubs: %s, urls: %s, hosts:%s' % (count, len(hubs), len(urls), len(self.pool)))        urls.update(hubs)        return urls    def size(self,):        return self.in_mem_count    def empty(self,):        return self.in_mem_count == 0    def __del__(self):        path = self.name + '.pkl'        try:            with open(path, 'wb') as f:                pickle.dump(self.pool, f)            print('self.pool saved!')        except:            pass

UrlPool的实现有些复杂,且听我一一分解。

UrlPool的使用

先看看它的主要成员及其用途:

  • self.db是一个UrlDB的示例,用来永久存储url的永久状态

  • self.pool是用来存放url的,它是一个字典(dict)结构,key是url的主机,值是一个用来存储这个主机的所有url的集合(set)。

  • self.pending用来管理正在下载的url状态。它是一个字典结构,key是url,value是它被pop的时间戳。当一个url被pop()时,就是它被下载的开始。当该url被set_status()时,就是下载结束的时刻。如果一个url被添加()入池时,发现它已经被套的时间超过pending_threshold时,就可以再次入库等待被下载。否则,暂不入池。

  • self.failue是一个字典,key是url,value是识别的次数,超过failure_threshold就会被永久记录为失败,不再尝试下载。

  • hub_pool是一个用来存储hub page面的字典,key是hub url,value是上次刷新该hub页面的时间。

以上成员就构成了我们这个网址池的数据结构,再通过以下成员方法对这个网址池进行操作:

load_cache()和dump_cache()对网址池进行缓存
load_cache()在init()中调用,创建池的时候,尝试去加载上次退出时缓存的URL池;
dump_cache()在del()中调用,也就是在网址池销毁前(比如爬虫意外退出),把内存中的URL pool缓存到硬盘。
这里使用了pickle模块,这是一个把内存数据序列化到硬盘的工具

** 2. set_hubs()方法设置hub URL **
hub网页就是像百度新闻那样的页面,整个页面都是新闻的标题和链接,是我们真正需要的新闻的聚合页面,并且这样的页面会不断更新,把最新的新闻聚合到这样的页面,我们称它们为hub页面,其URL就是hub url。在新闻爬虫中添加大量的这样的url,有助于爬虫及时发现并抓取最新的新闻。
该方法就是将这样的hub url列表传给网址池,在爬虫从池中取URL时,根据时间间隔(self.hub_refresh_span)来取集枢纽网址。

** 3. add(),addmany(),push_to_pool()对网址池进行入池操作**
把url放入网址池时,先检查内存中的self.pending是否存在该url,即是否正在下载该。网址如果正在下载就不入池;如果正下载或已经超时,就进行到下一步;
接着检查该网址是否已经在性LevelDB中存在,存在就表明之前已经成功下载或彻底失败,不再下载了也不入池。如果没有则进行到下一步;
最后通过push_to_pool()把url放入self.pool中。存放的规则是,按照url的主机进行分类,相同主机的url放到一起,在取出时 -个主取一个url,尽量保证每次取出的一批url都是指向不同的服务器的,这样做的目的也是为了尽量减少对抓取目标服务器的请求压力。力争做一个服务器友好的爬虫O(∩∩ _∩)O

pop()对网址池进行出池操作
爬虫通过该方法,从网址池中获取一批url去下载。取出url分两步:
第一步,先从self.hub_pool中获得,方法是遍历hub_pool,检查每个集线器-URL距上次被弹出的时间间隔是否超过毂页面刷新间隔(self.hub_refresh_span),来决定毂-URL是否应该被弹出。
第二步,从self.pool中获取。前面push_to_pool中,介绍了流行的原则,就是每次取出的一批URL都是指向不同服务器的,有了self.pool的特殊数据结构,安装这个原则获取网址就简单了,按主机(自我.pool的键)遍历self.pool即可。

set_status()方法设置网址池中url的状态
其参数status_code是Http响应的状态码。爬虫在下载完URL后进行url状态设置。
首先,把该url成self.pending中删除,已经下载完毕,不再是未决状态;
接着,根据STATUS_CODE来设置URL状态,200和404的直接设置为永久状态;其它状态就记录失败次数,并再次入池进行后续下载尝试。

通过以上成员变量和方法,我们把这个网址池(UrlPool)解析的清清楚楚。小猿们可以毫不客气的收藏起来,今后在写爬虫时可以用它方便的管理URL,并且这个实现只有一个PY文件,方便加入到任何项目中。

爬虫知识点

网址的管理
网址的管理,其目的就是为了:不重抓,不漏抓。

pickle模块
把内存数据保存到硬盘,再把硬盘数据重新加载到内存,这是很多程序停止和启动的必要步骤.pickle就是实现数据在内存和硬盘之间转移的模块。

leveldb模块
这是一个经典且强大的硬盘型key-value数据库,非常适合url-status这种结构的存储。

urllib.parse
解析网址的模块,在处理url时首先想到的模块就应该是它。

看完上述内容,你们对如何实现网址池URL Pool有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网精选频道,感谢大家的支持。

--结束END--

本文标题: 如何实现网址池URL Pool

本文链接: https://lsjlt.com/news/229160.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何实现网址池URL Pool
    今天就跟大家聊聊有关如何实现网址池URL Pool,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。对于比较大型的爬虫来说,URL管理的管理是个核心问题,管理不好,就可能重复下载,也可能...
    99+
    2023-06-02
  • php如何让Swoole/Pool进程池实现Redis持久连接
    本篇内容主要讲解“php如何让Swoole/Pool进程池实现Redis持久连接”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“php如何让Swoole/Pool进程池实现Redis持久连接”吧!...
    99+
    2023-07-05
  • Java UrlRewrite如何实现网站URL重写
    这篇文章给大家介绍Java UrlRewrite如何实现网站URL重写,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。现在大部分的网站和商城都会使用到URL重写,接触到这个,也是因为正在做的电子商务商城。URL重写,是将...
    99+
    2023-06-17
  • url地址如何获取
    这篇文章将为大家详细讲解有关url地址如何获取,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。url地址的获取方法:首先打开任意浏览器,网站地址栏就是对于的url地址路径信息;然后在命令窗口输入nslook...
    99+
    2023-06-13
  • Vue实现hash模式网址方式(就是那种带#的网址、井号url)
    目录Vue实现hash模式网址Vue路由中hash模式模式一:mode:‘hash’模式二:mode:‘history’Vue实现ha...
    99+
    2024-04-02
  • PHP检测网址如何实现
    这篇文章主要讲解了“PHP检测网址如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP检测网址如何实现”吧!一、PHP检测网址的意义在讲解PHP检测网址的具体实现方法之前,我们先来看...
    99+
    2023-07-05
  • thinkphp网址隐藏模块如何实现
    这篇“thinkphp网址隐藏模块如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“thinkphp网址隐藏模块如何实现...
    99+
    2023-07-06
  • Android中实现下载URL地址的网络资源的实例分享
    通过URL来获取网络资源并下载资源简单实例: package com.android.xiong.urltest; import java.io.IOException;...
    99+
    2022-06-06
    url Android
  • URL去重该如何实现
    URL去重该如何实现,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。IPv6编码地址数:2^128(约3.4&times;10^38)IPv6是IETF设...
    99+
    2023-06-17
  • nodejs实现获取当前url地址及url各种参数值
    //需要使用的模块 http url 当前url http://localhost:8888/select?aa=001&bb=002 var http = require('http...
    99+
    2022-06-04
    参数 地址 nodejs
  • Python如何实现获取内网IP地址
    本文小编为大家详细介绍“Python如何实现获取内网IP地址”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python如何实现获取内网IP地址”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。方法一import&n...
    99+
    2023-07-05
  • php如何实现连接池
    本篇内容介绍了“php如何实现连接池”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!什么是连接池在实际应用开...
    99+
    2024-04-02
  • C++如何实现对象池
    这篇“C++如何实现对象池”文章,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要参考一下,对于“C++如何实现对象池”,小编整理了以下知识点,请大家跟着小编的步伐一步一步的慢慢理解,接下来就让我们进入主题吧。前言需求...
    99+
    2023-06-26
  • nginx内存池如何实现
    这篇文章主要讲解了“nginx内存池如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nginx内存池如何实现”吧!一、简介最新稳定版本nginx1.20.2。为了能高效、快速的分配内存...
    99+
    2023-07-02
  • python进程池如何实现
    这篇文章主要介绍了python进程池如何实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python进程池如何实现文章都会有所收获,下面我们一起来看看吧。Python进程池是Python标准库中multipr...
    99+
    2023-07-05
  • 聊聊php怎么让Swoole/Pool进程池实现Redis持久连接
    本篇文章给大家带来了关于php的相关知识,其中主要介绍了通过PHPphp让Swoole/Pool进程池实现Redis持久连接,感兴趣的朋友下面一起来看一下吧,希望对大家有帮助。php 让 Swoole | Pool进程池实现Redis持久连...
    99+
    2023-05-14
    php
  • php如何实现自定义url
    这篇文章将为大家详细讲解有关php如何实现自定义url,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。php实现自定义url的方法:1、通过mod_rewrite配置htaccess文件实现URL重写;2、...
    99+
    2023-06-20
  • jquery中邮箱地址 URL网站地址正则验证实例代码
    email地址验证 复制代码 代码如下:function checkEmail(){    var email=$.trim($("#repo...
    99+
    2022-11-15
    邮箱地址 URL地址 正则
  • Java如何获取url地址文件流
    目录获取url地址文件流根据url获取文件的二进制上代码获取url地址文件流 public static InputStream getInputStreamFromUrl(Str...
    99+
    2024-04-02
  • Linux如何实现C线程池
    这篇文章主要介绍了Linux如何实现C线程池,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。多线程编程,创建一个线程,指定去完成某一个任务,等待线程的退出。虽然能够满足编程需求...
    99+
    2023-06-28
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作