零基础爬虫入门(四) | URL管理

举报
不温卜火 发表于 2020/12/03 01:06:09 2020/12/03
【摘要】   大家好,我是不温卜火,是一名计算机学院大数据专业大三的学生,昵称来源于成语—不温不火,本意是希望自己性情温和。作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!暂时只在csdn这一个平台进行更...

  大家好,我是不温卜火,是一名计算机学院大数据专业大三的学生,昵称来源于成语—不温不火,本意是希望自己性情温和。作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!暂时只在csdn这一个平台进行更新,博客主页:https://buwenbuhuo.blog.csdn.net/

PS:由于现在越来越多的人未经本人同意直接爬取博主本人文章,博主在此特别声明:未经本人允许,禁止转载!!!


1


网络爬虫的过程:
2

什么是URL 统一资源定位符是对可以从互联网得到的资源的位置和访问方法的一种简介的表示,是互联网上标准资源的地址。互联网上的每一个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎样处理它。

一、了解URL

URL的格式由三部分组成

第一部分是协议(或称为服务方式)
第二部分是存有该资源的主机IP地址(包括端口号)
第三部分是主机资源的具体地址,比如目录和文件名等

3

二、常见的协议

Http:超文本传输协议资源
Https:用安全套接字层传送的超文本传输协议
Ftp:文本传输协议
Mailto:电子邮件地址
File:当地电脑或网上分享的文件

协议名称是由一串不区分大小写的字母组成,以:作为结束符
协议所表示的是获取该资源需要使用的协议。如HTTP、HTTPS等
浏览器也支持一些额外的协议,如data:和javascript:等
4

三、规范URL

3.1、为什么要规范化URL

  1. 不规则的URL会让网络爬虫抓取信息识别困难,造成信息遗漏
  2. 不规则URL通常表现为:

URL含有多余字符
URL缺少字符串
URL字符大小写敏感

  1. 规范URL是一个把URL标准化的过程,即将一个URL转化为一个符合规范的等价URL
  2. 规范的URL对于爬虫非常重要

下面我们以央视网的一个网址:
https://www.cctv.com/index.shtml

  1. 使用超级文本传输协议HTTP,其计算机与域名为www.cctv.com,超级文本文件(文本类型为.html)是在目录下的index.shtml
  2. 使用URL表示文件时,服务器方式用file表示,后面要有主机IP地址、文件的存储路径(即目录)和文件名等信息
  3. 有时可以省略目录和文件名,但"/"符号不能省略

四、URL管理

爬虫最主要的处理对象时URL,根据URL地址取得所需要的文件内容,对URL进行进一步的处理
准确的理解URL对理解爬虫至关重要

此部分主要有两个知识点:
1、URL去重
2、URL重定向

五、URL去重

5.1、URL去重的重要性

  • 网络爬虫爬取重复的URL链接,会下载相同网页的内容,造成计算资源的消耗,给服务器带来不必要的负担
  • 解决重复下载的问题,可以提高爬虫效率,减少不必要的资源消耗
  • 深度优先(DFS)和广度优先(BFS)的抓取策略,遇到的网页链接重复是因为网页的链接形成一个闭环
  • 无论是BFS还是DFS都不可避免地反复遍历这个环中的URL,从而造成无限循环
  • 为了避免无限循环,更需要取出重复的URL

所有的URL去重都是在内存上进行的——>可提速

5.2、Hash去重

  • Hash,也称为哈希,散列,是把任意长度的输入,通过给定的函数,转换为长度固定的输出
  • Hash的实质是一种压缩映射,散列值的空间通常远小于输入的空间
  • 不需要遍历所有的元素,提高了查找效率

举个例子:

  • 每个散列值对应一个桶,同一个桶存放的是所有散列值相同的元素
  • 88经过hash函数之后,得到一个散列值8,所以就把88放在8号桶中
    1
    Hash算法是检测一个元素是否存在的高效算法。对于一个输入,我们只需要计算其散列值,并在这个散列值对应的桶中查找元素是否存在就行了,不需要遍历所有所有元素。如在上图中,要检测数字88是否存在,只需要检测88号桶中是否存在数字88即可。

