异步爬虫之aiohttp的使用

news2025/1/4 18:41:37

在上一篇博客我们介绍了异步爬虫的基本原理和 asyncio 的基本用法,并且在最后简单提及了使用aiohttp 实现网页爬取的过程。本篇博客我们介绍一下 aiohttp 的常见用法。

基本介绍

前面介绍的 asyncio模块,其内部实现了对 TCP、UDP、SSL协议的异步操作,但是对于 HTTP 请求来说,就需要用 aiohttp 实现了。

aiohttp 是一个基于 asyncio 的异步 HTTP 网络模块,它既提供了服务端,又提供了客户端。其中我们用服务端可以搭建一个支持异步处理的服务器,这个服务器就是用来处理请求并返回响应的,类似于 Django、Flask、Tormado 等一些 Web服务器。而客户端可以用来发起请求,类似于使用 requests 发起一个 HTTP 请求然后获得响应,但 requests 发起的是同步的网络请求,aiohttp 则是异步的。

本篇博客我们主要了解一下 aiohttp 客户端部分的用法。

基本实例

我们来看一个基本的 aiohttp 请求案例,代码如下:

import aiohttp
import asyncio


async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text(), response.status


async def main():
    async with aiohttp.ClientSession() as session:
        html, status = await fetch(session, 'https://cuiqingcai.com')
        print(f'html:{html[:100]}...')
        print(f'status:{status}')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

这里使用 aiohttp 爬取了本书作者的个人博客,获得了源码和响应状态码,并打印出来,运行结果如下:

html:<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content...
status:200

由于网页源码过长,这里只截取了输出的一部分。可以看到,我们成功获取了网页的源代码及响应状态码 200,也就是完成了一次基本的 HTTP请求,即我们成功使用 aiohttp 通过异步的方式完成了网页爬取。当然,这个操作用之前讲的 requests 也可以做到。

能够发现,aiohttp 的请求方法的定义和之前有明显的区别,主要包括如下几点。

  • 首先在导人库的时候,除了必须引人 aiohttp这个库,还必须引人asyncio 库。因为要实现异步爬取,需要启动协程,而协程则需要借助于asyncio 里面的事件循环才能执行。除了事件循环asyncio 里面也提供了很多基础的异步操作。
  • 异步爬取方法的定义和之前有所不同,每个异步方法的前面都要统一加 async 来修饰。
  • with as 语句前面同样需要加 async 来修饰。在 Pvthon 中,with as 语句用于声明一个上下文管理器,能够帮我们自动分配和释放资源。而在异步方法中,withas前面加上async代表声明一个支持异步的上下文管理器。
  • 对于一些返回协程对象的操作,前面需要加 await 来修饰。例如 response 调用 text 方法,查询 API可以发现,其返回的是协程对象,那么前面就要加 await;而对于状态码来说,其返回值就是一个数值,因此前面不需要加 await。所以,这里可以按照实际情况做处理,参考官方文档说明,看看其对应的返回值是怎样的类型,然后决定加不加await 就可以了。
  • 最后,定义完爬取方法之后,实际上是 main 方法调用了 fetch 方法。要运行的话,必须启用事件循环,而事件循环需要使用 asyncio库,然后调用 run_until_complete 方法来运行。

注意:在 Python 3.7 及以后的版本中,我们可以使用 asyncio.run(main())代替最后的启动操作,不需要显示声明事件循环,run 方法内部会自动启动一个事件循环。但这里为了兼容更多的Python 版本,依然显式声明了事件循环。

URL参数设置

对于 URL 参数的设置,我们可以借助 params 参数,传入一个字典即可,实例如下:

import aiohttp
import asyncio


async def main():
    params = {'name': 'germey', 'age': 25}
    async with aiohttp.ClientSession() as session:
        async with session.get('https://www.httpbin.org/get', params=params) as response:
            print(await response.text())


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

运行结果如下:

{
  "args": {
    "age": "25", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Python/3.9 aiohttp/3.11.11", 
    "X-Amzn-Trace-Id": "Root=1-677217f5-3bc6954d23f245465ccbb806"
  }, 
  "origin": "111.18.92.1", 
  "url": "https://www.httpbin.org/get?name=germey&age=25"
}

这里可以看到,实际请求的 URL 为 https://www.httpbin.org/get?name=germey&age-25,其中的参数对应于 params 的内容。

其他请求类型

aiohttp 还支持其他请求类型,如 POST、PUT、DELETE 等,这些和 requests 的使用方式有点类
似,实例如下:

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

要使用这些方法,只需要把对应的方法和参数替换一下。

