Leave a Comment
Python Socket请求网站获取数据
https://www.cnblogs.com/xiao-apple36/p/8673356.html
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 多路复用非阻塞效率要高于非阻塞方式
"""