「Python|aiohttp|并发与协程」将HTTP请求提速成百上千倍!一次性掌握把requests请求改成协程的通用方法

news2024/12/24 21:56:26

本文主要介绍如何通过使用aiohttp库将同步的http请求改成异步方式请求,从而降低等待网络IO过程中时间和计算资源的浪费。
主要包括如何将常见的requests请求改用aiohttp异步执行以及如何将异步的批量请求方法封装成普通方法/同步调用方式,给业务模块调用。

文章目录

  • 场景说明
    • 大量数据请求场景
    • 用协程解决网络请求密集的任务
  • 解决方案
    • 使用requests库发送请求
    • 异步请求代码的结构
    • 批量请求的同步方式和异步方式代码对照
    • 代码结构差异
      • 导包差异
      • send_single_request差异
      • send_batch_requests差异
      • 外部业务模块调用差异
    • 差异说明
    • 异步方式返回结果无序的解决方案
      • 方式一:按请求顺序排序(请求顺序作为返回数据标识)
      • 方式二:自定义返回数据标识,返回一个键值对
  • 源代码

场景说明

大量数据请求场景

在爬虫通过定制化接口获取数据或者后端处理大量数据请求时,由于数据量较大,所以如果单纯使用同步方式的requests请求,对单台机器的利用率不高,数据处理的速度较慢,同时如果要提速需要增加机器会导致成本提高。

在这种情况下,使用并发是一种成本比较低的方式。在并发中多进程的并发是用于处理计算逻辑较多计算量较大的计算密集型任务,数据请求属于网络IO等待时间占处理时间的大头,不属于计算密集型,而是属于IO密集型任务。

用协程解决网络请求密集的任务

处理IO密集型任务,使用多线程或协程来实现效率的提升。而由于python默认的cpython解释器有全局解释器锁GIL的限制,使得python的多线程会有效率提高,但是无法完全发挥多线程的优势。

而协程在处理IO密集型任务上相当合适,但是在写法上跟我们习惯的同步方式执行的代码写法有些差别。

解决方案

在python中,将http请求使用异步方式执行,主要是使用aiohttp库来快速实现。

我们接下来将明确把一个同步方式的requests请求有哪几个部分,然后通过将各个部分替换成异步的aiohttp请求,来展示将同步请求改成异步请求的步骤。

使用requests库发送请求

最简单的请求代码如下:

import requests

response = requests.get("http://httpbin.org/get")

当然我们在业务中会需要使用到其他参数来满足我们的业务需求,所以可以考虑请求分为三个部分:

  • 构造请求相关的参数/值
  • 发送请求(考虑复用与目标服务器的连接,会需要使用session发送请求)
  • 处理返回结果

代码结构如下:

"""请求过程分为三个部分:
1. 构造参数
2. 发送请求
3. 处理返回结果
"""

import requests

"""构造参数, 包括但不限于:
- headers
- url
- parameters
- proxy
- post请求的body
- ......
"""
headers = {
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'Cache-Control': 'no-cache',
    'Pragma': 'no-cache',
    'Proxy-Connection': 'keep-alive',
    'Referer': 'http://httpbin.org/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
    'accept': 'application/json',
}
url = 'http://httpbin.org/get'

"""
# 新建会话,使用会话发送请求
# 也可以直接requests.get()进行请求
"""
session = requests.session()
response = session.get(url, headers=headers, verify=False)

"""处理返回结果
主要是对返回结果的状态,返回值进行条件检查
在满足某些条件时执行对应的处理逻辑
"""
print(response.status_code)
if response.status_code == 200:
    print(response.json())

异步请求代码的结构

