• 异步爬虫


    一 线程池实现异步爬虫

    import time
    import requests
    from multiprocessing.dummy import Pool
    
    start_time = time.time()
    
    
    def get_page(url):
        print("正在下载:", url)
        response = requests.get(url)
        time.sleep(3)
        print("下载完成", url)
        return {'url': url, 'content': response.text}
    
    
    urls = [
        'http://www.jd.com',
        'https://www.baidu.com',
        'https://www.python.org'
    ]
    
    if __name__ == '__main__':
        # 实例化一个线程对象
        pool = Pool(4)
        # 将列表中每一个列表元素传给get_page进行处理
        pool.map(get_page, urls)

    使用线程池爬取梨视频数据

    import time, re
    import requests
    from multiprocessing.dummy import Pool
    from lxml import etree
    
    # 线程池处理的是阻塞较为耗时的操作
    start_time = time.time()
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
    }
    # 对下述url发起请求解析出视频详情页的url和视屏名称
    url = "https://www.pearvideo.com/category_5"
    page_text = requests.get(url=url, headers=headers).text
    
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
    urls = []  # 存储所有视频的连接
    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(detail_url, name)
        # 对详情页的url发起请求
        detail_page_text = requests.get(url=detail_url, headers=headers).text
        # 从详情页中解析出视频的地址 该视频地址在js中
        ex = 'srcUrl="(.*?)",vdoUrl'
        video_url = re.findall(ex, detail_page_text)[0]
        urls.append({'name': name, "url": video_url})
    
    
    def get_video_data(data):
        url = data['url']
        video_name = data['name']
        print(video_name, "正在下载")
        video_data = requests.get(url=url, headers=headers).content
    
        # 持久化存储操作
    
        with open(video_name, 'wb') as fp:
            fp.write(video_data)
            print(video_name, "下载成功!")
    
    
    # 使用线程池对视频数据进行请求(较为耗时的阻塞操作)
    pool = Pool(4)
    pool.map(get_video_data, urls)
    pool.close()
    pool.join()

    二 单线程+异步协程(推荐):

    event_loop: 事件循环, 相当于一个无限循环, 我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行
    coroutine:协程对象, 我们可以将协程对象注册到事件循环中, 它会被事件循环调用。我们可以使用async关键字来定义一个方法, 这个方法在调用时不会立即被执行,而是返回一个协程对象
    task: 任务, 它是对协程对象的进一步封装, 包含了任务的各个状态
    async: 定义一个协程
    await: 用来挂起阻塞方法的执行

    协程使用示例

    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的使用

    task是对 coroutine 对象的进一步封装,它里面相比 coroutine 对象多了运行状态,比如 running、finished 等,我们可以用这些状态来获取协程对象的执行情况。

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

    furture的使用

    另外定义 task 对象还有一种方式,就是直接通过 asyncio 的 ensure_future() 方法,返回结果也是 task 对象,这样的话我们就可以不借助于 loop 来定义,即使我们还没有声明 loop 也可以提前定义好 task 对象,写法如下:

    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)

    绑定回调函数

    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)
    
    # 回调函数
    def callback_full(task):
        print(task.result())
    
    # 将回调函数绑定到任务对象中
    task.add_done_callback(callback_full)
    loop.run_until_complete(task)

    多任务协程

    如果我们想执行多次请求应该怎么办呢?我们可以定义一个 task 列表,然后使用 asyncio 的 wait() 方法即可执行。

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

    多任务异步爬虫

    import asyncio
    import time
    import requests
    
    start = time.time()
    urls = [
        'http://www.jd.com',
        'https://www.baidu.com',
        'https://www.python.org'
    ]
    
    
    async def get_page(url):
        print("正在下载", url)
        # request.get是基于同步, 必须使用基于异步的网络请求模块进行指定url的请求发送
        # aiohttp: 基于异步网络请求模块
        response = requests.get(url=url)
        print("下载完毕", response.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()
    print(end - start)

     aiohttp+多任务异步协程

    #环境安装:pip install aiohttp
    # 使用该模块中的ClientSession
    import asyncio
    import time
    import requests
    import aiohttp
    
    start = time.time()
    urls = [
        'http://www.jd.com',
        'https://www.baidu.com',
        'https://www.python.org'
    ]
    
    
    async def get_page(url):
        print("正在下载", url)
        # aiohttp: 基于异步网络请求模块
        async with aiohttp.ClientSession() as session:
            # get():发起get请求
            # post(): 发起post请求
            # get和post可以添加headers(UA伪装)参数, params/data(携带参数), proxy="http://ip:port"(代理IP) 
            async with await session.get(url) as response:
                # text()方法返回字符串形式的响应数据
                # read()返回的二进制形式的响应数据
                # json()返回josn对象
                # 注意:在获取相应数据之前一定要使用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()
    print(end - start)
  • 相关阅读:
    CentOS7利用docker安装MySQL5.7
    阿里云安装Nginx+vue项目部署
    python3 读取串口数据
    CentOS7.0安装EMQ代理服务
    Vue项目接入MQTT
    使用Python发送、订阅消息
    Android监听消息通知栏点击事件
    获取 Android APP 版本信息工具类(转载)
    Android 获取手机的厂商、型号、Android系统版本号等工具类(转载)
    树莓派通过语音模块下发指令点亮小灯泡
  • 原文地址:https://www.cnblogs.com/harryblog/p/11388933.html
Copyright © 2020-2023  润新知