aiohttp 简易使用教程

本文介绍aiohttp库的使用方法,包括安装、基本请求、发起session请求、URL参数传递等内容。此外,还详细讲解了如何处理响应内容、自定义请求头和Cookie、POST数据、控制连接数量及设置代理等高级功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 前言

本文翻译自aiohttp的官方文档,如有纰漏,欢迎指出。

aiohttp分为服务器端和客户端,本文只介绍客户端。

由于上下文的缘故,请求代码必须在一个异步的函数中进行:

async def fn():

pass

 

1. aiohttp安装

 

pip install aiohttp

 

 

1.1. 基本请求用法

 

 

async with aiohttp.request('GET','https://github.com') as r:
        await r.text()

其中r.text(), 可以在括号中指定解码方式,编码方式,例如

await resp.text(encoding='windows-1251')

 

或者也可以选择不编码,适合读取图像等,是无法编码的

await resp.read()

 

#使用示例, 进行一次请求

import aiohttp, asyncio

async def main():#aiohttp必须放在异步函数中使用
    async with aiohttp.request('GET', 'https://api.github.com/events') as resp:
        json = await resp.json()
        print(json)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

------------------------------------------------------------------------------
#使用示例,进行多次请求

import aiohttp, asyncio

async def main():#aiohttp必须放在异步函数中使用
    tasks = []
    [tasks.append(fetch('https://api.github.com/events?a={}'.format(i))) for i in range(10)]#十次请求
    await asyncio.wait(tasks)

async def fetch(url):
    async with aiohttp.request('GET', url) as resp:
        json = await resp.json()
        print(json)    

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
------------------------------------------------------------------------------
#使用示例,进行多次请求,并限制同时请求的数量

import aiohttp, asyncio

async def main(pool):#aiohttp必须放在异步函数中使用
    tasks = []
    sem = asyncio.Semaphore(pool)#限制同时请求的数量
    [tasks.append(control_sem(sem, 'https://api.github.com/events?a={}'.format(i))) for i in range(10)]#十次请求
    await asyncio.wait(tasks)

async def control_sem(sem, url):#限制信号量
    async with sem:
        await fetch(url)

async def fetch(url):
    async with aiohttp.request('GET', url) as resp:
        json = await resp.json()
        print(json)    

loop = asyncio.get_event_loop()
loop.run_until_complete(main(pool=2))

上面的示例中可以正确的使用协程进行请求,但是由于aiohttp自身的原因会报 Unclosed client session 的警告。官方不推荐使用aiohttp.request的方式请求,可以将 aiohttp.request 换成 aiohttp.ClientSession(**kw).request的方式即可。 

具体请看2.发起一个session请求

 

2.发起一个session请求

 

首先是导入aiohttp模块:

 

import aiohttp

然后我们试着获取一个web源码,这里以GitHub的public Time-line页面为例:

 

async with aiohttp.ClientSession() as session:
    async with session.get('https://api.github.com/events') as resp:
        print(resp.status)
        print(await resp.text())

上面的代码中,我们创建了一个 ClientSession 对象命名为session,然后通过session的get方法得到一个 ClientResponse 对象,命名为resp,get方法中传入了一个必须的参数url,就是要获得源码的http url。至此便通过协程完成了一个异步IO的get请求。

 

有get请求当然有post请求,并且post请求也是一个协程:

 

session.post('http://httpbin.org/post', data=b'data')

用法和get是一样的,区别是post需要一个额外的参数data,即是需要post的数据。

 

除了get和post请求外,其他http的操作方法也是一样的:

 

session.put('http://httpbin.org/put', data=b'data')
session.delete('http://httpbin.org/delete')
session.head('http://httpbin.org/get')
session.options('http://httpbin.org/get')
session.patch('http://httpbin.org/patch', data=b'data')

小记:

 

不要为每次的连接都创建一次session,一般情况下只需要创建一个session,然后使用这个session执行所有的请求。

每个session对象,内部包含了一个连接池,并且将会保持连接和连接复用(默认开启)可以加快整体的性能。

#使用示例

import aiohttp, asyncio

async def main(pool):#启动
    sem = asyncio.Semaphore(pool)
    async with aiohttp.ClientSession() as session:#给所有的请求,创建同一个session
        tasks = []
        [tasks.append(control_sem(sem, 'https://api.github.com/events?a={}'.format(i), session)) for i in range(10)]#十次请求
        await asyncio.wait(tasks)