POST请求

对于 POST表单提交,其对应的请求头中的Content-Type为application/x-www-form-urlencoded.我们可以用如下方式来实现:

import aiohttp
import asyncio


async def main():
    data = {'name': 'germey', 'age': 25}
    async with aiohttp.ClientSession() as session:
        async with session.post('https://www.httpbin.org/post', data=data) as response:
            print(await response.text())


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

运行结果如下:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "age": "25", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "18", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Python/3.9 aiohttp/3.11.11", 
    "X-Amzn-Trace-Id": "Root=1-67721981-6891ead2152d4e70567cd9ab"
  }, 
  "json": null, 
  "origin": "111.18.92.1", 
  "url": "https://www.httpbin.org/post"
}

对于 POST JSON 数据提交,其对应的请求头中的 Content-Type 为 application/json,我们只需要将 post 方法里的 data 参数改成 json 即可,实例代码如下:

import aiohttp
import asyncio


async def main():
    data = {'name': 'germey', 'age': 25}
    async with aiohttp.ClientSession() as session:
        async with session.post('https://www.httpbin.org/post', json=data) as response:
            print(await response.text())


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

运行结果如下:

{
  "args": {}, 
  "data": "{\"name\": \"germey\", \"age\": 25}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "29", 
    "Content-Type": "application/json", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Python/3.9 aiohttp/3.11.11", 
    "X-Amzn-Trace-Id": "Root=1-67721ce2-0439155d5dce282c3803cca6"
  }, 
  "json": {
    "age": 25, 
    "name": "germey"
  }, 
  "origin": "111.18.92.1", 
  "url": "https://www.httpbin.org/post"
}

可以发现,其实现也和requests 非常像,不同的参数支持不同类型的请求内容。

响应

对于响应来说,我们可以用如下方法分别获取其中的状态码、响应头、响应体、响应体二进制内容、响应体 JSON 结果,实例代码如下:

import aiohttp
import asyncio


async def main():
    data = {'name': 'germey', 'age': 25}
    async with aiohttp.ClientSession() as session:
        async with session.post('https://www.httpbin.org/post', data=data) as response:
            print('status:', response.status)
            print('headers:', response.headers)
            print('body:', await response.text())
            print('bytes:', await response.read())
            print('json:', await response.json())


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

运行结果如下:

status: 200
headers: <CIMultiDictProxy('Date': 'Mon, 30 Dec 2024 04:40:46 GMT', 'Content-Type': 'application/json', 'Content-Length': '510', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
body: {
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "age": "25", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "18", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "www.httpbin.org", 
    "User-Agent": "Python/3.9 aiohttp/3.11.11", 
    "X-Amzn-Trace-Id": "Root=1-6772244e-191cbb78229cbd5412777470"
  }, 
  "json": null, 
  "origin": "111.18.92.1", 
  "url": "https://www.httpbin.org/post"
}

bytes: b'{\n  "args": {}, \n  "data": "", \n  "files": {}, \n  "form": {\n    "age": "25", \n    "name": "germey"\n  }, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Content-Length": "18", \n    "Content-Type": "application/x-www-form-urlencoded", \n    "Host": "www.httpbin.org", \n    "User-Agent": "Python/3.9 aiohttp/3.11.11", \n    "X-Amzn-Trace-Id": "Root=1-6772244e-191cbb78229cbd5412777470"\n  }, \n  "json": null, \n  "origin": "111.18.92.1", \n  "url": "https://www.httpbin.org/post"\n}\n'
json: {'args': {}, 'data': '', 'files': {}, 'form': {'age': '25', 'name': 'germey'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '18', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'www.httpbin.org', 'User-Agent': 'Python/3.9 aiohttp/3.11.11', 'X-Amzn-Trace-Id': 'Root=1-6772244e-191cbb78229cbd5412777470'}, 'json': None, 'origin': '111.18.92.1', 'url': 'https://www.httpbin.org/post'}

可以看到,这里有些字段前面需要加 await,有些则不需要。其原则是,如果返回的是一个协程对象(如 async修饰的方法),那么前面就要加 await,具体可以看 aiohttp的API,其链接为:https://docs.aiohttp.org/en/stable/client_reference.html.

超时设置

我们可以借助 clientTimeout 对象设置超时,例如要设置1秒的超时时间,可以这么实现:

import aiohttp
import asyncio


async def main():
    timeout = aiohttp.ClientTimeout(total=1)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get('https://www.httpbin.org/get') as response:
            print('status:', response.status)


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

如果在1秒之内成功获取响应,那么运行结果如下:

