asyncio+aiohttp异步爬虫

概念

进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。进程是操作系统动态执行的基本单元。
线程:一个进程中包含若干线程,当然至少有一个线程,线程可以利用进程所拥有的资源。线程是独立运行和独立调度的基本单元。
协程:协程是一种用户态的轻量级线程。协程无需线程上下文切换的开销,也无需原子操作锁定及同步的开销。
同步:不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,称这些程序单元是同步执行的。
异步:为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。
多进程:多进程就是利用 CPU 的多核优势,在同一时间并行地执行多个任务。多进程模式优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程,但是操作系统能同时运行的进程数是有限的。
多线程:多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。

Python 中使用协程最常用的库莫过于asyncio,然后我们还需要了解一些概念:

event_loop 事件循环

程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数

coroutine 协程

协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

task 任务

一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态

future

代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别

async/await 关键字

python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。


不知道为什么我一看概念这些东西,讲的简单点的还好,一长就容易走神...所以这里只是简单复制粘贴了下

Python手册中关于asyncio的部分
aiohttp手册

很多网上教的例子都在手册上有了,所以直接看手册就行,下面就来根据实例理解理解吧

实例

这个是我爬取站酷网上“潇湘过客莫念”的雪中悍刀行的插图,如下所示,通过最基本的requests抓取图片链接,没有遇到反爬

get_content是返回访问图片地址的content数据;downloader是将图片保存到本地的方法;run是提取目标图片链接并循环访问,并且记录整个过程的耗时

import time
import requests


def get_content(link):
    r = requests.get(link)
    content = r.content
    return content


def downloader(img):
    content = get_content(img[1])
    with open('D:\\MY\\雪中\\lists2\\' + str(img[0]) + '.jpg', 'wb') as f:
        f.write(content)
    print('下载成功!' + str(img[0]))


def run():
    start = time.time()  # 记录起始时间戳
    base_url = 'https://www.zcool.com.cn/work/content/show?p=2&objectId=6455837'
    r = requests.get(base_url)
    image_list = r.json()['data']['allImageList']
    for i, image in enumerate(image_list):
        downloader((i, image['url']))
    end = time.time()  # 获取结束时间戳
    print('共运行了{}秒'.format(end - start))  # 程序耗时


if __name__ == '__main__':
    run()

运行结果:共运行了99.09104537963867

接着我们来看看通过asyncio+aiohttp的方法的异步爬取,同样的,代码分成三个部分(代码在Python3.6环境运行)

import asyncio
import time
import aiohttp
import requests


async def get_content(link):
    async with aiohttp.ClientSession() as session:
        response = await session.get(link)
        content = await response.read()
        return content


async def downloader(img):
    content = await get_content(img[1])
    with open('D:\\MY\\雪中\\lists1\\' + str(img[0]) + '.jpg', 'wb') as f:
        f.write(content)
    print('下载成功!' + str(img[0]))


def run():
    start = time.time()  # 记录起始时间戳
    base_url = 'https://www.zcool.com.cn/work/content/show?p=2&objectId=6455837'
    r = requests.get(base_url)
    image_list = r.json()['data']['allImageList']
    loop = asyncio.get_event_loop()
    tasks = [asyncio.ensure_future(downloader((i, image['url']))) for i, image in enumerate(image_list)]
    loop.run_until_complete(asyncio.wait(tasks))
    end = time.time()  # 获取结束时间戳
    print('共运行了{}秒'.format(end - start))  # 程序耗时


if __name__ == '__main__':
    run()

运行结果:共运行了56.29711127281189秒,跟上面的同步代码比较,节省了一半左右的时间,这里的每张图片大小在几M

那么我们来看看代码是如何执行的

这里需要注意的是run方法中增加了loop = asyncio.get_event_loop(),就是创建一个event_loop事件循环,这个事件循环叫loop,然后我们要做的就是把协程放到loop事件循环中,协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete(coroutine)方法将协程包装成为了一个任务task对象,task对象是Future类的子类,保存了协程运行后的状态,用于未来获取协程的结果

那么哪来的协程呢?这里以async关键字定义的方法就是一个协程,这个方法在调用时不会立即被执行,而是返回一个协程对象,协程对象需要注册到事件循环,由事件循环调用

tasks = [asyncio.ensure_future(downloader((i, image['url']))) for i, image in enumerate(image_list)]

这行代码的作用实际上就是通过循环生成多个downloader协程,传入一个元组类型的参数,这个元祖中包含两个参数,然后通过asyncio.ensure_future()方法返回task任务(一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态,通过asyncio.ensure_future(coroutine)创建task,同样的可以通过loop.create_task(coroutine)创建task),最终得到一个tasks任务列表

loop.run_until_complete(asyncio.wait(tasks))

而这行代码说实话我没有深入的理解,就大概理解为事件循环执行run_until_complete方法,等到其中的每一个可等待对象都执行完毕,对于其中的每一个对象,去执行downloader协程,运行到await get_content(img[1])时挂起当前的协程,去执行get_content()这个协程,引用了aiohttp里的ClientSession类,建立 了一个session对象,通过session.get()得到response以及最后的content并最终返回content

然后继续执行downloader中的保存图片部分

以上就是用asyncio+aiohttp的简单的异步爬虫实例,很明显相较于一般的爬虫能节省很多时间啦

参考

* asyncio.wait如何理解
asyncio.wait实现的异步
* python中重要的模块--asyncio
* 【Python3爬虫】使用异步协程编写爬虫

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容