上回说到,突破反爬虫限制的方法之一就是多用几个代理IP,但前提是咱们得拥有有效的代理IP,下面咱们来介绍抓取代理IP并多线程快速验证其有效性的过程。python
1、抓取代理IP安全
提供免费代理IP的网站还挺多的,我在‘西刺代理’上一阵猛抓后本身的IP就被其屏蔽了。只好换‘IP巴士’并乖乖的减缓抓取速度了。贴上抓取代码网络
import urllib.request import urllib import re import time import random #抓取代理IP ip_totle=[] #全部页面的内容列表 for page in range(2,6): url='http://ip84.com/dlgn/'+str(page) headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)"} request=urllib.request.Request(url=url,headers=headers) response=urllib.request.urlopen(request) content=response.read().decode('utf-8') print('get page',page) pattern=re.compile('<td>(\d.*?)</td>') #截取<td>与</td>之间第一个数为数字的内容 ip_page=re.findall(pattern,str(content)) ip_totle.extend(ip_page) time.sleep(random.choice(range(1,3))) #打印抓取内容 print('代理IP地址 ','\t','端口','\t','速度','\t','验证时间') for i in range(0,len(ip_totle),4): print(ip_totle[i],' ','\t',ip_totle[i+1],'\t',ip_totle[i+2],'\t',ip_totle[i+3])
复制以上代码便可抓取IP巴士上的大陆高匿代理IP了,其余地区或类型的可自行改URL,多是网站内容在实时更新的缘由,若从第一页开始抓取不怎么稳定,因此我从第二页开始抓取,打印部分结果以下多线程
2、验证代理IP的有效性app
因为所处的网络可能连不上此代理或该代理连不上目标网址等缘由,咱们抓取的代理有多是无效的,咱们有必要验证所抓取代理IP的有效性。在urllib.request包中的ProxyHandler类能够设置代理访问网页,代码以下dom
import urllib.request url = "http://quote.stockstar.com/stock" #打算抓取内容的网页 proxy_ip={'http': '27.17.32.142:80'} #想验证的代理IP proxy_support = urllib.request.ProxyHandler(proxy_ip) opener = urllib.request.build_opener(proxy_support) opener.addheaders=[("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64)")] urllib.request.install_opener(opener) print(urllib.request.urlopen(url).read())
若IP是有效的,则可打印出网页源码,不然会出现错误。因此咱们能够经过以上代码对所抓取的代理IP逐个进行验证。socket
3、多线程快速验证ide
按顺序逐个验证代理IP的有效性速度比较慢,python中有多线程模块,多线程相似于同时执行多个不一样程序,使用多线程能够把占据长时间的程序中的任务放到后台去处理,在一些须要等待的任务实现上线程就比较有用了。函数
Python经过两个标准库thread和threading提供对线程的支持。thread模块提供了低级别的、原始的线程以及一个简单的锁。threading 是咱们经常使用的用于python 多线程的模块,其功能更加丰富。threading 模块中提供了一个Thread 类,这个类能够实例化一个对象,每一个对象表明一个线程。下面咱们介绍下本文使用threading模块中的类。网站
咱们先介绍线程锁,若是有多个线程同时操做一个对象,若是没有很好地保护该对象,会形成程序结果的不可预期,好比咱们的一个print语句只打印出一半的字符,这个线程就被暂停,执行另外一个去了,因此咱们看到的结果会很乱,这种现象叫作“线程不安全”。Threading模块为咱们提供了Threading.Lock类,咱们建立一个该类对象,在线程函数执行前,“抢占”该锁,执行完成后,“释放”该锁,则咱们确保了每次只有一个线程占有该锁。这时候对一个公共的对象进行操做,则不会发生线程不安全的现象了。咱们先创建了一个threading.Lock类对象lock,使用lock.acquire()得到了这个锁。此时,其余的线程就没法再得到该锁了,他们就会阻塞在“if lock.acquire()”这里,直到锁被另外一个线程释放:lock.release()。
而后咱们来介绍threading模块中的Thread 类。class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *,daemon=None), 这个构造函数一般会用一些关键字参数,下面咱们了解下这些关键字:
group :这个变量做为保留变量,是为了之后扩展用的,暂时能够不用考虑。
target: 是经过run()方法调用的可调用对象。默认为无,这意味着什么都不作。
name:线程的名字。默认状况下,一个惟一的名称是”thread-n”的形式,其中n是一个小的十进制数。
args:元组参数,为target所调用的。
kwargs:关键字参数的字典,为target所调用的。
daemon: 设置daemon是否daemon 若是没有显示设置,daemon的属性时从当前线程继承。
若是子类重写此构造函数,它必须确保在作别的事情以前调用基类的构造函数thread.__init__()。本文所用到Thread 类的方法有:
start(self)
开始线程的运行,每一个线程对象只有调用最多一次。它将调用被调用对象的run()方法,并控制各个对象独立运行。也就是说被调用的对象必要要有run() 方法,在使用Thread 类来实例化对象的时候,由于Thread 中已经有了run() 方法了,因此能够不用理。可是,在基础Thread 建立子类的时候,通常咱们要重写子类的run()方法。
join(self, timeout=None)
阻塞主线程,直到调用该方法的子线程运行完毕或者超时。timeout 表示超时时间,能够是一个数,例如整数,小数,分数,表示超时的时间,单位是秒。返回值为 None,能够在 join 超时以后调用 isAlive 确认线程是否结束。若是线程还活动,说明 join 触发了超时,此时你能够继续调用 join 或者作其余处理。当 timeout 没有给或者为 None 的时候,将阻塞直到调用此方法的子线程结束。一个线程能够调用屡次 join 方法。
多线程验证的主要程序以下
#多线程验证 threads=[] for i in range(len(proxys)): thread=threading.Thread(target=test,args=[i]) threads.append(thread) thread.start() #阻塞主进程,等待全部子线程结束 for thread in threads: thread.join()
一开始我我令元组参数args=(i),结果报‘test() argument after * must be an iterable, not int’的错误,误打误撞把小括号改成中括号后就有用了,暂时不解中其原因,望知情者能告知。程序部分运行结果以下
亲测多线程验证比单线程验证快了好几倍,因此之后在爬取网页量比较大时就能够先用此程序抓一些有效的代理IP,这样就能够解决IP被屏蔽的问题啦。python3抓取代理IP并用多线程快速验证的完整代码以下
import urllib.request import urllib import re import time import random import socket import threading #抓取代理IP ip_totle=[] for page in range(2,6): url='http://ip84.com/dlgn/'+str(page) #url='http://www.xicidaili.com/nn/'+str(page) #西刺代理 headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64)"} request=urllib.request.Request(url=url,headers=headers) response=urllib.request.urlopen(request) content=response.read().decode('utf-8') print('get page',page) pattern=re.compile('<td>(\d.*?)</td>') #截取<td>与</td>之间第一个数为数字的内容 ip_page=re.findall(pattern,str(content)) ip_totle.extend(ip_page) time.sleep(random.choice(range(1,3))) #打印抓取内容 print('代理IP地址 ','\t','端口','\t','速度','\t','验证时间') for i in range(0,len(ip_totle),4): print(ip_totle[i],' ','\t',ip_totle[i+1],'\t',ip_totle[i+2],'\t',ip_totle[i+3]) #整理代理IP格式 proxys = [] for i in range(0,len(ip_totle),4): proxy_host = ip_totle[i]+':'+ip_totle[i+1] proxy_temp = {"http":proxy_host} proxys.append(proxy_temp) proxy_ip=open('proxy_ip.txt','w') #新建一个储存有效IP的文档 lock=threading.Lock() #创建一个锁 #验证代理IP有效性的方法 def test(i): socket.setdefaulttimeout(5) #设置全局超时时间 url = "http://quote.stockstar.com/stock" #打算爬取的网址 try: proxy_support = urllib.request.ProxyHandler(proxys[i]) opener = urllib.request.build_opener(proxy_support) opener.addheaders=[("User-Agent","Mozilla/5.0 (Windows NT 10.0; WOW64)")] urllib.request.install_opener(opener) res = urllib.request.urlopen(url).read() lock.acquire() #得到锁 print(proxys[i],'is OK') proxy_ip.write('%s\n' %str(proxys[i])) #写入该代理IP lock.release() #释放锁 except Exception as e: lock.acquire() print(proxys[i],e) lock.release() #单线程验证 '''for i in range(len(proxys)): test(i)''' #多线程验证 threads=[] for i in range(len(proxys)): thread=threading.Thread(target=test,args=[i]) threads.append(thread) thread.start() #阻塞主进程,等待全部子线程结束 for thread in threads: thread.join() proxy_ip.close() #关闭文件