首页 > 其他 > 详细

基于redis实现高并发下的IP代理池可靠更换

时间:2020-01-05 17:16:47      阅读:104      评论:0      收藏:0      [点我收藏+]

业务需求

现需对某国外图片网站进行大量爬取,为提高效率使用多进程,对多个子目录下的图片同时爬取。由于网站对单IP的下载量有限额,需要在额度耗尽时自动从代理池里更换新代理。IP的可用额度无法在本地计算或实时获取,只有在耗尽时才能从目标网站得到异常通知。

业务分析

虽然是单机并发,但所面对的问题其实属于分布式领域。由于网站并未对访问频率作出限制,所以只需考虑IP的下载总量即可,可让所有进程都走同一个代理IP;又因为代理会随时变更,所以应该在每次下载请求时实时获取,这里使用redis维护代理地址比较合适。爬虫用Python编写,所以用Python的redis客户端。

业务实现

假设代理池中有5个地址,192.168.1.1:1080~192.168.1.1:1084,首先我们随机选一个,比如192.168.1.1:1080,作为初始代理。在redis中新建元素proxy和proxylist

127.0.0.1:6379> set proxy 192.168.1.1:1080
127.0.0.1:6379> RPUSH proxylist 192.168.1.1:1080 192.168.1.1:1081 192.168.1.1:1082 192.168.1.1:1083 192.168.1.1:1084

Python代码

# 版本1

def getproxy():
    proxy = rconn.get(proxy).decode()
    return {http:proxy, https:proxy}

def changeproxy():
    proxies = rconn.lrange(proxylist, 0, 5)
    current_proxy = getproxy().get(http)
    rconn.set(current_proxy, cooling, ex=100000) #建立一个key为当前代理IP的键,表示此IP已经进入冷却,并用ex设定冷却时间
    for proxy in proxies:
        if not rconn.get(proxy): #排除在冷却时间内的IP
            res=rconn.set(proxy,proxy)
            return res
    
    return None

但此法不久后在实践中遇到了一个问题:某次一个干净的代理被设置成冷却中了;后发现,因为任务是并发的,当1号请求返回异常并更改了代理后,使用了前代理的2号请求才返回出异常,于是又触发了一次更换请求,相当于短时间内连续更换了两次代理,造成了资源的浪费。

# 版本2

def getproxy():
    proxy = rconn.get(proxy).decode()
    return {http:proxy, https:proxy}

def changeproxy():
    proxies = rconn.lrange(proxylist, 0, 5)
    current_proxy = getproxy().get(http)
    ‘‘‘
    检测是否有保护标志
    ‘‘‘
    if rconn.get(protect_time): 
        return True
    rconn.set(current_proxy, cooling, ex=100000) #建立一个key为当前代理IP的键,表示此IP已经进入冷却,并用ex设定冷却时间
    for proxy in proxies:
        if not rconn.get(proxy): #排除在冷却时间内的IP
            res=rconn.set(proxy,proxy)
            ‘‘‘
            在成功更换代理后,放置一个有效期为30s的保护标志,该标志存在期间禁止代理更换。这个有效期理论上最短要设置为一次请求报文的往返时间
            ‘‘‘
            rconn.set(protect_time, yes, ex=30)
            return res
    
    return None

这种方法能避免有效代理被跳过,但如果代理池里不小心混入了脏代理,且被更换到了,那在这30s的保护时间内,脏代理也会被“保护”,即使时间不长,我们也要想办法避免。笔者想了很久,有没有在客户端不传任何参数的情况下解决这一点,包括错误计数器,进程标识,保护时间削减等等,但最后发现,唯一可靠的还是客户端传当前代理给代理服务器。

# 版本3(最终)

def getproxy():
    proxy = rconn.get(proxy).decode()
    return {http:proxy, https:proxy}

def changeproxy(local_proxy):
    # local_proxy是客户端发起请求并被返回异常时所用的代理IP
    proxies = rconn.lrange(proxylist, 0, 5)
    current_proxy = getproxy().get(http)
    if local_proxy!=current_proxy:
        return True
    else:
        rconn.set(current_proxy, cooling, ex=100000) #建立一个key为当前代理IP的键,表示此IP已经进入冷却,并用ex设定冷却时间
        for proxy in proxies:
            if not rconn.get(proxy): #排除在冷却时间内的IP
                res=rconn.set(proxy,proxy)
                return res
        
        return None

可以看到,最终版本不仅更加简洁,也解决了上述提到的问题。

------

如果要细究,最终版也是有漏洞的,因为整个更换代理的操作并不具备原子性,依旧可能造成代理被跳过(虽然概率极其微小)。而redis又没有真正的事务,所以最好为changeproxy()再加一把锁,或者对最终版做一处微小的修改:

    else:
        flag = rconn.set(current_proxy, cooling, ex=100000, nx=True) #nx参数令存在该键时就不建立,并返回false
        if not flag:
            return True
        for proxy in proxies:
            if not rconn.get(proxy): 
                res=rconn.set(proxy,proxy)
                return res

基于redis实现高并发下的IP代理池可靠更换

原文:https://www.cnblogs.com/qjfoidnh/p/12152945.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!