如何从头搭建一个搜索引擎_HTTP请求,hash_table,bloomFilter和python并发编程

日期:2016年10月05日
标题:HTTP请求,hash_table,bloomFilter和python并发编程
编号:2

一.HTTP协议
  • HTTP请求
    • 本质上说,一个HTTP请求起始于用户端向HTTP服务器发送一个URL请求,一个标准的HTTP请求由以下几个部分组成:
      • <request-line> 请求行,用来说明请求类型,URL和HTTP版本
      • <headers> 头部信息,用来说明服务器要使用的附加信息
      • <CRLF> 回车换行符(/r/n),用于表明头部信息的结束
      • [<request-body><CRLF>] 主题数据,可不添加
  •  HTTP的数据交互方式
    • GET(查):用于获取信息,不会修改服务器上的数据
    • POST(改):向服务器发送修改请求,从而修改服务器。例如在论坛上回帖,在博客上评论,也可以仅用来获取数据
    • DELETE(删):可以通过GET/POST等价实现
    • PUT(加):增加,放置数据,也可以通过GET/POST来实现
    • GET和POST的区别: GET请求的数据是放在URL之后的,例如login.action?name=hyddd&password=idontknow&verify=%E4%BD%E5%A5%BD(a.以?来分割 URL 和 GET 的数据 b.不同数据之间通过&连接 c.遇到 中文使用 BASE64 编码) 而POST的数据放在HTTP的正文之中,GET能提交的数据要比POST少,且GET的数据是明文传输的,安全性低
  •  利用python来模拟HTTP请求
    • cookielib:cookielib模块的主要作用是提供可存储cookie的对象,以便于与urllib2模块配合使用来访问Internet资源。还可以用来处理包含cookie数据的文件。例如可以利用本模块的CookieJar类的对象来捕获cookie并在后续连接请求时重新发送。
    • urllib2模块的 OpenerDirector 操作类。这是一个管理很多处理类(Handler)的类。而所有这些 Handler 类都对应处理相应的协议,或者特殊功能。
    • cookielib模块一般与urllib2模块配合使用,主要用在urllib2.build_oper()函数中作为urllib2.HTTPCookieProcessor()的参数。
import urllib,urllib2,cookielib   //需要使用cookielib来存储发送给服务器的信息
from bs4 import BeautifulSoup

def bbs_set(id,pw,text):
    cj = cookielib.CookieJar();   //创建一个存储cookie的cookiejar对象

//urllib2.urlopen()函数不支持验证、cookie或者其它HTTP高级功能。要支持这些功能,必须使用build_opener()函数创建自定义Opener对象。
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) //利用HTTPCookieProcessor(cookie处理器)处理cookiejar对象,以此为初值定义opener对象
    urllib2.install_opener(opener) //安装opener,在安装之后调用urlopen()都会使用安装过的opener对象
    postdata = urllib.urlencode({     //使用字典表示要发送的POST信息,接着用urllib.urlencode将post信息编码为服务器所能识别的格式
        'id':id,
        'pw':pw,
        'submit':'login'  //?有疑问     此处的key全是HTML的input tag中的name属性,点按钮的动作通过给input按钮的name赋值HTML上的value值来实现,例如 <input type="submit" name="submit" class="button" value="login" id="Submit1" />
        })
//通过Request的方式打开url
    req = urllib2.Request(url='https://bbs.sjtu.edu.cn/bbslogin',data=postdata)
    response = urllib2.urlopen(req)


    postdata2 = urllib.urlencode({
        'text':text,
        'type':'update'
        })
    req = urllib2.Request(url='https://bbs.sjtu.edu.cn/bbsplan',data = postdata2)
    response2=urllib2.urlopen(req)

    content = urllib2.urlopen('https://bbs.sjtu.edu.cn/bbsplan').read()
    soup = BeautifulSoup(content)

    print str(soup.find('textarea').string).strip().decode('utf-8')

text='shanghai'
_id='crawler'
pw='515030910612'
bbs_set(_id,pw,text)



二.HASH散列
  1.  hash table
    1. 将字符串根据规则(hash function)放在b个不同的bucket中,这样查找只需要查找对应的bucket,将搜索范围缩短为原来的1/b
  2.  hash function :
    1. 好的hash function应该能够将keywords按照规则平均地分配到b个bucket中
    2. 参考(General Purpose Hash Function Algorithms)中给出了几种Hash Function的写法(GeneralHashFunctions_-_Python.zip文件)。使用时可以选择多个哈希函数。也可以选择其中一种,给定不同的seed生成多个哈希函数。
def simple_hash_string(keyword,b):
    if keyword!='':
        return ord(keyword[0]) % b
    else :
        return 0

def better_hash_function(keyword,b):
    if keyword!='':
        return (ord(keyword[0])+ord(keyword[-1])) % b
    else :
        return 0

def hashtable_get_bucket(table,keyword):
    if keyword!='':
        bucket_num = (ord(keyword[0])+ord(keyword[-1]))%len(table)
    else:
        bucket_num = 0
    return table[bucket_num]


def hashtable_lookup(table,keyword):
    bucket_num = better_hash_function(keyword,len(table))
    if keyword in table[bucket_num]:
        return True
    else:
        return False



def make_hashtable(b):
    try:
        if b==0:
            table = []
        else:
            table = [[]]*b
        return table
    except:
        print "b must be greater than 0!\n"

