Python Socket請求網站獲取數據


 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 多路復用非阻塞效率要高於非阻塞方式 """

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM