Scrapy 爬虫去重效率优化之 Bloom Filter的算法的对接

 

From:https://cloud.tencent.com/developer/article/1084962

Python分布式爬虫打造搜索引擎Scrapy精讲—将bloomfilter(布隆过滤器)集成到scrapy-redis中
https://www.cnblogs.com/adc8868/p/7442306.html

 

scrapy redis + bloomfilter :https://github.com/pujinxiao/project_pjx/tree/master/s0vkaq/ScrapyRedisTest/ScrapyRedisTest/utils

 

具体的 bloomfilter 概念和原理应该查看这篇文章:传送,还有《海量数据处理算法》以及《大规模数据处理利器》

BloomFilter Based on py3(基于py3的布隆过滤器):https://github.com/Sssmeb/BloomFilter

 

首先回顾一下 Scrapy-Redis 的去重机制。Scrapy-Redis 将 Request 的指纹存储到了 Redis 集合中,每个指纹的长度为40,例如27adcc2e8979cdee0c9cecbbe8bf8ff51edefb61 就是一个指纹,它的每一位都是16 进制数。

我们计算一下用这种方式耗费的存储空间。每个十六进制数占用 4 b (bit),1个指纹用40个十六进制数表示,占用空间为20 B (Byte),1万个指纹即占用空间200 KB,1亿个指纹占用 2 GB。当爬取数量达到上亿级别时,Redis的占用的内存就会变得很大,而且这仅仅是指纹的存储。Redis还存储了爬取队列,内存占用会进一步提高,更别说有多个Scrapy项目同时爬取的情况了。当爬取达到亿级别规模时,Scrapy-Redis提供的集合去重已经不能满足我们的要求。所以我们需要使用一个更加节省内存的去重算法Bloom Filter。

 

 

 

Bloom Filter

 

 

1. 了解Bloom Filter

 

Bloom Filter,中文名称叫作布隆过滤器,是1970年由Bloom提出的,它可以被用来检测一个元素是否在一个集合中。Bloom Filter的空间利用效率很高,使用它可以大大节省存储空间。Bloom Filter使用位数组表示一个待检测集合,并可以快速地通过概率算法判断一个元素是否存在于这个集合中。利用这个算法我们可以实现去重效果。

本节我们来了解Bloom Filter的基本算法,以及Scrapy-Redis中对接Bloom Filter的方法。

 

 

2. Bloom Filter的算法

 

在Bloom Filter中使用位数组来辅助实现检测判断。在初始状态下,我们声明一个包含m位的位数组,它的所有位都是0,如下图所示。

现在我们有了一个待检测集合,其表示为S={x1, x2, …, xn}。接下来需要做的就是检测一个x是否已经存在于集合S中。在Bloom Filter算法中,首先使用 k 个相互独立、随机的散列函数 来 将 集合S 中的每个元素 x1, x2, …, xn 映射到长度为 m 的位数组上,散列函数得到的结果记作位置索引,然后将位数组该位置索引的位置 1例如,我们取 k为 3,表示有三个散列函数,x1 经过三个散列函数映射得到的结果分别为 1、4、8,x2 经过三个散列函数映射得到的结果分别为 4、6、10,那么位数组的1、4、6、8、10 这五位就会置为1,如下图所示。

如果有一个新的元素x,我们要判断x是否属于S集合,我们仍然用k个散列函数对x求映射结果。如果所有结果对应的位数组位置均为1,那么x属于S这个集合;如果有一个不为1,则x不属于S集合。

例如,新元素x经过三个散列函数映射的结果为4、6、8,对应的位置均为1,则x属于S集合。如果结果为4、6、7,而7对应的位置为0,则x不属于S集合。

注意,这里m、n、k满足的关系是m>nk,也就是说位数组的长度m要比集合元素n和散列函数k的乘积还要大。

这样的判定方法很高效,但是也是有代价的,它可能把不属于这个集合的元素误认为属于这个集合。我们来估计一下这种方法的错误率。当集合S={x1, x2,…, xn} 的所有元素都被k个散列函数映射到m位的位数组中时,这个位数组中某一位还是0的概率是:

散列函数是随机的,则任意一个散列函数选中这一位的概率为1/m,那么1-1/m就代表散列函数从未没有选中这一位的概率,要把S完全映射到m位数组中,需要做kn次散列运算,最后的概率就是1-1/m的kn次方。

