在做爬取网站信息的时候,通常网站都会这反爬虫设计,如果一个ip访问太过频繁,会被列入黑名单或者出现验证码需要输入。星期天在爬取58同城信息的时候也遇到这个问题,百度了一下,发现以下两个方法最为省心省力。

1.设置时间间隔

既然一个ip访问太过频繁会被列入黑名单,那就不频繁的访问,导入time包,每一次加载完成后就强制休眠2s。例如

import time
wb_data = requests.get("http://cn.58.com/bijibendiannao/33724988041130x.shtml")
time.sleep(2)

2.设置代理IP

方法1虽然很有效但是会浪费很多不必要的时间,既然同一个ip访问太频繁会被加入黑名单,那不同的ip访问也会解决这个问题。

代理ip获取地址

代理ip获取网站:http://www.xicidaili.com/nn/1
获取代理ip的时候一定要注意协议是http还是https,血泪教训,不然很可能发生狗血报错,找半天找不出原因,就是因为http与https的协议搞错。

def get_ip_list(url, headers):
    web_data = requests.get(url, headers=headers)
    soup = BeautifulSoup(web_data.text, 'lxml')
    ips = soup.find_all('tr')
    ip_list = []
    for i in range(1, len(ips)):
        ip_info = ips[i]
        tds = ip_info.find_all('td')
        ip_list.append(tds[5].text+'://'+tds[1].text + ':' + tds[2].text)

    return ip_list

验证代理ip

假如我们从上述代理ip的网站获取了如下ip:

IP_LIST ='''
    HTTP://61.135.217.7:80
    HTTPS://114.215.83.184:3128
    HTTP://122.114.31.177:808
    HTTP://123.56.89.238:60443
    HTTPS://49.79.193.138:61234
    HTTPS://183.159.93.28:18118
    HTTPS://183.159.80.14:18118
    HTTP://114.99.28.110:18118
    HTTPS://183.159.88.201:18118
    HTTPS://183.159.80.246:18118
    HTTP://49.79.193.93:61234
    HTTP://111.155.116.237:8123
    HTTPS://117.68.194.66:18118
    HTTPS://60.177.228.169:18118
    HTTPS://183.159.87.2:18118
    HTTPS://183.159.88.75:18118
    HTTP://183.159.83.235:18118
    HTTP://202.104.184.5:808
    HTTP://14.120.181.241:61234
    HTTP://14.118.254.216:6666
    HTTP://124.235.121.216:8118
'''

(再说一遍,这个ip前的http还是https要与代理ip网站上的类型相同)
获取的ip可能不能用,所以我们需要验证可以用的ip

def cip(ip):
    try:
        if ip.keys()[0]=="http":
            requests.get('http://ip.chinaz.com/getip.aspx',proxies={'http':ip["http"]},timeout=3)
        else:
            requests.get('http://ip.chinaz.com/getip.aspx', proxies={'https': ip["https"]}, timeout=3)
    except:
        # print("failure")
        return False
    else:
        print(ip)
        return  True

而这个验证ip我一开始以为验证过的就可以一直用了,没想到刚验证过的,一秒之后就不能用了,所以我是在使用ip的时候才验证,而不是验证好了存一边使用的时候在抓取,例如。

def get_random_ip(ip_list):#从上文的IP_LIST随机获取ip的函数
    proxy_list = []
    for ip in ip_list.split():
        proxy_list.append( ip)
    proxy_ip = random.choice(proxy_list)
    if proxy_ip[4] == "S":
        proxies = {'https': proxy_ip}
    else:
        proxies = {'http': proxy_ip}
    return proxies


proxy = get_random_ip(IP_LIST)#先随机获取一个ip
while (not cip(proxy)) :#验证ip,如果ip有效则不在继续获取ip
  proxy = get_random_ip(IP_LIST)#如果无效则继续获得ip
wb_data =requests.get(url,proxies=proxy)#使用代理ip获取数据

3.补充

如果不出意外,上面两点就可以满足爬取需求了,但是还是会发生很多奇葩报错,所以建议把,wb_data = requests.get()换成如下代码,否则很容易因为http连接太多报出错误。

requests.adapters.DEFAULT_RETRIES = 5 #重试连接次数
s = requests.session()
s.keep_alive = False
wb_data =s.get(url,headers=header,proxies=proxy)

如果使用多进程去爬取数据,建议加上try….except去包裹request请求,虽然这样可能得不到出错原因,但是可以保证程序平稳运行下去,否则会出现wb_data的UnboundLocalError: local variable ‘wb_data’ referenced before assignment错误:

    try:
        s = requests.session()
        s.keep_alive = False
        wb_data = s.get(url, headers=header, proxies=proxy)
        time.sleep(1)
        soup = BeautifulSoup(wb_data.text,"lxml")
        no_longer_exist =  soup.find("p", attrs={'class':'et'})
        if no_longer_exist:
            print "nolonger"
            none_list_info.insert_one({"url":url})
            pass
        else:
            title = soup.select("head > title")[0].text  if soup.find_all("title","") and soup.select("head > title").__len__()>0 else None
            price = soup.select("span.price")[0].text if soup.find_all("span","price") and soup.select("span.price ").__len__()>0 else None
            date =soup.select("li.time")[0].text if soup.find_all("li","time") and soup.select("li.time ").__len__()>0 else None
            area = list(soup.select("span.c_25d ")[0].stripped_strings) if soup.find_all("span","c_25d") and soup.select("span.c_25d ").__len__()>0 else None
            list_info.insert_one({"title":title,"price":price,"date":date,"area":area})
    except:
        time.sleep(2)
        print "error "

多进程运行可以使用Pool

from multiprocessing import Pool
pool = Pool()#python根据你电脑的cpu数去决定开启几个进程
pool.map(get_list_info,urllist.split() )#get_list_info(url)是通过给定url获取相应界面信息的函数,urllist是目标url的字符串。

还有requests请求中的header也可以尝试更换,如果不经常更换我也忘了会不会报错,建议还是随机选取一个:

Headers='''
    Mozilla/5.0 (Windows; U; Windows NT 5.2) AppleWebKit/525.13 (KHTML, like Gecko) Version/3.1 Safari/525.13:
    Mozilla/5.0 (iPhone; U; CPU like Mac OS X) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A93 Safari/419.3:
    Mozilla/5.0 (Windows; U; Windows NT 5.2) Gecko/2008070208 Firefox/3.0.1: 
    Mozilla/5.0 (Windows; U; Windows NT 5.1) Gecko/20070309 Firefox/2.0.0.3: 
    Mozilla/5.0 (Windows; U; Windows NT 5.1) Gecko/20070803 Firefox/1.5.0.12 
'''
def Random_header(Headers):
    header_list = []
    for header in Headers.split(':'):
        header_list.append(header.strip())
    proxy_header = random.choice(header_list)

    return proxy_header

header['User-Agent'] = Random_header(Headers)
s = requests.session()
s.keep_alive = False
wb_data = s.get(url, headers=header, proxies=proxy)

最后附上git地址,大家有兴趣的可以看一下:
https://github.com/lixiaozhe666/58project

文章来源于互联网:python 爬虫之道高一尺,魔高一尺一

发表评论