• 隐藏侧边栏
  • 展开分类目录
  • 关注微信公众号
  • 我的GitHub
  • QQ:1753970025
Chen Jiehua

爬虫之IP免杀和多并发 

最近在爬一个网站的数据,代码写得差不多了,结果却发现这个网站一言不合就封IP……然而道高一尺魔高一丈,封IP这种小技巧这么奈何得了我!

代理IP

一般网站为了防止爬虫,都会对单个IP的请求频率进行限制,轻则短时间内屏蔽,重则永久黑名单……而我们的爬虫,一般公网 IP 都是固定的(当然,拨号上网可以获取到更多动态IP,不过也会局限与运营商的分配)。

因此网络上也就有很多公开的代理IP,不过大部分良莠不齐,而且也会受到实际网络比较大的影响(比如某个代理IP在电信网络可用,在移动网络可能就不行了)。这就需要一个抓取并筛选代理IP的过程,废话不多说,直接干!

IPProxy

参考:Github

通过这个工具,我们可以快速获取到大量可用的代理IP:

ip,port,anonymous,info,speed
111.1.23.189,80,3,中国-浙江-杭州,0.10514688491821289
111.1.23.141,80,3,中国-浙江-杭州,0.11330294609069824
111.1.23.169,80,3,中国-浙江-杭州,0.15795111656188965
120.52.72.53,80,3,中国-河北-廊坊,0.2509500980377197
120.52.72.59,80,3,中国-河北-廊坊,0.2850189208984375
183.61.236.55,3128,3,中国-广东-东莞,0.3199479579925537
183.61.236.54,3128,3,中国-广东-东莞,0.326632022857666
211.153.17.151,80,3,中国-北京-北京,0.3375699520111084
120.52.72.56,80,3,中国-河北-廊坊,0.3387718200683594
……

然后在我们的爬虫的网络请求中加入代理IP:

def load_proxyip(fpath):
    """ proxyip list with weight
    """
    total_weight, proxyip = 0, []
    with open(fpath) as f:
        csvreader = csv.DictReader(f, restval=0, delimiter=",",
                                   quotechar="\"", quoting=csv.QUOTE_MINIMAL)
        for row in csvreader:
            ip = row["ip"] + ":" + row["port"]
            weight = 1/float(row["speed"])
            total_weight += weight
            proxyip.append((ip, weight))

    return total_weight, proxyip


def get_proxyip(total_weight, proxyip):
    r = random.uniform(0, total_weight)
    upto = 0
    for ip, weight in proxyip:
        if upto + weight >= r:
            return ip
        upto += weight

    print "Error: total_weight=%s, random_weight=%s" % (total_weight, r)
    return "localhost:8888"

total_weight, proxyip = load_proxyip("proxyip.csv")
requests.get(url, proxies={"http": "http://"+get_proxyip(total_weight, proxyip)}, timeout=10}

完整示例代码参考:example.py

轻轻松松,封IP这种雕虫小技奈我何了~~~~

多并发

使用代理IP之后,网络请求的速度将会大大降低,原本10分钟能够完成的任务现在可能需要超过1个小时,这种龟速如何能忍受得了,多并发 势在必行!

偷偷懒,我们直接 gevent 来实现:

from gevent import monkey
from gevent.pool import Pool

monkey.patch_all()
# 设置线程池的大小
pool = Pool(128)

一个简单的 worker

def worker(url, proxyip):
    if proxyip:
        r = requests.get(url, proxies={"http": "http://" + proxyip}, timeout=10)
    else:
        r = requests.get(url, timeout=10)

    print r.text

加入到 gevent pool 中:

def main():
    for url in urls:
        proxyip = get_proxyip(...)
        pool.spawn(worker, url, proxyip)

    pool.join()


if __name__ == "__main__":
    main()

实际应用时,在 worker 内需要对 requests 报错 进行处理。

同时,对于通过代理IP抓取的数据,如果有异常,应该将 url 重新加入到队列(这里没有实现)中,更换代理IP重新发起请求。

码字很辛苦,转载请注明来自ChenJiehua《爬虫之IP免杀和多并发》

评论