def hashtable_add(table,keyword):
    bucket_num = better_hash_function(keyword,len(table))
    table[bucket_num].append(keyword)


def test_hash_function(func,size,filename):
    words=[]
    f = open(filename,'r')
    for line in f.readlines():
        for word in line.strip().split(' '):
            words.append(word)
    f.close()
    results = [0]*size
    words_used = []
    for word in words:
        if word not in words_used:
            bucket = func(word,size)
            results[bucket]+=1
            words_used.append(word)
    return results


三. BloomFilter
  1. 背景:
    1. 在爬虫中,用HashTable记录已爬URL太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。
    2. Gmail等Email提供商,需要过滤垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。每存储一亿个 email 地址, 就需要 1.6GB 的内存。而全世界至少有几十亿个发垃圾邮件的地址。
    3. 在上述应用中,需要快速判断某个元素是否属于集合,但是并不严格要求100%正确。例如将未爬网页误判为已爬网页的代价只是少爬几个网页;将正常邮件的地址误判为垃圾邮件地址,可以用通过建立白名单(存储那些可能别误判的邮件地址)的方式补救。另外,我们不关心集合里具体有哪些元素。例如我们不关心垃圾邮件集合里具体有哪些垃圾邮件地址。
  2.  bit-map:
    1. 建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。 初始状态时,BitSet是一个包含m位的位数组,每一位都置0。添加元素x时,通过哈希函数将x映射到0~m-1的某个位置h(x),将该位置置1。查找元素y时,对y用哈希函数,如果h(y)位置为1,则很有可能y属于集合。如果h(y)位置为0,则y肯定不属于集合。
    2. Bit-Map相当于没有bucket,而且m>>n的HashTable。由于没有存储keyword的bucket,bitmap更节省空间。若要降低冲突发生的概率到1%,就要将BitSet的长度m设置为keyword个数n的100倍。
    3. 缺点:冲突率高
  3.  BloomFilter:
    1. 为了降低冲突的概念,Bloom Filter使用了k个哈希函数,而不是一个。
    2. 假设有k个哈希函数,位数组大小为m,加入keyword的数量为n。 k个哈希函数记为h1, h2, …, hk, 对于keyword e,分别计算h1(e),h2(e),…,hk(e),然后将BitSet的h1(e),h2(e),…,hk(e)位设为1。这样就将keyword e映射到BitSet中的k个二进制位了。
    3. 检查keyword str是否存在的过程 :对于keyword str,分别计算h1(str),h2(str),h3(str),h4(str)。然后检查BitSet的第h1(str),h2(str),h3(str),h4(str)位是否为1,若其中任何一位不为1则可以判定str一定没有被记录过。若全部位都是1,则“认为”字符串str存在。   若一个字符串对应的Bit不全为1,则可以肯定该字符串一定没有被BloomFilter记录过。(这是显然的,因为字符串被记录过,其对应的二进制位肯定全部被设为1了)   但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被BloomFilter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。 不同于其他结构,BloomFilter中的keyword加入了就被不能删除了,因为删除会影响到其他keyword 。 BloomFilter跟单哈希函数Bit-Map不同之处在于:Bloom Filter使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。
    4. BloomFilter的在线演示 http://billmill.org/bloomfilter-tutorial/ http://www.jasondavies.com/bloomfilter/
    5. 哈希函数个数k、位数组大小m选择 :哈希函数个数k、位数组大小m、加入的字符串数量n的关系可以(BloomFilters- the math)。该文献证明了对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。 同时该文献还给出特定的k,m,n的出错概率。例如:根据(BloomFilters- the math) 1,哈希函数个数k取10,位数组大小m设为字符串个数n的20倍时,false positive发生的概率是0.0000889 ,这个概率基本能满足爬虫的需求了。



四.并发编程
  • Python中并发编程可以通过threading和Queue函数实现。Threading用来操作线程,Queue用来维护任务队列。 (PPT上使用了比较低级的Thread,和Threading是两个不一样的类,Threading使用OO方法,不需要指定target函数)
  • Queue:
    1. 三个子类:Queue(队列),LifoQueue(栈),PriorityQueue(优先级队列)
    2. q=Queue.Queue(size) 初始化队列,size可选
    3. q.put(obj) (对于Queue)将obj放入末尾
    4. q.get()从队列首pop出一个元素
    5. q.size():返回队列大小
    6. q.empty():判断是否为空
    7. q.full():判断队列是否达到最大数
    8. q.get_nowait():如果队列阻塞就不等待直接取出
    9. q.unfinished_tasks():表示q中未完成的工作(元素)数量
    10. q.task_done():完成一项工作之后向队列发出一个信号,同时q.unfinished_tasks()减一
    11. q.join():等到队列空了之后再执行别的工作
  •  thread:
def working():   
   while True:                  
      arguments = q.get()        #获取任务
      do_something_using(arguments)    #处理任务
     q.task_done()            #任务结束

for i in range(NUM):        #生成NUM个线程等待队列
      t = Thread(target=working)        #每个线程的工作进程为working()函数
     t.setDaemon(True)
      t.start()

for i in range(JOBS):   
     q.put(i)            #将任务放入队列

q.join()                #阻塞,等待所有任务完成
  • Threading:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值