一个不属于S的元素x如果误判定为在S中,那么这个概率就是k次散列运算得到的结果对应的位数组位置都为1,则误判概率为:

根据:

可以将误判概率转化为:

在给定m、n时,可以求出使得f最小化的k值为:

这里将误判概率归纳如下:

表中第一列为m/n的值,第二列为最优k值,其后列为不同k值的误判概率。当k值确定时,随着m/n的增大,误判概率逐渐变小。当m/n的值确定时,当k越靠近最优K值,误判概率越小。误判概率总体来看都是极小的,在容忍此误判概率的情况下,大幅减小存储空间和判定速度是完全值得的。

简单点说:Bloom Filter算法 就是有几个seeds,现在申请一段内存空间,一个seed可以和字符串哈希映射到这段内存上的一个位,几个位都为1即表示该字符串已经存在。插入的时候也是,将映射出的几个位都置为1

接下来,我们将Bloom Filter算法应用到Scrapy-Redis分布式爬虫的去重过程中,以解决Redis内存不足的问题。

 

布隆 优点

 

相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数。另外, Hash 函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。

布隆过滤器可以表示全集,其它任何数据结构都不能;

k 和 m 相同,使用同一组 Hash 函数的两个布隆过滤器的交并差运算可以使用位操作进行。

 

布隆 缺点

 

但是布隆过滤器的缺点和优点一样明显。误算率(False Positive)是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣。

另外,一般情况下不能从布隆过滤器中删除元素. 我们很容易想到把位列阵变成整数数组,每插入一个元素相应的计数器加1, 这样删除元素时将计数器减掉就可以了。然而要保证安全的删除元素并非如此简单。首先我们必须保证删除的元素的确在布隆过滤器里面. 这一点单凭这个过滤器是无法保证的。另外计数器回绕也会造成问题。

总的来说,布隆很适合来处理海量的数据,而且速度优势很强。

 

 

redis 与 bloom

 

去重”是日常工作中会经常用到的一项技能,在爬虫领域更是常用,并且规模一般都比较大。参考文章《基于Redis的Bloomfilter去重》,作者【九茶】还有另一篇文章可以参考《scrapy_redis去重优化,已有7亿条数据》

 

去重需要考虑两个点:去重的数据量、去重速度。为了保持较快的去重速度,一般选择在内存中进行去重。

  • 数据量不大时,可以直接放在内存里面进行去重,例如python可以使用set()进行去重。
  • 当去重数据需要持久化时可以使用redis的set数据结构。
  • 当数据量再大一点时,可以用不同的加密算法先将长字符串压缩成 16/32/40 个字符,再使用上面两种方法去重;
  • 当数据量达到亿(甚至十亿、百亿)数量级时,内存有限,必须用“位”来去重,才能够满足需求。Bloomfilter就是将去重对象映射到几个内存“位”,通过几个位的 0/1值来判断一个对象是否已经存在。
  • 然而Bloomfilter运行在一台机器的内存上,不方便持久化(机器down掉就什么都没啦),也不方便分布式爬虫的统一去重。如果可以在Redis上申请内存进行Bloomfilter,以上两个问题就都能解决了。

 

 

 

对接Scrapy-Redis

 

实现 Bloom Filter 时,首先要保证不能破坏 Scrapy-Redis 分布式爬取的运行架构。我们需要修改 Scrapy-Redis 的源码,将它的去重类替换掉。同时,Bloom Filter 的实现需要借助于一个位数组,既然当前架构还是依赖于Redis,那么位数组的维护直接使用Redi s就好了。

首先实现一个基本的散列算法,将一个值经过散列运算后映射到一个m位数组的某一位上,代码如下:

