上回說到,突破反爬蟲限制的方法之一就是多用幾個代理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() #關閉文件