一些想法

页面爬的多了,量上去了之后,就会遇到其他的问题,其实不管做什么技术量大了都会有问题。一般情况下,我认为解决"大量"问题的思路有两个:一种是着力于优化系统的能力,让原本只能一分钟处理100条的系统提升到一分钟1000条之类的,在我看来并行、分布式、集群都属于这个范畴,这种思路下,系统处理的内容没有变化只是单纯的处理速度变快了;另一种是着力于提高系统的工作效率, 比如说降低某算法的复杂度。

爬虫领域的增量式爬取属于后者,每种网站都有每种网站的特点。比如说小说连载网站、新闻或者知乎首页,这里拿知乎时间线举例,我基本每天醒来和睡觉前都会刷一波知乎,从头开始看直到看到上次载入的地方,假设我要抓取知乎的数据并保存到本地,不难发现最好的选择其实是每次只抓取上次没读过的新内容,抓评论也是一样,最优的选择是每次只抓取在上次抓取之后出现的新评论,然后再进行保存。有的时候,还有另外一种情况,就是原本存在的网页内容更新了,比如说有人在知乎上修改了他的回答。这时候,我们的爬虫就需要有分辨这些区别变化的能力。但这几个都是很简单的例子,实际情况会复杂很多。

不管是产生新页面,还是原本的页面更新,这种变化都被称为增量, 而爬取过程则被称为增量爬取。那如何进行增量式的爬取工作呢?回想一下爬虫的工作流程:

发送URL请求 ----- 获得响应 ----- 解析内容 ----- 存储内容

我们可以从几种思路入手:

在发送请求之前判断这个URL是不是之前爬取过

在解析内容后判断这部分内容是不是之前爬取过

写入存储介质时判断内容是不是已经在介质中存在

实现增量式爬取

不难发现,其实增量爬取的核心是去重, 至于去重的操作在哪个步骤起作用,只能说各有利弊,就像我说的,everything is tradeoff。

在我看来,前两种思路需要根据实际情况取一个(也可能都用)。第一种思路适合不断有新页面出现的网站,比如说小说的新章节,每天的最新新闻等等;第二种思路则适合页面内容会更新的网站。第三个思路是相当于是最后的一道防线。这样做可以最大程度上达到去重的目的。

去重的方法

最简单的去重方式自然是将所有访问过的URL和其对应的内容保存下来,然后过一段时间重新爬取一次并进行比较,然后决定是否需要覆盖。这显然是不实际的,因为会消耗很多资源。目前比较实际的做法就是给URL或者其内容(取决于这个网站采用哪种更新方式)上一个标识,这个标识有个比较好听的名字,叫数据指纹。

这里很容易想到的一种数据指纹就是哈希值,根据哈希函数的特性,我们可以为任意内容生成一个独一无二的定长字符串,之后只要比较这个哈希值就行了。哈希值是一个很伟大的发明,几乎在任何地方都有它的影子,它利用数学特性,计算机只要经过简单的计算就可以得到唯一的特征值,这个计算过程的开销基本可以忽略不计,当然这是题外话了。

不过即使用了哈希值,你仍需要一个地方存储所有的哈希值,并且要能做到方便的取用。如果你的存储介质是数据库,一般的数据库系统都能提供索引,如果把哈希值作为唯一索引呢,这应该是可行的。有些数据库也提供查询后再插入的操作,不过本质上应该也是索引。和哈希值类似的还有MD5校验码,殊途同归。

除了自建指纹,其实在发送请求时还有一些技巧,比如说304状态码,Last-modified字段,文件大小和MD5签名。具体参考[8],很好理解,就不细说了。

综上所述,在数据量不大的时候,几百个或者就几千个的时候,简单自己写个小函数或者利用集合的特性去重就行了。如果数据量够大,数据指纹的价值就体现出来了,它可以节省可观的空间,同时可以引入BloomFilter作为去重的手段。另外,如果要对数据做持久化(简单说就是去重操作不会被事故影响,比如说断电),就需要用到Redis数据库。

BloomFilter