BLOOMFILTER_HASH_NUMBER = 6
BLOOMFILTER_BIT = 30class HashMap(object):def __init__(self, m, seed):self.m = mself.seed = seeddef hash(self, value):"""Hash Algorithm:param value: Value:return: Hash Value"""ret = 0for i in range(len(value)):ret += self.seed * ret + ord(value[i])return (self.m - 1) & retclass BloomFilter(object):def __init__(self, server, key, bit=BLOOMFILTER_BIT, hash_number=BLOOMFILTER_HASH_NUMBER):"""Initialize BloomFilter:param server: Redis Server:param key: BloomFilter Key:param bit: m = 2 ^ bit:param hash_number: the number of hash function"""# default to 1 << 30 = 10,7374,1824 = 2^30 = 128MB, max filter 2^30/hash_number = 1,7895,6970 fingerprintsself.m = 1 << bitself.seeds = range(hash_number)self.maps = [HashMap(self.m, seed) for seed in self.seeds]self.server = serverself.key = keydef exists(self, value):"""if value exists:param value::return:"""if not value:return Falseexist = 1for map in self.maps:offset = map.hash(value)exist = exist & self.server.getbit(self.key, offset)return existdef insert(self, value):"""add value to bloom:param value::return:"""for f in self.maps:offset = f.hash(value)self.server.setbit(self.key, offset, 1)import redis
conn = redis.StrictRedis(host='localhost', port=6379)
bf = BloomFilter(conn, 'testbf', 5, 6)
bf.insert('Hello')
bf.insert('World')
result = bf.exists('Hello')
print(bool(result))
result = bf.exists('Python')
print(bool(result))

这里新建了一个 HashMap 类。构造函数传入两个值,一个是 m 位数组的位数,另一个是种子值 seed。不同的散列函数需要有不同的 seed,这样可以保证不同的散列函数的结果不会碰撞。

在hash()方法的实现中,value是要被处理的内容。这里遍历了value的每一位,并利用 ord() 方法取到每一位的ASCII码值,然后混淆 seed进行迭代求和运算,最终得到一个数值。这个数值的结果就由 value 和seed 唯一确定。我们再将这个数值和 m进行按位与运算,即可获取到m位数组的映射结果,这样就实现了一个由 字符串 和 seed 来确定的散列函数。当 m 固定时,只要seed值相同,散列函数就是相同的,相同的value必然会映射到相同的位置。所以如果想要构造几个不同的散列函数,只需要改变其seed就好了。以上内容便是一个简易的散列函数的实现。

接下来就是实现 Bloom Filter。Bloom Filter里面需要用到k个散列函数,这里要对这几个散列函数指定相同的m值和不同的seed值,

由于我们需要亿级别的数据的去重,即前文介绍的算法中的n为1亿以上,散列函数的个数k大约取10左右的量级。而m>kn,这里m值大约保底在10亿,由于这个数值比较大,所以这里用移位操作来实现,传入位数bit,将其定义为30,然后做一个移位操作1<<30,相当于2的30次方,等于1073741824,量级也是恰好在10亿左右,由于是位数组,所以这个位数组占用的大小就是2^30 b=128 MB。开头我们计算过Scrapy-Redis集合去重的占用空间大约在2 GB左右,可见Bloom Filter的空间利用效率极高。

随后我们再传入散列函数的个数,用它来生成几个不同的seed。用不同的seed来定义不同的散列函数,这样我们就可以构造一个散列函数列表。遍历seed,构造带有不同seed值的HashMap对象,然后将HashMap对象保存成变量maps供后续使用。

另外,server就是Redis连接对象,key就是这个m位数组的名称。

接下来,我们要实现比较关键的两个方法:一个是判定元素是否重复的方法exists(),另一个是添加元素到集合中的方法insert()

首先看下insert()方法。Bloom Filter算法会逐个调用散列函数对放入集合中的元素进行运算,得到在m位位数组中的映射位置,然后将位数组对应的位置置1。这里代码中我们遍历了初始化好的散列函数,然后调用其hash()方法算出映射位置offset,再利用Redis的setbit()方法将该位置1。

在exists()方法中,我们要实现判定是否重复的逻辑,方法参数value为待判断的元素。我们首先定义一个变量exist,遍历所有散列函数对value进行散列运算,得到映射位置,用getbit()方法取得该映射位置的结果,循环进行与运算。这样只有每次getbit()得到的结果都为1时,最后的exist才为True,即代表value属于这个集合。如果其中只要有一次getbit()得到的结果为0,即m位数组中有对应的0位,那么最终的结果exist就为False,即代表value不属于这个集合。

Bloom Filter的实现就已经完成了, 下面 就是 用一个实例来测试一下。

这里首先定义了一个Redis连接对象,然后传递给Bloom Filter。为了避免内存占用过大,这里传的位数bit比较小,设置为5,散列函数的个数设置为6。

调用insert()方法插入Hello和World两个字符串,随后判断Hello和Python这两个字符串是否存在,最后输出它的结果,运行结果如下:

True
False

很明显,结果完全没有问题。这样我们就借助Redis成功实现了Bloom Filter的算法。

