#!/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. python运行报错:ImportError: libcudnn_cnn_infer.so.8: cannot open shared object file

    运行报错: 解决方法: conda install cudatoolkit==11.8.0 python3 -m pip install nvidia-cudnn-cu11==8.7.0.84 成功解 ...

  2. 绑定国内主机IP的域名网站必须要备案

    买了个域名: http://devilmaycry812839668.top/ 然后绑定了国内的一个云主机,刚搭了个web server,一个网页都没有(短期内页没考虑做网页): 今天看了下web s ...

  3. 向日葵的平替:fastnat的使用

    相关: 向日葵的平替:一款个人开发的远程工具--fastnat ---------------------------------------------- 1. 下载安装 2. 注册设备 我这里在两 ...

  4. 如何修复ubuntu的uefi启动——如何将Ubuntu安装入移动硬盘中

    交代一下使用场景,个人平时经常使用Ubuntu系统,由于不喜欢总在一个地方呆但是来回搬电脑又不是十分的方便,于是想到了一个好的方案,那就是把Ubuntu系统安装到移动硬盘中,这样不论是在家还是在实验室 ...

  5. mysql 重置主键

    开发时总是要向数据库写入测试数据,删掉以后id(自增主键)依然还是在增长,这个问题我遇到好多次,也都没有在意. 最近这个习惯被朋友嫌弃 = =||| 就在网上搜索了下mysql重置主键的办法: ALT ...

  6. [CEOI2011] Matching 题解

    前言 题目链接:洛谷. 在上一题之后,模拟赛又放了一道 KMP 重定义相等的问题,但是寄了,故再记之. 题意简述 现在给出 \(1 \sim n\) 的排列 \(p\) 和序列 \(h_1, h_2, ...

  7. 白鲸开源 DataOps 平台加速数据分析和大模型构建

    作者 | 李晨 编辑 | Debra Chen 数据准备对于推动有效的自助式分析和数据科学实践至关重要.如今,企业大都知道基于数据的决策是成功数字化转型的关键,但要做出有效的决策,只有可信的数据才能提 ...

  8. Namomo Summer Camp 23 Day 1(GCPC2021)

    Namomo Summer Camp 23 Day 1(GCPC2021) Problem B: Brexiting and Brentering 签到 #include<bits/stdc++ ...

  9. curl可以访问虚拟机资源,但是宿主机浏览器不能访问

    如果想从宿主机访问到虚拟机内的php,需要关闭宿主机的代理,并且设置虚拟机内的防火墙不要屏蔽宿主机的ip. 设置虚拟机防火墙方法: 查找宿主机IP:win+r,输入ipconfig 打开虚拟机,输入s ...

  10. USACO 23023DEC 题解

    LG LG9979 [USACO23DEC] Target Practice S code LG9980 [USACO23DEC] Flight Routes G sol 1 已知邻接矩阵求路径数奇偶 ...