async def control_sem(sem, url, session):#限制信号量
    async with sem:
        await fetch(url, session)

async def fetch(url, session):#开启异步请求
    async with session.get(url) as resp:
        json = await resp.json()
        print(json)

loop = asyncio.get_event_loop()
loop.run_until_complete(main(pool=2))

速度快的不要不要的

 

3.在URL中传递参数

 

我们经常需要通过 get 在url中传递一些参数,参数将会作为url问号后面的一部分发给服务器。在aiohttp的请求中,允许以dict的形式来表示问号后的参数。举个例子,如果你想传递 key1=value1   key2=value2 到 httpbin.org/get 你可以使用下面的代码:

 

params = {'key1': 'value1', 'key2': 'value2'}
async with session.get('http://httpbin.org/get',
                       params=params) as resp:
                       assert resp.url == 'http://httpbin.org/get?key2=value2&key1=value1'

可以看到,代码正确的执行了,说明参数被正确的传递了进去。不管是一个参数两个参数,还是更多的参数,都可以通过这种方式来传递。除了这种方式之外,还有另外一个,使用一个 list 来传递(这种方式可以传递一些特殊的参数,例如下面两个key是相等的也可以正确传递):

 

 

params = [('key', 'value1'), ('key', 'value2')]
async with session.get('http://httpbin.org/get',
                       params=params) as r:
    assert r.url == 'http://httpbin.org/get?key=value2&key=value1'

除了上面两种,我们也可以直接通过传递字符串作为参数来传递,但是需要注意,通过字符串传递的特殊字符不会被编码:

 

 

async with session.get('http://httpbin.org/get',
                       params='key=value+1') as r:
        assert r.url == 'http://httpbin.org/get?key=value+1'

 

 

4.响应的内容

 

还是以GitHub的公共Time-line页面为例,我们可以获得页面响应的内容:

 

async with session.get('https://api.github.com/events') as resp:
    print(await resp.text())

运行之后,会打印出类似于如下的内容:

 

 

'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

resp的text方法,会自动将服务器端返回的内容进行解码--decode,当然我们也可以自定义编码方式:

 

 

await resp.text(encoding='gb2312')

除了text方法可以返回解码后的内容外,我们也可以得到类型是字节的内容:

 

 

print(await resp.read())

运行的结果是:

 

 

b'[{"created_at":"2015-06-12T14:06:22Z","public":true,"actor":{...

 

gzip和deflate转换编码已经为你自动解码。

小记:

text(),read()方法是把整个响应体读入内存,如果你是获取大量的数据,请考虑使用”字节流“(streaming response

 

5.特殊响应内容:json

 

如果我们获取的页面的响应内容是json,aiohttp内置了更好的方法来处理json:

 

async with session.get('https://api.github.com/events') as resp:
    print(await resp.json())

如果因为某种原因而导致resp.json()解析json失败,例如返回不是json字符串等等,那么resp.json()将抛出一个错误,也可以给json()方法指定一个解码方式:

 

 

print(await resp.json(
encoding='gb2312'

)) 或者传递一个函数进去:

 

 

print(await resp.json( lambda(x:x.replace('a','b')) ))

 

 

6.以字节流的方式读取响应内容

 

虽然json(),text(),read()很方便的能把响应的数据读入到内存,但是我们仍然应该谨慎的使用它们,因为它们是把整个的响应体全部读入了内存。即使你只是想下载几个字节大小的文件,但这些方法却将在内存中加载所有的数据。所以我们可以通过控制字节数来控制读入内存的响应内容:

 

async with session.get('https://api.github.com/events') as resp:
    await resp.content.read(10) #读取前10个字节

一般地,我们应该使用以下的模式来把读取的字节流保存到文件中:

 

 

with open(filename, 'wb') as fd:
    while True:
        chunk = await resp.content.read(chunk_size)
        if not chunk:
            break
        fd.write(chunk)

 

 

7.自定义请求头

 

如果你想添加请求头,可以像get添加参数那样以dict的形式,作为get或者post的参数进行请求:

 

import json
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
headers = {'content-type': 'application/json'}

await session.post(url,
                   data=json.dumps(payload),
                   headers=headers)

 

 

8.自定义Cookie

 

给服务器发送cookie,可以通过给 ClientSession 传递一个cookie参数:

 

url = 'http://httpbin.org/cookies'
cookies = {'cookies_are': 'working'}
async with ClientSession(cookies=cookies) as session:
    async with session.get(url) as resp:
        assert await resp.json() == {
           "cookies": {"cookies_are": "working"}}

 

可直接访问链接 “httpbin.org/cookies”查看当前cookie,访问session中的cookie请见第10节。

 

9.post数据的几种方式

 

(1)模拟表单post数据

 

payload = {'key1': 'value1', 'key2': 'value2'}
async with session.post('http://httpbin.org/post',
                        data=payload) as resp:
    print(await resp.text())

 

注意:data=dict的方式post的数据将被转码,和form提交数据是一样的作用,如果你不想被转码,可以直接以字符串的形式 data=str 提交,这样就不会被转码。

(2)post json

 

import json
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}