布朗过滤器虽然不是因为爬虫才出现的,但是却在这种情况下显得异常有用。布朗过滤器可以通过计算来判断某项数据是否存在于集合中,它原理和概念可以参考1和英文版的维基百科Bloom filter, 里面有详细的数学推理,它解释了为什么布朗会有误判情况出现,感兴趣可以学习一下,并不难。这里只提几点:

布朗过滤器是有误判率的,它会把原本不属于这个集合的数据误判为属于,但不会把原本属于集合的数据误判为不属于。

它是一个典型且高效的空间换时间的例子。

它的误判率是:

\left(1-\left[1-\frac{1}{m}\right]^{kn}\right)^k \approx \left( 1-e^{-kn/m} \right)^k

这里元素的数量n、 过滤容器的大小m(bits)和哈希函数的数量k存在的一定关系,它们三个共同确定了误判率;同样如果已知其中两项,通过调整另外一项也可以达到降低误判率的目的,具体参见Bloom Filters - the math。

Redis的集合使用

简单来说,Redis的集合就是Redis数据库中的集合类型,它具有无序不重复的特点。Python有redis客户端库,这里主要涉及到的就是SADD和SISMEMBER命令。下面会具体解释。

具体实现

BloomFilter

这里我们使用pybloom库,需要pip或者源码安装。pybloom库用起来非常简单,这里给两段最基本的代码:

from pybloom import BloomFilter

# 新建一个过滤器,长度为m,错误率为0.1%

bf = BloomFilter(capacity=1000, error_rate=0.001)

'''

不难理解,这句就相当于

for x in range(bf.capacity):

bd.add(x)

但说实话这种写法我第一次见到

'''

[bf.add(x) for x in range(bf.capacity)]

print (0 in bf)

print (5 in bf)

print (10 in bf)

# 这里是计算它的错误率

count = 0

amount = bf.capacity

for i in range(bf.capacity, bf.capacity + amount + 1):

if i in bf:

count += 1

print ("FP: {:2.4f}".format(count / float(amount)))

我从网上搜到文章大多只是介绍了如何新建一个Filter、怎么add以及查看元素是否属于这个Filter。实际上,如果阅读过源码,其实filter还提供了很多其他方法,同时这个库还提供了一个可自动扩展的Filter,作者比较推荐后者。

from pybloom import BloomFilter

# 新建

bf1 = BloomFilter(capacity=1000, error_rate=0.001)

bf2 = BloomFilter(capacity=1000, error_rate=0.001)

# 添加

[bf1.add(x) for x in range(3)]

[bf2.add(x) for x in range(3,6)]

# 复制

bf3 = bf1.copy()

# | 操作,三种都行

bf3.union(bf1)

bf3 = bf3 | bf2

bf3 = bf3 or bf2

# & 操作, 三种都行

bf3.intersection(bf1)

bf3 = bf3 & bf1

bf3 = bf3 and bf1

# 成员变量和支持的操作符

len(bf3)

3 in bf3

bf3.capacity

bf3.error_rate

# 也支持tofile和fromfile操作

# 具体的代码可参照源码中tests.py中的test_serialization()方法

可扩展的过滤器:

from pybloom import ScalableBloomFilter

# 新建, mode目前只有2种

# SMALL_SET_GROWTH = 2, LARGE_SET_GROWTH = 4

# 前者占内存少但速度慢,后者消耗内存快但速度快

bf1 = ScalableBloomFilter(initial_capacity=100, error_rate=0.001, mode=ScalableBloomFilter.SMALL_SET_GROWTH)

bf2 = ScalableBloomFilter(initial_capacity=1000, error_rate=0.001, mode=ScalableBloomFilter.LARGE_SET_GROWTH)

# 添加

[bf1.add(x) for x in range(3)]

[bf2.add(x) for x in range(3,6)]

# 两个属性(装饰器)

bf1.capacity

bf1.count

# 成员变量和支持的操作符

len(bf1)

3 in bf1

bf1.initial_capacity

bf1.error_rate

# 也支持tofile和fromfile操作

# 具体的代码可参照源码中tests.py中的test_serialization()方法

这里我建议看下这个库源码,核心部分差不多500行,里面很多写法很值得学习,而且都很容易理解。里面也涉及到了如何选取哈希函数。

Redis