关于异步请求,我们只需要知道下面几点:

  • 同步函数就是用def定义的函数
  • 异步函数就是用async def定义的函数,同时函数体里面会包含await语句
  • 同步请求的逻辑是用循环语句一次接着一次进行"发起请求,等待返回,处理返回结果"的过程
  • 异步请求的逻辑是生成不同的请求任务,然后(几乎)同时发出这批请求,然后请求返回的先后顺序,依次处理各个请求的返回结果
  • 同步请求是一个def 函数传入所有要请求的数据,然后依次传给另一个def函数去执行单个请求的发送和处理逻辑
  • 异步请求是一个async def函数接收所有要请求的数据,然后将各个数据传入另一个async def函数来生成请求任务并批量执行这些请求任务。异步需要额外的一个def函数来启动这个外层的async def 函数

批量请求的同步方式和异步方式代码对照

我们分别展示一下完整的代码结构,然后解释二者的差异。
假设我们批量请求的requests代码结构如下:

"""请求分为三个部分:
1. 构造参数
2. 发送请求
3. 处理返回结果

"""

import requests


def send_single_request(session, parameters: dict):
    # 构造参数, 包括但不限于headers, url, parameters, proxy, post请求的body
    headers = {
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache',
        'Proxy-Connection': 'keep-alive',
        'Referer': 'http://httpbin.org/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
        'accept': 'application/json',
    }
    url = 'http://httpbin.org/get'

    # 新建会话,使用会话发送请求
    # (也可以直接requests.get()进行请求)
    response = session.get(url, params=parameters, headers=headers)

    # 处理返回结果
    print(response.status_code)
    if response.status_code == 200:
        json_response = response.json()
        print(json_response)
        return json_response["args"]


def send_batch_requests(batch_data):
    all_result = []
    session = requests.session()

    for parameters in batch_data:
        request_result = send_single_request(session, parameters)
        all_result.append(request_result)

    return all_result


# 业务模块在有请求需求时调用接口的业务逻辑
def appliaction_logic_to_call_requests():
    batch_data_to_request = [{"num": num} for num in range(1, 11)]
    all_result = send_batch_requests(batch_data_to_request)
    # process the result


appliaction_logic_to_call_requests()

则改成批量的aiohttp异步请求的方式,代码结构如下:

"""请求分为三个部分:
1. 构造参数
2. 发送请求
3. 处理返回结果

"""

import aiohttp
import asyncio
import json


async def send_single_request(session, parameters: dict):
    # 构造参数, 包括但不限于headers, url, parameters, proxy, post请求的body
    headers = {
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache',
        'Proxy-Connection': 'keep-alive',
        'Referer': 'http://httpbin.org/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
        'accept': 'application/json',
    }
    url = 'http://httpbin.org/get'

    # 新建会话,使用会话发送请求
    # (也可以直接requests.get()进行请求)
    async with session.get(url, params=parameters, headers=headers) as response:
        raw_response = await response.read()

        # 处理返回结果
        print(response.status)
        if response.status == 200:
            json_response = json.loads(raw_response)
            print(json_response)
            return json_response["args"]


async def send_batch_requests(batch_data):
    all_result = []
    async with aiohttp.ClientSession() as session:
        # # 我更喜欢使用列表推导式来生成任务,简单明了
        # tasks = [asyncio.create_task(send_single_request(session, parameters)) for parameters in batch_data]

        tasks = []
        for parameters in batch_data:
            single_task = asyncio.create_task(send_single_request(session, parameters))
            tasks.append(single_task)
        all_result = await asyncio.gather(*tasks)

    return all_result

"""异步方式需要增加一个额外的函数给外部调用
这样对外部调用逻辑来说,批量请求是同步还是异步是隐藏的细节(封装)
"""
def logic_to_start_async_tasks(batch_data):
    all_result = asyncio.run(send_batch_requests(batch_data))
    return all_result


# 业务模块在有请求需求时调用接口的业务逻辑
def appliaction_logic_to_call_requests():
    batch_data_to_request = [{"num": num} for num in range(1, 11)]
    all_result = logic_to_start_async_tasks(batch_data_to_request)
    # process the result


appliaction_logic_to_call_requests()

代码结构差异

用版本管理工具查看两个版本的代码,差异如下:
红色高亮代码为旧版本代码,即同步方式的requests结构代码
绿色高亮代码为新版本代码,即异步方式的aiohttp结构代码