async with session.post(url, data=json.dumps(payload)) as resp:
    ...

其实json.dumps(payload)返回的也是一个字符串,只不过这个字符串可以被识别为json格式

 

(3)post 小文件

 

url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}

await session.post(url, data=files)

可以设置好文件名和content-type:

 

 

url = 'http://httpbin.org/post'
data = FormData()
data.add_field('file',
               open('report.xls', 'rb'),
               filename='report.xls',
               content_type='application/vnd.ms-excel')

await session.post(url, data=data)

如果将文件对象设置为数据参数,aiohttp将自动以字节流的形式发送给服务器。

 

(4)post 大文件

aiohttp支持多种类型的文件以流媒体的形式上传,所以我们可以在文件未读入内存的情况下发送大文件。

 

 

@aiohttp.streamer
def file_sender(writer, file_name=None):
    with open(file_name, 'rb') as f:
        chunk = f.read(2**16)
        while chunk:
            yield from writer.write(chunk)
            chunk = f.read(2**16)

# Then you can use `file_sender` as a data provider:

async with session.post('http://httpbin.org/post',
                        data=file_sender(file_name='huge_file')) as resp:
    print(await resp.text())

同时我们可以从一个url获取文件后,直接post给另一个url,并计算hash值:

 

 

async def feed_stream(resp, stream):
    h = hashlib.sha256()

    while True:
        chunk = await resp.content.readany()
        if not chunk:
            break
        h.update(chunk)
        stream.feed_data(chunk)

    return h.hexdigest()

resp = session.get('http://httpbin.org/post')
stream = StreamReader()
loop.create_task(session.post('http://httpbin.org/post', data=stream))

file_hash = await feed_stream(resp, stream)

因为响应内容类型是StreamReader,所以可以把get和post连接起来,同时进行post和get:

 

 

r = await session.get('http://python.org')
await session.post('http://httpbin.org/post',
                   data=r.content)

(5)post预压缩数据

 

在通过aiohttp发送前就已经压缩的数据, 调用压缩函数的函数名(通常是deflate 或 zlib)作为content-encoding的值:

 

async def my_coroutine(session, headers, my_data):
    data = zlib.compress(my_data)
    headers = {'Content-Encoding': 'deflate'}
    async with session.post('http://httpbin.org/post',
                            data=data,
                            headers=headers)
        pass

 

 

10.keep-alive, 连接池,共享cookie

 

ClientSession 用于在多个连接之间共享cookie:

 

async with aiohttp.ClientSession() as session:
    await session.get(
        'http://httpbin.org/cookies/set?my_cookie=my_value')
    filtered = session.cookie_jar.filter_cookies('http://httpbin.org')
    assert filtered['my_cookie'].value == 'my_value'
    async with session.get('http://httpbin.org/cookies') as r:
        json_body = await r.json()
        assert json_body['cookies']['my_cookie'] == 'my_value'

也可以为所有的连接设置共同的请求头:

 

 

async with aiohttp.ClientSession(
    headers={"Authorization": "Basic bG9naW46cGFzcw=="}) as session:
    async with session.get("http://httpbin.org/headers") as r:
        json_body = await r.json()
        assert json_body['headers']['Authorization'] == \
            'Basic bG9naW46cGFzcw=='

ClientSession 还支持 keep-alive连接和连接池(connection pooling)

 

 

11.cookie安全性

 

默认ClientSession使用的是严格模式的 aiohttp.CookieJar. RFC 2109,明确的禁止接受url和ip地址产生的cookie,只能接受 DNS 解析IP产生的cookie。可以通过设置aiohttp.CookieJar 的 unsafe=True 来配置:

 

jar = aiohttp.CookieJar(unsafe=True)
session = aiohttp.ClientSession(cookie_jar=jar)

 

 

12.控制同时连接的数量(连接池)

 

