第三十四篇 异步爬虫之协程爬取以及视频网站爬取实例

协程最大的优势就是:
协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。但相应的是协程的控制难度比较高,容易出错。

*单线程-异步协程:

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

使用方法:
1.导入模块:
import async asyncio的编程模型就是一个消息循环
2.创建事件循环对象

 loop = asyncio.get_event_loop()

3.创建任务

c=func()
c为函数执行的结果
task = asyncio.ensure_future(c)   #用于显示状态和传递参数

4.将协程对象注册到loop中然后启动

loop.run_until_complete(task)  

5.回调函数用法:

import asyncio

#async修饰函数,调用之后的一个协程对象
async def request(url):   
    print("正在请求的url",url)
    print("请求成功",url)
    return url
c=request("www.baidu.com")
#绑定回调
def callback_func(task):
    #result返回的是任务对象中封装的函数的返回值
    print("result :",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
result : www.baidu.com

看一个协程的代码:

当然也可以不需要task操作,直接将函数返回的协程对象传给注册到的loop中也可以。

import asyncio
import time

async def request(url):
    print("正在下载 ",url)
    #在异步协程中如果出现了同步模块相关的代码,异步无法实现,如time.sleep()
    #time.sleep(2)
    #async中也有sleep方法,是基于异步的
    await asyncio.sleep(2)  #遇到阻塞必须手动挂起
    print("下载完成 ",url)

start_time=time.time()
urls=[
    "www.baidu.com",
    "www.sougou.com",
    "www.goubanjia.com"
]

#任务列表,存放多个任务对象
tasks = []
for url in urls:
    c = request(url)
    task=asyncio.ensure_future(c)
    tasks.append(c)

loop = asyncio.get_event_loop()
#固定的语法格式,需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(tasks))

print(time.time()-start_time)

结果:

正在下载  www.baidu.com
正在下载  www.sougou.com
正在下载  www.goubanjia.com
下载完成  www.baidu.com
下载完成  www.sougou.com
下载完成  www.goubanjia.com
2.0048012733459473

应用协程如何爬取网页

使用异步网页模块:aiohttp模块

flask服务器:

from flask import Flask
import time

app=Flask(__name__)

@app.route('/tian')
def index_tian():
    time.sleep(2)
    return "hello tian"

@app.route('/jian')
def index_jian():
    time.sleep(2)
    return "hello jian"

@app.route('/ying')
def index_ying():
    time.sleep(2)
    return "hello ying"

if __name__ == '__main__':
    app.run(threaded=True)

在没有aiohttp异步请求时:

import asyncio
import requests
import aiohttp
import time

start_time=time.time()
urls=[
    "http://127.0.0.1:5000/tian","http://127.0.0.1:5000/jian","http://127.0.0.1:5000/ying"
]
async def get_page(url):
    print("正在下载",url)
    res=requests.get(url=url)  #requests模块是同步的,必须使用异步的网络请求模块
    print("下载完毕",res.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))

print(time.time()-start_time)

结果:

正在下载 http://127.0.0.1:5000/tian
下载完毕 hello tian
正在下载 http://127.0.0.1:5000/jian
下载完毕 hello jian
正在下载 http://127.0.0.1:5000/ying
下载完毕 hello ying
6.032746315002441

使用aiohttp异步请求时:
单线程的方式,让其效率大大提高


import asyncio
import requests
import aiohttp
import time

start_time=time.time()
urls=[
    "http://127.0.0.1:5000/tian","http://127.0.0.1:5000/jian","http://127.0.0.1:5000/ying"
]
async def get_page(url):  
    async with aiohttp.ClientSession() as session:   #所有with前必须有async修饰
        #get()/post  params/data  headers  proxy="http://ip:port"
        async with await session.get(url) as res:  #耗时的操作必须进行挂起操作
            page_text=await res.text()  #在获取响应数据之前必须手动挂起
            #text()字符串格式数据
            #read()二进制格式响应数据
            #json()返回json对象
            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))  #将参数传入并运行

print(time.time()-start_time)

结果:

hello tian
hello jian
hello ying
2.06882905960083

视频下载实例代码:

使用单线程异步协程爬取视频网站视频

import time
import asyncio
import aiohttp
import os
import requests
from lxml import etree
import re

