使用单线程爬取,多线程,协程爬取,异步爬取包图网视频
文章目录
- 使用单线程爬取,多线程,协程爬取,异步爬取包图网视频
- 第一步使用单线程爬取包图网视频
- 多线程爬取视频
- 用协程爬取
- 基于协程的异步爬取
第一步使用单线程爬取包图网视频
第一:我先选择要爬取的目标网站
https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0
确定要爬取的目标后面,我们后面直接套取以前的1写过一些的爬取基本四部法:
#第一步:
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
with open('baotuwang.html', 'w',encoding='utf-8') as f:
f.write(html_content)
在这个基础上我们要进行一个延伸,还一个数据的提取,为了防止代码的冗余,我们对代码进行一个封装成函数来进行一个简化:
import requests
from lxml import etree
import os
import time
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
#下载视频的函数
def downmp4(video_url,video_name):
‘’‘要具体填充’‘
#数据提取和选择函数
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#数据提取的步骤
if __name__ == '__main__':
parsePage()
下面就数据提取的详细网页分析解决一下:
打开浏览器自带的调试工具,我们可以按F12快捷键直接打开,也可以双击鼠标的右键打开
锁定要爬取的网页内容:
这个时候我们在要的网页内容里面点击鼠标的右键,出来如
copy 浏览器自带的xpath后,我们在xpath辅助上进行调
图片链接://div[2]/div[2]/ul/li/div/div/a/div[2]/img/@src
视频名字://div[2]/div[2]/ul/li/div/div/a/div[2]/img/@alt
或者//ul/li/@pr-data-title
后面我们就完成数据提取的部分:
#数据提取和选择函数
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#提取a标签
tree=etree.HTML(html_content)
video_page_url=tree.xpath("//ul/li/div/div/a/div[1]/video/@src")
print(video_page_url)
video_name=tree.xpath("//ul/li/@pr-data-title")
print(video_name)
start_time=time.time()
for i in range(len(video_page_url)):
#调用视频下载的函数
downmp4(video_page_url[i],video_name[i])
finish_time=time.time()
print("总共下载的时间是"+str(finish_time-start_time))
视频下载的函数:
#下载视频的函数
def downmp4(video_url,video_name):
#定义一个视频下载后存储的文件夹
path='包图网大国工匠视频2'
#对当前目录下面的文件夹进行判断,如果没有自动创建一个文件夹拉存储
if not os.path.exists(path):
os.mkdir(path)
start_time=time.time()
video_url="https:"+video_url
video_name=video_name.strip().replace("<strong>","").replace("</strong>","")
video_content = requests.get(url=video_url, headers=headers).content
with open(path+'./%s.mp4'%video_name, "wb") as f:
f.write(video_content)
finish_time=time.time()-start_time
return "每个视频下载的时间"+str(finish_time)
单线程下载视频完整代码:
import requests
from lxml import etree
import os
import time
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
#下载视频的函数
def downmp4(video_url,video_name):
path='包图网大国工匠视频2'
#对当前目录下面的文件夹进行判断,如果没有自动创建一个文件夹拉存储
if not os.path.exists(path):
os.mkdir(path)
start_time=time.time()
video_url="https:"+video_url
video_name=video_name.strip().replace("<strong>","").replace("</strong>","")
video_content = requests.get(url=video_url, headers=headers).content
with open(path+'./%s.mp4'%video_name, "wb") as f:
f.write(video_content)
finish_time=time.time()-start_time
return "每个视频下载的时间"+str(finish_time)
#数据提取和选择函数
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#提取a标签
tree=etree.HTML(html_content)
video_page_url=tree.xpath("//ul/li/div/div/a/div[1]/video/@src")
print(video_page_url)
video_name=tree.xpath("//ul/li/@pr-data-title")
print(video_name)
start_time=time.time()
for i in range(len(video_page_url)):
downmp4(video_page_url[i],video_name[i])
finish_time=time.time()
print("总共下载的时间是"+str(finish_time-start_time))
if __name__ == '__main__':
parsePage()
我们看看运行效果:
总共下载的时间是7.734321117401123
多线程爬取视频
我们先了解什么是多线程:
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
- 程序的运行速度可能加快。
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让。
线程可以分为:
- **内核线程:**由操作系统内核创建和撤销。
- **用户线程:**不需要内核支持而在用户程序中实现的线程。
我们在上面的代码中创建一个线程池就可以了:
##################线程池的实现方法1###############
#1.创建线程池,初始化线程数量
executor=ThreadPoolExecutor(4)
for i in range(len(video_page_url)):
#进入到详情页面下载视频,封装成一个函数----相当于任务,call_back是回调函数
executor.submit(downmp4,video_page_url[i],video_name[i]).add_done_callback(call_back) #提交任务
#关闭线程
executor.shutdown(True)
并且定义个线程函数:
#定义线程函数
def call_back(res):
#完成线程的操作,进行回调的函数
res=res.result()#获取结果
# print(res)
再在下载视频函数里面添加等到线程相关信息的代码,就可以得到线程的详细信息
#得到进程线程相关的信息
thread=threading.current_thread() #得到当前的线程对象
process=psutil.Process(os.getpid()) #得到当前的进程对象
print(thread.ident,thread.name,process.pid,process.name())
多线程爬取完整代码:
import requests
from lxml import etree
import threading, psutil, os
import time
from concurrent.futures import ThreadPoolExecutor
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
#定义线程函数
def call_back(res):
#完成线程的操作,进行回调的函数
res=res.result()#获取结果
# print(res)
def downmp4(video_url,video_name):
path='包图网大国工匠视频'
#对当前目录下面的文件夹进行判断,如果没有自动创建一个文件夹拉存储
if not os.path.exists(path):
os.mkdir(path)
#得到进程线程相关的信息
thread=threading.current_thread() #得到当前的线程对象
process=psutil.Process(os.getpid()) #得到当前的进程对象
print(thread.ident,thread.name,process.pid,process.name())
start_time=time.time()
video_url="https:"+video_url
video_name=video_name.strip().replace("<strong>","").replace("</strong>","")
video_content = requests.get(url=video_url, headers=headers).content
with open(path+'./%s.mp4'%video_name, "wb") as f:
f.write(video_content)
finish_time=time.time()-start_time
return "每个视频下载的时间"+str(finish_time)
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#提取a标签
tree=etree.HTML(html_content)
video_page_url=tree.xpath("//ul/li/div/div/a/div[1]/video/@src")
print(video_page_url)
video_name=tree.xpath("//ul/li/@pr-data-title")
print(video_name)
start_time=time.time()
##################线程池的实现方法1###############
#1.创建线程池,初始化线程数量
executor=ThreadPoolExecutor(4)
for i in range(len(video_page_url)):
#进入到详情页面下载视频,封装成一个函数----相当于任务,call_back是回调函数
executor.submit(downmp4,video_page_url[i],video_name[i]).add_done_callback(call_back) #提交任务
#关闭线程
executor.shutdown(True)
finish_time=time.time()
print("总共下载的时间是"+str(finish_time-start_time))
if __name__ == '__main__':
parsePage()
运行效果:
用协程爬取
什么是协程:
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
第一步导入异步IO包:
import asyncio 没有包的下载pip install asyncio
在单线程的代码上修改添加如下代码
#1.创建线程池,初始化线程数量
#创建事件循环
loop=asyncio.get_event_loop()
downmp4Task=[]
for i in range(len(video_page_url)):
# 生成协程对象
downMP4Proxy = downmp4(video_page_url[i], video_name[i])
# 封装为future对象,为了观看协程的状态信息
future = asyncio.ensure_future(downMP4Proxy)
print(future)
#把封装的协程对象future添加到数组中
downmp4Task.append(future)
#注册运行协程
loop.run_until_complete(asyncio.wait(downmp4Task))
loop.close()#关闭协程
在下载函数那里定义协程对象
async def downmp4(video_url,video_name):#定义协程对象
完整代码:
import requests
from lxml import etree
import threading, psutil, os
import time
from concurrent.futures import ThreadPoolExecutor
import asyncio
import aiohttp
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
def call_back(res):
data=res.result()
print(data)
#定义协程对象
async def downmp4(video_url,video_name):
path = '包图网大国工匠视频4'
# 对当前目录下面的文件夹进行判断,如果没有自动创建一个文件夹拉存储
if not os.path.exists(path):
os.mkdir(path)
#得到进程线程相关的信息
thread=threading.current_thread() #得到当前的线程对象
process=psutil.Process(os.getpid()) #得到当前的进程对象
print(thread.ident,thread.name,process.pid,process.name())
start_time=time.time()
video_url="https:"+video_url
video_name=video_name.strip().replace("<strong>","").replace("</strong>","")
video_content = requests.get(url=video_url, headers=headers).content
with open(path + './%s.mp4' % video_name, "wb") as f:
f.write(video_content)
# finish_time=time.time()-start_time
# return "每个视频下载的时间"+str(finish_time)
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#提取a标签
tree=etree.HTML(html_content)
video_page_url=tree.xpath("//ul/li/div/div/a/div[1]/video/@src")
print(video_page_url)
video_name=tree.xpath("//ul/li/@pr-data-title")
print(video_name)
start_time=time.time()
#1.创建线程池,初始化线程数量
loop=asyncio.get_event_loop()
downmp4Task=[]
for i in range(len(video_page_url)):
# 生成协程对象
downMP4Proxy = downmp4(video_page_url[i], video_name[i])
# 封装为future对象,为了观看协程的状态信息
future = asyncio.ensure_future(downMP4Proxy)
print(future)
downmp4Task.append(future)
loop.run_until_complete(asyncio.wait(downmp4Task))
loop.close()
finish_time=time.time()
print("总共下载的时间是"+str(finish_time-start_time))
if __name__ == '__main__':
parsePage()
运行效果:
基于协程的异步爬取
第一步导入异步IO包:
import asyncio
import aiohttp 没有包的下载pip install asyncio ,pip install aiohttp
在协程的代码上修改添加如下代码
添加如下代码就可以:
#建立session连接
async with aiohttp.ClientSession() as session:
#发起get,post请求
async with session.get(url=video_url, headers=headers)as video_response:
# 读取请求数据
video_content = await video_response.read()
完整代码:
import requests
from lxml import etree
import threading, psutil, os
import time
from concurrent.futures import ThreadPoolExecutor
import asyncio
import aiohttp
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3863.400 QQBrowser/10.8.4334.400',
}
def call_back(res):
data=res.result()
print(data)
async def downmp4(video_url,video_name):
path = '包图网大国工匠视频1'
# 对当前目录下面的文件夹进行判断,如果没有自动创建一个文件夹拉存储
if not os.path.exists(path):
os.mkdir(path)
#得到进程线程相关的信息
thread=threading.current_thread() #得到当前的线程对象
process=psutil.Process(os.getpid()) #得到当前的进程对象
print(thread.ident,thread.name,process.pid,process.name())
start_time=time.time()
video_url="https:"+video_url
video_name=video_name.strip().replace("<strong>","").replace("</strong>","")
#建立session连接
async with aiohttp.ClientSession() as session:
#发起get,post请求
async with session.get(url=video_url, headers=headers)as video_response:
# 读取请求数据
video_content = await video_response.read()
# video_content = requests.get(url=video_url, headers=headers).content
with open(path + './%s.mp4' % video_name, "wb") as f:
f.write(video_content)
# finish_time=time.time()-start_time
# return "每个视频下载的时间"+str(finish_time)
def parsePage():
# 第一步,确定爬虫地址
url = "https://ibaotu.com/tupian/gongjiangjingshen/7-0-0-0-0-0-0.html?format_type=0"
# 第二步:发送请求
response = requests.get(url=url, headers=headers)
# 第三步:获取数据
html_content = response.text
# 第四部:保存在本地
# with open('baotuwang.html', 'w',encoding='utf-8') as f:
# f.write(html_content)
#提取a标签
tree=etree.HTML(html_content)
video_page_url=tree.xpath("//ul/li/div/div/a/div[1]/video/@src")
print(video_page_url)
video_name=tree.xpath("//ul/li/@pr-data-title")
print(video_name)
start_time=time.time()
##################线程池的实现方法1###############
#1.创建线程池,初始化线程数量
loop=asyncio.get_event_loop()
downmp4Task=[]
for i in range(len(video_page_url)):
# 生成协程对象
downMP4Proxy = downmp4(video_page_url[i], video_name[i])
# 封装为future对象,为了观看协程的状态信息
future = asyncio.ensure_future(downMP4Proxy)
print(future)
downmp4Task.append(future)
loop.run_until_complete(asyncio.wait(downmp4Task))
finish_time=time.time()
print("总共下载的时间是"+str(finish_time-start_time))
if __name__ == '__main__':
parsePage()
效果:
上面爬虫的实现方式中线程池是最坏的选择,因为它既占用内存,又有线程竞争的危险需要程序员自己编程解决,而且产生的I/O阻塞也浪费了CPU占用时间。再来看看回调方式,它是一种异步方法,所以I/O阻塞的问题解决了,而且它是单线程的不会产生竞争,问题好像都解决了。然而它引入了新的问题,它的问题在于以这种方式编写的代码不好维护,也不容易debug。看来协程才是最好的选择,我们实现的协程异步编程模型使得一个单线程能够很容易地改写为协程。那是不是每一次做异步编程都要实现Task、Future呢?不是的,你可以直接使用asyncio官方标准协程库,它已经帮你把Task、Future封装好了,你根本不会感受到它们的存在,是不是很棒呢