网络爬虫基础-异步爬虫

异步爬虫

目的:在爬虫中使用异步实现高性能数据爬取

异步爬虫的方式:

  • 多线程/多进程(不建议)

    优点:可以为相关阻塞的操作,单独开启线程或进程,阻塞操作可以异步执行。

    弊端:无法无限制地开启多线程或者多进程:在遇到要同时处理成百上千个的连接请求时,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。

  • 线程池/进程池(适量使用)

    优点:降低系统对进程和线程创建和销毁的一个频率,从而很好地降低系统的开销。

    弊端:池中线程或进程的数量是有上限的。当阻塞操作远高于池中线程或进程数量时效果不明显。

    原则:线程池处理阻塞且耗时的操作

  • 单线程+异步协程(推荐)

线程池/进程池

示例:

'''使用线程池的方式执行'''
import time
from multiprocessing.dummy import Pool

start_time = time.time()
def get_page(str):
    print('正在下载', str)
    time.sleep(2)  # 模拟阻塞操作
    print('下载成功', str)
    pass


name_list = ['aa', 'bb', 'cc', 'Tony']

# 实例化线程池对象
pool = Pool(4)  # 实例化四个线程池对象
# 将列表中每一个列表元素对象传递给get_page处理

pool.map(get_page, name_list)

end_time = time.time()
print('{} second'.format(end_time - start_time))

总结:多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

异步协程

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

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

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

future:代表一个将来执行或还没执行的任务,实际上和task没有本质区别。

async:定义一个协程

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

示例:

import asyncio


async def requests(url):
    print('正在请求的url是', url)
    print('请求成功', url)
    return url

'''1. 定义一个协程'''
# async修饰的函数,调用之后返回一个协程对象
c = requests('www.baidu.com')
# # 创建一个事件循环对象
# loop = asyncio.get_event_loop()
# # 将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)

'''2.task的使用:对协程对象进一步封装''' 
# loop = asyncio.get_event_loop()
# # 基于loop创建task对象
# task = loop.create_task(c)
# print(task)  # 执行之前 pending
# # 将task对象注册到loop中,然后启动loop
# loop.run_until_complete(task)
# print(task)  # 执行之后 finish

'''3.ensure_future()的使用'''
# loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(c)
# print(task)  # pending
# loop.run_until_complete(task)
# print(task)  # finish

'''4.绑定回调'''
def call_back(task):
    # result 返回的就是任务对象中封装的协程对象对应函数的返回值
    print(task.result())

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中
# 默认将任务对象作为参数传递到回调函数
task.add_done_callback(call_back)
loop.run_until_complete(task)
# 当协程对象执行完毕之后,就去执行声明的 call_back() 方法。

多任务异步协程

同时进行多个请求,定义一个 task 列表,使用 asyncio 的 wait() 方法执行。

示例:

import asyncio
import time


async def request(url):
    print('正在下载', url)
    # 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步
    # time.sleep(2)
    # 当在asyncio中遇到阻塞操作必须进行手动挂起
    await asyncio.sleep(2)
    print('下载结束', url)
    pass

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

# 任务列表:存放多个任务对象
stasks = []
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    stasks.append(task)
# [asyncio.ensure_future(request(url)) for url in urls]
loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(stasks))
print(time.time()-start)

aiohttp

示例1:使用requests模块发起请求

'''在本地模拟一个慢速服务器'''
from flask import Flask
import time
app = Flask(__name__)
@app.route('/bobo')
def index_bobo():
    time.sleep(2)
    return 'Hello bobo'
@app.route('/jay')
def index_jay():
    time.sleep(2)
    return 'Hello jay'
@app.route('/tom')
def index_tom():
    time.sleep(2)
    return 'Hello tom'
if __name__ == '__main__':
    app.run(threaded=True)

接下来,将多任务异步协程操作应用在爬虫上:

import requests
import asyncio
import time
start = time.time()
urls = [
  'http://127.0.0.1:5000/bobo',
  'http://127.0.0.1:5000/jay',
   'http://127.0.0.1:5000/tom'
]
async def get_page(url):
    print('正在下载',url)
    response = requests.get(url=url)
    print('下载完毕:',response.text)
tasks = [asyncio.ensure_future(get_page(url)) for url in urls] 
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print('总耗时:',end-start)

requests模块是非异步模块,requests.get是基于同步,必须使用基于异步网络请求的模块(aiohttp)进行指定url的请求发送。

示例2:使用aiohttp模块发起请求

import asyncio
import time
import aiohttp

start = time.time()
urls = [
    'http://127.0.0.1:5000/bobo',
    'http://127.0.0.1:5000/jay',
    'http://127.0.0.1:5000/tom'
]

async def get_page(url):
    async with aiohttp.ClientSession() as session:
        # get()、post():
        # headers, params/data, proxy='http://ip:port'
        async with await session.get(url) as resp:
            # text()返回字符串型的响应数据
            # read()返回二进制形式的响应数据
            # json()返回的就是json对象
            # 注意:获取响应数据操作之前一定要使用await进行手动挂起
            page_text = await resp.text()
            print(page_text)

tasks = [asyncio.ensure_future(get_page(url)) for url in urls]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print('总耗时:', end-start)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值