异步思维——把请求与解析分开

简介: 异步思维——把请求与解析分开

摄影:产品经理切面薄而整齐,到底是用什么刀切的?

在昨天的文章《Callback ——从同步思维切换到异步思维》,我们举的例子似乎还不能很好地说明 Callback 的优势。今天我们再来看另外一个场景。

例如有下面这个代码片段:

async def parse(html):
    selector = fromstring(html)
    print('...解析 HTML 的数据...')
    next_page = selector.xpath('//a[@class="next_page"]/@href')
    if next_page:
        next_page_url = next_page[0]
        next_page_html = await request(next_page_url)
        await parse(next_page_html)

这种场景常常发生在需要翻页的时候,不同页面的处理逻辑是完全一样的,但是每次获取到了当前页面才能获取下一页。于是不少人使用递归的办法来解决问题。

如果页数非常多,那么你就会面临一个问题:超出最大递归深度,导致报错。

并且,在定义这个parse函数的时候,我使用了async def把它定义为一个异步函数。但实际上,解析 HTML 是一个 CPU 密集型的工作,它没有 IO 等待,根本就没有必要异步!但为了在里面调用await request(next_page_url),却必须要使用async def

为了解决这个问题,我们可以把递归改成循环。于是可能有人会这样写代码:

...无关代码...
url_list = ['初始 URL']
while url_list:
    url = url_list.pop()
    html = await request(url)
    data = parse(html)
    next_page_url = data['next_page_url']
    if next_page_url:
        url_list.append(next_page_url)

通过这种写法,parse函数可以直接定义成普通函数,并且每次只会调用1层,不会递归调用。这就同时解决了两个问题。

看到这里,大家可能发现了,实际上我们只有在涉及到 IO 请求的地方,才需要使用async/await。在解析网页的地方,只需要使用普通函数就可以了。

而对于aiohttp请求网页来说,它的逻辑非常简单,你告诉它urlheadersmethodbody。它返回源代码给你。它不需要关心你传入的这一批URL 是不是对应同一个类型的页面,甚至不需要关心你请求的是不是同一个网站!

在这种情况下,如果我们使用 Callback,那么优势就凸现出来了。我们来看下面这个例子:

def parse_1(html):
    print('处理页面1的源代码')
def parse_2(html):
    print('处理页面2的源代码')
def parse_3(html):
    print('处理另外一个网站的源代码')
class RequestObj:
    def __init__(self, url, headers=None, method='get', body=None, callback):
        self.url = url
        self.headers = headers
        self.method = method
        self.body = body
        self.callback = callback
async def request(req_obj):
    async with aiohttp.ClientSession() as session:
        if req_obj.method == 'get':
            resp = await session.get(req_obj.url, headers=req_obj.headers)
        else:
            resp = await session.post(req_obj.url, headers=req_obj.headers, json=req_obj.body)
        html = await resp.text(encoding='utf-8')
    req_obj.callback(html)
async main():
    req_obj_list = [
        RequestObj('页面1的 url', headers1, callback=parse_1),
        RequestObj('页面2的 url', headers2, method='post', body={'xx': 1}, callback=parse_2),
        RequstObj('另一个网站的 url', headers3, callback=parse_3)
    ]
    tasks = [request(x) for x in req_obj_list]
    await asyncio.gather(*tasks)

在这个代码片段中,不同的 parse 函数处理不同的 url。我们在创建 RequestObj 对象的时候,把不同的 parse 函数通过 callback 参数与 url 关联起来。那么下载器在请求完成 url 以后,要做的仅仅是调用这个 callback 函数。

这样一来,假设有一个网站,我们先访问列表页,然后从列表页中拿到每一个详情页的 URL 去访问详情页。列表页可以翻页,详情页也可以翻页。通过维护一个全局的队列,我们可以实现,列表页要翻页的时候,把RequestObj 对象放到队列中,详情页要翻页的时候,把 RequestObj 对象也放到队列中。而负责请求网站的代码,不关心它自己请求的是哪个页面,它只管请求,然后调用 callback 传入 html 即可。这样就是实现了,列表页和详情页同时请求。速度大大提升。

下一篇文章,我们来实现这个全局的队列。

目录
相关文章
|
1天前
|
存储 并行计算 前端开发
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术(二)
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术
42 1
|
1天前
|
缓存 负载均衡 网络协议
【亮剑】一次完整的HTTP请求的重要性和详细过程
【4月更文挑战第30天】本文介绍了HTTP请求的重要性和详细过程。首先,DNS解析将域名转换为IP地址,通过递归和迭代查询找到目标服务器。接着,TCP三次握手建立连接。然后,客户端发送HTTP请求,服务器处理请求并返回响应。最后,理解这个过程有助于优化网站性能,如使用DNS缓存、HTTP/2、Keep-Alive、CDN和负载均衡等实践建议。
|
1天前
|
并行计算 数据处理 开发者
Python并发编程:解析异步IO与多线程
本文探讨了Python中的并发编程技术,着重比较了异步IO和多线程两种常见的并发模型。通过详细分析它们的特点、优劣势以及适用场景,帮助读者更好地理解并选择适合自己项目需求的并发编程方式。
|
1天前
|
JavaScript 大数据 开发者
Node.js的异步I/O模型与事件循环:深度解析
【4月更文挑战第29天】本文深入解析Node.js的异步I/O模型和事件循环机制。Node.js采用单线程与异步I/O,遇到I/O操作时立即返回并继续执行,结果存入回调函数队列。事件循环不断检查并处理I/O事件,通过回调函数通知结果,实现非阻塞和高并发。这种事件驱动编程模型简化了编程,使开发者更专注业务逻辑,为高并发场景提供高效解决方案。
|
1天前
|
前端开发 API UED
AngularJS的$http服务:深入解析与进行HTTP请求的技术实践
【4月更文挑战第28天】AngularJS的$http服务是核心组件,用于发起HTTP请求与服务器通信。$http服务简化了通信过程,通过深入理解和实践,能构建高效、可靠的前端应用。
|
1天前
|
Web App开发 前端开发 Java
SpringBoot之请求的详细解析
SpringBoot之请求的详细解析
22 0
|
1天前
|
存储 缓存 Java
SpringBootWeb请求响应之前言及状态码的详细解析
SpringBootWeb请求响应之前言及状态码的详细解析
11 0
|
1天前
|
JSON 安全 Java
Android网络部分-----网络数据请求、解析
Android网络部分-----网络数据请求、解析
Android网络部分-----网络数据请求、解析
|
1天前
|
Linux 程序员 C++
【C++ 常见的异步机制】探索现代异步编程:从 ASIO 到协程的底层机制解析
【C++ 常见的异步机制】探索现代异步编程:从 ASIO 到协程的底层机制解析
152 2
|
1天前
|
监控 算法 Unix
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
【Linux 异步操作】深入理解 Linux 异步通知机制:原理、应用与实例解析
78 0

推荐镜像

更多