200

如果超时,则会抛出 TimeoutError 异常,其类型为 asyncio.TimeoutError,我们进行异常捕获即可。另外,声明clientTimeout对象时还有其他参数,如connect、socket、connect等,详细可以参考官方文档:https://docs.aiohttp.org/en/stable/client_quickstart.html#timeouts。

并发限制

由于 aiohttp 可以支持非常高的并发量,如几万、十万、百万都是能做到的,但面对如此高的并发量,目标网站很可能无法在短时间内响应,而且有瞬间将目标网站爬挂掉的危险,这提示我们需要控制一下爬取的并发量。

一般情况下,可以借助 asyncio 的 Semaphore 来控制并发量,实例代码如下:

import aiohttp
import asyncio

CONCURRENCY = 5
URL = 'https://www.baidu.com'
semaphore = asyncio.Semaphore(CONCURRENCY)
session = None


async def scrape_api():
    async with semaphore:
        print('scraping', URL)
    async with session.get(URL) as response:
        await asyncio.sleep(1)
        return await response.text()


async def main():
    global session
    session = aiohttp.ClientSession()
    scrape_index_tasks = [asyncio.ensure_future(scrape_api()) for _ in range(10000)]
    await asyncio.gather(*scrape_index_tasks)


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

参考文献

https://docs.aiohttp.org/

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

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

相关文章

【分布式数据库与数据存储方案】详解

分布式数据库与数据存储方案 一、分布式数据库概述 &#xff08;一&#xff09;概念 分布式数据库是一种将数据分散存储在多个物理节点上的数据库系统&#xff0c;这些节点通过网络进行连接和通信&#xff0c;对外呈现出一个统一的逻辑数据库&#xff0c;用户或应用程序可以像…

【分布式文件存储系统Minio】2024.12保姆级教程

文章目录 1.介绍1.分布式文件系统2.基本概念 2.环境搭建1.访问网址2.账号密码都是minioadmin3.创建一个桶4.**Docker安装miniomc突破7天限制**1.拉取镜像2.运行容器3.进行配置1.格式2.具体配置 4.查看桶5.给桶开放权限 3.搭建minio模块1.创建一个oss模块1.在sun-common下创建2.…

产品经理2025年展望

产品经理作为连接技术、设计与市场需求的桥梁&#xff0c;在快速变化的商业环境中扮演着至关重要的角色。展望2025年&#xff0c;随着技术的不断进步和消费者需求的日益多样化&#xff0c;产品经理的工作将面临更多挑战与机遇。 一、人工智能与自动化深化应用&#xff1a; 到…

风力涡轮机缺陷检测数据集,91.4%准确识别率,18912张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注

风力涡轮机缺陷检测数据集&#xff0c;91.4&#xff05;准确识别率&#xff0c;18912张图片&#xff0c;支持yolo&#xff0c;PASICAL VOC XML&#xff0c;COCO JSON格式的标注 数据集下载&#xff1a; &#xff59;&#xff4f;&#xff4c;&#xff4f; &#xff56;&#…

五、Vue 循环语句

文章目录 简介一、基础数组迭代二、对象属性迭代三、整数循环 简介 在 Vue.js 的世界里&#xff0c;当我们需要处理重复性的结构并依据数据动态渲染时&#xff0c;v-for 指令就成了不可或缺的工具&#xff0c;它赋予开发者简洁且强大的能力&#xff0c;轻松应对各种列表渲染场景…

用css实现瀑布流布局

上效果 知识理解 column-count: 4; column-gap: 15px;实现固定四行瀑布流布局 columns: 200px auto;column-gap: 15px;由浏览器根据容器的宽度自动调整&#xff0c;尽可能一行多个200px宽度的列数 <!DOCTYPE html> <html lang"en"><head><me…

275-增强型多功能数据采集卡PCIe-6251-EX

产品特点&#xff1a; 高速高精度数据采集&#xff0c;16bit10MSPS&#xff0c;32路单端/16路差分高速高精度任意波形发生&#xff0c;14bit165MHz&#xff0c;2路完全独立完全可编程的I/O端口&#xff0c;33个完全可编程的量程选择&#xff0c;0~5V/0~10V/5V/10VPCIe通信接口…

如何将联系人从Android转移到 OPPO? [解决了]

概括 OPPO Reno4系列预计将于2020年10月1日上午9点30分举行线上发布会。从其官方预告片中我们不难发现&#xff0c;OPPO Reno4旗舰手机试图诠释梦想、挑战、勇气、自信和可能性。 3D曲面屏&#xff0c;图形流畅&#xff0c;机身更轻薄&#xff0c;色彩真实。听起来棒极了&…