接下来继续修改Scrapy-Redis的源码,将它的dupefilter逻辑替换为Bloom Filter的逻辑。这里主要是修改RFPDupeFilter类的request_seen()方法,实现如下:

def request_seen(self, request):fp = self.request_fingerprint(request)    if self.bf.exists(fp):        return Trueself.bf.insert(fp)    return False

利用 request_fingerprint()方法获取Request的指纹,调用Bloom Filter的exists()方法判定该指纹是否存在。如果存在,则说明该Request是重复的,返回True,否则调用Bloom Filter的insert()方法将该指纹添加并返回False。这样就成功利用Bloom Filter替换了Scrapy-Redis的集合去重。

对于Bloom Filter的初始化定义,我们可以将__init__()方法修改为如下内容:

def __init__(self, server, key, debug, bit, hash_number):self.server = serverself.key = keyself.debug = debugself.bit = bitself.hash_number = hash_numberself.logdupes = Trueself.bf = BloomFilter(server, self.key, bit, hash_number)

其中bithash_number需要使用from_settings()方法传递,修改如下:

@classmethod
def from_settings(cls, settings):server = get_redis_from_settings(settings)key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}debug = settings.getbool('DUPEFILTER_DEBUG', DUPEFILTER_DEBUG)bit = settings.getint('BLOOMFILTER_BIT', BLOOMFILTER_BIT)hash_number = settings.getint('BLOOMFILTER_HASH_NUMBER', BLOOMFILTER_HASH_NUMBER)    return cls(server, key=key, debug=debug, bit=bit, hash_number=hash_number)

其中,常量DUPEFILTER_DEBUGBLOOMFILTER_BIT统一定义在defaults.py中,默认如下:

BLOOMFILTER_HASH_NUMBER = 6
BLOOMFILTER_BIT = 30

现在,我们成功实现了Bloom Filter和Scrapy-Redis的对接。

 

代码地址为:https://github.com/Python3WebSpider/ScrapyRedisBloomFilter

 

使用的方法和 Scrapy-Redis基本相似,在这里说明几个关键配置。

# 去重类,要使用Bloom Filter请替换DUPEFILTER_CLASS
DUPEFILTER_CLASS = "scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter"
# 散列函数的个数,默认为6,可以自行修改
BLOOMFILTER_HASH_NUMBER = 6
# Bloom Filter的bit参数,默认30,占用128MB空间,去重量级1亿
BLOOMFILTER_BIT = 30
  1. DUPEFILTER_CLASS是去重类,如果要使用Bloom Filter,则DUPEFILTER_CLASS需要修改为该包的去重类。
  2. BLOOMFILTER_HASH_NUMBER是Bloom Filter使用的散列函数的个数,默认为6,可以根据去重量级自行修改。
  3. BLOOMFILTER_BIT即前文所介绍的BloomFilter类的bit参数,它决定了位数组的位数。如果BLOOMFILTER_BIT为30,那么位数组位数为2的30次方,这将占用Redis 128 MB的存储空间,去重量级在1亿左右,即对应爬取量级1亿左右。如果爬取量级在10亿、20亿甚至100亿,请务必将此参数对应调高。

 

 

测试

 

源代码附有一个测试项目,放在tests文件夹,该项目使用了ScrapyRedisBloomFilter来去重,Spider的实现如下:

from scrapy import Request, Spiderclass TestSpider(Spider):name = 'test'base_url = 'https://www.baidu.com/s?wd='def start_requests(self):for i in range(10):url = self.base_url + str(i)            yield Request(url, callback=self.parse)# Here contains 10 duplicated Requests    for i in range(100): url = self.base_url + str(i)            yield Request(url, callback=self.parse)    def parse(self, response):self.logger.debug('Response of ' + response.url)

start_requests()方法首先循环10次,构造参数为0~9的URL,然后重新循环了100次,构造了参数为0~99的URL。那么这里就会包含10个重复的Request,我们运行项目测试一下:

scrapy crawl test

最后的输出结果如下:

{'bloomfilter/filtered': 10, 
'downloader/request_bytes': 34021, 
'downloader/request_count': 100, 
'downloader/request_method_count/GET': 100, 
'downloader/response_bytes': 72943, 
'downloader/response_count': 100, 
'downloader/response_status_count/200': 100, 
'finish_reason': 'finished', 
'finish_time': datetime.datetime(2017, 8, 11, 9, 34, 30, 419597), 
'log_count/DEBUG': 202, 
'log_count/INFO': 7, 
'memusage/max': 54153216, 
'memusage/startup': 54153216, 
'response_received_count': 100, 
'scheduler/dequeued/redis': 100, 
'scheduler/enqueued/redis': 100, 
'start_time': datetime.datetime(2017, 8, 11, 9, 34, 26, 495018)}

