携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第26天,点击查看活动详情
你也可以手敲一个高速下载器(十一)Curl 解析器
前言
上一节,我们说了curl相关的理论知识和在我们的业务中的解决方案,这一节就来详细说说怎么在python中实现这个curl解析器
借鉴与改造
我们上一节说了,有几种解决方案包括自己开发和使用第三方的库,这里我们就找到一个类似的库:github.com/ClericPy/to…
可以看到实现的功能就是我们想要的,也弄的很详细,所以我们可以安装这个库,然后引用这个包,但是我们这看看,这里实现的也是过于复杂了,我们用不上这么多的功能,所以我们可以采取另一种方案:就是看看他是怎么实现的,然后改造成我们自己的。
分析代码
可以看到他的这个代码是有一个_Curl类和一个 curlparse方法组成的,我们依次进行分析一下:
_Curl类
这个类主要是创建了ArgumentParser的对象,然后把curl相关的参数一一对应起来便于解析,但是他的这个参数过于详细了,我们是用不到这些参数的,到时候至用我们需要的参数即可。
curlparse方法
在代码里有一些base64编码解码相关的操作,我们这里用不上,这部分的直接略过即可,然后我么即可看到关键的代码:
lex_list = shlex.split(string.strip())
args, unknown = _Curl.parser.parse_known_args(lex_list)
第一行是一个shlex.split方法,我们没有见过,去看下官方文档是怎么说的:
官方文档:shlex —— 简单的词法分析
shlex 类:shlex 类可用于编写类似 Unix shell 的简单词法分析程序。通常可用于编写“迷你语言”(如 Python 应用程序的运行控制文件)或解析带引号的字符串
shlex.split()方法:用类似 shell 的语法拆分字符串
也就是说这个方法会把一个命令字符串分割成列表的形式,我们来尝试一下:
>>> import shlex
>>> cmd = """curl http://www.xx.com -H 'Accept: application/json, text/plain, */*' -H 'sec-gpc: 1' -H 'dnt: 1' --data-raw '{"title": "hello"}'"""
>>> shlex.split(cmd)
['curl', 'http://www.xx.com', '-H', 'Accept: application/json, text/plain, */*', '-H', 'sec-gpc: 1', '-H', 'dnt: 1', '--data-raw', '{"title": "hello"}']
可以看到,我们把命令的字符串转换成了命令的列表,这就是以shell的形式的词法分析来切割的字符串,而不是传统意义上的以特定分隔符来切断字符串。
第二行就是调用上面的_Curl类来解析把转换完成的命令列表转换为输入参数,使用到的方法是ArgumentParser().parse_known_args()。这个方法的主要作用是有解析参数由程序的输入参数改变成定义好的命令列表。
后面就是依次的解析各个参数,包括了url、请求头、请求头、超时时间、重试次数、代理等等,我们重写的时候这部分代码也是会简化的。
CURL解析器
分析好了他的代码后,就可以写自己的解析器了,首先结构要来改一下,把他的解析方法变成解析器,因为他的_Curl类的唯一目的就是用来调用ArgumentParser类,我们就直接把ArgumentParser和解析方法变成属性和类函数,同时他的这个解析函数是支持url参数和curl字符串的,正好和我们的业务是相符合的,这部分的代码可以保留
初始化方法
在初始化里面,主要就是定义一些属性,和实例化一个解析器,具体先看代码:
def __init__(self, curl_string: str):
"""
初始化方法
Args:
curl_string: curl字符串
"""
# 定义属性
self.url: str = ""
self.method: str = ""
self.headers: dict = {}
self.data: dict = {}
# 设置curl字符串并作处理
self._curl_string = curl_string.replace('\\\n', ' ').strip()
# 设置参数解析器
self._parser = ArgumentParser()
# 初始化参数
self._init_parser()
# 判断传进来的是url地址还是curl字符串
if self._curl_string.startswith("http"):
self.url = self._curl_string
self.method = 'GET'
else:
self._parse_curl()
前面的定义属性是定义了我们下载器会用的几个属性,也就是说只解析这几个就够了,其中_init_parser就是初始化并定义解析器参数用的,下面会详细的说明,在最后就是判断传入的数据是url还是curl
初始化解析器
在上节我们说了,最终需要包含的属性,我们这里就把这些属性添加到解析器里面,详细见代码:
def _init_parser(self):
"""
初始化解析器
Returns:
"""
self._parser.add_argument("curl")
self._parser.add_argument("url", default='')
self._parser.add_argument("-X", "--request", default="")
self._parser.add_argument("-d", "--data", "--data-raw")
self._parser.add_argument("-F", "--form", "--form-string")
self._parser.add_argument("-H", "--header", action="append", default=[])
self._parser.add_argument("--compressed", action="store_true")
这个就是按照ArgumentParser解析器的语法,和curl的命令去定义的
解析CURL字符串
这里是主要的解析方法,利用ArgumentParser他把curl字符串解析成各个属性的方法,代码如下:
def _parse_curl(self):
"""
解析CURL字符串
Returns:
"""
lex_list = shlex.split(self._curl_string)
args, _ = self._parser.parse_known_args(lex_list)
# 设置请求url
self.url = args.url
# 设置请求头
for header in args.header:
key, value = header.split(":", 1)
self.headers[key.title()] = value.strip()
# 设置请求数据
data = args.data or args.form or '{}'
self.data = json.loads(data)
# 设置请求方法
# 如果没有传递请求方法:存在请求数据则设置为POST请求,否则为GET
# 如果传递了请求方法:按照传递了的值来设置
self.method = args.request if args.request else "POST" if data else "GET"
这里的代码中就使用到了我们前置知识中提到的两个方法,其他的要注意一下str.title()方法,我们先去文档看一下具体含义:
返回原字符串的标题版本,其中每个单词第一个字母为大写,其余字母为小写
我们在看一下标准的http请求头的格式,正好也是这个格式
结语
好了,这节的解析器先到这里,我们完成了由第三库中的代码,到我们自己的代码这个过程,其中要注意的是遇到没有见过的函数和库一定要去查询一下,为啥要这么写,这样是自己的代码,代码会在结束后放出来。