Python爬虫之高性能异步爬虫

Python爬虫之高性能异步爬虫

  • 一:异步爬虫的方式
  • 二:线程池的基本使用
  • 三:线程池案例
  • 四:协程的使用
    • 4.1 初识协程
    • 4.2 多任务异步协程
  • 五:aiohttp异步网络请求模块
    • 5.1 安装aiohttp模块
    • 5.2 aiohttp案例

一:异步爬虫的方式

1:多线程,多进程(不建议)
好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
弊端:无法无限制的开启多线程或者多进程
2:线程池,进程池(适当的使用)
好处:可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销
弊端:池中线程或进程的数量是有上限
3:单线程+异步协程(推荐)
event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
coroutine:协程对象,可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或还没有执行的任务,实际上和task没有本质的区别。
async:定义一个协程。
await:用来挂起阻塞方法的执行。

二:线程池的基本使用

首先看一个单线程串行方式的执行:

import time
# 使用单线程串行方式执行
def get_page(str):
    print('downloading: ', str)
    time.sleep(2)   # 模拟阻塞
    print('download success: ', str)
name_list = ['a', 'b', 'c', 'd']
start_time = time.time()
for i in range(len(name_list)):
    get_page(name_list[i])
end_time = time.time()
print('%d second' % (end_time - start_time))

输出结果为:

downloading:  a
download success:  a
downloading:  b
download success:  b
downloading:  c
download success:  c
downloading:  d
download success:  d
8 second

采用线程池的方式执行:

import time
from multiprocessing.dummy import Pool
# 使用线程池的方式执行
start_time = time.time()
def get_page(str):
    print('downloading: ', str)
    time.sleep(2)  # 模拟阻塞
    print('download success: ', str)
name_list = ['a', 'b', 'c', 'd']
# 实例化一个线程池对象
pool = Pool(4)  # 开辟4个线程
# 将列表中的每一个元素传递给get_page执行
pool.map(get_page, name_list)
end_time = time.time()
print(end_time - start_time)

输出结果:

downloading: downloading:  bdownloading:  downloading: 
 ca

 d
download success:  download success: download success: download success: a
   b
cd
2.030400037765503

三:线程池案例

from multiprocessing.dummy import Pool
import requests
import re
from lxml import html
etree = html.etree

url = 'https://www.pearvideo.com/category_5'
headers = {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
page_text = requests.get(url = url, headers = headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')

video_data = []
for li in li_list:
    detail_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
    name = li.xpath('./div/a/div[2]/text()')[0] + '.mp4'
    # print(name, detail_url)
    detail_page_text = requests.get(url = detail_url, headers = headers).text
    pattern = re.compile(r'srcUrl="(.*)",vdoUrl')
    video_url = pattern.findall(detail_page_text)[0]
    dic = dict(name = name, url = video_url)
    video_data.append(dic)

# print(video_data)
def get_video_data(dic):
    url = dic['url']
    print(dic['name'], ' downloading')
    data = requests.get(url = url, headers = headers).content
    with open(dic['name'], 'wb') as fp:
        fp.write(data)
        print(dic['name'], ' download success')
pool = Pool(4)
pool.map(get_video_data, video_data)
pool.close()
pool.join() # 主线程等待子线程结束

四:协程的使用

4.1 初识协程

# 单任务协程
import asyncio
async def request(url):
    print('正在请求的url是:', url)
    print('请求成功:', url)
# async修饰的函数,调用后返回一个协程对象
c = request('www.baidu.com')
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(c)

task的使用:

import asyncio
async def request(url):
    print('正在请求的url是:', url)
    print('请求成功:', url)
# async修饰的函数,调用后返回一个协程对象
c = request('www.baidu.com')
# task的使用
loop = asyncio.get_event_loop()
# 基于loop创建了一个task对象
task = loop.create_task(c)
print(task)		# 输出task状态
loop.run_until_complete(task)
print(task)

输出结果:

<Task pending coro=<request() running at G:/pywwroot/spider/yibu.py:6>>
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
<Task finished coro=<request() done, defined at G:/pywwroot/spider/yibu.py:6> result=None>

future的使用:

import asyncio
async def request(url):
    print('正在请求的url是:', url)
    print('请求成功:', url)
# async修饰的函数,调用后返回一个协程对象
c = request('www.baidu.com')
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
print(task)
loop.run_until_complete(task)
print(task)

输出结果:

<Task pending coro=<request() running at G:/pywwroot/spider/yibu.py:6>>
正在请求的url是: www.baidu.com
请求成功: www.baidu.com
<Task finished coro=<request() done, defined at G:/pywwroot/spider/yibu.py:6> result=None>

绑定回调:

import asyncio
async def request(url):
    print('正在请求的url是:', url)
    print('请求成功:', url)
    return url
# async修饰的函数,调用后返回一个协程对象
c = request('www.baidu.com')

def callback_func(task):
    print(task.result())
# 绑定回调
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)

输出结果:

正在请求的url是: www.baidu.com
请求成功: www.baidu.com
www.baidu.com

4.2 多任务异步协程

import asyncio, time
async def request(url):
    print('正在下载:', url)
    # 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步
    # time.sleep(2)
    # 当在asyncio中遇到阻塞操作必须进行手动挂起
    await asyncio.sleep(2)
    print('下载完成:', url)
start = time.time()
urls = [
    'www.baidu.com',
    'www.sogou.com',
    'www.goubanjia.com'
]
# 任务列表:存放多个任务对象
tasks = []
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)
loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(tasks))
print(time.time() - start)

五:aiohttp异步网络请求模块

上面已经看到了多任务异步协程,为什么还要使用aiohttp模块呢?我们先看一个协程异步多任务的案例:

import asyncio, time
import requests
start_time = time.time()
urls = [
    'https://www.baidu.com',
    'https://www.sogou.com',
    'https://www.csdn.net/'
]
async def get_page(url):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}
    print('正在下载: ', url)
    # requests.get是基于同步的;必须使用基于异步的网络请求模块进行指定url的请求发送
    # aiohttp:基于异步网络请求的模块
    response = requests.get(url = url, headers = headers)
    print('下载完成: ', url)
tasks = []
for url in urls:
    c = get_page(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end_time = time.time()
print(end_time - start_time)

输出结果:

正在下载:  https://www.baidu.com
下载完成:  https://www.baidu.com
正在下载:  https://www.sogou.com
下载完成:  https://www.sogou.com
正在下载:  https://www.csdn.net/
下载完成:  https://www.csdn.net/
14.021636009216309

运行代码后发现,代码并没有异步执行,而是同步执行了。其主要原因就是requests.get是基于同步的;必须使用基于异步的网络请求模块进行指定url的请求发送

5.1 安装aiohttp模块

pip install aiohttp

5.2 aiohttp案例

import asyncio, time
import requests
import aiohttp

start_time = time.time()
urls = [
    'https://www.baidu.com',
    'https://www.sogou.com',
    'https://www.csdn.net/'
]
async def get_page(url):
    async with aiohttp.ClientSession() as session:
        async with await session.get(url) as response:
            # text()返回字符串数据;read()返回二进制数据;json()返回json对象
            # 在获取响应数据操作之前一定要使用await进行手动挂起
            page_text = await response.text()
            print(page_text)

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

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end_time = time.time()
print(end_time - start_time)

你可能感兴趣的:(Python爬虫,Python爬虫,Python线程,Python进程,Python协程,aiohttp模块)