#1. 默认,阻塞IO实现http请求
# requests 包基于urllib,urllib又是基于socket实现的。 凡是web请求,数据库连接,网络连接,数据通信等,最底层的实现都是基于socket实现的。
# socket是操作系统提供的功能,只是不同的语言将socket封装为不同的接口而已。
# 如何通过socket,完成urlib的get请求?
import socket
from urllib.parse import urlparse # urlparse 只是用于url解析,并不会处理socket
def get_url(url):
# 通过socket请求html,解析url
url = urlparse(url)
host = url.netloc # 提取主域名
path = url.path
if path == "": #如果path为空,直接请求主域名
path = "/"
# 建立socket连接
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect((host,80)) # http链接一般都为80端口,否则会导致无法连接。
# 发送数据
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path,host).encode("utf8")) # Get请求,然后是相对路径。
# 将所有数据读取完
data = b"" # 数据为bytes类型
while True:
d = client.recv(1024)
if d:
data += d
else:
break
data = data.decode("utf8")
html_data = data.split("\r\n\r\n")[1] # \r 回车符号 \n 换行符号, 去掉HTTP头的信息
print(html_data)
client.close()
if __name__=="__main__":
get_url("http://www.baidu.com")
#2. 通过非阻塞io实现http请求
# 虽然使用了非阻塞io,整个while循环还是不停的等待,基于上一步执行结果,整个程序的返回时间并没有明显提升,没有显著提高并发。
import socket
from urllib.parse import urlparse # urlparse 只是用于url解析,并不会处理socket
def get_url(url):
# 通过socket请求html,解析url
url = urlparse(url)
host = url.netloc # 提取主域名
path = url.path
if path == "": #如果path为空,直接请求主域名
path = "/"
# 建立socket连接
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.setblocking(False) # 此处可用设置client,在client.connect(host,80)后立即返回,不等待连接成功(三次握手)
try: # 如果不用try/excep处理,调用client.connect((host,80)) 后会抛异常“BlockingIOError: [WinError 10035] 无法立即
# 完成一个非阻止性套接字操作。”,虽然抛异常,连接请求还是已经发送出去了
client.connect((host,80)) # 阻塞不会消耗CPU
except BlockingIOError as e:
pass # 继续向下执行
# 发送数据
while True:
try:
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(path,host).encode("utf8")) # Get请求,然后是相对路径。
# 调用该client.send语句是,还是抛出异常,“OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)
# 没有提供地址,发送或接收数据的请求没有被接受。”,原因是这里没有建立好连接。此处发送基于前面的连接,为了实现该功能的成功执行,只有不停的
# 尝试,用while True
break # 如果不成功,不停的尝试,如果成功,就要从while循环中break出来。
except OSError as e:
pass
# 将所有数据读取完
data = b"" # 数据为bytes类型
while True:
try:
d = client.recv(1024) # 如果send出去,没有接收到,此处就会报“BlockingIOError: [WinError 10035] 无法立即完成一个非阻止性套接字操作。”
# 同样此处用try/except处理
except BlockingIOError as e:
continue # 如果抛出异常,则继续while循环
if d:
data += d
else:
break # 如果读取数据为空,说明执行完毕,退出while循环
data = data.decode("utf8")
html_data = data.split("\r\n\r\n")[1] # \r 回车符号 \n 换行符号, 去掉HTTP头的信息
print(html_data)
client.close()
if __name__=="__main__":
get_url("http://www.baidu.com")
def register(self, fileobj, events, data=None): # fileobj是指socket, events代表事件(EVENT_READ & EVENT_WRITE两个事件),data为回调函数
key = super().register(fileobj, events, data)
if events & EVENT_READ:
self._readers.add(key.fd)
if events & EVENT_WRITE:
self._writers.add(key.fd)
return key
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 通过非阻塞io实现http请求
import socket
from urllib.parse import urlparse
from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE
selector = DefaultSelector()
urls = ['http://www.baidu.com']
stop = False
# 使用select完成http请求
class Fetcher:
def connected(self, key):
# 注销事件
selector.unregister(key.fd)
self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format(self.path, self.host).encode('utf-8'))
#
selector.register(self.client.fileno(), EVENT_READ, self.readable)
def readable(self, key):
d = self.client.recv(1024)
if d:
self.data += d
else:
# 注销
selector.unregister(key.fd)
self.data = self.data.decode('utf-8')
html_data = self.data.split('\r\n\r\n')[1]
print(self.data)
print(html_data)
self.client.close()
urls.remove(self.spider_url)
if not urls:
global stop
stop = True
def get_url(self, url):
self.spider_url = url
# 通过socket请求html
url = urlparse(url)
self.host = url.netloc
self.path = url.path
self.data = b""
if self.path == '':
self.path = '/'
# 建立连接
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.setblocking(False)
try:
self.client.connect((self.host, 80))
except BlockingIOError as e:
pass
# 注册事件
selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
'''
register(fileobj, events, data=None)
fileobj:文件描述符
events:监听事件
data:回调函数
'''
def loop():
# 事件循环,不停的请求socket的状态并调用对应的回调函数
# 1.select本身是不支持register模式。
# 2.socket状态变化以后的回调是由程序员完成的。
while not stop:
# windows下会报错,但是linux下不会报错
ready = selector.select()
for key, mask in ready:
call_back = key.data
call_back(key)
if __name__ == '__main__':
fetcher = Fetcher()
fetcher.get_url('http://www.baidu.com')
loop()