最后统计的第一行的结果:

'bloomfilter/filtered': 10,

这就是Bloom Filter过滤后的统计结果,它的过滤个数为10个,也就是它成功将重复的10个Reqeust识别出来了,测试通过。
 

以上内容便是Bloom Filter的原理及对接实现,Bloom Filter的使用可以大大节省Redis内存。在数据量大的情况下推荐此方案。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/495891.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么 AI 工程师要懂一点架构?

作者 | 王咏刚&#xff08;公众号ID&#xff1a;ban-qing-ren&#xff09;AI 时代&#xff0c;我们总说做科研的 AI 科学家、研究员、算法工程师离产业应用太远&#xff0c;这其中的一个含义是说&#xff0c;搞机器学习算法的人&#xff0c;有时候会因为缺乏架构&#xff08;In…

CompletableFuture详解~CompletionStage

CompletableFuture 分别实现两个接口 Future与 CompletionStage。 Future 接口大家都比较熟悉&#xff0c;这里主要讲讲 CompletionStage。 CompletableFuture 大部分方法来自CompletionStage 接口&#xff0c;正是因为这个接口&#xff0c;CompletableFuture才有如此强大功能…

Python 异步 IO 、协程、asyncio、async/await、aiohttp

From &#xff1a;廖雪峰 异步IO &#xff1a;https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Await入门指南 &#xff1a;https://zhuanlan.zhihu.com/p/27258289 Python 生成器 和 yield 关键字&#xff1a;https://blog.csdn.net/free…

智能语音简史:这场技术革命从哪开始?

来源&#xff1a;与非网1952年&#xff0c;贝尔实验室&#xff08;Bell Labs&#xff09;制造一台6英尺高自动数字识别机“Audrey”&#xff0c;它可以识别数字0&#xff5e;9的发音&#xff0c;且准确度高达90&#xff05;以上。并且它对熟人的精准度高&#xff0c;而对陌生人…

精益创业~如何驾驭愿景

开发-测量-认知 反馈循环 循环中把总时间缩至最短 要把科学的方法运用到新创企业中&#xff0c;我们必须找到哪些假设是需要测试的。这是新创企业计划中风险最大的部分&#xff0c;这部分内容依赖 信念飞跃 Leap-of-Faith 式的大胆假设。其中最重要的两个假设是 价值假设 和 增…

Python 中 异步协程 的 使用方法介绍

静觅 崔庆才的个人博客&#xff1a;Python中异步协程的使用方法介绍&#xff1a;https://cuiqingcai.com/6160.html Python 异步 IO 、协程、asyncio、async/await、aiohttp&#xff1a;https://blog.csdn.net/freeking101/article/details/85286199 1. 前言 在执行一些 IO 密…

半导体终极武器光刻机:为何中国难望ASML项背?!有了全套图纸也做不出来

来源&#xff1a; XuS风险创投行指甲盖大小的芯片&#xff0c;密布千万电线&#xff0c;纹丝不乱&#xff0c;需要极端精准的照相机——光刻机。光刻机精度&#xff0c;决定了芯片的上限。EUV半导体业的终极武器这全靠总部后头那栋最高机密的巨型厂房&#xff0c;里头身穿无尘衣…

AI 与人类未来

来源&#xff1a;腾讯网摘要&#xff1a;今天的人类学&#xff0c;依托协同进化理论&#xff0c;对AI充满信心。 社会产生前后&#xff0c;人类遭遇的进化机制不同。产生之前&#xff0c;是纯粹生态的进化机制&#xff0c;由偶然性和适应性控制&#xff0c;由创造性进化的跃迁…

试玩C++ 操作页面控件

最近数字和金山吵的热火朝天的&#xff0c;群里有人说网友的投票可能有工具刷出来的&#xff0c;觉得应该很有意思&#xff0c;就想自己试一下&#xff0c;玩了半天终于可以操作页面进行投票了&#xff0c;但这个投票做了IP限制&#xff0c;所以工具也无用武之地啊&#xff01;…

Visual Studio “类视图”和“对象浏览器”图标

