单线程实现了多任务异步协程

Posted zhao159461

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单线程实现了多任务异步协程相关的知识,希望对你有一定的参考价值。

单线程+多任务异步协程:


意义:提升爬取数据的效率,我们也可以使用线程池,

异步爬虫方式:

  • 多线程/多进程(电脑吃不消,没办法无节制开启)不建议
  • 池:池中的线程或进程也是无法任意开启.
  • 单线程+多任务异步协程(推荐)(500个协程,最优)

概念:

协程:本质就是一个对象,协程对象,怎么去获取?可以使用asynic该关键字去修饰一个函数定义,此时的函数就叫它特殊函数,当该特殊函数被调用之后,就可以返回一个协程对象,特殊之处不仅仅是返回一个协程对象,当函数内部实现的语句不会被立即执行(时间循环开启后执行),导入模块import asycio

import asyncio
#特殊函数:
async  def test(num):
        print(num)
c = test(10)
print(c)
#内部函数语句没有被执行,返回的是一个协程对象,一个地址,仅仅一个协程对象没有意义

技术图片

任务对象:

  • 本质上就是对协程对象的进一步封装,封装之后也是一个协程对象,任务对象变相等于一个特殊函数.
  • 给任务对象绑定一个回调:add_done_callback(callback)时间循环执行完成后再执行回调函数.
import asyncio
#特殊函数:
async  def test(num):
        print(num)
c = test(10)
#内部函数语句没有被执行,返回的是一个协程对象,一个地址,仅仅一个协程对象没有意义

#进行封装,根据一个协程对象封装一个任务对象
task = asyncio.ensure_future(c)

技术图片

绑定回调:

import asyncio
import time

async def request(url):
          print('正在请求:',url)
          time.sleep(2)
          print('请求完成':url)
          return url

        
#定义一个任务对象的回调函数:
#task参数就是该函数被绑定的任务对象,必须要有.
def task_callback(task):
    print('a callback')
    print(task)
    print(task.result())
#task.result()返回的是特殊函数内部的返回值.



c1 = request('www.qq.com')#协程对象
c2 = request('www.qq.com')
c3 = request('www.qq.com')
task_A = asyncio.ensure_future(c1)
task_B = asyncio.ensure_future(c2)
task_C = asyncio.ensure_future(c3)#任务对象

#绑定回调:函数请求完毕后调用
#只传回调函数的名字,没有传递参数,task就是参数,
task.add_done_callback(task_callback)


#创建时间循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到事件循环对象中并且开启事件循环
loop.run_until_complete(task_A)#放协程对象会是可以的,肯定不会把协程对象直接注册到事件循环

事件循环:(eventloop)我们必须将任务对象注册到事件循环对象中,开启事件循环对象.无限的循环对象(无法确定执行次数是多少次,遇到多少次阻塞).

事件循环开启之后,a,b,c都开始循环执行,都会有阻塞,如果单线程的话,会耗时,事件循环是异步核心,事件循环对象执行任务对象是基于异步的,执行a对象的发现阻塞时,挂起阻塞对象,开始同时执行b,b遇到阻塞时也会执行c,但是此时当a阻塞完毕时,他会直接去执行a,等a处理完成后再去执行c.(a被执行了两次,所以跟任务对象个数无关)

import asyncio
import time

async def request(url):
          print('正在请求:',url)
          time.sleep(2)
          print('请求完成':url)

c1 = request('www.qq.com')#协程对象
c2 = request('www.qq.com')
c3 = request('www.qq.com')
task_A = asyncio.ensure_future(c1)
task_B = asyncio.ensure_future(c2)
task_C = asyncio.ensure_future(c3)#任务对象

#创建事件循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到时间循环对象中并且开启事件循环
loop.run_until_complete(task_A)#放协程对象会是可以的,肯定不会把协程对象直接注册到事件循环

关键字:

await:

当前阻塞时,交出cpu的控制权,就挂起.

注意事项:

  • 特殊函数内部不可以出现不支持异步的模块代码
  • 特殊函数内部遇到阻塞操作时,必须使用await对其进行手动挂起.
  • 如果想要将多个任务注册到事件循环中,必须将多个任务对象装进一个列表中,必须使用wait方法将列表中的任务进行挂起.

多任务异步协程对象:

import asyncio
import time

#在特殊函数内部不可以出现不支持异步模块相关的代码.time模块不支持异步,
async def request(url):
          print('正在请求:',url)
          #time.sleep(2)
          #阻塞之后挂起,手动的设置
          await asyncio.sleep(2)
          print('请求完成':url)
          return url
urls = [
    'www.goo.com',
    'www.sss.com',
    'www.sss.com',
    'www.sss.com',
]

