Python爬蟲開發與專案實戰(1)
第一章 回顧Python程式設計
1.1 安裝Python(略)
1.2 搭建開發環境(略)
1.3 IO程式設計
檔案讀寫
-
開啟檔案
open(name[.mode[.buffering]])
- 引數mode和buffering是可選的
- 預設模式是讀模式,預設緩衝區是無
-
mode引數
‘r’ ‘w’ ‘a’ ‘b’ ‘+’ 讀模式 寫模式 追加模式 二進位制模式(可新增到其他模式中使用) 讀/寫模式(可新增到其他模式中使用) - ‘b’引數一般用來處理二進位制檔案,如mp3音樂或者影像
-
緩衝區
- 如果無緩衝區,那麼I/O操作會將資料直接寫到硬碟上
- 如果引數為正數,則代表緩衝區的大小,資料先寫到記憶體裡,使用flush或者close函式才會將資料更新到硬碟
- 如果引數為負數,則取緩衝區的預設大小
-
檔案讀取
- read()方法可以一次性將檔案內容全部讀到記憶體中
- read(size)方法可以一次讀取至多size個位元組
- 如果檔案是文字檔案,readline()方法可以每次讀取一行內容
- readlines()方法可以一次讀取所有內容並按行返回列表
filename = '/file' ####################### # VER 1 ####################### f = open(filename) f.read() # 檔案內容的str物件 f.close() ####################### # VER 2 ####################### try: f = open(filename) print(f.read()) finally: if(f): f.close() ####################### # VER 3 ####################### with open(filename) as f: print(f.read()) ####################### # VER 4 ####################### with open(filename) as f: for line in f.readlines(): print(line.strip())
-
檔案寫入
filename = '/file' str = 'write content' ####################### # VER 1 ####################### f = open(filename, 'w') f.write(str) f.close() ####################### # VER 2 ####################### with open(filename, 'w') as f: f.write(str)
操作檔案和目錄
操作 | 作用 |
---|---|
os.getcwd() | 獲得當前Python指令碼工作的目錄路徑 |
os.listdir() | 返回指定目錄下的所有檔案和目錄名 |
os.remove(path) | 刪除一個檔案 |
os.removedirs(dir) | 刪除多個空目錄 |
os.path.isfile(filepath) | 檢驗給出的路徑是否是一個檔案 |
os.path.isdir(dirpath) | 檢驗給出的路徑是否是一個目錄 |
os.isabs() | 判斷是否是絕對路徑 |
os.path.exists() | 檢驗路徑是否真的存在 |
os.path.split() | 分離一個路徑的目錄名和檔名 |
os.path.splitext() | 分離副檔名 |
os.path.dirname(filepath) | 獲取路徑名 |
os.path.basename(filepath) | 獲取檔名 |
os.getenv(), os.putenv() | 讀取和設定環境變數 |
os.linesep | 給出當前平臺的行終止符(Windows:\r\n,Linux:‘\n’,Mac:’r’) |
os.name | 指示正在使用的平臺(Windows:‘nt’,Linux/Unix:‘posix’) |
os.rename(old, new) | 重新命名檔案或目錄 |
os.makedirs(dir) | 建立多級目錄 |
os.mkdir(dir) | 建立單個目錄 |
os.stat(file) | 獲取檔案屬性 |
os.chmod(file) | 修改檔案許可權與時間戳 |
os.path.getsize(filename) | 獲取檔案大小 |
shutil.copytree(olddir, newdir) | 複製資料夾,兩個引數都必須是目錄,且newdir必須不存在 |
shutil.copyfile(oldfile, newfile), shutil.copy(oldfile, newfile) | 複製檔案,copyfile()中兩個引數都必須是檔案;copy()中,oldfile只能是檔案,而newfile可以是檔案也可以是目標目錄 |
shutil.move(oldpos, newpos) | 移動檔案或目錄 |
os.rmdir(dir), shutil.rmtree(dir) | 刪除目錄,os.rmdir()只能刪除空目錄;shutil.rmtree()可以刪除空目錄或有內容的目錄 |
序列化操作
-
把記憶體中的變數程式設計可儲存或可傳輸的過程,就是序列化
-
將記憶體中的變數序列化後,可以把序列化後的內容寫入磁碟,或者通過網路傳輸到別的機器上,實現程式狀態的儲存和共享。讀取這些資料的過程,稱為反序列化。
-
在Python中提供了兩個模組來實現序列化:cPickle和pickle,前者是使用C語言編寫的,效率比後者高很多
-
一般編寫程式時,會先匯入cPickle模組,如果不存在該模組,再匯入pickle模組:
try: import cPickle as pickle expect ImportError: import pickle
-
dumps()方法可以將任意物件序列化成一個str,loads()方法可以讀取這樣的str並將其轉換為物件
-
dump()方法可以將任意物件序列化並寫入檔案中,load()方法可以讀取這樣的檔案並將其轉換為物件
try: import cPickle as pickle expect ImportError: import pickle data = '要序列化的任意格式資料' filename = './file' ####################### # dumps ####################### pickle.dumps(data) ####################### # dump ####################### f = open(filename, 'wb') pickle.dump(data, f) f.close() ####################### # loads ####################### loads(str) # 經過dumps得到的str ####################### # dump ####################### f = open(filename, 'rb') data = pickle.load(f) f.close()
1.4 程式和執行緒
多程式
- Python主要有兩種方法實現多程式
- os.fork(),僅支援Unix/Linux作業系統
- multiprocessing模組中的Process類,是跨平臺的實現方式
os模組中的fork方法
-
fork方法是呼叫一次,返回兩次。fork方法會在當前程式中複製出一份幾乎完全相同的子程式
-
子程式永遠返回0,父程式返回子程式的ID
-
getpid方法用於獲取當前程式的ID,getppid方法使用者獲取父程式的ID
import os if __name__ == '__main__': print('current Process (%s) start ...'%(os.getpid())) pid = os.fork() 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 (70923) start ... # I(70923) created a child process (70924). # I am child process (70924) and my parent process is (70923)
multiprocessing中的Process類
-
建立子程式時,只需要傳入一個執行函式和函式的引數
-
用start()方法啟動程式
-
用join()方法實現程式間的同步
import os from multiprocessing import Process def run_proc(name): print('Child process %s (%s) Running...' % (name, os.getpid())) if __name__ == '__main__': print('Parent process %s.' % os.getpid()) for i in range(5): p = Process(target=run_proc, args=(str(i), )) print('Process will start') p.start() p.join() print('Process end') # 輸出: # Parent process 128285. # Process will start # Process will start # Process will start # Process will start # Process will start # Child process 1 (128287) Running... # Child process 0 (128286) Running... # Child process 4 (128290) Running... # Process end # Child process 3 (128289) Running... # Child process 2 (128288) Running...
multiprocessing模組程式池Pool類
-
Pool可以提供指定數量的程式供使用者呼叫,預設大小是CPU的核數
-
當有新的請求到來時:
- 如果程式池未滿,那麼會創構建一個新的程式來執行請求
- 如果程式池已滿,那麼會等待已有程式結束,再建立新的程式來執行請求
-
apply_async()方法可以向程式池請求一個程式
-
Pool物件呼叫join()方法會等待所有子程式執行完畢
-
呼叫join()方法之前必須先呼叫close()方法,呼叫close()之後就不能繼續新增新的Process了
from multiprocessing import Pool import os, time, random def run_task(name): print('Task %s (pid = %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()) p = Pool(processes=3) # 設定程式池的最大程式數為3 for i in range(5): p.apply_async(run_task, args=(i, )) print('Waiting for all subprocesses done...') p.close() p.join() print('All subprocesses done.') # 輸出: # Current process 128323. # Waiting for all subprocesses done... # Task 0 (pid = 128324) is running... # Task 1 (pid = 128326) is running... # Task 2 (pid = 128325) is running... # Task 2 end. # Task 3 (pid = 128325) is running... # Task 1 end. # Task 4 (pid = 128326) is running... # Task 4 end. # Task 0 end. # Task 3 end. # All subprocesses done.
程式間通訊
-
Python建立了多種程式間通訊的方式,如Queue,Pipe,Value+Array等。本書主要講解前兩種方式。
-
Queue和Pipe的區別在於Pipe常用來在兩個程式間通訊,而Queue用來在多個程式間實現通訊
-
Queue:
- Queue是多程式安全的佇列
- put()方法用於插入資料到佇列中
- 可選引數blocked和timeout:如果blocked為True(預設),且timeout為正值,則該方法會阻塞timeout的時間,直到該佇列有剩餘的空間,如果超時,會丟擲Queue.Full異常。如果blocked為False,且佇列已滿,會立即丟擲Queue.Full異常
- get()方法可以從佇列中讀取並刪除一個元素
- 可選引數blocked和timeout:如果blocked為True(預設),且timeout為正值,則該方法會阻塞timeout的時間,直到該佇列有可選的元素,如果超時,會丟擲Queue.Empty異常。如果blocked為False,且佇列為空,會立即丟擲Queue.Empty異常
from multiprocessing import Process, Queue import os, time, random # 寫資料程式執行的程式碼: 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_writer1 = Process(target=proc_write, args=(q,['url_1', 'url_2', 'url_3'])) proc_writer2 = Process(target=proc_write, args=(q,['url_4','url_5','url_6'])) proc_reader = Process(target=proc_read, args=(q,)) # 啟動子程式proc_writer,寫入: proc_writer1.start() proc_writer2.start() # 啟動子程式proc_reader,讀取: proc_reader.start() # 等待proc_writer結束: proc_writer1.join() proc_writer2.join() # proc_reader程式裡是死迴圈,無法等待其結束,只能強行終止: proc_reader.terminate() # 輸出: # Process(128409) is writing... # Put url_1 to queue... # Process(128410) is writing... # Put url_4 to queue... # Process(128411) is reading... # Get url_1 from queue. # Get url_4 from queue. # Put url_5 to queue... # Get url_5 from queue. # Put url_2 to queue... # Get url_2 from queue. # Put url_3 to queue... # Get url_3 from queue. # Put url_6 to queue... # Get url_6 from queue.
-
Pipe:
- Pipe常用來在兩個程式間進行通訊,兩個程式分別位於管道的兩端
- Pipe方法返回(conn1, conn2),代表一個管道的兩個端
- duplex引數:如果為True,則為全雙工模式,即管道兩端均可收發;如果為False,則conn1只負責接收訊息,conn2只負責傳送訊息
- send()方法:傳送訊息
- recv()方法:接收訊息
- 如果沒有訊息可接收,recv方法會一直阻塞,如果管道已被關閉,那麼recv方法會丟擲EOFError
import multiprocessing import random import time,os 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.terminate() # 輸出: # Process(128467) send: url_0 # Process(128468) rev:url_0 # Process(128467) send: url_1 # Process(128468) rev:url_1 # Process(128467) send: url_2 # Process(128468) rev:url_2 # Process(128467) send: url_3 # Process(128468) rev:url_3 # Process(128467) send: url_4 # Process(128467) send: url_5 # Process(128468) rev:url_4 # Process(128467) send: url_6 # Process(128468) rev:url_5 # Process(128467) send: url_7 # Process(128467) send: url_8 # Process(128468) rev:url_6 # Process(128468) rev:url_7 # Process(128468) rev:url_8 # Process(128467) send: url_9 # Process(128468) rev:url_9
多執行緒
- 多執行緒類似於同時執行多個不同程式
- 可以把執行時間長的任務放到後臺去處理
- 程式的執行速度可能加快
- 在一些需要等待的任務實現上,如使用者輸入、檔案讀寫和網路收發資料等情況下,可以釋放一些珍貴的資源如記憶體佔用等
- Python標準庫為多執行緒提供了兩個模組:thread和threading,前者是低階模組;後者是高階模組,對前者進行了封裝。絕大多數情況下只需要使用threading模組
用threading模組建立多執行緒:
- threading模組一般通過兩種方式建立多執行緒:
- 把一個函式傳入並建立Thread例項,然後呼叫start方法開始執行
- 直接從thread.Thread繼承並建立執行緒類,然後重寫__init__方法和run方法
- 第一種方法:
import random
import time, threading
# 新執行緒執行的程式碼:
def thread_run(urls):
print('Current %s is running...' % threading.current_thread().name)
for url in urls:
print('%s ---->>> %s' % (threading.current_thread().name,url))
time.sleep(random.random())
print('%s ended.' % threading.current_thread().name)
print('%s is running...' % threading.current_thread().name)
t1 = threading.Thread(target=thread_run, name='Thread_1',args=(['url_1','url_2','url_3'],))
t2 = threading.Thread(target=thread_run, name='Thread_2',args=(['url_4','url_5','url_6'],))
t1.start()
t2.start()
t1.join()
t2.join()
print('%s ended.' % threading.current_thread().name)
# 輸出:
# MainThread is running...
# Current Thread_1 is running...
# Thread_1 ---->>> url_1
# Current Thread_2 is running...
# Thread_2 ---->>> url_4
# Thread_2 ---->>> url_5
# Thread_1 ---->>> url_2
# Thread_2 ---->>> url_6
# Thread_1 ---->>> url_3
# Thread_2 ended.
# Thread_1 ended.
# MainThread ended.
- 第二種方法:
import random
import threading
import time
class myThread(threading.Thread):
def __init__(self,name,urls):
threading.Thread.__init__(self,name=name)
self.urls = urls
def run(self):
print('Current %s is running...' % threading.current_thread().name)
for url in self.urls:
print('%s ---->>> %s' % (threading.current_thread().name,url))
time.sleep(random.random())
print('%s ended.' % threading.current_thread().name)
print('%s is running...' % threading.current_thread().name)
t1 = myThread(name='Thread_1',urls=['url_1','url_2','url_3'])
t2 = myThread(name='Thread_2',urls=['url_4','url_5','url_6'])
t1.start()
t2.start()
t1.join()
t2.join()
print('%s ended.' % threading.current_thread().name)
# 輸出:
# MainThread is running...
# Current Thread_1 is running...
# Thread_1 ---->>> url_1
# Current Thread_2 is running...
# Thread_2 ---->>> url_4
# Thread_2 ---->>> url_5
# Thread_1 ---->>> url_2
# Thread_2 ---->>> url_6
# Thread_1 ---->>> url_3
# Thread_1 ended.
# Thread_2 ended.
# MainThread ended.
執行緒同步
-
如果多個執行緒共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正確性,需要對多個執行緒進行同步
-
使用Thread物件的Lock和RLock可實現簡單的執行緒同步
- 這兩個物件都有acquire和release方法,對於那些每次只允許一個執行緒操作的資料,可以將其放到acquire和release之間
-
對於Lock物件,如果一個執行緒連續兩次進行acquire操作,由於第一次acquire沒有release,第二次acquire將掛起執行緒,導致永遠都不會release,形成死鎖
-
對於RLock物件,允許一個執行緒多次進行acquire操作,其內部有一個counter維護acquire的次數,而且每次acquire必須對應於一個release,在完成所有的release操作後,別的執行緒才能申請RLock物件
import threading mylock = threading.RLock() num=0 class myThread(threading.Thread): def __init__(self, name): threading.Thread.__init__(self,name=name) def run(self): global num while True: mylock.acquire() print('%s locked, Number: %d'%(threading.current_thread().name, num)) if num>=4: mylock.release() print('%s released, Number: %d'%(threading.current_thread().name, num)) break num+=1 print('%s released, Number: %d'%(threading.current_thread().name, num)) mylock.release() if __name__== '__main__': thread1 = myThread('Thread_1') thread2 = myThread('Thread_2') thread1.start() thread2.start() # 輸出: # Thread_1 locked, Number: 0 # Thread_1 released, Number: 1 # Thread_1 locked, Number: 1 # Thread_1 released, Number: 2 # Thread_1 locked, Number: 2 # Thread_1 released, Number: 3 # Thread_1 locked, Number: 3 # Thread_1 released, Number: 4 # Thread_1 locked, Number: 4 # Thread_1 released, Number: 4 # Thread_2 locked, Number: 4 # Thread_2 released, Number: 4
全域性直譯器鎖(GIL)
- 在Python的原始直譯器CPython中存在這GIL(Global Interpreter Lock全域性直譯器鎖)
- 由於GIL的存在,在進行多執行緒操作的時候,不能呼叫多個CPU核心,只能利用一個核心
- 因此在進行CPU密集型操作的時候,不推薦使用多執行緒,而更傾向於多程式
- 對於IO密集型操作,如Python爬蟲的開發,多執行緒可以明顯提高效率。(絕大多數時間爬蟲是在等待socket返回資料,網路IO的操作延時比CPU大得多)
協程
-
協程,又稱微執行緒,纖程,是一種使用者級的輕量級執行緒
-
協程擁有自己的暫存器上下文和棧
-
協程能保留上一次呼叫時的狀態,每次過程重入時,就相當於進入上一次呼叫的狀態
-
在併發程式設計中,協程與執行緒類似,每個協程表示一個執行單元,有自己的本地資料,與其他協程共享全域性資料和其他資源
-
協程需要使用者自己來編寫排程邏輯。對於CPU來說,協程其實是單執行緒,所以CPU不用處理排程、切換上下文,這就省去了CPU的切換開銷
-
Python通過yield提供了對協程的基本支援,但是不完全。而使用第三方gevent庫是更好的選擇
-
gevent是一個基於協程的Python網路函式庫,使用greenlet在libev事件迴圈頂部提供了以一個有高階別併發性的API
- 基於libev的快速事件迴圈,Linux上是epoll機制
- 基於greenlet的輕量級執行單元
- API複用了Python標準庫裡的內容
- 支援SSL的協作式sockets
- 可通過執行緒池或c-ares實現DNS查詢
- 通過monkey patching功能使得第三方模組變成協作式
-
gevent對協程的支援,本質上是greenlet在實現切換工作
-
greenlet工作流程:如果進行訪問網路的IO操作時出現阻塞,greenlet就顯式切換到另一端沒有被阻塞的程式碼段執行,直到原先的阻塞狀況消失以後,再自動切換回原來的程式碼段繼續處理
-
因此,greenlet是一種合理安排的序列方式
-
有了gevent自動切換協程,就保證總有greenlet在執行,而不是等待IO,這就是協程比一般多執行緒效率高的原因
-
spawn()方法可以用來形成協程
-
joinall()方法可以新增這些協程任務並啟動執行
-
從執行結果來看,3個網路操作是併發執行的,而且結束順序不同,但其實只有一個執行緒
from gevent import monkey; monkey.patch_all() import gevent import urllib.request def run_task(url): print('Visit --> %s' % url) try: response = urllib.request.urlopen(url) data = response.read() print('%d bytes received from %s.' % (len(data), url)) except Exception as e: print(e) if __name__=='__main__': urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/'] greenlets = [gevent.spawn(run_task, url) for url in urls ] gevent.joinall(greenlets) # 輸出: # Visit --> https://github.com/ # Visit --> https://www.python.org/ # Visit --> http://www.cnblogs.com/ # 71025 bytes received from http://www.cnblogs.com/. # 219611 bytes received from https://github.com/. # 49982 bytes received from https://www.python.org/.
-
gevent中還提供了對池的支援
-
當擁有動態數量的greenlet需要進行併發管理時,就可以使用池
from gevent import monkey monkey.patch_all() import urllib.request from gevent.pool import Pool def run_task(url): print('Visit --> %s' % url) try: response = urllib.request.urlopen(url) data = response.read() print('%d bytes received from %s.' % (len(data), url)) except Exception as e: print(e) return 'url:%s --->finish' % url if __name__=='__main__': pool = Pool(2) urls = ['https://github.com/','https://www.python.org/','http://www.cnblogs.com/'] results = pool.map(run_task,urls) print(results) # 輸出: # Visit --> https://github.com/ # Visit --> https://www.python.org/ # 219605 bytes received from https://github.com/. # Visit --> http://www.cnblogs.com/ # 71065 bytes received from http://www.cnblogs.com/. # 49982 bytes received from https://www.python.org/. # ['url:https://github.com/ --->finish', 'url:https://www.python.org/ --->finish', 'url:http://www.cnblogs.com/ --->finish']
分散式程式
-
分散式程式指的是將Process程式分佈到多臺機器上,充分利用多臺機器的效能完成複雜的任務
-
multiprocessing的managers子模組支援把多程式分佈到多臺機器上
-
通過一個服務程式作為排程者,將任務分佈到其他多個程式中,依靠網路通訊進行管理
-
舉個例子:要爬取一個網站上所有的圖片
- 如果使用多程式,一般是一個程式負責抓取圖片的連結地址,其他程式負責從這些地址中進行下載和儲存
- 如果使用分散式,則是一臺機器負責抓取連結,其他機器負責下載儲存
- 分散式遇到的問題是,抓取到的連結需要暴露在網路中,才能被其他機器訪問到
- 分散式程式將這一過程進行了封裝,即“本地佇列的網路化”
-
實現上面的例子,建立分散式程式(服務程式)需要分為六個步驟
- 建立佇列Queue,用於程式間通訊。在分散式多程式環境下,必須通過QueueManager獲得的Queue介面來新增任務
- 將佇列在網路上註冊,暴露給其他程式(主機),註冊後獲得網路佇列,相當於本地佇列的映像
- 建立一個物件(QueueManager(BaseManager))例項manager,繫結埠和驗證口令
- 啟動管理manager,監管資訊通道
- 通過管理例項的方法獲得通過網路訪問的Queue物件,即把網路佇列實體化成可以使用的本地佇列
- 建立任務到本地佇列中,自動上傳任務到網路佇列中,分配給任務程式進行處理
-
建立任務程式需要以下步驟:
- 使用QueueManager註冊用於獲取Queue的方法名稱,任務程式只能通過名稱來在網路上獲取Queue
- 使用埠和驗證口令連線伺服器
- 從網路上獲取Queue,進行本地化
- 從task佇列中獲取任務,並把結果寫入result佇列
-
服務程式樣例(Linux版):
import random,time import queue as Queue from multiprocessing.managers import BaseManager #實現第一步:建立task_queue和result_queue,用來存放任務和結果 task_queue=Queue.Queue() result_queue=Queue.Queue() class Queuemanager(BaseManager): pass #實現第二步:把建立的兩個佇列註冊在網路上,利用register方法,callable引數關聯了Queue物件, # 將Queue物件在網路中暴露 Queuemanager.register('get_task_queue',callable=lambda:task_queue) Queuemanager.register('get_result_queue',callable=lambda:result_queue) #實現第三步:繫結埠8001,設定驗證口令‘qiye’。這個相當於物件的初始化 manager=Queuemanager(address=('',8001),authkey=bytes('qiye', encoding='utf-8')) #實現第四步:啟動管理,監聽資訊通道 manager.start() #實現第五步:通過管理例項的方法獲得通過網路訪問的Queue物件 task=manager.get_task_queue() result=manager.get_result_queue() #實現第六步:新增任務 for url in ["ImageUrl_"+str(i) for i in range(10)]: print('put task %s ...' %url) task.put(url) #獲取返回結果 print('try get result...') for i in range(10): print('result is %s' %result.get(timeout=10)) #關閉管理 manager.shutdown() # 輸出: # put task ImageUrl_0 ... # put task ImageUrl_1 ... # put task ImageUrl_2 ... # put task ImageUrl_3 ... # put task ImageUrl_4 ... # put task ImageUrl_5 ... # put task ImageUrl_6 ... # put task ImageUrl_7 ... # put task ImageUrl_8 ... # put task ImageUrl_9 ... # try get result... # result is ImageUrl_0--->success # result is ImageUrl_1--->success # result is ImageUrl_2--->success # result is ImageUrl_3--->success # result is ImageUrl_4--->success # result is ImageUrl_5--->success # result is ImageUrl_6--->success # result is ImageUrl_7--->success # result is ImageUrl_8--->success # result is ImageUrl_9--->success
-
服務程式樣例(Windows版):
# taskManager.py for windows import queue as Queue from multiprocessing.managers import BaseManager from multiprocessing import freeze_support #任務個數 task_number = 10 #定義收發佇列 task_queue = Queue.Queue(task_number) result_queue = Queue.Queue(task_number) def get_task(): return task_queue def get_result(): return result_queue # 建立類似的QueueManager: class QueueManager(BaseManager): pass def win_run(): #windows下繫結呼叫介面不能使用lambda,所以只能先定義函式再繫結 QueueManager.register('get_task_queue',callable = get_task) QueueManager.register('get_result_queue',callable = get_result) #繫結埠並設定驗證口令,windows下需要填寫ip地址,linux下不填預設為本地 manager = QueueManager(address = ('127.0.0.1',8001),authkey = b'qiye') #啟動 manager.start() try: #通過網路獲取任務佇列和結果佇列 task = manager.get_task_queue() result = manager.get_result_queue() #新增任務 for url in ["ImageUrl_"+str(i) for i in range(10)]: print('put task %s ...' %url) task.put(url) print('try get result...') for i in range(10): print('result is %s' %result.get(timeout=10)) except: print('Manager error') finally: #一定要關閉,否則會爆管道未關閉的錯誤 manager.shutdown() if __name__ == '__main__': #windows下多程式可能會有問題,新增這句可以緩解 freeze_support() win_run()
-
任務程式樣例:
#coding:utf-8 import time from multiprocessing.managers import BaseManager # 建立類似的QueueManager: class QueueManager(BaseManager): pass # 實現第一步:使用QueueManager註冊獲取Queue的方法名稱 QueueManager.register('get_task_queue') QueueManager.register('get_result_queue') # 實現第二步:連線到伺服器: server_addr = '127.0.0.1' print('Connect to server %s...' % server_addr) # 埠和驗證口令注意保持與服務程式設定的完全一致: m = QueueManager(address=(server_addr, 8001), authkey=bytes('qiye', encoding='utf-8')) # 從網路連線: m.connect() # 實現第三步:獲取Queue的物件: task = m.get_task_queue() result = m.get_result_queue() # 實現第四步:從task佇列取任務,並把結果寫入result佇列: while(not task.empty()): image_url = task.get(True,timeout=5) print('run task download %s...' % image_url) time.sleep(1) result.put('%s--->success'%image_url) # 處理結束: print('worker exit.') # 輸出: # Connect to server 127.0.0.1... # run task download ImageUrl_0... # run task download ImageUrl_1... # run task download ImageUrl_2... # run task download ImageUrl_3... # run task download ImageUrl_4... # run task download ImageUrl_5... # run task download ImageUrl_6... # run task download ImageUrl_7... # run task download ImageUrl_8... # run task download ImageUrl_9... # worker exit.
-
服務程式(Linux版)輸出:
-
任務程式輸出:
-
1.5 網路程式設計
Socket
-
Socket(套接字)是網路程式設計的一個抽象概念,用於表示“開啟了一個網路連線”
-
建立一個socket連結需要知道目標IP地址和埠號,以及指定協議型別
-
Python提供了兩個基本的socket模組:
- Socket:提供了標準的BSD Sockets API
- SocketServer:提供了伺服器中心類,可以簡化網路伺服器的開發
-
套接字格式為:socket(family, type[, protocal]),使用給定的地址族,套接字型別,協議編號(預設為0)來建立套接字
-
套接字型別:
Socket型別 描述 socket.AF_UNIX 只能用於單一的Unix系統程式間通訊 socket.AF_INET 伺服器之間網路通訊 socket.AF_INET6 IPv6 socket.SOCK_STREAM 流式socket,用於TCP socket.SOCK_DGRAM 資料包式socket,用於UDP socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而原始套接字可以。其次,SOCK_RAW可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭 socket.SOCK_SEQPACKET 可靠的連續資料包服務 建立TCP Socket s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 建立UDP Socket s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -
Socket函式:
Socket函式 描述 服務端Socket函式 s.bind(address) 將套接字繫結到地址,在AF_INET下,以元組(host, port)的形式表示地址 s.listen(backlog) 開始監聽TCP傳入連線。backlog指定在拒絕連線之前,作業系統可以掛起的最大連線數量,至少為1,大部分應用程式設為5就可以了 s.accept() 接受TCP連線並返回(conn, address),其中conn是新的套接字物件,可以用來接收和傳送資料。address是連線客戶端的地址 客戶端Socket函式 s.connect(address) 連線到address的套接字。一般address的格式為元組(hostname, port),如果連線出錯,返回socket.error錯誤 s.connect_ex(address) 功能與s.connect相同,但成功返回0,失敗返回errno的值 公共Socket函式 s.recv(bufsize[,flag]) 接受TCP套接字的資料。以字串形式返回,bufsize指定要接收的最大資料量,flag提供有關訊息的其他資訊,通常可以忽略 s.send(string[,flag]) 傳送TCP資料。將string中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,可能小於string的位元組大小 s.sendall(string[,flag]) 完整傳送TCP資料。將string中的資料傳送到連線的套接字, 但在返回前會嘗試傳送所有資料,成功返回None,失敗則丟擲異常 s.recvfrom(bufsize[,flag]) 接受UDP套接字的資料。與s.recv()類似,但返回值是(data, address),data是包含接收資料的字串,address是傳送資料的套接字地址 s.sendto(string[,flag], address) 傳送UDP資料,將資料傳送到套接字。address格式為(ipaddr, port)的元組,指定遠端地址。返回傳送的位元組數 s.close() 關閉套接字 s.getpeername() 返回連線套接字的遠端地址。通常是元組(ipaddr, port) s.getsockname() 返回套接字自己的地址。通常是元組(ipaddr, port) s.setsockopt(level, optname, value) 設定給定套接字選項的值 s.getsockopt(level, optname[, buflen]) 返回套接字選項的值 s.settimeout(timeout) 設定套接字操作的超時期,timeout是一個浮點數,單位是秒,為None時表示沒有超時期。一般應在剛建立套接字時設定,因為可能會用於連線操作。 s.setblocking(flag) 如果flag非0,則將套接字設為非阻塞模式,否則設為阻塞模式(預設)。非阻塞模式下,send無法傳送資料,或recv無法接收資料,將引起socket.error異常
TCP程式設計
-
TCP是一種面向連線的通訊方式,主動發起的連線叫客戶端,被動響應連線的叫服務端
-
服務端建立和執行TCP連線需要以下步驟:
- 建立socket,繫結socket到本地IP與埠
- 開始監聽連線
- 進入迴圈,不斷接收客戶端的連線請求
- 接收傳來的資料,併傳送給對方資料
- 傳輸完畢後,關閉socket
#coding:utf-8 import socket import threading import time def dealClient(sock, addr): #第四步:接收傳來的資料,併傳送給對方資料 print('Accept new connection from %s:%s...' % addr) sock.send(b'Hello,I am server!') while True: data = sock.recv(1024) time.sleep(1) if not data or data.decode('utf-8') == 'exit': break print('-->>%s!' % data.decode('utf-8')) sock.send(('Loop_Msg: %s!' % data.decode('utf-8')).encode('utf-8')) #第五步:關閉套接字 sock.close() print('Connection from %s:%s closed.' % addr) if __name__=="__main__": #第一步:建立一個基於IPv4和TCP協議的Socket # 套接字繫結的IP(127.0.0.1為本機ip)與埠 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('127.0.0.1', 9999)) #第二步:監聽連線 s.listen(5) print('Waiting for connection...') while True: # 第三步:接受一個新連線: sock, addr = s.accept() # 建立新執行緒來處理TCP連線: t = threading.Thread(target=dealClient, args=(sock, addr)) t.start() # 輸出: # Waiting for connection... # Accept new connection from 127.0.0.1:37092... # -->>Hello,I am a client! # Connection from 127.0.0.1:37092 closed.
-
客戶端建立和執行TCP連線需要以下步驟:
- 建立socket,連線遠端地址
- 連線後傳送資料和接收資料
- 傳輸完畢後,關閉socket
#coding:utf-8 import socket #初始化Socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #連線目標的ip和埠 s.connect(('127.0.0.1', 9999)) # 接收訊息 print('-->>'+s.recv(1024).decode('utf-8')) # 傳送訊息 s.send(b'Hello,I am a client') print('-->>'+s.recv(1024).decode('utf-8')) s.send(b'exit') #關閉套接字 s.close() # 輸出: # -->>Hello,I am server! # -->>Loop_Msg: Hello,I am a client!
-
伺服器端輸出:
-
客戶端輸出
UDP程式設計
-
TCP是面向連線的協議,需要建立連線,以流的形式傳送資料
-
UDP無連線的協議,但傳送資料後無法確保資料能夠到達目的端
-
UDP具有不可靠性,但速度比TCP快得多
-
服務端建立和執行UDP需要以下步驟:
- 建立socket,繫結指定的IP和埠
- 直接傳送資料和接收資料
- 關閉socket
#coding:utf-8 import socket #建立Socket,繫結指定的ip和埠 #SOCK_DGRAM指定了這個Socket的型別是UDP。繫結埠和TCP一樣。 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('127.0.0.1', 9999)) print('Bind UDP on 9999...') while True: # 直接傳送資料和接收資料 data, addr = s.recvfrom(1024) print('Received from %s:%s.' % addr) s.sendto(b'Hello, %s!' % data, addr) # 輸出: # Bind UDP on 9999... # Received from 127.0.0.1:59835. # Received from 127.0.0.1:59835.
-
客戶端建立和執行UDP需要以下步驟:
- 建立socket
- 與服務端進行資料交換
- 關閉socket
#coding:utf-8 import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in [b'Hello', b'World']: # 傳送資料: s.sendto(data, ('127.0.0.1', 9999)) # 接收資料: print(s.recv(1024).decode('utf-8')) s.close() # 輸出: # Hello, Hello! # Hello, World!
-
伺服器端輸出:
-
客戶端輸出:
相關文章
- python爬蟲實操專案_Python爬蟲開發與專案實戰 1.6 小結Python爬蟲
- Python爬蟲開發與專案實戰pdfPython爬蟲
- Python爬蟲開發與專案實戰(2)Python爬蟲
- Python爬蟲開發與專案實戰--分散式程式Python爬蟲分散式
- 不踩坑的Python爬蟲:Python爬蟲開發與專案實戰,從爬蟲入門 PythonPython爬蟲
- python書籍推薦-Python爬蟲開發與專案實戰Python爬蟲
- Python爬蟲開發與專案實戰 4: HTML解析大法Python爬蟲HTML
- 視訊教程-Python網路爬蟲開發與專案實戰-PythonPython爬蟲
- Python爬蟲開發與專案實踐(3)Python爬蟲
- 完整的python專案例項-《Python爬蟲開發與專案實戰》pdf完整版Python爬蟲
- python爬蟲-33個Python爬蟲專案實戰(推薦)Python爬蟲
- Python網路爬蟲實戰專案大全 32個Python爬蟲專案demoPython爬蟲
- python爬蟲實戰教程-Python爬蟲開發實戰教程(微課版)Python爬蟲
- python專案開發例項-Python專案案例開發從入門到實戰——爬蟲、遊戲Python爬蟲遊戲
- Python網路爬蟲實戰小專案Python爬蟲
- Python網路爬蟲實戰專案大全!Python爬蟲
- python3網路爬蟲開發實戰_Python3 爬蟲實戰Python爬蟲
- Python開發爬蟲專案+程式碼Python爬蟲
- 爬蟲實戰專案集合爬蟲
- 爬蟲專案實戰(一)爬蟲
- 爬蟲實戰專案合集爬蟲
- Python靜態網頁爬蟲專案實戰Python網頁爬蟲
- 《python 爬蟲開發與實戰》html基礎詳解Python爬蟲HTML
- 精通 Python 網路爬蟲:核心技術、框架與專案實戰Python爬蟲框架
- Java 爬蟲專案實戰之爬蟲簡介Java爬蟲
- Python 3網路爬蟲開發實戰Python爬蟲
- Go語言專案實戰:併發爬蟲Go爬蟲
- Python爬蟲入門學習實戰專案(一)Python爬蟲
- python3網路爬蟲開發實戰_Python 3開發網路爬蟲(一)Python爬蟲
- 《Python3網路爬蟲開發實戰》教程||爬蟲教程Python爬蟲
- python爬蟲開發微課版pdf_Python爬蟲開發實戰教程(微課版)Python爬蟲
- 32個Python爬蟲實戰專案,滿足你的專案慌Python爬蟲
- Python爬蟲開源專案合集Python爬蟲
- [Python3網路爬蟲開發實戰] 分散式爬蟲原理Python爬蟲分散式
- 2019最新崔慶才python3網路爬蟲開發專案實戰(完整)Python爬蟲
- Python學習筆記——爬蟲之Scrapy專案實戰Python筆記爬蟲
- Python3網路爬蟲開發實戰Python爬蟲
- Python3網路爬蟲開發實戰——第1章 開發環境Python爬蟲開發環境