Python高性能异步爬虫

Python高性能异步爬虫

方法:

  1. **多线程,多进程(不建议):**可以为相关阻塞操作单独开启线程或进程,阻塞操作可以异步执行。但无法无限制地开启线程或进程。

  2. **线程池、进程池(适当使用):**可以降低系统开启或者销毁一个线程或者进程的频率,从而很好的降低系统开销。但也不是无限制的。

  3. 单线程+异步协程(推荐):

event_loop:事件循环,相当于一个无限循环,可以吧一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。

coroutine:协程对象,我们可以讲协程对象注册到事件循环当中,他会被事件循环调用。可以使用 async 关键字来定义一个方法,当这个方法被调用时不会立即执行,而是返回一个协程对象。

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

future:代表将来执行或者还未执行的任务,在本质上和task没有区别。

async:定义一个协程。

await:用来挂起阻塞方法的执行。

import asyncio

async def test_func(a):
    print("start:",a)
    print("end:",a)

# async修饰的函数,调用后返回一个协程(coroutine)对象
f = test_func(3)

# 创建一个事件循环对象
loop = asyncio.get_event_loop()

# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(f)

下面task与future的使用分别对应两种创建任务的方法,可任选其一创建任务。

task的使用

# task的使用
loop = asyncio.get_event_loop()

task = loop.create_task(f)
print(task)

loop.run_until_complete(task)
print(task)

future的使用

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(f)
print(task)
loop.run_until_complete(task)
print(task)

绑定回调

# 绑定回调

def callback_func(task):
    # result返回的就是任务对象封装的协程对象对应的函数的返回值
    print(task.result())

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(f)

# 将回调函数绑定到任务对象中
task.add_done_callback(callback_func)

loop.run_until_complete(task)

异步协程的实现

操作代码展示:
import asyncio
import time

async def request(url):
    print('正在下载:',url)
    # 注意:在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。
    # time.sleep(2)
    # 注意:需要实现异步协程的阻塞操作前面要加await关键字才能实现异步协程,需要收到手动挂起。
    await asyncio.sleep(2)
    print("下载成功!",url)

start = time.time()
urls = [
    'www.baidu.com',
    'www.sougou.com',
    'www.douban.com'
]

tasks = []
for url in urls:
    f = request(url)
    task = asyncio.ensure_future(f)
    tasks.append(task)

loop = asyncio.get_event_loop()
# 需要将任务列表tasks封装到async.wait中
loop.run_until_complete(asyncio.wait(tasks))

print("函数耗时:",time.time()-start)
结果展示:
结果一:(使用了同步模块的代码的情况)
正在下载: www.baidu.com
下载成功! www.baidu.com
正在下载: www.sougou.com
下载成功! www.sougou.com
正在下载: www.douban.com
下载成功! www.douban.com
函数耗时: 6.001343250274658
结果二:(异步协程实现结果)
正在下载: www.baidu.com
正在下载: www.sougou.com
正在下载: www.douban.com
下载成功! www.baidu.com
下载成功! www.sougou.com
下载成功! www.douban.com
函数耗时: 2.0021145343780518

实现的两注意:

注意:在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。

注意:需要实现异步协程的阻塞操作前面要加await关键字才能实现异步协程,需要收到手动挂起。

aiohttp模块

使用aiohttp的原因:

使用async异步协程爬取网站时,用requests模块会导致不能实现异步爬取,原因是requests模块是同步模块,其中get、post等阻塞操作是同步方法,不能实现异步运行。

代码:

import aiohttp
import asyncio
import time

now = lambda : time.time()
start = now()
header = {
     
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0'
}
url_list = [
    'https://www.baidu.com',
    'https://www.sougou.com/',
    'https://www.douban.com/'

]
async def get_page(url):
    print("开始爬取:",url)
    # 使用aiohttp.ClientSession创建一个session对象
    async with aiohttp.ClientSession() as session:
        # 注意使用await挂起
        async with await session.get(url,headers=header) as response:
            # text()返回字符串形式的响应数据。
            # read()返回二进制形式的数据。
            # json()返回的是json对象。
            # 注意上述都为方法,不是属性。
            page_text = await response.text()
            print(len(page_text))
    print("爬取成功:",url)

tasks = []

for url in url_list:
    c = get_page(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print("耗时:",now()-start)

结果:

开始爬取: https://www.baidu.com
开始爬取: https://www.sougou.com/
开始爬取: https://www.douban.com/
291324
爬取成功: https://www.baidu.com
15584
爬取成功: https://www.sougou.com/
81219
爬取成功: https://www.douban.com/
耗时: 1.2360711097717285

总结:

单线程+异步协程的效率非常高,但在以后的使用还需要多注意。本文的例子比较简单,真正实现高效率的异步协程的爬虫还需要多实战、多练习。

你可能感兴趣的:(爬虫,python,爬虫)