也可以理解为同时请求的数量,为了限制同时打开的连接数量,我们可以将限制参数传递给连接器:

 

conn = aiohttp.TCPConnector(limit=30)#同时最大进行连接的连接数为30,默认是100,limit=0的时候是无限制

限制同时打开限制同时打开连接到同一端点的数量((host, port, is_ssl) 三的倍数),可以通过设置 limit_per_host 参数:

 

 

conn = aiohttp.TCPConnector(limit_per_host=30)#默认是0

 

 

13.自定义域名解析

 

我们可以指定域名服务器的 IP 对我们提供的get或post的url进行解析:

 

from aiohttp.resolver import AsyncResolver

resolver = AsyncResolver(nameservers=["8.8.8.8", "8.8.4.4"])
conn = aiohttp.TCPConnector(resolver=resolver)

 

 

14.设置代理

 

aiohttp支持使用代理来访问网页:

 

async with aiohttp.ClientSession() as session:
    async with session.get("http://python.org",
                           proxy="http://some.proxy.com") as resp:
        print(resp.status)

当然也支持需要授权的页面:

async with aiohttp.ClientSession() as session:
    proxy_auth = aiohttp.BasicAuth('user', 'pass')
    async with session.get("http://python.org",
                           proxy="http://some.proxy.com",
                           proxy_auth=proxy_auth) as resp:
        print(resp.status)

或者通过这种方式来验证授权:

session.get("http://python.org",
            proxy="http://user:pass@some.proxy.com")

 

 

15.响应状态码 response status code

 

可以通过 resp.status来检查状态码是不是200:

 

async with session.get('http://httpbin.org/get') as resp:
    assert resp.status == 200

 

 

16.响应头

 

我们可以直接使用 resp.headers 来查看响应头,得到的值类型是一个dict:

 

>>> resp.headers
{'ACCESS-CONTROL-ALLOW-ORIGIN': '*',
 'CONTENT-TYPE': 'application/json',
 'DATE': 'Tue, 15 Jul 2014 16:49:51 GMT',
 'SERVER': 'gunicorn/18.0',
 'CONTENT-LENGTH': '331',
 'CONNECTION': 'keep-alive'}

或者我们可以查看原生的响应头:

 

 

>>> resp.raw_headers
((b'SERVER', b'nginx'),
 (b'DATE', b'Sat, 09 Jan 2016 20:28:40 GMT'),
 (b'CONTENT-TYPE', b'text/html; charset=utf-8'),
 (b'CONTENT-LENGTH', b'12150'),
 (b'CONNECTION', b'keep-alive'))

 

 

17.查看cookie

 

 

url = 'http://example.com/some/cookie/setting/url'
async with session.get(url) as resp:
    print(resp.cookies)

 

18.重定向的响应头

 

 

如果一个请求被重定向了,我们依然可以查看被重定向之前的响应头信息:

 

