運維學python之爬蟲中級篇(一)進程

最近流感肆虐京城,各大醫院爆滿,不巧我也被病毒擊中, 起初咳嗽小感冒喝了點感冒沖劑以爲可以扛過去,結果發展爲嗓子乾啞,最後又開始發燒,折騰好幾天,沒辦法去醫院走了一圈花了大洋,也算是好了些,從今天開始,就要介紹中級篇了,主要內容就是:進程、線程、協程、分佈式、網絡編程、數據存儲(無數據庫版)。馬上開始,介紹多進程。

1 進程介紹

python開發中,進程與線程是非常重要的,打造分佈式爬蟲,提高工作效率都離不開進程與線程。
進程
進程就是一個程序在一個數據集上的一次動態執行過程。 進程一般由程序、數據集、進程控制塊三部分組成。我們編寫的程序用來描述進程要完成哪些功能以及如何完成;數據集則是程序在執行過程中所需要使用的資源;進程控制塊用來記錄進程的外部特徵,描述進程的執行變化過程,系統可以利用它來控制和管理進程,它是系統感知進程存在的唯一標誌。

2 多進程

python實現多進程的方式有2種,一種爲os模塊中的fork方法,另一種爲multiprocessing模塊,兩種方式 的區別是fork方法只支持Unix/Linux系統,不支持Windows,而後一種方法是跨平臺的。

2.1 fork方式實現多進程

fork方法比較特殊,普通方法調用一次,返回一次,但fork方法調用一次,返回兩次,因爲操作系統將當前父進程複製出一個子進程,這兩個進程幾乎完全相同,於是fork方法分別在父進程和子進程中返回,子進程中永遠返回0,父進程中返回的是子進程的id,os模塊的getpid用於獲取子進程id,getppid用於獲取父進程id,見下面示例:

# -*- coding:utf-8 -*-

import os

if __name__ == '__main__':
    print 'current Process %s start ...' % (os.getpid())
    pid = os.fork()
    print pid
    if pid < 0:
        print 'error in fork'
    elif pid == 0:
        print 'I am child process %s and my parent process is %s' % (os.getpid(), os.getppid())
    else:
        print 'I %s created a child process %s.' %(os.getpid(), pid)

返回結果如下:

# 父進程
current Process 11989 start ...
# 子進程pid
11990
I 11989 created a child process 11990.
# 子進程pid此時返回0
0
I am child process 11990 and my parent process is 11989

2.2 multiprocessing方式多進程

multiprocessing的一個類Process來描述進程對象,創建子進程時只需要傳入一個函數和函數參數就可以創建一個Process實例,用start()方法啓動進程,join()方法實現進程間同步,見下例:

# -*- coding: utf-8 -*-
import os
from multiprocessing import Process

def run_proc(name):
    """
    定義函數
    """
    print('child process %s (%s) running...' % (name, os.getpid()))

if __name__ == '__main__':
    # 打印當前進程pid
    print('Parent process %s.' % os.getpid())
    # 循環方式生成process實例
    for i in range(5):
        p = Process(target=run_proc, args=str(i))
        print('Process will start.')
        p.start()
    # 這裏的join要注意,默認父進程執行完自己的任務以後,就退出了,
    # 此時子進程會繼續執行自己的任務,直到自己的任務結束,而加上
    # join後join所完成的工作就是進程同步,即父進程任務結束之後,
    # 進入阻塞狀態,一直等待其他的子進程執行結束之後,主進程再終止
    p.join()
    print('Process end.')

結果如下:

Parent process 3488.
Process will start.
Process will start.
Process will start.
Process will start.
Process will start.
child process 0 (3148) running...
child process 3 (12580) running...
child process 2 (2668) running...
child process 1 (13664) running...
child process 4 (13916) running...
Process end.

以上兩種方式生成少量進程可以實現,但是如果要有成百上千的進程生成就不那麼好實現了,這是進程池Pool就發揮作用了。

2.3 Pool

multiprocessing提供了一個Pool類來代表進程池對象,Pool可以提供指定數量的進程供用戶調用,默認的大小是cpu核數,如果有新請求提交到Pool後,如果進程池沒有滿,就會創建一個新進程來執行該請求,如果進程池已經滿了,那麼請求就會等待,直到進程池中有進程結束,纔會創建新進程,見示例:

# -*- coding: utf-8 -*-

import os
import time
import random
from multiprocessing import Pool

def run_task(name):
    print('Tast %s (%s) is running' % (name, os.getpid()))
        # 等待時間
    time.sleep(random.random() * 3)
    print('Task %s end.' % name)