path="./多线程,多进程/aiohttp实例视频/" 
if not os.path.exists(path):
    os.mkdir(path)

start_time=time.time()

headers={
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"
    }

# urls=[
#     "https://video.pearvideo.com/mp4/third/20191120/cont-1624716-10964589-180710-hd.mp4",
#     "https://video.pearvideo.com/mp4/adshort/20191120/cont-1624622-14612668_adpkg-ad_hd.mp4",
#     "https://video.pearvideo.com/mp4/third/20191121/cont-1624965-11472701-151804-hd.mp4"
#     ]
def find_url(url):     #遍历抓取视频url的函数
    res=requests.get(url=url,headers=headers).text
    tree=etree.HTML(res)
    name=tree.xpath('//div[@class="popularem-ath"]/a/h2/text()')
    detail_url=tree.xpath('//li[@class="popularem clearfix"]/div[@class="popularem-ath"]/a/@href')
    video_lis=[]
    for n,d in zip(name,detail_url):
        video_name=n+".mp4"
        video_path=path+video_name
        detail_url="https://www.pearvideo.com/"+d
        res_detail=requests.get(url=detail_url,headers=headers).text
        video_url=re.findall(r'srcUrl="(.*?)",vdoUrl=',res_detail)[0]
        video_lis.append(video_url)
    return video_lis

async def get_page(url):   #定义协程方法爬取url
    async with aiohttp.ClientSession() as session:   #所有with前必须有async修饰
        async with await session.get(url,headers=headers) as res:  #耗时的操作必须进行挂起操作
            down_time=time.time()
            page_text=await res.read()  #在获取响应数据之前必须手动挂起
            name=url.split("/")[-1]
            path_name=path+name
            with open(path_name,"wb") as f:
                f.write(page_text)
            print(url+" 下载完成,用时: %.2f秒"%(time.time()-down_time))

urls=find_url("https://www.pearvideo.com/popular_4")  #得到所有视频的url地址列表
tasks=[]
for url in urls:  #将函数的返回值逐一加入到task中
    c=get_page(url)
    task=asyncio.ensure_future(c)
    tasks.append(task)

loop=asyncio.get_event_loop()  #注册循环
loop.run_until_complete(asyncio.wait(tasks))  #启动循环,参数为列表时,固定方法用wait

print(time.time()-start_time)

结果:

单线程异步协程用时171秒

https://video.pearvideo.com/mp4/third/20191120/cont-1624716-10964589-180710-hd.mp4 下载完成,用时: 21.13秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624965-11472701-151804-hd.mp4 下载完成,用时: 23.08秒
https://video.pearvideo.com/mp4/adshort/20191120/cont-1624622-14612668_adpkg-ad_hd.mp4 下载完成,用时: 36.18秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624908-12308265-134207-hd.mp4 下载完成,用时: 43.87秒
https://video.pearvideo.com/mp4/adshort/20191118/cont-1623848-14604356_adpkg-ad_hd.mp4 下载完成,用时: 71.18秒
https://video.pearvideo.com/mp4/third/20191121/cont-1624864-12308265-115404-hd.mp4 下载完成,用时: 108.35秒
https://video.pearvideo.com/mp4/adshort/20191120/cont-1624599-14612561_adpkg-ad_hd.mp4 下载完成,用时: 124.40秒
https://video.pearvideo.com/mp4/third/20191118/cont-1623816-10887340-142811-hd.mp4 下载完成,用时: 165.08171.87573504447937

使用线程池爬取视频网站:

import requests
from lxml import etree
import re
import os
import time
from multiprocessing.dummy import Pool

path="./多线程,多进程/li_video/"  #设置路径
if not os.path.exists(path):
    os.mkdir(path)

headers={
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"
    }

def download_video(dic):  #抓取视频的函数,传入参数为字典,得先获取字典内的键值
    video_url=dic['url']
    video_path=dic['path']
    video_name=dic['name']
    down_time=time.time()
    video_data=requests.get(url=video_url,headers=headers).content
    with open(video_path,"wb") as f:
        f.write(video_data)
    print(video_name+" 下载完成,用时: %.2f秒"%(time.time()-down_time))