导包差异

在这里插入图片描述

send_single_request差异

在这里插入图片描述

send_batch_requests差异

在这里插入图片描述

外部业务模块调用差异

在这里插入图片描述

差异说明

我们可以看到异步的的代码结构主要差异如下:

  • 具体进行异步请求的函数需要使用async def进行定义
  • async def的函数体必须包含await语句:
    • 批量创建和调度任务的函数send_batch_requestsawait用于等待任务的执行和返回:await asyncio.gather(*tasks)
    • 具体发送请求和处理返回结果的函数send_single_requestawait用于等待外部网络服务器返回awiat session.get()以及读取返回值await response.read()
    • send_single_request中也可以对response调用json()方法,但是也要使用await,(例:json_response = await response.json()),可以自行选择使用哪种用法。
  • session创建用的函数不同:
    • requestsrequests.session()创建一个可以复用的新会话
    • aiohttpaiohttp.ClientSession()创建一个可以复用的新会话
    • aiohttp的session不一定要搭配上下文管理器使用,也可以用session = aiohttp.ClientSession()创建session;使用上下文可以更好避免session没有关闭的问题
  • 可能存在一些属性或方法的名称有差异:
    • requests中的response状态码的属性名为status_code
    • aiohttp中的response状态码的属性名为status
  • 异步方法需要增加一个同步函数来启动异步创建和执行任务的逻辑
    • 为了让外部调用完全没有感知,我们在实际业务中会使用send_batch_requests作为同步函数的名称,异步函数可以改为async def create_and_execute_tasks,这样对于外部来说,在同步和异步两个版本中调用的API名称没有变化,都是调用的send_batch_requests方法

只要注意以上这些差异,就可以熟练地在异步和同步方式之间转换。

异步方式返回结果无序的解决方案

当我们使用同步的方式进行请求的时候,由于是完成上一个请求才会执行下一个请求,所以返回结果是有序的,如下:
在这里插入图片描述

而当我们使用异步的方式进行请求时,由于请求是直接接连发出的,不会等待上一个请求完成返回并处理后才发出下一个请求,并且请求返回的次序是不固定的(尽管我们是有序发出的请求),所以处理完的结果可能是无序的,如下:
在这里插入图片描述

由于我们在使用中需要知道哪个返回结果是对应哪个请求数据的,所以返回结果无序是需要解决的。解决的方法也很简单,异步请求的方法返回一个唯一标志,然后外部管理异步任务的异步方法根据返回的唯一标志进行排序或者其他处理

方式一:按请求顺序排序(请求顺序作为返回数据标识)

代码变动如下:
在这里插入图片描述
在这里插入图片描述
这样一来,最后返回的结果就是有序的:
在这里插入图片描述

方式二:自定义返回数据标识,返回一个键值对

使用数据请求顺序来说可以让返回结果有序,但从业务实际应用来说,这种方式应用场景较少。
因为实际数据使用都是通过数据内容来定位数据,而不是数据的顺序来定位数据。

举一个具体例子:

我们要请求10个用户的数据,由于对每一个用户可能是走同一套逻辑,所以这种情况可以是批量请求,然后按顺序把"用户ID"和"根据用户ID请求到的用户数据"传入后续的处理逻辑中。

但事实上用户数据的不同信息可能是由不同请求去获取的,比如用户信息包括地址和手机,而地址和手机分别存在不同的数据库表中,或者由不同的API获取,所以我们可以是发出get/user/addressget/user/telephone两个请求,然后返回结果是{"user_id": "xxx", "telephone": "xxxx"}{"user_id": "xxx", "address": "xxxxxx"},这种时候我们是知道要请求哪些信息项的,比如info_items = ["address", "telephone"]

所以可以有两种写法,如下:

"""写法一:主要还是用顺序作为数据标识以及排序依据
然后根据item在info_items的顺序与在结果的顺序一一对应的特点来返回最终结果
"""
info_items = ["address", "telephone"]

