上回說到,突破反爬蟲限制的方法之一就是多用幾個代理IP,但前提是我們得擁有有效的代理IP,下面我們來介紹抓取代理IP並多線程快速驗證其有效性的過程。
一、抓取代理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,可能是網站內容在實時更新的原因,若從第一頁開始抓取不怎么穩定,所以我從第二頁開始抓取,打印部分結果如下
二、驗證代理IP的有效性
由於所處的網絡可能連不上此代理或該代理連不上目標網址等原因,我們抓取的代理有可能是無效的,我們有必要驗證所抓取代理IP的有效性。在urllib.request包中的ProxyHandler類可以設置代理訪問網頁,代碼如下
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逐個進行驗證。
三、多線程快速驗證
按順序逐個驗證代理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() #關閉文件