程序员社区

aiohttp异步爬取简单使用

了解aiohttp

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

我们先了解一下aiohttp客户端部分使用。

基本使用

基本实例

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

import aiohttp
import asyncio

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

async def main():
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64, verify_ssl=False)) as session:
        html, status = await get(session, 'https://baidu.com')
        print(f"html:{html}....")
        print(f"status:{status}")

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

在这里使用aiohttp来爬取百度首页,获得源码和响应状态码并输出,运行结果如下:

html:

<!DOCTYPE html><!--STATUS OK-->

    <html><head><meta http-eq

........
status:200

这里源码比较长,只复制一部分,可以看到已成功获取网页源代码以及响应状态码200,完成了一次HTTP请求,即我们成功使用aiohttp通过异步方式进行了网页爬取,当然这个操作,requests同样可以做到,

在使用aiohttp请求是,需要注意以下几点:

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

注意:在python3.7以上的版本,我们可以使用asyncio.run(main())来替代最后的启动操作,不需要显示声明事件循环,run方法内部会自动启动一个事件循环。

异步-URL参数设置

GET传入params参数

       对于URL参数的设置,可以借助于params参数,传入一个字典即可

【代码示例】:

import aiohttp
import asyncio

async def main():
    params = {'name': 'name', 'age': 22}
    async  with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
        async with session.get('https://xxx/xx/xx', params=params) as response:
            print(await response.text())

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

其实际请求的URL为 https://xxx/xx/xx?name=name&age=22 

其它请求类型

另外aiohttp还支持其它的请求类型,如:POST、PUT、DELETE等等,这个和requests的使用方式有点类似。

【示例如下】:

  1. session.post('https://xx/xx/post',data=b"data")
  2. session.put('https://xx/xx/put',data=b"data")
  3. session.delete('https://xx/xx/delete')
  4. session.head('https://xx/xx/get')
  5. session.options('https://xx/xx/get')
  6. session.patch('https://xx/xx/patch',data=b"data")

POST数据

对于POST表单提交。

【示范】如下:

import aiohttp
import asyncio
async def main():
    data = {'data': 'xxx'}
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
        #如果请求头Content-type为application/x-www-form-urlencoded,用下方来实现
        async  with session.post('https://xxx/post', data=data) as response:
        ##如果请求头Content-type为application/application/json,我们只需要把参数修改为:
        # async  with session.post('https://xxx/post', json=data) as response:
            print(await response.text())
if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

注意,post请求代码含有两种方式

超时设置

超时设置,我们需要借助aiohttp里面ClientTimeout对象来实现。

【示例】:

import aiohttp
import asyncio

async def main():
    timeout=aiohttp.ClientTimeout(total=1)  #设置一秒超时
    async with aiohttp.ClientSession(timeout=timeout,connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
        async with session.get('http://xxx/xxx') as response:
            print(await response.text())

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

如果超时的话,会抛出TimeoutError异常,其类型为asyncio.TimeoutError,我们可以异常捕获即可

另外ClienTimeout对象声明还有其它参数,如,connect、socket_connent等等,详细可以参考官方文档: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(connector=aiohttp.TCPConnector(verify_ssl=False))
    acrape_index_tasks=[asyncio.ensure_future(scrape_api()) for _ in range(10000)]
    await asyncio.gather(*acrape_index_tasks)
if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

在这里我们声明CONCURRENCY代表爬取的最大并发量为5,同时声明爬取的目标URL为百度。接着我们借助于Semaphore创建一个信号量对象。赋值为semaphore,这样我们就可以用它来控制最大并发量了,我们这里把它直接放置对应的爬取方式里面,使用async with 语句将semaphore作为上下文对象即可。这样,信号量可以控制进入爬取最大协程数量,最大数量就是我们声明的CONCURRENCY的值。

在main方法里面,我们声明了10000个task,传递给gather方法运行,倘若不加以限制,这1000个task会被同时执行,并发量太大,但有信号量的控制之后,同时运行的task的数量最大会被控制在5个,这样就能给aiohttp限制速度了。

点击查看 异步案例使用

赞(0) 打赏
未经允许不得转载:IDEA激活码 » aiohttp异步爬取简单使用

一个分享Java & Python知识的社区