tasks = []
for index, item in enumerate(info_items):
	API = f"get/user/{item}"
    session = aiohttp.ClientSession()
    single_task = asyncio.create_task(send_single_request(session, item, identity=index))
    tasks.append(single_task)
all_result = await asyncio.gather(*tasks)
all_result = sorted(all_result, key=lambda result: int(result[0]))
all_result = [result[1] for result in all_result]

user_info = {
      info_item[index]: all_result[index] for index in range(len(info_items))
}
return user_info

"""方式二: 直接用item作为identity
然后直接用identity和返回结果生成键值对
"""
info_items = ["address", "telephone"]

tasks = []
for item in info_items:
    API = f"get/user/{item}"
    session = aiohttp.ClientSession()
    single_task = asyncio.create_task(send_single_request(session, item, identity=item))
    tasks.append(single_task)
all_result = await asyncio.gather(*tasks)

user_info = {
      result[0]: result[1] for result in all_result
}
return user_info

个人推荐第二种方式,编写上更符合python的风格,阅读上也更清晰明了

源代码

完整的异步代码结构如下:

"""请求分为三个部分:
1. 构造参数
2. 发送请求
3. 处理返回结果

"""

import aiohttp
import asyncio
import json


async def send_single_request(session, parameters: dict, identity):
    # 构造参数, 包括但不限于headers, url, parameters, proxy, post请求的body
    headers = {
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache',
        'Proxy-Connection': 'keep-alive',
        'Referer': 'http://httpbin.org/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
        'accept': 'application/json',
    }
    url = 'http://httpbin.org/get'

    # 新建会话,使用会话发送请求
    # (也可以直接requests.get()进行请求)
    async with session.get(url, params=parameters, headers=headers) as response:
        raw_response = await response.read()

        # 处理返回结果
        # print(response.status)
        if response.status == 200:
            json_response = json.loads(raw_response)
            # json_response = await response.json()
            # print(json_response)
            # return identity, json_response
            return identity, {"args": json_response["args"], "url": json_response["url"]}


async def create_and_execute_tasks(batch_data):
    all_result = {}
    async with aiohttp.ClientSession() as session:
        # # 我更喜欢使用列表推导式来生成任务,简单明了
        # tasks = [asyncio.create_task(send_single_request(session, parameters)) for parameters in batch_data]

        tasks = []
        for index, parameters in enumerate(batch_data):
            single_task = asyncio.create_task(send_single_request(session, parameters, identity=index))
            tasks.append(single_task)
        identity_and_data_pairs = await asyncio.gather(*tasks)
        all_result = {pair[0]: pair[1] for pair in identity_and_data_pairs}

    return all_result


"""异步方式需要增加一个额外的函数给外部调用
这样对外部调用逻辑来说,批量请求是同步还是异步是隐藏的细节(封装)
"""


def send_batch_requests(batch_data):
    all_result = asyncio.run(create_and_execute_tasks(batch_data))
    return all_result


# 业务模块在有请求需求时调用接口的业务逻辑
def appliaction_logic_to_call_requests():
    batch_data_to_request = [{"num": num} for num in range(1, 11)]
    all_result = send_batch_requests(batch_data_to_request)
    return all_result
    # process the result


print("异步方式返回结果")
print(appliaction_logic_to_call_requests())

在实际业务开发中,根据具体的业务需求修改send_single_request以及create_and_execute_tasks即可,比如:

  • send_single_request中get请求方式改为post请求方式,并且增删使用的参数
  • create_and_execute_tasks从batch_data单个数据parameters中生成identity

掌握了上面这些,使用python写异步的http请求,就手到擒来了(●ˇ∀ˇ●)

好书推荐:

  • 流畅的python
  • Python编程 从入门到实践 第2版
  • Python数据结构与算法分析 第2版

好课推荐:

  • 零基础学python
  • python核心技术与实战
  • python自动化办公实战