类视图”和“对象浏览器”显示一些图标&#xff0c;每个图标表示不同类型的符号&#xff0c;如命名空间、类、函数或变量。下表对显示的图标进行说明&#xff0c;并对每个图标进行描述。 图标说明图标说明 命名空间 方法或函数 类 运算符 接口 属性 结构 字段或变量 联…

【数据结构】数据结构知识思维导图

From&#xff1a;https://blog.csdn.net/flowing_wind/article/details/81431354 思维导图源文件&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Z44pX_jn3P6L4BSS13WmUA 提取码&#xff1a;zmga 数据结构知识思维导图&#xff1a;

特斯拉VS Waymo:谁将赢得无人驾驶汽车竞赛?

来源&#xff1a;腾讯科技据外媒报道&#xff0c;现在正有一场从硅谷延伸到底特律的竞赛正在进行&#xff0c;即谁能最先制造出比人类司机驾车更安全的无人驾驶汽车&#xff1f;与几年前相比&#xff0c;这是一项更为艰巨的任务&#xff0c;因为人类司机了解更多东西&#xff0…

Silverlight Blend动画设计系列十二:三角函数(Trigonometry)动画之自由旋转(Free-form rotation)...

说到对象的旋转&#xff0c;或许就会联想到对象角度的概念。对象的旋转实现实际上就是利用对象的角度改变来实现的位置变换&#xff0c;在《Silverlight & Blend动画设计系列二&#xff1a;旋转动画&#xff08;RotateTransform&#xff09;》一文中有对对象的不同角度变换…

vscode 调试 C++/JavaScript

Microsoft Visual Studio Code&#xff1a;https://blog.csdn.net/freeking101/article/details/86715578 IntelliJ IDEA&#xff1a;https://www.jetbrains.com/products/ 在调试 JavaScript 代码时&#xff0c; 其中 三种 比较 简单&#xff1a; 1.使用 Chrome 等 浏览器 调…

美国三院院士「迈克尔•乔丹」长文论述:为什么说「人工智能革命」尚未发生...

作者&#xff1a;Michael Jordan雷克世界」编译&#xff1a;嗯~是阿童木呀、KABUDA、EVA人工智能&#xff08;AI&#xff09;是当前时代的颂歌。这句话是由技术人员、学者、记者和风险投资家一致提出且真诚赞扬的。就像其他许多从技术学术领域跨越到通用领域的短语一样&#xf…

网络爬虫干货总结!

转载&#xff1a;https://cloud.tencent.com/developer/article/1366434 bilibili 视频 - 聊聊 Python 的应用 - 健壮高效的网络爬虫&#xff1a;https://www.bilibili.com/video/av34379204/ 昨天的时候我参加了掘金组织的一场 Python 网络爬虫主题的分享活动&#xff0c;主要…

GAN 的发展对于研究通用人工智能有什么意义?

作者&#xff1a;Lyken 来源&#xff1a;知乎GAN对于人工智能的意义&#xff0c;可以从它名字的三部分说起&#xff1a;Generative Adversarial Networks。为了方便讲述&#xff0c;也缅怀过去两周在某论坛上水掉的时间&#xff0c;我先从Networks讲起。Networks&#xff1a…

AlldayTest 产品使用--文件

“文件”一栏&#xff0c;可实现新建/打开项目、导入&#xff08;出&#xff09;项目、配置信息等功能。 1、新建项目 运行AlldayTest&#xff0c;点击&#xff1a;“文件”-->“新建”&#xff0c;在出现的窗体内输入新创建项目的名称&#xff0c;选择路径保存即可。Allday…

移动端 爬虫工具 与 方法 介绍

From&#xff1a;https://www.cnblogs.com/zyfd/p/9681080.html 本文主要介绍了移动端爬虫的工具与方法&#xff0c;作为一个入门的大纲。没有详细介绍的也给出了本人学习过程中借鉴的资料的链接&#xff0c;适合对移动端爬虫感兴趣的同学入门。 一、抓包模拟 基本原理&#xf…

“芯痛”之下阿里苦心研发NPU AI芯片究竟哪款PU更厉害?

来源&#xff1a;OFweek人工智能网4月19日&#xff0c;有消息称&#xff0c;阿里巴巴达摩院正在研发一款神经网络芯片——Ali-NPU&#xff0c;主要运用于图像视频分析、机器学习等AI推理计算。按照设计&#xff0c;这款芯片性能将是目前市面上主流CPU、GPU架构AI芯片的10倍&…