if __name__ == '__main__':
    print('current process %s.' % os.getpid())
    # 創建容量爲3的進程池
    p = Pool(processes=3)
    # 依次執行5個進程
    for i in range(5):
            # apply_async非阻塞方式
        p.apply_async(run_task, args=str(i))
    print('Writing for all subprocesses done...')
    # 調用close後就不能再添加新進程了
    p.close()
    # 調用join方法會等待所有子進程結束,要在close之後執行join
    p.join()
    print('All subprocessed done.')

程序執行結果如下:

current process 18044.
Writing for all subprocesses done...
Tast 0 (15000) is running
Tast 1 (16796) is running
Tast 2 (12500) is running
Task 0 end.
Tast 3 (15000) is running
Task 3 end.
Tast 4 (15000) is running
Task 2 end.
Task 1 end.
Task 4 end.
All subprocessed done.

2.4 進程間通信

如果創建的大量進程,那麼進程間通信是必不可少的,python提供了多種進程間通信方式,我們主要講Queue和Pipe兩種方式,Pipe常用來在兩個進程間通信,Queue用來在多個進程間通信。
Queue
有兩個方法put和get可以進行Queue操作:
put方法用來插入數據到隊列中,get方法用來讀取並刪除隊列內容,下面通過列子來說明:

# -*- coding: utf-8 -*-

import os
import time
import random
from multiprocessing import Process, Queue

def proc_write(q, urls):
    """
    寫數據進程執行的代碼
    """
    print('Process %s is writing...' % os.getpid())
    for url in urls:
        q.put(url)
        print('Put %s to queue...' % url)
        time.sleep(random.random())

def proc_read(q):
    """
    讀數據進程執行的代碼
    """
    print('Process %s is reading...' % os.getpid())
    while True:
        url = q.get(True)
        print('Get %s from queue.' % url)

if __name__ == '__main__':
    # 父進程創建queue,並傳給各個子進程
    q = Queue()
    proc_write1 = Process(target=proc_write, args=(q, ['url1', 'url2', 'url3']))
    proc_write2 = Process(target=proc_write, args=(q, ['url4', 'url5', 'url6']))
    proc_read1 = Process(target=proc_read, args=(q,))
    # 啓動子進程寫入
    proc_write1.start()
    proc_write2.start()
    # 啓動子進程讀取
    proc_read1.start()
    # 等待proc_write結束
    proc_write1.join()
    proc_write2.join()
    # proc_read1進程是死循環,無法等待結束,強制結束
    proc_read1.terminate()

執行結果如下:

Process 13088 is writing...
Put url1 to queue...
Process 7700 is writing...
Put url4 to queue...
Process 11688 is reading...
Get url1 from queue.
Get url4 from queue.
Put url5 to queue...
Get url5 from queue.
Put url2 to queue...
Get url2 from queue.
Put url6 to queue...
Get url6 from queue.
Put url3 to queue...
Get url3 from queue.

可以看到上面示例,兩個進程往裏寫數據,一個進程往外讀數據。
最後介紹一下Pipe通信,Pipe常用於兩個進程間通信,兩個進程分別位於管道兩端,返回的conn1, conn2代表管道的兩端,注意duplex參數,如果爲True表示全雙工模式,兩端均可以send和recv,如果爲False表示conn1只負責接收,conn2只負責發,示例如下:

# -*- coding: utf-8 -*-

import os
import time
import random
import multiprocessing

def proc_send(pipe, urls):
    """
    發送端函數
    """
    for url in urls:
        print('Process %s send: %s ' % (os.getpid(), url))
        pipe.send(url)
        time.sleep(random.random())

def proc_recv(pipe):
    """
    接收端函數
    """
    while True:
        print('Process %s rev: %s ' % (os.getpid(), pipe.recv()))
        time.sleep(random.random())

if __name__ == '__main__':
    # 創建實例
    pipe = multiprocessing.Pipe()
    # 建立發送進程
    p1 = multiprocessing.Process(target=proc_send, args=(pipe[0], ['url_' + str(i) for i in range(10)]))
    # 建立接收進程
    p2 = multiprocessing.Process(target=proc_recv, args=(pipe[1],))
    # 啓動
    p1.start()
    p2.start()
    # 等待執行完成
    p1.join()
    p2.join()

同樣是創建兩個子進程,一個發數據,一個收數據,結果如下:

Process 16904 send: url_0 
Process 2016 rev: url_0 
Process 16904 send: url_1 
Process 2016 rev: url_1 
Process 16904 send: url_2 
Process 2016 rev: url_2 
Process 16904 send: url_3 
Process 2016 rev: url_3 
Process 16904 send: url_4 
Process 16904 send: url_5 
Process 2016 rev: url_4 
Process 16904 send: url_6 
Process 2016 rev: url_5 
Process 2016 rev: url_6 
Process 16904 send: url_7 
Process 2016 rev: url_7 
Process 16904 send: url_8 
Process 2016 rev: url_8 
Process 16904 send: url_9 
Process 2016 rev: url_9 

多進程到此結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章