首页 > 技术文章 > 爬虫入门四(提升爬取数据的效率:线程池和异步协程)

zhangdadayou 2019-12-06 22:23 原文

1、提升爬取数据的效率:线程池

简述

from multiprocessing.dummy import Pool
map(func,alist): # func 函数 alist 可迭代元素 分别将里面的值 传入func中
    可以让func回调函数处理alist中的每一个列表元素,这个处理的过程是基于异步。

示例1:线程池的应用

import requests
import time
from multiprocessing.dummy import Pool
start = time.time()
#开启线程池
pool = Pool(3)
# 要访问的url
urls = [
    'http://127.0.0.1:5000/index',
    'http://127.0.0.1:5000/index',
    'http://127.0.0.1:5000/index'
]
#用作与网络请求(耗时) 
def req(url):
    return requests.get(url).text
# 开启 进程池里面有三个进程 分别将url中元素 传入req函数中 
# 最后 page_text_list 是线程池结果 类型是列表
page_text_list = pool.map(req,urls)
print(page_text_list)
print('总耗时:',time.time()-start)

2、单线程+多任务异步协程asyncio

1、特殊函数

就是async关键字修饰的一个函数的定义
特殊之处:
	特殊函数被调用后会返回一个协程对象
	特殊函数调用后内部的程序语句没有被立即执行

2、协程

对象:协程=特殊的函数 协程表示就是一组特定的操作

3、任务对象

高级协程(对协程进一步的封装)
	任务对象 == 协程 == 特殊的函数
		任务对象 == 特殊得函数
	
绑定回调
	task.add_done_callback(task)
		- 参数task:当前回调函数对应的任务对象
		- task.result():返回的就是任务对象对应的特殊函数的返回值
		

4、事件循环对象

- 创建事件循环对象
- 将任务对象注册到对象中并开启该对象
- 作用:loop可以将其内部注册的所有任务对象进行异步执行

5、挂起

挂起:就是交出cpu的使用权。

重点:不支持异步模块,则会中断异步效果

在特殊函数内部的实现中,不可以出现不支持异步的模块代码,如果出现了,
    则会中断整个的异步效果!!!
    
 requests模块不支持 使用 aiohttp模块
 time.sleep不支持 使用 await asyncio.sleep(2)

示例1:协程的基础

import asyncio
from time import sleep

#特殊的函数
async def get_request(url):
    print('正在下载:',url)
    sleep(2)
    print('下载完毕:',url)

    return 'page_text'
#回调函数的定义(普通的函数)
def parse(task):
    #参数表示的就是任务对象  task.result()就是 get_request的返回值
    print('i am callback!!!',task.result())

#特殊函数的调用 c 就是一个协程
c = get_request('www.1.com')

#创建一个任务对象
task = asyncio.ensure_future(c)
#给任务对象绑定一个回调函数
task.add_done_callback(parse)

#创建一个事件循环对象
loop = asyncio.get_event_loop()
#将任务对象注册到该对象中并且开启该对象
loop.run_until_complete(task)#让loop执行了一个任务

示例二:多任务异步爬虫

import asyncio
import requests
import time
from bs4 import BeautifulSoup
#将被请求的url全部整合到一个列表中
urls = ['http://127.0.0.1:5000/bobo','http://127.0.0.1:5000/jay','http://127.0.0.1:5000/tom']
# 计算开始事件
start = time.time()

#特殊函数
async def get_request(url):
    #requests模块不支持异步,中断了整个的异步效果
    page_text = requests.get(url).text
    return page_text
# 回调函数 普通的函数
def parse(task):
    page_text = task.result()
    soup = BeautifulSoup(page_text,'lxml')
    data = soup.find('div',class_="tang").text
    print(data)
    
tasks = []
# 循环urls 
for url in urls:
    # 将参数带入 get_request 并执行 c就是协程
    c = get_request(url)
    # 创建一个任务对象
    task = asyncio.ensure_future(c)
    # 给任务对象绑定一个回调函数
    task.add_done_callback(parse)
    # 并将 任务对象 添加到tasks列表中
    tasks.append(task)
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
#将任务对象列表注册到该对象中并且开队该对象列表 当有多个对象时 假如遇见io阻塞 则必须执行完这个任务才执行下一个 所以这时候用到了 wait 挂起 遇见io阻塞则执行下一个
loop.run_until_complete(asyncio.wait(tasks))
# 输出总耗时
print('总耗时:',time.time()-start)

# 执行结果为 6 秒多 因为requests模块不支持异步所以 中断了异步结果  这时候需要用aiohttp模块

aiohttp的用法

# aiohttp时一个支持异步的网络请求模块
# 环境安装
# 编码流程
		# 大致框架
    	with aiohttp.ClientSession() as s:
           # 代理使用方法 #s.get(url,headers,params,proxy="http://ip:port")
                with s.get(url) as response:
                    # 二进制读取方法 requests则是用.content
                    #response.read()二进制(.content)
                    
                    # aiohttp中获取文本是方法不是属性
                    page_text = response.text()
                    return page_text
                
                
                
 # 细节补充
- 在每一个with前加上async
            - 需要在每一个阻塞操作前加上await
                async with aiohttp.ClientSession() as s:                   #s.get(url,headers,params,proxy="http://ip:port")
                    async with await s.get(url) as response:
                        #response.read()二进制(.content)
                        page_text = await response.text()
                        return page_text

示例三:基于aiohttp多任务异步爬虫

import asyncio
import aiohttp
import time
from bs4 import BeautifulSoup
#将被请求的url全部整合到一个列表中
urls = ['http://127.0.0.1:5000/bobo','http://127.0.0.1:5000/jay','http://127.0.0.1:5000/tom']
# 开始事件
start = time.time()

# 特殊函数
async def get_request(url):
    # 基于上下文管理 来使用 aiohttp 
    async with aiohttp.ClientSession() as s:
        #s.get(url,headers,params,proxy="http://ip:port")
        async with await s.get(url) as response:
            #response.read()二进制(.content)
            page_text = await response.text()
            return page_text
# 回调函数
def parse(task):
    # 获取 特殊函数的返回值 并赋值给page_text
    page_text = task.result()
    # 生成bs4的对象
    soup = BeautifulSoup(page_text,'lxml')
    # 获取该位置的文本
    data = soup.find('div',class_="tang").text
    # 输出该文本
    print(data)

tasks = []
# 循环urls
for url in urls:
    # 执行特殊函数 并给了c 其实就是一个协程
    c = get_request(url)
    # 创建一个任务对象
    task = asyncio.ensure_future(c)
    # 绑定回调函数
    task.add_done_callback(parse)
    # 并将任务对象添加到列表中
    tasks.append(task)
# 创建事件循环对象
loop = asyncio.get_event_loop()
#将任务对象列表注册到该对象中并且开队该对象列表 当有多个对象时 假如遇见io阻塞 则必须执行完这个任务才执行下一个 所以这时候用到了 wait 挂起 遇见io阻塞则执行下一个
loop.run_until_complete(asyncio.wait(tasks))

print('总耗时:',time.time()-start)
# 结果为 2s多点

推荐阅读