def task_callback(task):
    print(task.result())
    
tasks = []
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(cc)
    task.add_done_callback(task_callback)
    task.append(task)#装进一个列表内
    
    
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))#将任务列表中的任务对象进行挂起操作
    

requests模块不支持异步,从而我们引入支持异步的模块aiohttp

aiohttp模块:支持异步网络请求模块,pip install aiohttp

import asyncio
import time
import aiohttp
start = time.time()


#在特殊函数内部不可以出现不支持异步模块相关的代码
#简单的框架:在此基础上补充细节
async def request(url):
        with aiohttp.ClientSession() as s:
            #s.get/post和requests中的get/post用法几乎一样:url,headers,data/params
            #在s.get中如果使用代理操作:proxy = 'http://ip:port'
            with s.get(url) as response:
                #获取字符串类型的响应数据:response.text()
                #获取byte类型:response.read()
                page_text = response.text()
                return page_text
#细节1:在每一个with前加上async关键字
#细节2:在get方法前和response.text()前加上await关键字进行手动挂起操作

#真实代码:
async def request(url):
    async with aiohttp.ClientSession() as s:
       async with await s.get(url) as response:
        page_text = await response.text()
        return page_text
urls = []
for i in range(500):
    urls.append('https://127.0.0.1:5000/bobo')

def parse(task):
    page_text = task.result()
    print(page_text+',请求数据')
    
tasks =[]
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    task.add_done_callback(parse)
    tasks.append(task)
    
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

print(time.time()-start)
    

aiohttp案例:

import aiohttp
import asyncio
from lxml import etree

headers = 
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/73.0.3683.86 Safari/537.36'


async def request(url):
    async with aiohttp.ClientSession() as s:
        async with await s.get(url,headers=headers) as response:
            page_text = await response.text()
            return page_text

urls = []
all_titles = []
url = 'http://wz.sun0769.com/index.php/question/reply?page=%d'
for page in range(10):
    u_page = page*30
    new_url = format(url%u_page)
    urls.append(new_url)

def parse(task):
    page_text = task.result()#page_text
    tree = etree.HTML(page_text)
    tr_list = tree.xpath('/html/body/div[4]/table[2]//tr')
    for tr in tr_list:
        title = tr.xpath('./td[2]/a[1]/text()')[0]
        print(title)
        all_titles.append(title)
    
tasks = []
for url in urls:
    c = request(url)#协程对象
    task = asyncio.ensure_future(c)#封装成任务对象
    task.add_done_callback(parse)
    tasks.append(task)
    
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
    
    

async:

selenium基本使用:

概念:基于浏览器自动化的模块.

selenium和爬虫之间的关联:很便捷的捕获动态加载的数据,实现模拟登陆,登陆成功之后可以将页面的爬取.

使用环境:

pip install selenium

下载一个浏览器的驱动程序

创建一个浏览器对象

from  selenium import webdriver


bro = webdriver.Chrome(executable_path = 'chromedriver.exe')#驱动程序路径

#发起指定url请求
bro.get('http:www.baidu.com')

#再搜索框中搜索

#可以使用find系列方法标签定位
bro.find_element_by_xpath('//*[@id="key"]')

#向搜索框中写入商品名称
search_input.send_keys('iphone')
sleep(2)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')

btn.click()
sleep(2)

bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
bro.execute_script('window.scrollTo(0,-document.body.scrollHeight)')
page_text = bro.page_source
with open('./jd.html','w',encoding='utf-8')as p:
    p.write(page_text)
#关闭浏览器
bro.quit()

动作链:

如果想要触发一系列的连续的行为动作.actionchains

from selenium import webdriver
from selenium.webdriver import ActionChains #动作连
from time import sleep

bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

#定位要拖动的标签
#定位的标签是存在于iframe的子页面中,如果直接使用find做定位,是定位不到的
# target_ele = bro.find_element_by_id('draggable')

#像定位iframe中子页面中的标签必须进行如下操作
bro.switch_to.frame('iframeResult')
target_ele = bro.find_element_by_id('draggable')

#基于动作连实现滑动操作
action = ActionChains(bro)
#点击且长按
action.click_and_hold(target_ele)

for i in range(5):
    #perform()表示立即执行动作连指定好的动作
    action.move_by_offset(17,0).perform()
    sleep(0.5)

action.release()

sleep(4)

bro.quit()

以上是关于单线程实现了多任务异步协程的主要内容,如果未能解决你的问题,请参考以下文章

单线程多任务异步协程

单线程多任务异步抓取(asyncio)

爬虫第四章 单线程+多任务异步协程

python爬虫 单线程的多任务异步协程

python-协程多线程多进程性能比较

关于线程