def find_url(url):     #遍历抓取视频url的函数
    res=requests.get(url=url,headers=headers).text
    tree=etree.HTML(res)
    name=tree.xpath('//div[@class="popularem-ath"]/a/h2/text()')
    detail_url=tree.xpath('//li[@class="popularem clearfix"]/div[@class="popularem-ath"]/a/@href')
    video_lis=[]
    for n,d in zip(name,detail_url):
        video_name=n+".mp4"
        video_path=path+video_name
        detail_url="https://www.pearvideo.com/"+d
        res_detail=requests.get(url=detail_url,headers=headers).text
        video_url=re.findall(r'srcUrl="(.*?)",vdoUrl=',res_detail)[0]
        print(video_url)
        dic={                   #将参数存储在字典中
            "path":video_path,
            "url":video_url,
            "name":video_name
        }
        video_lis.append(dic)
    pool=Pool(len(video_lis)) #实例话线程池
    pool.map(download_video,video_lis)
    pool.close()
    pool.join()

    
start_time=time.time()
find_url("https://www.pearvideo.com/popular_4")
end_time=time.time()
print("一共用时 %.2f秒"%(end_time-start_time))

结果:
线程池用时187秒

小姐姐要买车,销售竟然想出了这招.mp4 下载完成,用时: 92.09秒
从机油看爱车健康,保养还能这么玩.mp4 下载完成,用时: 100.49秒
环法上海站,我坐它体验了一圈赛道.mp4 下载完成,用时: 138.14秒
林肯总裁不惧车市寒冬,看好飞行家.mp4 下载完成,用时: 145.14秒
在机场举办汽车发布会,他是飞行家.mp4 下载完成,用时: 159.37秒
车市不好,供应商如何调整自己?.mp4 下载完成,用时: 162.65秒
实拍改装领克03:竟和法拉利撞脸?.mp4 下载完成,用时: 187.50
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于Python 3的多协程以及队列爬取时光网电视剧top100的示例代码: ```python import requests from bs4 import BeautifulSoup import asyncio import aiohttp import time from queue import Queue async def fetch(session, url): async with session.get(url) as response: return await response.text() async def parse(html): soup = BeautifulSoup(html, 'html.parser') for item in soup.select('div[class="mov_con"] ul li'): rank = item.find('span', class_='top_num').text name = item.find('span', class_='mov_title').text score = item.find('span', class_='total_score').text print(rank, name, score) async def worker(session, queue): while not queue.empty(): url = queue.get() html = await fetch(session, url) await parse(html) async def main(): urls = [f'http://www.mtime.com/top/tv/top100/index-{i+1}.html' for i in range(10)] queue = Queue() for url in urls: queue.put(url) async with aiohttp.ClientSession() as session: tasks = [asyncio.create_task(worker(session, queue)) for _ in range(10)] await asyncio.gather(*tasks) if __name__ == '__main__': start_time = time.time() asyncio.run(main()) end_time = time.time() print(f'Time used: {end_time - start_time} seconds') ``` 这段代码使用了Python的asyncio库和aiohttp库来实现多协程异步爬取网页,使用了Python的queue模块来实现任务队列。首先,我们定义了`fetch`函数来异步获取网页内容,其返回值为响应的文本内容。然后,我们定义了`parse`函数来解析网页内容,提取出电视剧的排名、名称和评分,并输出到控制台。接着,我们定义了`worker`函数来作为协程的工作函数,从任务队列中取出一个URL并异步地解析该URL对应的网页。最后,我们定义了`main`函数来创建任务队列,创建异步协程,以及启动异步任务。在`main`函数中,我们先创建了10个URL,然后将这些URL放入任务队列中。接着,我们使用`async with`语句创建一个异步会话,并使用`create_task`函数创建10个异步协程,每个协程都调用`worker`函数,从任务队列中取出一个URL,并异步地解析对应的网页。最后,我们使用`asyncio.gather`函数等待所有的异步协程执行完毕。在程序执行结束后,我们还输出了程序的执行时间。 需要注意的是,由于时光网在一定时间内会对IP进行限制,如果爬虫速度过快可能会被封禁IP,因此我们在程序中设置了一个1秒钟的延时,以避免被封禁。如果您需要更高的爬取速度,请自行调整代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值