异步爬虫

1. 多进程 多线程     (不建议)
2. 进程池 线程池     (适当)
3. 单线程 + 异步协程  (推荐)
参考: https://www.cnblogs.com/bobo-zhang/p/10735140.html

多线程

线程池爬取梨视频

import re
import random
import requests
from lxml import etree
from multiprocessing.dummy import Pool

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'
}
url = 'https://www.pearvideo.com/category_1'
page_text = requests.get(url=url, headers=headers).text

tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideonListUl"]/li')

# 实例化一个线程池对象
pool = Pool(4)
video_url_list = []  # 所有的视频连接
for li in li_list:
    detail_url = 'https://www.peavideo.com' + li.xpath('./div/a/@href')[0]
    detail_page_text = requests.get(url=detail_url, headers=headers).text
    ex = 'srcUrl="(.*?)", vdoUrl='
    video_url = re.findall(ex, detail_page_text, re.S)[0]
    video_url_list.append(video_url)

def request_video(url):
    return requests.get(url=url, headers=headers).content

def save_video(data):
    name = str(random.randint(0, 999)) + '.mp4'
    with open(name, 'wb') as f:
        f.write(data)

# 异步获取4个视频的二进制文件
video_data_list = pool.map(request_video, video_url_list)

# 进行视频持久化存储
pool.map(save_video, video_data_list)

单线程 + 异步协程

asyncio 模块

asyncio 第三方模块, 可以实现异步网络操作, 并发和协程
官网描述: 当代码需要执行一个耗时的 I/O 操作的时候, 它只发出 I/O 的指令, 并不等待 I/O 的结果, 然后去执行其它的代码, 以提高效率。

关于 asyncio 的关键字:
1) event_loop 事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,函数就会被循环执行. 程序是按照设定的顺序从头执行到尾, 运行的次数也是完全按照设定. 当编写异步程序时, 其中有些部分程序的运行是耗时比较久, 需要先让出当前程序的控制权, 让其在背后运行, 而另一部分先运行起来, 当背后的运行的程序完成后, 也需要及时通知主程序已经完成任务可以进行下一步操作, 但这个过程时间并不确定, 需要主程序监听状态, 一旦检测完成, 就开始下一步, loop就是这个持续不断的监听器.

2) coroutine 协程:协程对象(一个特殊函数的返回值),指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

3) task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程对象进一步封装,其中包含了任务的各种状态

4) future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别

5) async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。
aiohttp  # 支持异步的请求模块

实例

import time
import asyncio

now = lambda : time.time()

# 非异步模块的代码:在此处如果存在非异步操作代码,则会彻底让asyncio失去异步的效果
async def do_some_work(x):
    print("waiting:", x)
    # await 后面就是调用耗时的操作(暂时挂起阻塞任务, 继续执行其他任务)
	await asyncio.sleep(x)   # 模拟了阻塞或者耗时操作
    return "Done after {}s".format(x)
    
    
start = now()
# 这里是一个协程对象,这个时候do_some_work函数并没有执行
coroutine = do_some_work(2)

#  创建一个事件loop(循环)
loop = asyncio.get_event_loop()

# task对象是Future类的子类,保存了协程运行后的状态,用于未来获取协程的结果
# task = loop.create_future()
task = asyncio.ensure_future(coroutine)

# 将协程注册到事件循环loop中,并启动事件循环
loop.run_until_complete(task)

print("Time:",now()-start)

asyncio + aiohttp 实现协程

import time
import aiohttp
import asyncio

async def get_page(url):
    async with aiohttp.ClientSession() as session:         # 实例化session对象
        async with await session.get(url=url) as response: # 发送get请求
            text = await response.text()
            print(text)

urls = [
    'http://127.0.0.1:5000/xixi',
    'http://127.0.0.1:5000/haha',
    'http://127.0.0.1:5000/hehe',
]
start = time.time()
# 任务列表, 放置多个任务对象
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
    cro = get_page(url)
    task = asyncio.ensure_future(cro)
    tasks.append(task)
    
# 将多个任务对象对应的列表注册到事件循环中
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)

回调函数用于解析数据

import time
import aiohttp
import asyncio

def callback(task):
    print('this is callback')
    # 获取响应数据
    page_text = task.result()
    print("在回调函数中, 实现数据解析")

async def get_page(url):
    async with aiohttp.ClientSession() as session:
        async with await session.get(url=url) as response:
            text = await response.text()
            print(text)

urls = [
    'http://127.0.0.1:5000/xixi',
    'http://127.0.0.1:5000/haha',
    'http://127.0.0.1:5000/hehe',
]
start = time.time()
tasks = []
loop = asyncio.get_event_loop()
for url in urls:
    cro = get_page(url)
    task = asyncio.ensure_future(cro)
    # 给任务对象绑定回调函数, 用于解析响应数据
    task.add_done_callback(callback)
    tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)

模拟服务器

from flask import Flask
import time

app = Flask(__name__)

@app.route('/xixi')
def func1():
    time.sleep(2)
    return 'xixi'

@app.route('/haha')
def func2():
    time.sleep(2)
    return 'haha'

@app.route('/hehe')
def func3():
    time.sleep(2)
    return 'hehe'

if __name__ == '__main__':
    app.run(threaded=True)
posted @ 2019-06-27 10:13  言值  阅读(194)  评论(0编辑  收藏  举报