Python中多程序在爬蟲中的使用
本來這周準備寫一個整合ip池,多程序的高效爬取所有職位詳細資訊的爬蟲的,結果在多程序這一塊兒折騰了好久,簡直把我氣死,雖然內容其實不多,但把自己學習的過程寫下來,希望能幫到有同樣困惑的朋友。
我參照的是廖雪峰老師寫的一個Python教程,有興趣大家可以百度一下,我覺得還是挺適合像我這樣的新手的。
OK,話不多說,在上一篇介紹IP池的使用的文章中,最後我也提到了,通過開啟網站的方式驗證IP可用性的方法執行的很慢,所以必須想辦法加快這個驗證的速度,最容易想到的就是採用多工的方式。
實現多工最常見的兩種方式就是多程序和多執行緒了。多程序,顧名思義,多個程序併發執行,可以有效提高程式的執行效率,優點是非常穩定,即使有子程序崩潰了,主程序和其他程序依然可以繼續執行,但缺點是在windows下建立程序的開銷比較大,而且如果程序太多,往往會影響整個系統的排程。而多執行緒是指一個程序內多個執行緒同時執行,進而提高程式執行效率,其優點可能是比多程序稍微快一點,但缺點也很明顯,多執行緒中一個執行緒出現了問題就會導致整個程序崩潰,因此穩定性不是很高。
然後由於Python直譯器在執行程式碼是有一個GIL鎖(Global Interpreter Lock),它的作用是任何Python程序執行前都必須先獲得GIL鎖,然後沒執行100條位元組碼釋放GIL鎖,讓別的程序有機會執行,因此多執行緒在Python中只能交替執行,所以針對改善我們的爬蟲程式,我們選擇使用多程序的方法。
在windows系統下,Python可以通過multiprocessing庫實現多程序,先從最簡單地開啟一個子程序開始
在上面的程式碼中,我們定義了一個函式用於輸出當前程序的id,然後利用multiprocessing中的Process函式新建一個程序,start()函式用於啟動程序,join()函式用於子程序執行完後再向下執行,然而結果並不如預料那般from multiprocessing import Process import os def run_proc(name): print 'Run child process %s (%s) ' %(name,os.getpid()) if __name__=='__main__': print 'Parent process %s ' %os.getpid() p=Process(target=run_proc,args=('test',)) print 'Process will start' p.start() p.join() print 'Process end'
子程序似乎沒有執行,在這個地方我花費了很長時間,一直在想哪兒有問題,最後證明程式碼確實沒問題,問題在於多程序這個操作在IDE裡似乎無法執行,所以我又換到命令列裡執行以下。這裡提示一下,win7或者問win10在資料夾裡,按住shift郵件就會出現開啟命令列,python yourfile.py 即可執行,上述程式結果如下:
我們可以看到成功地打開了一個新程序,好的,第一步完成了,那下一步是如何同時開啟多個程序呢?別急,multiprocessing模組中還提供了Pool物件,可以同時開啟多個程序,一個簡單的測試程式碼如下
from multiprocessing import Pool
import time
import os
def task(name):
print 'Run task %s (%s)...'%(name,os.getpid())
print time.time()
time.sleep(3)
if __name__=='__main__':
print 'Parent process %s'%os.getpid()
p=Pool()
for i in range(9):
p.apply_async(task,args=(i,))
print 'Waiting for all subprocess done ...'
p.close()
p.join()
print 'All subprocess done'
在這裡我們定義了一個task函式,用於輸出當前程序的id和當前程序開始的時間,然後建立了一個Pool物件,它可以引入processes引數來指定同時啟動程序的數量,預設的是你機器的核心數。join()函式用於等待所有子程序執行完畢,而join()函式之前必須引用close()函式來禁止新的程序的建立,執行結果如下我們可以看到前面八個程序幾乎是同時開始的,而最後一個程序是在有一個程序結束之後(等待3s)開始的,因為我機器的核心數是8,所以這很合乎常理。到這裡,我們對Python中多程序的使用已經有一定的認識了,下一步就是應用到我們的ip驗證當中了,其實方法和我們的測試程式一模一樣,我們把之前獲取可用IP地址的函式拆分成一個獲取所有代理的函式和一個驗證函式,方便我們進行多程序的操作,通過單程序執行和多程序執行,我們比較多程序方法引入帶來效率的提高,具體實現程式碼如下
# -*- coding: utf-8 -*-
"""
__author__='CD'
"""
import requests
from bs4 import BeautifulSoup
import csv
import time
from multiprocessing import Pool
def getIp(numpage):
csvfile = file('ips.csv', 'wb')
writer = csv.writer(csvfile)
url='http://www.xicidaili.com/nn/'
user_agent='IP'
headers={'User-agent':user_agent}
for i in xrange(1,numpage+1):
real_url=url+str(i)
response=requests.get(real_url,headers=headers)
content=response.text
bs=BeautifulSoup(content)
trs=bs.find_all('tr')
for items in trs:
tds=items.find_all('td')
temp=[]
try:
temp.append(tds[1].text)
temp.append(tds[2].text)
writer.writerow(temp)
except:
pass
getIp(1)
def getProxy():
reader=csv.reader(open('ips.csv'))
Proxy=[]
for row in reader:
proxy={"http":row[0]+':'+row[1]}
Proxy.append(proxy)
return Proxy
def test(proxy):
try:
response=requests.get('http://www.baidu.com',proxies=proxy,timeout=2)
if response:
return proxy
except:
pass
if __name__=='__main__':
proxy=getProxy()
IPPool1=[]
time1=time.time()
for item in proxy:
IPPool1.append(test(item))
time2=time.time()
print 'singleprocess needs '+str(time2-time1)+' s'
pool=Pool()
IPPool2=[]
temp=[]
time3=time.time()
for item in proxy:
temp.append(pool.apply_async(test,args=(item,)))
pool.close()
pool.join()
for item in temp:
IPPool2.append(item.get())
time4=time.time()
print 'multiprocess needs '+str(time4-time3)+' s'
在這個程式裡,我們只爬取了第一頁的ip並進行了驗證結果如下
可以看到,採用多程序之後,驗證的時間縮短了8倍,這是因為我採用了預設的設定,同一時間的程序數為我機器的核心數是8,如果將程序數進一步提高可以獲得更快的速度,當然速度的減小並不是線性的,肯定後面效果越來越差,因為程序的切換需要時間,多程序也增加了系統排程的難度等等。
好了,利用Python的多程序來減小代理ip驗證所需時間的技能get到了,當然完全可以舉一反三,因為爬蟲本來就是個IO密集型的工作,所以我們在爬取網頁時同樣也可以使用多程序來減小等待時間,哈哈哈,真開心~