Python的Redis客户端库也是开源的,地址是:redis-py。不过在开始之前,你首先需要一个有Redis数据库运行的主机(搭建一个很简单)。

这里需要解释不少东西,首先,上文中有一节本来不叫“Redis的集合”而是叫“Redis集合”,我一开始以为这是一种名叫Redis的特殊集合,然后这个集合带有不可插入重复内容的特性,事实上这里大错特错了。还记得我们的初衷是“去重”,实际上,包括Python在内的很多语言已经实现了具有无序不重复特性的内置数据结构:集合(Set)。也就是说从去重这点看的话,有集合这种数据结构就够了,跟Redis并没有什么关系。

那么Redis是什么?它是一种数据库,它的官网是这样描述的:

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.

关于Redis数据库还有几个关键词:key-value,高性能,数据持久化,数据备份,原子操作以及跟这里相关的一个特性:支持集合数据类型。这才是为什么做增量爬取时我们要用到Redis数据库:我们可以通过将URL或者页面内容的指纹作为key存入Redis数据库中的集合里,利用集合的不重复性达到去重的目的,每次爬虫要处理URL或者页面时会先去Redis数据库里检查一下是否已经存在,因为Redis数据库着力于key-value形式的存储,所以这一步的速度将会很可观;其次Redis可以将内存中的内容持久化到磁盘,并且其每一次操作都是原子操作,这就保证了爬虫的可靠性,即爬虫不会应为意外停止而损失数据。

说了这么多,现在就能知道为什么这里要用到Redis的集合。如果只考虑本文相关的内容,那么和本文有关的Redis数据库操作命令只有两个:SADD和SISMEMBER,前者可以向集合中插入一条数据,成功返回1,失败返回0;后者可以查询某元素是否在集合中存在,存在返回1,不存在返回0。

我在一台虚拟机Ubuntu-14.04上安装了Redis数据库并配置了远程连接,客户端测试如下:

>>> import redis

>>> r = redis.StrictRedis(host='192.168.153.131', port=6379, db=0)

>>> r.sadd('1','aa')

1

>>> r.sadd('1','aa')

0

>>> r.sadd('2','aa')

1

>>> r.sadd('3','aa')

1

>>> r.sismember('1','aa')

True

>>> r.sismember('1','b')

False

>>>

但应该如何将这一特性融入到爬虫中呢?如果是自己写的爬虫代码,添加上述代码即可;如果使用的是scrapy框架,我们可以在middleware上下功夫,在spider模块收到要处理的URL时,写一个Spider中间件用来判断这条URL的指纹是否在Redis数据库中存在,如果存在的话,就直接舍弃这条URL;如果是要判断页面的内容是否更新,可以在Download中间件中添加代码来校验,原理一样。当然,数据库的操作可以用类似write()和query()的方法进行封装,此处不表。

参考