5.2.1、常用的构造Hash函数的方法

  • 直接寻址法:取关键字或关键字的某个线性函数值为散列地址(并不常用)
  • 数字分析法:抽取关键字中的一部分来计算存储位置(适用于关键词较长的情况)
  • 平方取中法:关键字先平方,截取中间X位作为存储位置(适用于不知道关键字的分布)
  • 折叠法:拆分关键字
  • 随机数法:使用随机数作为存储位置
  • 除留余数法:适用余数作为存储位置

5.2.2、Hash去重所遇到的问题及解决方法

问题:

  • 通常hash函数映射得到的散列值,并不能保证唯一性
  • 不同的输入可能会得到相同的散列值,这种现象称为Hash碰撞

解决方法:

  • 开放寻址法
  • 拉链法

1、开放寻址法

开放寻址:所有的元素经过Hash映射后都存放在散列表中

  • 当新的元素进入散列表中,检查散列表的各项,直到发现有“空”的位置,将该元素放入为止
    eg:学校的厕所门,有人门是关着的,没人门是能拉开的,就这样慢慢能找到“空”的位置

  • 常用的开放寻址方法有以下三种:
    ①线性探测:h(k,i) = (h’(k)+i) mod m, 0 ≤ i ≤ m-1
    ②二次探测:h(k,i) = (h’(k)+i*i)% m, 0 ≤ i ≤ m-1
    ③双重散列:h(k,i) = (h1(k)+ih2(k)) mod m, 0 ≤ i ≤ m-1

  • 开发寻址法通过占用散列表的其他空闲位置,来解决Hash碰撞的问题

  • 这样做会导致后续加入的元素发生Hash碰撞的风险升高

  • 对于采用开放寻址法的Hash散列表来说,需要控制它的装载因子

  • 装载因子是哈希表保存的元素数量和哈希表容量的比。采用开放寻址的Hash散列表的装载因子不大于0.5

2、拉链法

拉链法:将Hash散列表看作一个链表数组。数组中的位置要么为空,要么指向散列到该位置的链表

  • 链表法把元素添加到链表中来解决Hash碰撞。具有相同散列值的元素会插入相对应的链表中
  • 拉链法的代价不会超过向链表中添加元素,也无需执行再散列

拉链法的实现过程:
2
拉链法的优点
优点:

  • 解决了Hash表堆叠的现象,减少了平均查询的长度
  • 在单链表中执行更改这样的操作相比于开放寻址法更为简单,我们只需要把删除的元素的地址前后关联一下即可

两者对比:

  • 数据量比较小的时候开放寻址法是不需要重新开辟空间的,当数据量比较大的时候,开放寻址法要不停的按照装填因子成倍的申请新的空间,会造成存储空间过大。

5.2.3、使用Hash来对URL进行去重

  • 首先要设置一个Python的数据类型—集合,来保存已经爬取过的URL
import requests,re
count = 3
r = re.compile(r'href=[\'"]?(http[^\'">]+)')
seed = 'http://httpbin.org/'
queue = [seed]
used = set()   # 设置一个集合,保存已经抓取过的URL
storage = {}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1、为什么要用集合

Python语言的set

  • 集合对象是一组无序排列的可哈希的值
  • 集合本身无序,不能创建索引,执行切片操作
  • 集合内元素不重复
  • 集合元素为不可变对象

2、具体实现的逻辑

  • 用深度(或宽度)优先递归地搜寻新地URL
  • 如果新发现的URL包含在这个集合中就舍弃
  • 否则加入到未爬取队列中

eg:

while len(queue) > 0 and count > 0 : try: url = queue.pop(0) html = requests.get(url).text storage[url] = html  #将已经抓取过的URL存入used集合中 used.add(url) new_urls = r.findall(html)   # 将新发行未抓取的URL添加到queue中 for new_url in new_urls: if new_url not in used and new_url not in queue: queue.append(new_url) count -= 1 except Exception as e : print(url) print(e)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3、统计URL管理器中的URL数量

from collections import Counter
url_count = Counter(queue)
for url,count in url_count.most_common(10): print(url,count)

  
 
  • 1
  • 2
  • 3
  • 4

所得结果如下图:
3

4、略加修改的代码:

import requests,re
import time
from collections import Counter
count = 3
recount = 0
allcount = 1
r = re.compile(r'href=[\'"]?(http[^\'">]+)')
seed = 'http://httpbin.org/'
queue = [seed]
used = set()   # 设置一个集合,保存已经抓取过的URL
storage = {}

