Python Socket請求網站獲取數據
---阻塞 I/O ->收快遞,快遞如果不到,就干不了其他的活
---非阻塞I/0 ->收快遞,不斷的去問,有沒有送到,有沒有送到,...如果送到了就接收
---I/O多路復用 ->找個代理人(select), 去收快遞。快遞到了,就通知用戶.
一 . 阻塞方式
blocking IO 會一直block 對應的進程,直到操作完成
# 客戶端請求網站-阻塞實現(一次一次的請求) import socket import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 # socket阻塞請求網站 def blocking(pn): sock = socket.socket() sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn)) sock.send(request_url.encode()) response = b'' chunk = sock.recv(1024) while chunk: # 循環接收數據,因為一次接收不完整 response += chunk chunk = sock.recv(1024) # print(response.decode()) return response def block_way(): for i in range(5): blocking(i) if __name__ == '__main__': start = time.time() block_way() print('請求5次頁面耗時{}'.format(time.time() - start)) """ 請求5次頁面耗時2.4048924446105957 """
二. 非阻塞方式
non-blcoking在kernel還沒准備好數據的情況下,會立即返回(會拋出異常)
# 客戶端請求網站-非阻塞實現 import socket import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 # socket非阻塞請求網站(時間消耗在不斷的while循環中,和阻塞的時間差不多) def blocking(pn): sock = socket.socket() sock.setblocking(False) # 設置為非阻塞 try: # connect連接需要一定時間,非阻塞發送完請求,立即返回,如果沒有數據會報異常 sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 except BlockingIOError: # 非阻塞套接字捕獲異常 pass request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn)) while True: # 不斷發送http請求 try: sock.send(request_url.encode()) break except OSError: pass response = b'' while True: # 不斷地接收數據 try: chunk = sock.recv(1024) # 沒有數據返回時會收到異常 while chunk: # 循環接收數據,因為一次接收不完整 response += chunk chunk = sock.recv(1024) break except BlockingIOError: # 處理非阻塞異常 pass # print(response.decode()) return response def block_way(): for i in range(5): blocking(i) if __name__ == '__main__': start = time.time() block_way() print('請求5次頁面耗時{}'.format(time.time() - start)) """ 請求5次頁面耗時2.681565046310425 時間消耗在不斷的while循環中,和阻塞的時間差不多 """
三. 多線程方式
# 客戶端請求網站-線程池實現 import socket from multiprocessing.pool import ThreadPool import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 def blocking(pn): sock = socket.socket() sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn)) sock.send(request_url.encode()) response = b'' chunk = sock.recv(1024) while chunk: # 循環接收數據,因為一次接收不完整 response += chunk chunk = sock.recv(1024) # print(response.decode()) return response def block_way(): pool = ThreadPool(5) for i in range(10): pool.apply_async(blocking, args=(i,)) pool.close() # close()執行后不會有新的進程加入到pool pool.join() # join函數等待子進程結束 if __name__ == '__main__': start = time.time() block_way() print('請求10次頁面耗時{}'.format(time.time() - start)) """ 請求10次頁面耗時1.1231656074523926 """
四. 多進程方式
# 客戶端請求網站-進程池實現 import socket from multiprocessing import Pool import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 def blocking(pn): """ 發送請求,接收數據 :param pn: :return: """ sock = socket.socket() sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn)) sock.send(request_url.encode()) response = b'' chunk = sock.recv(1024) while chunk: # 循環接收數據,因為一次接收不完整 response += chunk chunk = sock.recv(1024) # print(response.decode()) return response def block_way(): pool = Pool(5) for i in range(10): pool.apply_async(blocking, args=(i,)) pool.close() # close()執行后不會有新的進程加入到pool pool.join() # join函數等待子進程結束 if __name__ == '__main__': start = time.time() block_way() print('請求10次頁面耗時{}'.format(time.time() - start)) """ 請求10次頁面耗時1.1685676574707031 略慢於線程池實現方式,因為進程相對於線程開銷比較大 """
五. 協程方式
# 客戶端請求網站-協程實現 from gevent import monkey;monkey.patch_all() # 加補丁,實現非阻塞 import socket import gevent import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 def blocking(pn): """ 發送請求,接收數據 :param pn: :return: """ sock = socket.socket() sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(pn)) sock.send(request_url.encode()) response = b'' chunk = sock.recv(1024) while chunk: # 循環接收數據,因為一次接收不完整 response += chunk chunk = sock.recv(1024) # print(response.decode()) return response def block_way(): tasks = [gevent.spawn(blocking,i) for i in range (10)] #啟動協程 gevent.joinall(tasks) # 阻塞等待所有操作都執行完畢 if __name__ == '__main__': start = time.time() block_way() print('請求10次頁面耗時{}'.format(time.time() - start)) """ 請求10次頁面耗時0.6231002807617188 效率高於線程方式,協程相當於單線程並發(微線程),開銷小於線程 """
六. IO多路復用
I/O多路復用就是通過一種機制,操作系統通過一個進程可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
# 客戶端請求網站-I/O多路復用 import socket import selectors import time # 訪問網站 ACCESS_URL = 'www.baidu.com' # 端口 ACCESS_PORT = 80 sel = selectors.DefaultSelector() flag = False # 結束標志 num_list = [0] * 5 class Crawler(object): def __init__(self,pn): self.pn = pn self.response = b'' def req_connect(self): """ 請求建立連接 :return: """ sock = socket.socket() sock.setblocking(False) try: sock.connect((ACCESS_URL, ACCESS_PORT)) # 連接網站 ,發出一個HTTP請求 except BlockingIOError: # 非阻塞套接字捕獲異常 pass sel.register(sock,selectors.EVENT_WRITE, self.conn_send) # 監聽socket,向服務端發送數據WRITE def conn_send(self,sock): """ 發送請求數據 :param sock: :return: """ # 取消上面注冊監聽的套接字 sel.unregister(sock) request_url = 'GET {} HTTP/1.0\r\nHost: www.baidu.com\r\n\r\n'.format('/s?wd={}'.format(self.pn)) sock.send(request_url.encode()) # 當我們發送數據之后,就等接收數據 sel.register(sock,selectors.EVENT_READ, self.read) def read(self, sock): global flag chunk = sock.recv(1024) if chunk: self.response += chunk else: # 沒有數據可讀 print(self.response) sel.unregister(sock) num_list.pop() if not num_list: flag = True # 事件循環 def loop(): while not flag: events = sel.select() # 監聽發生了變化的套接字 for key, mask in events: callback = key.data callback(key.fileobj) if __name__ == '__main__': start = time.time() # print(num_list) for i in range(5): crawler = Crawler(i) # 實例 crawler.req_connect() loop() print('請求5次頁面耗時{}'.format(time.time() - start)) """ 請求5次頁面耗時0.5865745544433594 多路復用非阻塞效率要高於非阻塞方式 """