#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
@Name: cyt_record_download
@IDE:PyCharm
@Author:qtclm
@Date:2022/11/13 9:31
'''
import math
import os
import re
import shutil
import time
from collections.abc import Iterable
import concurrent.futures import numpy as np
from Crypto.Cipher import AES
import requests
from bs4 import BeautifulSoup cookie_dict = {} def check_filename(file_name, ignore_chars: (list, tuple, set) = [],
priority_matching_chars: (list, tuple, set) = []):
'''
校验文件名称的方法,在 windows 中文件名不能包含('\','/','*','?','<','>','|') 字符
Args:
ignore_chars: 不需要被替换的字符集合,范围中chars
priority_matching_chars: 优先被匹配的字符集合,如果不为空,直接使用此集合定义的字符即进行替换 Returns:
'''
if priority_matching_chars:
for i in priority_matching_chars:
file_name = file_name.replace(i, '')
return file_name
chars = ['\\', '\/', '*', '?', '<', '>', '|', '\n', '\b', '\f', '\t', '\r', '-', ' ', '.', ':', '[', ']',":"]
chars = [i for i in chars if i not in ignore_chars]
for i in chars:
file_name = file_name.replace(i, '')
return file_name
def login_and_set_cookie(username='', password=''):
'''登录与获取cookie'''
login_url = 'http://www.wsavs.com/login/loginin'
data = f'mobile={username}&password={password}'
content_type = 'application/x-www-form-urlencoded; charset=UTF-8'
headers = {'content-type': content_type}
login_resp = requests.post(url=login_url, data=data, headers=headers)
cookie = login_resp.headers['Set-Cookie'] if login_resp.headers.get('Set-Cookie') else {}
cookie_dict['Cookie'] = cookie def get_my_course_list():
'''获取我的课程列表'''
my_course_list_url = 'http://www.wsavs.com/mycenter/mycourse'
my_course_list_resp = requests.get(url=my_course_list_url, params=None, headers=cookie_dict)
my_course_list_html = my_course_list_resp.text
# my_course_list_html_file = 'my_couse_list.html'
# with open(my_course_list_html_file, 'w', encoding='utf-8') as f:
# f.write(str(my_course_list_html))
# 解析课程列表
# with open(my_course_list_html_file, 'r', encoding='utf-8') as file:
soup = BeautifulSoup(my_course_list_html, 'html.parser') # 这里没有装lxml的话,把它去掉用默认的就好 lxml
# 匹配带有class属性的div标签
my_couse_name_div = soup.find_all('div', attrs={'class': re.compile("flex1")})
my_course_list = []
for i in my_couse_name_div:
course_name = i.find('div', attrs={'class': re.compile("f18 cor3 mb5")})
course_name = course_name.string if course_name else course_name
course_url = i.find('a')
course_url = course_url['href'] if course_url and course_url.get('href') else course_url
my_course_dict = {}
if course_name and course_url:
my_course_dict['course_name'] = course_name
my_course_dict['course_url'] = course_url
my_course_list.append(my_course_dict)
return my_course_list def get_course_video_url_for_course_detail_url(course_url_detail, course_name):
'''
根据课程详情url获取课程视频url地址
:param course_url_detail:
:param course_name:
:return:
'''
# 获取课程视频url
course_detail_video_resp = requests.get(url=course_url_detail, params=None, headers=cookie_dict)
# with open(f"{course_name}_video.html", 'w', encoding='utf-8') as f2:
# f2.write(str(course_detail_video_resp.text)) # with open(f"{course_name}_video.html", 'r', encoding='utf-8') as f2:
soup_video = BeautifulSoup(course_detail_video_resp.text, 'html.parser') # 这里没有装lxml的话,把它去掉用默认的就好 lxml
course_video_url = soup_video.find('source', attrs={'type': "application/x-mpegURL", "id": "source"})
course_video_url = course_video_url['src'] if course_video_url and course_video_url.get('src') else course_video_url
return course_video_url def get_my_course_detail(course_url, course_name):
'''
获取课程详细信息
:param course_url:
:return:
'''
my_course_detail_resp=requests.get(url=course_url,params=None,headers=cookie_dict)
# with open(f"{course_name}.html",'w',encoding='utf-8') as f:
# f.write(str(my_course_detail_resp.text)) # with open(f"{course_name}.html", 'r', encoding='utf-8') as f:
soup = BeautifulSoup(my_course_detail_resp.text, 'html.parser') # 这里没有装lxml的话,把它去掉用默认的就好 lxml
course_detail_div = soup.find_all('div', attrs={'class': re.compile("pl20 pr20 pt20 pb40 xiangqing")})
course_name_dict = {}
for _course in course_detail_div:
course_detail_names_div = _course.find_all('div', attrs={'class': re.compile("f16 fb cor3")})
course_detail_urls_div = _course.find_all('a')
course_detail_list = []
for course_name_detail, course_url_detail in zip(course_detail_names_div, course_detail_urls_div):
course_name_detail = course_name_detail.string if course_name_detail else course_name_detail
course_url_detail = course_url_detail['href'] if course_url_detail.get('href') else course_url_detail
# 获取课程视频url
course_video_url = get_course_video_url_for_course_detail_url(course_url_detail=course_url_detail,
course_name=course_name)
course_detail_dict = {}
if course_name_detail and course_video_url:
course_detail_dict['course_name_detail'] = check_filename(file_name=course_name_detail)
course_detail_dict['course_url_detail'] = course_video_url
course_detail_list.append(course_detail_dict)
course_name_dict[course_name] = course_detail_list
return course_name_dict def get_ts_url(url_course,cyt_course_dir):
''' :param url_course: 课程url
:param cyt_course_dir: 保存的课程目录
:return: 课程所有的ts_url,加密对象
'''
# 课程信息
resp_course_info = requests.get(url=url_course, params=None, headers=None).text
# 获取加密url
get_key_url = re.search("URI.*\"", resp_course_info)
get_key_url = get_key_url.group() if get_key_url else None
IV = re.search("IV.*", resp_course_info)
# 获取加密key
IV = IV.group()[3:] if IV else None
# 获取所有的视频url
ts_urls = re.findall("v.+ts\?start=.+", resp_course_info)
ts_url_prefix=url_course[:url_course.rfind("/")+1]
ts_urls = [ts_url_prefix+i for i in ts_urls]
decrypt_key = get_key_url[get_key_url.find('"') + 1:get_key_url.rfind('"')]
# 获取加密key
resp_key_result = requests.get(url=decrypt_key, params=None, headers=None, cookies=cookie_dict).content
cryptor = AES.new(resp_key_result, AES.MODE_CBC, iv=IV[:16].encode('utf-8'))
if not os.path.exists(cyt_course_dir):
os.makedirs(cyt_course_dir)
return ts_urls, cryptor def write_course_to_file(cryptor, ts_url, file_name):
'''
:param cryptor: AES加密对象
:param ts_url: 视频url
:param file_name: 文件名称
:return:
'''
ts_resp = requests.get(url=ts_url, params=None, headers=None, cookies=cookie_dict).content
result = cryptor.decrypt(ts_resp)
with open(f'{file_name}.mpg', 'wb') as f:
f.write(result) # 比较两个list的长度,长度的list用None补起
def compare_list_polishing(list1: Iterable, list2: Iterable, polishing_str=None) -> (list, tuple):
'''polishing_str:补齐的字符'''
if not (isinstance(list1, Iterable) or isinstance(list2, Iterable)):
raise Exception("list1/list2必须是可迭代类型")
l_con = len(list1)
l_pr = len(list2)
if l_con != l_pr:
l_difference = l_con - l_pr
_list = []
if l_difference < 0:
_list.extend(list1)
for i in range(abs(l_difference)):
_list.append(polishing_str)
return _list, list2
else:
_list.extend(list2)
for i in range(abs(l_difference)):
_list.append(polishing_str)
return list1, _list
return list1, list2 def down_course_sync(url_course,cyt_course_dir):
'''视频下载同步版'''
start_time = time.time()
if not os.path.exists(cyt_course_dir):
os.makedirs(cyt_course_dir)
else:
print(f"{cyt_course_dir}课程已下载,无需重复下载")
return
ts_urls, cryptor = get_ts_url(url_course=url_course, cyt_course_dir=cyt_course_dir)
for index,ts_url in enumerate(ts_urls):
write_course_to_file(cryptor=cryptor, ts_url=ts_url, file_name=f'{cyt_course_dir}/{index}')
print('%2.2f second' % (time.time() - start_time)) def mpg_video_merge(cyt_course_dir,out_file_name,merge_num=300):
'''
合并视频
:param cyt_course_dir:
:param out_file_name:
:return:
'''
out_file_name = f'{out_file_name}_all.mpg'
# 视频合并基础方法
def merge_base(command_str,mpg_file_list,merge_file_name):
for mpg_file in mpg_file_list:
if ('mpg' in mpg_file) and ( out_file_name not in mpg_file):
command_str += mpg_file + "+"
command_str = command_str[:-1] if command_str[-1] == '+' else command_str
command_str = command_str + f' {merge_file_name}'
# print("command_str:", command_str)
res = os.system(command_str)
print(f'{cyt_course_dir}文件已合并为{merge_file_name}')
pwd=os.getcwd()
if not os.path.exists(os.path.join(os.getcwd(),cyt_course_dir)):
raise Exception("目录不存在")
# 切换目录到课程目录
os.chdir(cyt_course_dir)
if not (os.path.exists(out_file_name) or os.path.exists(f'../{out_file_name}')) :
print("文件开始合并")
dir_files = os.listdir(os.path.join(pwd,cyt_course_dir))
# 对文件进行排序
dir_files.sort(key=lambda x: x[x.rfind('.') + 1:] == 'mpg' and 'all' not in x and int(x[:x.rfind('.') ]))
dir_files = [i for i in dir_files if i[-4:]=='.mpg']
# 第一次合并视频文件 (每次合并得数量限制到300以内,避免参数过多造成合并失败)
split_num = math.ceil(len(dir_files)/merge_num)
dir_files=np.array_split(dir_files,split_num)
# print(dir_files)
# 保存第一次合并后得文件名称
dir_files_merge = []
command_str = "copy /B "
for index,mpg_file_list in enumerate(dir_files):
merge_base(command_str=command_str,mpg_file_list=mpg_file_list,merge_file_name=f"{index}_all.mpg")
dir_files_merge.append(f"{index}_all.mpg")
# 第二次合并
merge_base(command_str=command_str, mpg_file_list=dir_files_merge, merge_file_name=out_file_name)
# 删除第一次合并后得视频文件
# for i in dir_files_merge:
# os.remove(i)
else:
print(f'{out_file_name}文件已存在,无需合并')
if not os.path.exists(f'../{out_file_name}'):
# 移动合并后的视频文件到上级目录
shutil.move(out_file_name,f'../{out_file_name}')
print(f"{out_file_name}文件移动到上级目录")
else:
print(f"{out_file_name}文件无需移动到上级目录")
# 切换目录到原目录
os.chdir(pwd) def down_course_batch_thread(url_course, cyt_course_dir,thread_num=20):
'''视频下载多线程版'''
start_time = time.time()
if not os.path.exists(cyt_course_dir):
os.makedirs(cyt_course_dir)
else:
print(f"{cyt_course_dir}课程已下载,无需重复下载")
return
ts_urls, cryptor = get_ts_url(url_course=url_course, cyt_course_dir=cyt_course_dir)
ts_urls, cryptors = compare_list_polishing(ts_urls, [cryptor], polishing_str=cryptor)
with open(f'{cyt_course_dir}/ts_urls.txt', 'w') as f:
f.write(str(ts_urls))
file_names = [f'{cyt_course_dir}/{index}' for index in range(len(ts_urls))]
with concurrent.futures.ThreadPoolExecutor(max_workers=thread_num) as executor:
for url, data in zip(ts_urls, executor.map(write_course_to_file, cryptors, ts_urls, file_names)):
# print('%r' % url)
pass
print('%s课程下载耗时,%2.2f second' % (cyt_course_dir,time.time() - start_time)) if __name__ == "__main__":
start_time=time.time()
login_and_set_cookie(username='xx',password='xxx')
course_list = get_my_course_list()
for i in course_list:
course_detail_info=get_my_course_detail(**i)
for course in course_detail_info:
for course_detail in course_detail_info[course]:
down_course_batch_thread(url_course=course_detail["course_url_detail"],cyt_course_dir=f'{course}/{course_detail["course_name_detail"]}')
mpg_video_merge(cyt_course_dir=f'{course}/{course_detail["course_name_detail"]}',out_file_name=course_detail["course_name_detail"])
print('程序执行总耗时%2.2f second' % (time.time() - start_time))

2024.3.10更新

修复视频文件过多时合并失败得问题,使用多次合并策略解决

python多线程应用-批量下载视频课程(宠医堂)的更多相关文章

  1. python多线程爬虫+批量下载斗图啦图片项目(关注、持续更新)

    python多线程爬虫项目() 爬取目标:斗图啦(起始url:http://www.doutula.com/photo/list/?page=1) 爬取内容:斗图啦全网图片 使用工具:requests ...

  2. Python + Selenium +Chrome 批量下载网页代码修改【新手必学】

    Python + Selenium +Chrome 批量下载网页代码修改主要修改以下代码可以调用 本地的 user-agent.txt 和 cookie.txt来达到在登陆状态下 批量打开并下载网页, ...

  3. JS下载单个图片、单个视频;批量下载图片,批量下载视频

    下载单张图片 import JSZip from "jszip"; import FileSaver from "file-saver"; downloadIa ...

  4. Python爬虫实战 批量下载高清美女图片

    彼岸图网站里有大量的高清图片素材和壁纸,并且可以免费下载,读者也可以根据自己需要爬取其他类型图片,方法是类似的,本文通过python爬虫批量下载网站里的高清美女图片,熟悉python写爬虫的基本方法: ...

  5. 【Python爬虫案例学习2】python多线程爬取youtube视频

    转载:https://www.cnblogs.com/binglansky/p/8534544.html 开发环境: python2.7 + win10 开始先说一下,访问youtube需要那啥的,请 ...

  6. 利用python爬虫关键词批量下载高清大图

    前言 在上一篇写文章没高质量配图?python爬虫绕过限制一键搜索下载图虫创意图片!中,我们在未登录的情况下实现了图虫创意无水印高清小图的批量下载.虽然小图能够在一些移动端可能展示的还行,但是放到pc ...

  7. python爬虫-图片批量下载

    # 爬起摄图网的图片批量下载# coding:utf-8 import requests from bs4 import BeautifulSoup from scipy.misc import im ...

  8. python图片爬虫 - 批量下载unsplash图片

    前言 unslpash绝对是找图的绝佳场所, 但是进网站等待图片加载真的令人捉急, 仿佛是一场拼RP的战争 然后就开始思考用爬虫帮我批量下载, 等下载完再挑选, 操作了一下不算很麻烦, 顺便也给大家提 ...

  9. python requirements.txt批量下载安装离线

    有些情况下我们需要下载N个第三方包,或者下载的包依赖其它包,一个个下载非常浪费时间.这时我们可以通过如下两种方式的命令批量下载. 方式1 pip download -d /tmp/packagesdi ...

  10. mac + win ,用python一行代码批量下载哔哩哔哩视频

    ​ 首先,你的机器已经安装python,然后是安装you-get. 打开终端,输入 pip3 install you-get,回车,即开始安装,如下图所示. ​编辑 安装成功后,比如要下载某个视屏,首 ...

随机推荐

  1. DeepMind Lab的一些python例子—————(Ubuntu22.04系统安装DeepMind Lab)后续

    相关资料: Ubuntu22.04系统安装DeepMind Lab ====================================================== 关于DeepMind ...

  2. 随时随地与 LLMs 聊天的开源项目「GitHub 热点速览」

    众所周知,本地运行 LLMs 需要下载模型(体积大),并且还比较吃硬件配置.近日 GitHub 推出了 GitHub Models 服务,让开发者可以在 GitHub 上免费测试 Llama.Phi ...

  3. IP一致性论文

    IP一致性:指的是给定输入的图像,要求保持图像中的ID不变,IP可能是Identity Property,要求能够识别出是同一个身份. 目前通过IP的一致性技术,可以用于短视频短剧上,是一个新兴的市场 ...

  4. Vim:从光标位置开始全局搜索和替换

    /\v SEARCHTERM :%s/\vBEFORE/AFTER/gc

  5. 2023 CCPC 女生题解

    gym B. 终焉之茧 \(\star\) 显然两个维度分别做 单谷函数,起始点 \(A\) 是一个端点.一个 naive 的想法是三分目标点 \(B\),但交互次数会超.二分关于 \(B\) 对称点 ...

  6. AvaloniaChat-v0.0.2:兼容智谱AI 快速使用指南

    智谱AI介绍 北京智谱华章科技有限公司(简称"智谱AI")致力于打造新一代认知智能大模型,专注于做大模型的中国创新.公司合作研发了中英双语千亿级超大规模预训练模型GLM-130B, ...

  7. armbian挂载sd卡记录

    mkdir -p /mnt/mmctouch  /etc/init.d/mount.shvim /etc/init.d/mount.sh内容见图mount /dev/mmcblk1p1 /mnt/mm ...

  8. LaTeX 编译中文文档

    介绍 LaTeX 原生不支持中文.为了添加中文的功能,我们需要引入宏包.XeLaTeX 原生支持中文.不过由于默认使用的字体是英文字体,我们需要设置中文字体之后才能用.不过由于一些原因,在使用 LaT ...

  9. docker高级篇第二章-分布式存储之实战案例:主从容错切换迁移案例

    在上一篇,学会了3主3从的Redis集群搭建.那么接下来,我们就来学习Redis集群主从容错切换迁移案例 本次案例从模拟两个场景 1:数据读写存储 a:一个新key数据来了,是否会按照预设的进入槽中? ...

  10. Patlibc———更快捷的更换libc

    起初是为了简化做pwn题目时,来回更换libc的麻烦,为了简化命令,弄了一个小脚本,可以加入到/usr/local/bin中,当作一个快捷指令 这个写在了tools库(git clone https: ...