免责声明:自本文章发布起, 本文章仅供参考,不得转载,不得复制等操作。浏览本文章的当事人如涉及到任何违反国家法律法规造成的一切后果由浏览本文章的当事人自行承担与本文章博客主无关。以及由于浏览本文章的当事人转载,复制等操作涉及到任何违反国家法律法规引起的纠纷和造成的一切后果由浏览本文章的当事人自行承担与本文章博客主无关。
1. 基础知识
爬取请求过程中遇到阻塞, 采用高性能异步爬虫爬取数据.
异步爬虫方式:
- 多线程, 多进程.
1.1 好处: 可以为每一个阻塞操作开启单独的线程或者进程, 实现异步进行.
1.2 弊端: 不可以无限制的开启多进程或者多线程, 开启过多系统的开销大, 也会降低爬取速度.- 线程池, 进程池. (适当使用)
2.1 好处: 降低了系统对线程或者进程创建和销毁的频率, 降低系统的开销.
2.2 弊端: 池中的线程或者进程的数量有上限.- 单线程 + 异步协程. (推荐)
协程相关知识点:
event_loop
: 事件循环, 相当子一个无限循环, 可以把一些函数注册到这个事件循环上, 当满足某些条件的时候, 函数就会被循环执行.coroutine
: 协程对象, 我们可以将协程对象注册到事件循环中, 它会被事件循环调用. 可以使用async
关键字来定义一个方法, 这个方法在调用时不会立即被执行, 而是返回一个协程对象.task
: 任务, 它是对协程对象的进一步封装, 包含了任务的各个状态.future
: 代表将来执行或还没有执行的任务, 实际上和 task 没有本质区别. async 定义一个协程.await
: 用来挂起阻塞方法的执行.
2. 线程基本使用
单线程串行方式, 未使用线程池.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 导包
import time
# 发送请求
def get_page(page):
print("开始加载: %d" % page)
time.sleep(2)
print("加载完成: %d" % page)
if __name__ == '__main__':
# pages
pages = [1, 2, 3, 4]
# 开始时间
start_time = time.time()
# 开始加载
for p in pages:
get_page(p)
# 结束时间
end_time = time.time()
# 总时间
print("总时间: %d s" % (end_time - start_time))
使用线程池方式执行
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 导包
from multiprocessing.dummy import Pool
import time
# 发送请求
def pool_page(page):
print("开始加载: %d" % page)
time.sleep(2)
print("加载完成: %d" % page)
if __name__ == '__main__':
# 对象
pages = 4
# 计算运行时间
start_time = time.time()
# 实例化线程对象
pool = Pool(4)
# 将 list 的每一个元素传递给 pool_page(page) 处理
pool_map = pool.map(pool_page, range(1, pages + 1))
# 关闭线程
pool.close()
# 总时间
print("总时间: %d s" % (time.time() - start_time))
3. 协程基本使用
基础使用
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import asyncio
# async 修饰的函数
async def request_url(url):
print("正在请求 " + url)
print("成功请求 " + url)
if __name__ == '__main__':
# 得到协程对象
coroutine = request_url("www.baidu.com")
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将协议对象注册到 loop 中, 然后启动 loop
loop.run_until_complete(coroutine)
task 的基本使用.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import asyncio
# async 修饰的函数
async def request_url(url):
print("正在请求 " + url)
print("成功请求 " + url)
if __name__ == '__main__':
# 得到协程对象
coroutine = request_url("www.baidu.com")
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 创建 task 对象
task = loop.create_task(coroutine)
print(task)
# 将 task 对象注册到 loop 中, 然后启动 loop
loop.run_until_complete(task)
print(task)
future 任务的基本使用. 和 task 创建对象有区别.
区别在于 task 对象由时间循环创建.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import asyncio
# async 修饰的函数
async def request_url(url):
print("正在请求 " + url)
print("成功请求 " + url)
if __name__ == '__main__':
# 得到协程对象
coroutine = request_url("www.baidu.com")
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 创建 task 对象
task = asyncio.ensure_future(coroutine)
print(task)
# 将 task 对象注册到 loop 中, 然后启动 loop
loop.run_until_complete(task)
print(task)
绑定回调 的基本使用
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import asyncio
# async 修饰的函数
async def request_url(url):
print("正在请求 " + url)
print("成功请求 " + url)
return url
# 回调函数
def callback(callback_task):
# result() 方法是任务中封装的协程对象对应函数的返回值
print(callback_task.result())
if __name__ == '__main__':
# 得到协程对象
coroutine = request_url("www.baidu.com")
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 创建 task 对象
task = asyncio.ensure_future(coroutine)
# 将回调对象绑定在任务对象中, task 成功后完成前看是回调该函数.
task.add_done_callback(callback)
# 将 task 对象注册到 loop 中, 然后启动 loop
loop.run_until_complete(task)
4. 多任务协程
需要注意:
- 异步协程中出现同步模块相关的代码, 则无法进行异步.
- 当 asyncio 遇到阻塞操作必须进行手动挂起.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import asyncio
import time
# async 修饰的函数
async def request_url(url):
print("正在请求 " + url)
# 异步协程中出现同步模块相关的代码, 则无法进行异步
# time.sleep(2)
# 当 asyncio 遇到阻塞操作必须进行手动挂起
await asyncio.sleep(2)
print("成功请求 " + url)
if __name__ == '__main__':
# 开始时间
start_time = time.time()
# 请求的 url
urls = [
"https://www.baidu.com",
"https://www.sougou.com/",
"https://blog.csdn.net/"
]
# 任务列表
tasks = []
for u in urls:
# 得到协程对象
coroutine = request_url(u)
# 创建任务对象
task = asyncio.ensure_future(coroutine)
# 添加任务对象
tasks.append(task)
# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将 tasks 对象封装到 wait 中后注册到 loop 中, 然后启动 loop
loop.run_until_complete(asyncio.wait(tasks))
# 总时间
print("总时间: %d s" % (time.time() - start_time))