while len(queue) > 0 and count > 0 : try: url = queue.pop(0) html = requests.get(url).text storage[url] = html  #将已经抓取过的URL存入used集合中 used.add(url) new_urls = r.findall(html)   # 将新发行未抓取的URL添加到queue中 for new_url in new_urls: allcount += 1 if new_url not in used and new_url not in queue: queue.append(new_url) else: print("重复:"+url) recount += 1 count -= 1 except Exception as e : print(url) print(e)

print("重复网站:"+str(recount))
print("总网站:"+str(allcount))
url_count = Counter(queue)
for url,count in url_count.most_common(10): print(url,count)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4
去重的重要性:
因为网站结构的关系,它会进行重复的引用。

  • 上面的代码可以防止无穷循环,但是比较多时就会体现出劣势
  • 如果URL过多,那么占用的内存空间也会很大

总结:

  • 优点:速度快
  • 缺点:占用大量内存空间

5.3、URL压缩

  • URL压缩基于MD5算法对URL进行加密压缩,生成散列值,来判断URL的唯一值
  • MD5是一种基于Hash的加密算法,它可以压缩URL生成:
    ①一个压缩的128位整数
    ②一个Hash物理地址
  • 使用MD5算法进行Hash映射,发生Hash碰撞的几率小,为网络爬虫抓取所使用

使用第三方库hashlib来实现MD5映射算法

import hashlib
src1 = 'https://baidu.com'
m1 = hashlib.md5()
m1.update(src1.encode('utf-8'))
print(m1.hexdigest())

src2 = 'https://baidu.com'
m2 = hashlib.md5()
m2.update(src2.encode('utf-8'))
print(m2.hexdigest())

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5

5.4、Bloom Filter

Bloom Filter是在1970年代由Bloom出的一种多哈希函数映射的快速查找算法

它是一种空间效率高的随机数据结构

  • 使用位数组表示一个集合
  • 判断一个元素是否属于这个集合

Bloom Filter的基本思路是:通过多个不同的Hash函数来解决“冲突”

Bloom Filter主要包含以下两个部分:

  • 1个比特数组:长度为m,并初始化为0
  • k个hash函数:进行URL哈希,哈希值范围[0,m-1]

Bloom Filter的任务是,判断URL是否已经抓取过

  • URL哈希之后,得到k个范围在[0,m-1]的值,然后判断这k个位置上是否都是1,如果都是1,就认为这个URL已经抓取过,否则没有抓取

在下图中,有三个hash函数。假设x,y,z是已经抓取过的URL
6
w是要判断的URL:

  • 可以看到,w经过hash之后三个对应的位置上有一个不是1,我们可以肯定这个URL没有被抓取过

5.4.1、Bloom Filter的缺点

Bloom Filter的查询时间和空间效率虽高,但是有以下缺点:

  • Bloom Filter集合中的元素无法删除
  • 如何确定位数组的大小以及hash函数的个数
  • Bloom Filter会出现错误判断,无法达到零错误

5.4.2、Bloom Filter通常的应用场景

  • 设置黑名单
  • 过滤垃圾短信
  • 检测重复URL

Python中有很多Bloom Filter的开源实现,我们这里选用pybloom工具包

pybloom的主要类和函数有:

  • BloomFilter(capacity,error_rate)
  • ScalableBloomFilter(initial_capacity,error_rate,mode)

举例

创建一个Bloom Filter:

  • 容量为1000,误判率为0.001,pybloom自动计算需要多少个hash函数,需要多少比特的数组
import pybloom_live
f = pybloom_live.BloomFilter(capacity = 1000,error_rate = 0.001)
[f.add(x) for x in range(10)]

  
 
  • 1
  • 2
  • 3

7

from pybloom_live import BloomFilter
bf = BloomFilter(capacity=1000, error_rate=0.001) # 创建一个 BloomFilter,误判率为0.001,容量为1000
try: for i in range(0, bf.capacity + 1000): bf.add(i)
except Exception as e: print (i) print (type(e), e)
# 当前已经添加的不同元素数目
print (bf.count, len(bf))
# num_slices 表示有几个 hash 函数,bits_per_slice 表示每个 hash 函数占用的比特数,num_bits = num_slices * bits_per_slice
print (bf.num_slices, bf.bits_per_slice, bf.num_bits )

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