>>> resp = await session.get('http://example.com/some/redirect/')
>>> resp
<ClientResponse(http://example.com/some/other/url/) [200]>
>>> resp.history
(<ClientResponse(http://example.com/some/redirect/) [301]>,)

 

 

19.超时处理

 

默认的IO操作都有5分钟的响应时间 我们可以通过 timeout 进行重写:

 

async with session.get('https://github.com', timeout=60) as r:
    ...

如果 timeout=None 或者 timeout=0 将不进行超时检查,也就是不限时长。

 

 

<think>好的,用户现在想学习或开发与Python相关的服务器编程,需要教程或实现指导。先回顾之前的对话,用户之前询问了如何搭建本地服务器,并连接到手机,我给出了使用Python内置HTTP服务器和Apache的方法,以及进阶配置和安全建议。现在用户进一步想深入Python服务器编程,可能需要更底层的知识,比如使用socket编程,或者使用框架如Flask、Django,或者异步框架如FastAPI、aiohttp。 用户提供的引用中提到了实现简易Web服务器和异步编程,这可能暗示他们对这两个方向感兴趣。需要结合这些信息来构建回答。用户可能需要从基础开始,逐步深入,同时涉及同步和异步的方法。此外,用户可能希望了解实际应用场景和项目示例,以便实践。 接下来要确定回答的结构。首先分层次,从基础到进阶,涵盖不同的库和框架。可能分为以下几个部分:基础网络编程(socket)、同步HTTP服务器(http.server)、Web框架(Flask)、异步编程(asyncio、aiohttp)、项目实践。每个部分给出代码示例,并解释关键点,如处理请求、路由、中间件、数据库连接等。同时,结合引用中的内容,强调异步编程的优势和应用场景。 还需要注意用户可能的痛点,比如如何处理并发、性能优化、安全性等。在教程中提供相关建议,如使用异步提升性能,使用WSGI服务器部署,或者结合Nginx做反向代理。此外,安全问题如输入验证、防止SQL注入等也需要提及。 最后,生成相关问题,帮助用户进一步探索,比如比较不同框架,优化服务器性能,或者具体的项目实现步骤。确保问题覆盖用户可能的下一个步骤,引导深入学习。</think>### Python服务器编程从入门到进阶指南 (结合网络编程核心原理与主流框架实现) --- #### 一、基础网络编程:理解底层协议 1. **Socket编程核心** - **TCP服务端模板**: ```python import socket # 创建TCP套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(('0.0.0.0', 8000)) # 绑定所有网络接口 server_socket.listen(5) # 设置等待队列长度 while True: client_socket, addr = server_socket.accept() print(f"来自 {addr} 的连接") client_socket.send(b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from raw socket!") client_socket.close() ``` - **关键点**:`bind()`绑定端口、`listen()`监听连接、`accept()`接受请求[^1] 2. **HTTP协议解析实践** - 解析请求头示例: ```python data = client_socket.recv(1024).decode('utf-8') headers = data.split('\r\n') method, path, _ = headers[0].split(' ') print(f"Method: {method}, Path: {path}") ``` --- #### 二、同步HTTP服务器开发 1. **标准库`http.server`深度使用** - **自定义请求处理器**: ```python from http.server import HTTPServer, BaseHTTPRequestHandler class CustomHandler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() self.wfile.write(f"<h1>访问路径: {self.path}</h1>".encode()) HTTPServer(('', 8000), CustomHandler).serve_forever() ``` - **扩展功能**:添加路由解析、文件服务、POST数据处理 2. **WSGI接口实现** - 符合PEP 3333规范的最小WSGI应用: ```python def application(environ, start_response): status = '200 OK' headers = [('Content-Type', 'text/plain')] start_response(status, headers) return [b"Hello WSGI World"] # 使用第三方库运行 from wsgiref.simple_server import make_server make_server('', 8000, application).serve_forever() ``` --- #### 三、异步服务器开发(高性能场景) 1. **asyncio核心架构** - **异步TCP服务器**: ```python import asyncio async def handle_client(reader, writer): data = await reader.read(100) writer.write(b"HTTP/1.1 200 OK\r\n\r\nAsync Response") await writer.drain() writer.close() async def main(): server = await asyncio.start_server(handle_client, '0.0.0.0', 8000) async with server: await server.serve_forever() asyncio.run(main()) ``` - **优势**:单线程处理数千并发连接[^2] 2. **aiohttp框架实战** - **REST API服务示例**: ```python from aiohttp import web async def hello(request): return web.Response(text="Hello aiohttp") app = web.Application() app.router.add_get('/', hello) web.run_app(app, port=8000) ``` - **高级功能**:WebSocket支持、中间件、Session管理 --- #### 四、生产级项目开发要点 1. **安全防护** - 输入验证:使用`bleach`库过滤HTML - 速率限制:`slowapi`防止暴力攻击 - CORS配置:`aiohttp_cors`处理跨域 2. **性能优化** - 连接池:`asyncpg`管理PostgreSQL连接 - 缓存策略:集成`redis-py`异步客户端 - 负载测试:`locust`模拟高并发场景 3. **部署方案** - **开发环境**:`uvicorn`(ASGI服务器)+ 热重载 - **生产环境**: ```bash # Gunicorn + Uvicorn Worker gunicorn -w 4 -k uvicorn.workers.UvicornWorker app:app ``` - **容器化**:Dockerfile配置Python环境与依赖 --- #### 五、学习资源推荐 1. **官方文档** - [Python Socket](https://docs.python.org/3/library/socket.html) - [aiohttp](https://docs.aiohttp.org/) 2. **实战项目** - 实时聊天室(WebSocket) - API网关(路由、鉴权、限流) - 文件托管服务(断点续传、权限控制) ```python # 快速启动模板(Flask + SocketIO) from flask import Flask from flask_socketio import SocketIO app = Flask(__name__) socketio = SocketIO(app, cors_allowed_origins="*") @socketio.on('message') def handle_message(data): print('received message: ' + data) if __name__ == '__main__': socketio.run(app, port=5000) ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值