python爬虫实时更新数据_爬虫的增量式抓取和数据更新相关推荐

  1. 爬虫的增量式抓取和数据更新

    不管是产生新页面,还是原本的页面更新,这种变化都被称为增量, 而爬取过程则被称为增量爬取.那如何进行增量式的爬取工作呢?回想一下爬虫的工作流程: 发送URL请求 ----- 获得响应 ----- 解析 ...

  2. python爬虫实时更新数据_Python爬虫 更新中

    基础 一.环境配置 查看已安装的包 pip freeze 导出 pip freeze >requirements.txt 安装 pip install -r requirements.txt - ...

  3. r语言抓取网页数据_使用R进行网页抓取的简介

    r语言抓取网页数据 by Hiren Patel 希伦·帕特尔(Hiren Patel) 使用R进行网页抓取的简介 (An introduction to web scraping using R) ...

  4. datagridview实时更新数据_旭诺云盒|智能办公新趋势进出口数据自动提取,通关状态实时更新...

    春节期间,很多公司同事都被滞留在老家无法返回公司上班,为了保证公司业务正常运转,同事之间依靠邮件.微信.QQ等工具进行文件和数据的传递,增加了很多数据整理时间.且电子口岸.单一窗口这些进出口企业频繁使 ...

  5. datagridview实时更新数据_急报!福布斯实时数据更新马云身家656亿美元登顶中国首富...

    10月29日,蚂蚁集团正式开启申购.随着蚂蚁集团上市在即,福布斯实时更新了马云的身家.数据显示,截止到12:00,其身家已达656亿美元,再次登顶中国首富. 福布斯中国富豪榜实时数据 按马云持股阿里巴 ...

  6. cookie追加数据_集算器 SPL 抓取网页数据

    [摘要] 集算器 SPL 支持抓取网页数据,根据抓取定义规则,可将网页数据下载到在本地进行统计分析.具体定义规则要求.使用详细情况,请前往乾学院:集算器 SPL 抓取网页数据! 网站上的数据源是我们进 ...

  7. python flask 实时刷新数据_从Flask实时更新变量而无需刷新页面

    Je suis ici La température de la pièce est de: {{ temp }}°. function() { $SCRIPT_ROOT = {{ request.s ...

  8. echarts实时更新数据_虎牙为S10拼了8.0年度更新!随时回放实时数据,还能养柴犬...

    S10总决赛马上就要来临了,对于广大玩家来说,这真的是每年一度的狂欢盛宴.世界各地二十多支顶尖强队汇聚中国上海,直至决出最强者方才罢休. 作为英雄联盟中的世界杯,各大平台也都十分重视S10.尤其是虎牙 ...

  9. oracle主从表分离怎么实时更新数据_高可用数据库主从复制延时的解决方案

    MySQL主从复制的延时一直是业界困扰已久的问题.延时的出现会降低主从读写分离的价值,不利于数据实时性较高的业务使用MySQL. UDB是UCloud推出的云数据库服务,上线已达六年,运营了数以万计的 ...

最新文章

  1. 尚未备份数据库 *** 的日志尾部。如果该日志包含您不希望丢失的工作,请使用 BACKUP LOG WITH NORECOVERY 备份该日志。...
  2. LeetCode:326. Power of Three
  3. PaperWeekly 第二十三期 --- 机器写诗
  4. 百度AICA再添57位“首席AI架构师”,人工智能与产业场景结合愈发深入
  5. 浪潮服务器更换硬盘_总金额2.5亿!浪潮信息助力中国移动部署NFV项目
  6. HDU-2084(简单DP)
  7. Swift NSDate的一个分类,把Mon Apr 04 19:45:37 +0800 2016这种格式的时间转换为2016-04-04 11:45:37 +0000
  8. 不同VLAN间的通信
  9. Android 系统(213)---如何内置多张静态壁纸(图片)到系统中
  10. 二叉平衡树的算法复杂度笔记
  11. GOOGLE怎么用?
  12. 怎么把数据文件上传云服务器,如何将数据上传到云服务器上
  13. 树莓派WIFI配置遇到的坑 之 连接不上WIFI
  14. Python实战——选择最佳旅游攻略,让旅游更加便捷(爬虫实战)
  15. android手机到iphone6s,iPhone6s相当于现在什么档次的安卓手机?结果让国产手机品牌汗颜...
  16. node js fcoin api 出现 api key check fail : {status:1090,msg:Illegal API signature}
  17. python excel文件转换成字符串_python利用pandas将excel文件转换为txt文件的方法
  18. 卫宁、智云的医疗SaaS出路:纵深、拓宽
  19. 【LaTeX】IEEE会议模板中插入双栏图片(解决报错:Undefined control sequence. \subfloat
  20. rock游戏服务器显示未知错误,rockstar未知错误 | 手游网游页游攻略大全

热门文章

  1. 输出100以内所有3的倍数的和。
  2. JS清空数组的几种方法比较
  3. 无线端自定义模块html编辑,关于阿里巴巴无线端旺铺自定义模块如何设置图片全屏自动适应的问题...
  4. 为什么MCU晶体两边要各接一个对地电容?
  5. 给国行Sony9500H更换美区950H主板
  6. cavans图形绘制(直线、矩形、原型、贝塞尔曲线、渐变背景)
  7. 华为19级工程师三年心血终成趣谈网络协议文档(附清华大牛视频讲解和课件)
  8. 1024程序员节,1024表示的另一种黄黄的内涵
  9. 一个快速批量查询快递物流数据的小工具,可同时多种快递物流信息跟踪查件
  10. 微信小程序入门Demo(石头剪刀布)