写文不易,如果对你有帮助的话,来一波点赞、收藏、关注吧~👇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/892671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker容器进入的4种方式(推荐最后一种)

Docker容器进入的4种方式【推荐最后一种】 在使用Docker创建了容器之后,大家比较关心的就是如何进入该容器了,其实进入Docker容器有好几多种方式,这里我们就讲一下常用的几种进入Docker容器的方法。 一、使用docker attach进入Docker容器二、…

问题解决 | 关于torch中遇到的错误及解决

本博客主要解决在torch使用中遇到的问题与解决~ 1.安装相关问题 1.1.conda虚拟环境内无法安装torch(pip install torch ) 解决方案: 如果是GPU版本,先查看cuda版本如果nvcc -V 命令运行后出来的是cuda11.3的话,其他…

修改 el-select 背景图 样式

1. 原图------------效果图 2. css /***********大的背景图***************/ .el-popper.is-pure {background: url(/src/assets/imgList/memuBG.png) no-repeat;border: none;background-size: 100% 100%; }/*********选中行的字体***********/ .el-select-dropdown__item.s…

【数据库系统】--【3】DBMS数据组织

DBMS数据组织 01关系表的数据组织02索引的数据组织03元数据的数据组织04数据组织的优化 01关系表的数据组织 02索引的数据组织 03元数据的数据组织 04数据组织的优化 小结. ●关系表的数据组织 ●索引的数据组织 B树索引HASH索引Bitmap索引 ●元数据的数据组织 ●数据组织的优…

一文了解汽车芯片的分类及用途介绍