uniapp 微信小程序开发使用高德地图、腾讯地图

一、高德地图 1.注册高德地图开放平台账号 &#xff08;1&#xff09;创建应用 这个key 第3步骤&#xff0c;配置到项目中locationGps.js 2.下载高德地图微信小程序插件 &#xff08;1&#xff09;下载地址 高德地图API | 微信小程序插件 &#xff08;2&#xff09;引入项目…

Rabbitmq追问2

分析rabbitmq 默认使用姿势是什么 direct fanout还是什么 public void convertAndSend(String exchange, String routingKey, Object object, CorrelationData correlationData) throws AmqpException { this.send(exchange, routingKey, this.convertMessageIfNecessary(obje…

工作中常用Vim的命令

Hi, 我是你们的老朋友&#xff0c;主要专注于嵌入式软件开发&#xff0c;有兴趣不要忘记点击关注【码思途远】 目录 0. ctags -R 1.认识 Vim的几种工作模式 2.高频使用命令 2.1 修改文件 2.2 关于行号 2.3 删除多行&#xff0c;删除部分 2.4 复制粘贴 2.5 光标移动 2.…

【ComfyUI + 自定义节点】图片叠加掩膜(mask)部分扣掉(变黑)

Comfyui的官方示例&#xff1a;https://github.com/comfyanonymous/ComfyUI/blob/master/custom_nodes/example_node.py.example 一、代码 &#xff08; 逻辑&#xff1a;将图片对应掩膜覆盖的区域替换为黑色&#xff09; 因为comfyui加载的图片后&#xff0c;转化为tensor i…

RocketMQ学习笔记(持续更新中......)

目录 1. 单机搭建 2. 测试RocketMQ 3. 集群搭建 4. 集群启动 5. RocketMQ-DashBoard搭建 6. 不同类型消息发送 1.同步消息 2. 异步消息发送 3. 单向发送消息 7. 消费消息 1. 单机搭建 1. 先从rocketmq官网下载二进制包&#xff0c;ftp上传至linux服务器&#xff0c…

【二】arcgis JavaScript api 实现加载不同坐标系的底图和三维服务

提示&#xff1a;如果是天地图底图参考这篇文章 【一】arcgis JavaScript api 实现加载不同坐标系的底图和三维服务_arcgis js api 调用三维地图服务-CSDN博客 需求&#xff1a; 前端开发实现底图&#xff08;wkid&#xff1a;3857&#xff0c;web墨卡托&#xff09;&#x…

PDF怎么压缩得又小又清晰?5种PDF压缩方法

PDF 文件在日常办公与学习中使用极为频繁&#xff0c;可想要把它压缩得又小又清晰却困难重重。一方面&#xff0c;PDF 格式本身具有高度兼容性&#xff0c;集成了文字、图像、矢量图等多样元素&#xff0c;压缩时难以兼顾不同元素特性&#xff0c;稍不注意&#xff0c;文字就会…

数据结构与算法之动态规划: LeetCode 62. 不同路径 (Ts版)

不同路径 https://leetcode.cn/problems/unique-paths/description/ 描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “…

Edge如何获得纯净的启动界面

启动Edge会出现快速链接&#xff0c;推广链接&#xff0c;网站导航&#xff0c;显示小组件&#xff0c;显示信息提要&#xff0c;背景 ●复杂页面 ●精简页面 点击页面设置按钮 关闭快速链接 关闭网站导航 关闭小组件 关闭信息提要 关闭背景 关闭天气提示 精简页面看起来十分舒…

如何利用java爬虫获得AMAZON商品详情

在数字化时代&#xff0c;数据的价值不言而喻&#xff0c;尤其是对于电商平台而言&#xff0c;获取商品的详细信息对于优化用户体验、制定营销策略至关重要。亚马逊作为全球最大的电商平台之一&#xff0c;拥有海量的商品信息。本文将介绍如何使用Java编写爬虫程序&#xff0c;…

人工智能基础软件-Jupyter Notebook

简介&#xff1a; Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 Jupyter Notebook是以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代码&#xff0c;代码的运行结果也会直…

数据库系列之分布式数据库下误删表怎么恢复?

数据的完整性是数据库可用性的基本功能&#xff0c;在实际应用数据库变更操作过程中可能因为误操作导致误删表或者truncate操作影响业务的正常访问。本文介绍了分布式数据库中在误删表场景下的数据恢复方案&#xff0c;并进行了对比。 1、数据库误删表恢复方案 应用数据的完整…