8
上面我们创建一个 BloomFilter,容量为 1000,误判率为 0.001,pybloom 会自动计算需要多少个 hash 函数,需要多少比特的数组。当添加 1002 时,bf 的长度是1002 > 1001,所以会抛出 IndexError 异常。也就是说pybloom 为了保证设定的 error_rate,最多只能插入 capacity + 1 个不同的元素。接下来让我们验证一下bf的误判率是否低于 0.001:

true_count = 0
false_count = 0
start = 1000
end = 100000
for i in range(start, end): if i in bf: true_count += 1 else: false_count += 1
print(float(true_count)/(end-start) < 0.001)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

9
可以看到,bf 的错误率确实是小于 0.001 的。但有一个问题,这种方法需要我们事先就知道唯一元素的数目,而我们往往是无法估计唯一元素的数目的。所以pybloom还提供了一个可以自动扩展的 BloomFilter:ScalableBloomFilter

from pybloom_live import ScalableBloomFilter
sbf = ScalableBloomFilter(initial_capacity=1000, error_rate=0.001) # 创建一个 ScalableBloomFilter,误判率为0.001,容量为1000
for i in range(0, sbf.initial_capacity + 1000): sbf.add(i)
print (sbf.count, len(sbf))

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

10
可以看到,我们创建了一个初始容量为 1000 的 ScalableBloomFilter,当添加的唯一元素数目超过1000以后仍然可以继续添加。事实上,创建一个ScalableBloomFilter更简单的方法是直接使用 sbf = ScalableBloomFilter(),这样就会创建出一个初始容量为0,误判率为0.001的 Bloom Filter。

六、URL重定向

重定向(redirect)允许一个网页在不同的域名下显示

重定向有两种形式:

  • Dispatch:服务器端重定向,网页在加载之前先改变了URL
  • Redirect:客户端重定向,有时你会在网页上看到“5秒之后自动跳转…”之类的消息,表示在跳转到新URL之前网页需要加载内容

6.1、客户端重定向

客户端重定向是在服务器将页面内容发送到浏览器之前,由浏览器执行JavaScript完成的页面跳转,而不是服务器完成的跳转

当浏览器访问页面的时候,有时很难区分这两种重定向:

  • 由于客户端重定向执行很快,加载页面时你甚至感觉不到任何延迟,所以会让你觉得这个重定向就是一个服务器端重定向

客户端重定向,也成为HTTP重定向,是HTTP协议规定的一种机制。

重定向的机制如下图:
11

6.2、服务器重定向

服务器重定向是在处理客户端提交的request过程中,服务器将request先后委托多个处理单元接替进行处理的过程
12

6.3、差别

  • 在网络爬虫进行数据采集的时候,这两种重定向的差异是很明显的
  • 根据具体情况,服务器端重定向一般可以通过Pythonurllib库解决,不需要使用Selenium
  • 客户端重定向不能像服务器重定向一样,除非使用工具执行JavaScript

6.4、客户端重定向的类型

重定向的类型有很多种,301和302是最常见的两种

  • 301 Moved Permancently :永久重定向(稳定,静态化)
  • 302 Moved Temporarily:临时重定向(慎用)

6.5、301重定向的必要性

当网页A用301重定向转到网页B时,搜索殷勤肯定网页A永久的改变位置,或者说实际上不存在,搜索引擎就会把网页B当作唯一有效目标

这样做的好处:

  • 没有网址规范化问题
  • 网页A的PageRank级别会传到网页B
  • 不会因为域名更换而不收录

七、简单小结

我们其实完全不需要为这些烦心,因为不需要,面向数据分析的爬虫基本都是垂直爬虫,我们要抓取的内容是比较明确的,网络的交互过程事先都已经分析清楚了,爬取的层次不会很深。这也就意味着我们要抓取的 URL 比较少,通常都小于千万量级,并且其中重复的 URL 也比较少。通常情况下用 hash 的方式去重就已经够用了,甚至不需要使用 Bloom Filter。

26

美好的日子总是短暂的,虽然还想继续与大家畅谈,但是本篇博文到此已经结束了,如果还嫌不够过瘾,不用担心,我们下篇见!


27

  好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
  如果我的博客对你有帮助、如果你喜欢我的博客内容,请“点赞” “评论”“收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
  码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注我哦!

29
29

文章来源: buwenbuhuo.blog.csdn.net,作者:不温卜火,版权归原作者所有,如需转载,请联系作者。

原文链接:buwenbuhuo.blog.csdn.net/article/details/105184265

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。