汽车芯片按其功能可分为控制类(MCU和AI芯片)、功率类、传感器和其他(如存储器)四种类型。市场基本被国际巨头所垄断。人们常说的汽车芯片是指汽车里的计算芯片,按集成规模可分为MCU芯片和AI芯片(SoC芯片&am…

Camunda 7.x 系列【18】服务任务

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 2.7.9 本系列Camunda 版本 7.19.0 源码地址:https://gitee.com/pearl-organization/camunda-study-demo 文章目录 1. 概述2. 代码委托2.1 Java 委托3. 流程建模3.1 基础3.2 实现3.3 结束4. 测试1. 概述 用户…

vue3+vite+ts引入海康威视监控

1.首先我们在海康威视官网上面下载这个 下载以后打开demo 把这三个文件放进你的public里面去 在vue3项目里的index.html引入 <!--三个必要的js文件引入--><script src"./public/hikvision/jquery-1.12.4.min.js"></script><script src".…

Python学习笔记_基础篇(十一)_socket编程

python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的。硬件中的CPU是计算机的核心&#xff0c;它承担计算机的所有任务。 操作系统是运行在硬件之上的软件&#xff0c;是计算机的管理者&#xff0c;它负责资源的管理和分配、任务的调度。 程序是运行…

基于 spring boot 的动漫信息管理系统【源码在文末】

半山腰总是最挤的&#xff0c;你得去山顶看看 大学生嘛&#xff0c;论文写不出&#xff0c;代码搞不懂不要紧&#xff0c;重要的是&#xff0c;从这一刻就开始学习&#xff0c;立刻马上&#xff01; 今天带来的是最新的选题&#xff0c;基于 spring boot 框架的动漫信息管理系…

Mysql_5.7下载安装与配置基础操作教程

目录 一、Mysql57下载与安装 二、尝试登录Mysql 三、配置Mysql环境变量 一、Mysql57下载与安装 首先&#xff0c;进入Mysql下载官网&#xff1a;MySQL Community Downloads 随后&#xff0c;选择版本5.7.43&#xff0c;系统选择Windows&#xff0c;随后下方会出现两个下载选…

81-基于stm32单片机DHT11温湿度MQ4可燃气体天然气浓度检测系统自动散热加湿排气Proteus仿真+源码...

资料编号&#xff1a;081 一&#xff1a;功能介绍&#xff1a; 1、采用stm32单片机OLED显示屏MQ4可燃气体浓度检测DHT11温湿度电机按键&#xff0c;制作一个温湿度采集、MQ4可燃气体浓度采集&#xff0c;OLED显示相关数据&#xff0c; 2、通过按键设置温度上限、湿度下限、可燃…

浅谈OCR中的David Shepard

在OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;中&#xff0c;David Shepard是一种早期的OCR技术&#xff0c;也被称为Shepards Method。 David Shepard是该OCR方法的原始作者。这种方法基于边界追踪算法&#xff0c;用于识别印刷体文本…

mkv视频格式怎么转换为mp4?

mkv视频格式怎么转换为mp4&#xff1f;实际上&#xff0c;我们将MKV格式的文件转换成 MP4格式之后&#xff0c;能够在很大程度上提高原文件的利用率&#xff0c;也保证了文件的兼容性。很多时候&#xff0c;由于格式限制问题&#xff0c;文件在某些设备和软件上无法正常播放。所…

❤ 全面解析若依框架vue2版本(springboot-vue前后分离--前端部分)

❤ 解析若依框架之前台修改 1、修改页面标题和logo 修改网页上的logo ruoyi-ui --> public --> favicon.ico&#xff0c;把这个图片换成你自己的logo 修改网页标题 根目录下的vue.config.js const name process.env.VUE_APP_TITLE || ‘若依管理系统’ // 网页标题 换成…

WebGL游戏站优化实录【myshmup.com】

myshmup.com 允许在浏览器中创建 shmup&#xff08;射击&#xff09;游戏。 你可以使用具有创意通用许可证的资源或上传自己的艺术作品和声音。 创建的游戏可以在网站上发布。 该平台不需要编码&#xff0c;游戏对象的配置是在用户界面的帮助下执行的。 后端是使用Django框架开…

实力演绎不凡服务,伊士曼漆面保护膜裁切数据系统焕新面世 创新成就不凡服务体验

中国&#xff0c;上海&#xff0c;近日——汽车后市场数字化服务新时代即将到来。全球性特种材料公司伊士曼正通过创新升级的全新漆面保护膜裁切数据系统「EMN CORE PATTERN」&#xff0c;应对汽车后市场转型趋势&#xff0c;以迈入全新阶段的漆面保护膜智能裁切技术。用更专业…

读SQL学习指南(第3版)笔记01_背景知识

1. 数据库 1.1. 一组相关信息 1.2. 电话簿肯定是最为普及且常用的数据库 2. 非关系型数据库系统 2.1. 层次数据库系统 2.1.1. 以一个或多个树形结构来表示数据 2.1.2. 提供了定位特定客户信息树的工具&#xff0c;并能够遍历该树找到所需的账户和/或交易 2.1.3. 树中的每…

VMWARE15.5.7安装RedHat 7.9黑屏

在win7上面最高版本为vmware15.5.7 我安装后&#xff0c;安装成功了redhat5.5 但是因为操作系统有点老。安装其他软件时遇到问题。想改为7.0以上linux。 但是遇到了问题。安装时&#xff0c;一直黑屏。包括再安装5.5也是同样效果。 找了很多方案都无济于事。后面搜到一个方案…

SpringBoot3.x Starter自动装配的坑

最近有个写IAM平台的设想&#xff0c;里面涉及到了行为审计&#xff0c;就在思考如果其他各个模块都去写一遍审计平台的配置和方法&#xff0c;那多麻烦&#xff0c;不如写个工具类。但是一般的工具类要使用方法就得实例化&#xff0c;十分的繁琐。于是写一个Starter的想法应运…

docker run 命令30个常用参数详解

文章目录 0.前言docker run 命令示例 2.Docker run 多种用法知其然知其所以然1. 基本用法2. 启动交互式容器3. 映射端口4. 挂载文件/目录5. 设置环境变量6. 指定容器名称7. 后台运行容器8. 重启策略9. 其他参数 2. docker run 命令参数详解1. -d&#xff1a;以后台模式&#xf…