【JavaScript爬虫记录】记录一下使用JavaScript爬取m4s流视频过程(内含ffmpeg合并)

news2025/2/19 14:32:00

前言

前段时间发现了一个很喜欢的视频,可惜网站不让下载,简单看了一下视频是被切片成m4s格式的流文件,初步想法是将所有的流文件下载下来然后使用ffmpeg合并成一个完整的mp4,于是写了一段脚本来实现一下,电脑没有配python环境,所以使用JavaScript实现,合并功能需要安装ffmpeg,没有的小伙伴自行安装哦

前置知识
  1. m4s文件(复制百度)

    M4S 文件是使用 MPEG-DASH 流技术通过 Internet 流式传输的一小段视频。它包含二进制数据形式的视频片段。接收应用程序(通常是网络浏览器或媒体播放器)按接收顺序播放这些片段。第一个 M4S 段由它包含的初始化数据标识。在 summary 中,m4s文件是完整文件的单个小媒体片段。 M4S 文件基于 ISO 基础媒体文件 (ISOBMFF) 格式。大文件的这些小片段可以通过 HTTP 独立下载。因此,如果您有一个大的 MP4 电影文件,则可以使用 MPEG-DASHHTTP 上的动态自适应流式传输)技术将其分段为 M4S 分段文件,从而对其进行流式传输。如果将此大型电影文件作为 M4S 下载到光盘,则会下载多个 M4S 文件。如果将所有这些 .m4s 段连接起来,就会生成一个完整的可播放文件。除非文件的第一个初始化段也可用,否则媒体播放器无法播放文件。

思路整理
  1. 找到目标m4s文件的接口,观察接口规律,拼接URL批量下载
  2. 然后将文件写入本地,再遍历目录生成ffmpeg合并用的文化列表目录
  3. 然后调用ffmpeg终端命令合并
  4. 最后清理临时文件

开始实现

首先观察到目标m4s文件的url格式都是https://xxxxxx/1080.mp4/seg-1-v1-a1.m4s / https://xxxxxx/1080.mp4/seg-2-v1-a1.m4s等等,猜测只是通过目标的序号来管理分片,那考虑使用循环来批量下载,先写几个函数来处理基本的功能,例如下载文件 / 生成临时目录 / 本地写入 / 清理临时文件等

请求函数

const fetchData = async (url) => {
    try {
        let response = await fetch(url, {
            method: 'GET',
            headers: {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.60"
            }
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        let m4sData = await response.blob();

        if (m4sData instanceof Blob) {
            console.log(m4sData.size); // 打印 Blob 对象的大小
        } else {
            console.log("m4sData.data 不是一个 Blob 对象");
        }

        return m4sData;
    } catch (error) {
        console.log("get_m4sData下载失败");
        console.log(error);
    }
};

本地写入的函数

const writeFile = async (fileName, file) => {
    fs.writeFile(fileName, file, (err) => {
        if (err) {
            console.log("写入失败:", err);
            return
        }
        console.log(`${fileName}写入成功`);
    })
}

生成临时目录的函数

const generateFileList = () => {
    // 获取 assets 目录下所有目标文件
    const files = fs.readdirSync(folderPath)
      .filter(file => file.endsWith('.ts'))
      .sort((a, b) => {
        // 提取文件名中的数字部分进行比较
        const numA = parseInt(a.match(/seg-(\d+)-v1-a1\.ts/)[1], 10);
        const numB = parseInt(b.match(/seg-(\d+)-v1-a1\.ts/)[1], 10);
        return numA - numB;
      });

    // 生成文件列表内容(使用 Unix 路径分隔符)
    const listContent = files
      .map(file => `file '${path.join(file).replace(/\\/g, '/')}'`)
      .join('\n');
  
    // 写入文件列表
    const listPath = path.join(folderPath, 'list.txt');
    fs.writeFileSync(listPath, listContent);
    console.log('文件列表已生成:', listPath);
    return listPath;
};

合并视频的函数

const mergeSegments = () => {
    const listPath = path.join(folderPath, 'list.txt').replace(/\\/g, '/');
    const outputFile = './mergeVideo/merged_video.mp4';
    console.log(listPath);
    
    // 检查文件列表是否存在
    if (!fs.existsSync(listPath)) {
      console.error('错误:文件列表未生成');
      process.exit(1);
    }
  
    execSync(
      `ffmpeg -f concat -safe 0 -i "${listPath}" -c copy "${outputFile}"`,
      { stdio: 'inherit' }
    );
    console.log('合并完成:', outputFile);
  };

移除临时文件和善后优化的函数

// 删除 assets 目录下的所有文件
const deleteAllFilesInAssets = () => {
    const folderPath = path.join('./assets');
    const files = fs.readdirSync(folderPath);

    files.forEach(file => {
        const filePath = path.join(folderPath, file);
        fs.unlinkSync(filePath);
    });

    console.log('assets 目录下的所有文件已删除');
};

// 随机改名 
const renameMergedVideo = () => {
    const oldPath = path.join('./mergeVideo', 'merged_video.mp4');
    const videoFileName = generateRandomString()
    const newPath = path.join('./mergeVideo', `video_${videoFileName}.mp4`);

    if (fs.existsSync(oldPath)) {
        fs.renameSync(oldPath, newPath);
        console.log(`文件已重命名为video_${videoFileName}.mp4 `);
    } else {
        console.log('文件 merged_video.mp4 不存在');
    }
};
// 生成一个随机的8位数字加大小写字母的字符串
const generateRandomString = () => {
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let results = '';
    const length = 8;
    for (let i = 0; i < length; i++) {
        const randomIndex = Math.floor(Math.random() * characters.length);
        results += characters.charAt(randomIndex);
    }
    return results;
};

接下来就可以编写我们的main函数了,只需要挨个调用上面的辅助函数即可

export const main = async (Number_of_data_segments,BASE_URL,rootPath = folderPath) => {
    for (let i = 1; i <= Number_of_data_segments; i++) {
        let url = `seg-${i}-v1-a1.ts`
        console.log(`正在下载第${i}个数据段,标识为${url}`);
        let m4s = await fetchData(BASE_URL + url)
        console.log(`${i}个数据段下载完成`);
        await writeFile(`${rootPath}/${url}`, Buffer.from(await m4s.arrayBuffer()))
    }
    console.log(`下载完成`);

    console.log(`生成目录映射`);
    
    generateFileList();

    console.log(`合并数据段`);
    mergeSegments();

    deleteAllFilesInAssets()
    renameMergedVideo()
}

总结

最后试了一下,效果还是蛮不错的,这些都是最终合成的视频
在这里插入图片描述
这只是个简单的脚本,很多地方都可以优化,例如可以通过网络状态来判断分片数量,就不再需要手动去查看分片数量了,这些地方有兴趣的小伙伴可以自行尝试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2298102.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【线性代数】1行列式

1. 行列式的概念 行列式的符号表示: 行列式的计算结果:一个数 计算模型1:二阶行列式 二阶行列式: 三阶行列式: n阶行列式: 🍎计算行列式 计算模型2:上三角形行列式 上三角形行列式特征:主对角线下皆为0。 上三角形行列式: 化上三角形通用方法:主对角线下,…

数据结构(考研)

线性表 顺序表 顺序表的静态分配 //线性表的元素类型为 ElemType//顺序表的静态分配 #define MaxSize10 typedef int ElemType; typedef struct{ElemType data[MaxSize];int length; }SqList;顺序表的动态分配 //顺序表的动态分配 #define InitSize 10 typedef struct{El…

安装WPS后,导致python调用Excel.Application异常,解决办法

在使用xlwings编辑excel文件时&#xff0c;默认调用的是“Excel.Application”&#xff0c;如果安装过wps&#xff0c;会导致该注册表为WPS&#xff0c;会导致xlwings执行异常 因为安装过WPS&#xff0c;导致与Excel不兼容的问题&#xff0c;想必大家都听说过。有些问题及时删…

vue3实战-----集成sass

vue3实战-----集成sass 1.安装2.使用3.全局样式文件中不能使用变量 1.安装 在使用scss之前需要安装sass和sass-loader两个插件。 2.使用 安装好之后就可以在组件中使用scss了。需要加上lang“scss”。 注意:scss中变量用$,less中变量用。 3.全局样式文件中不能使用变量 …

二分查找sql时间盲注,布尔盲注

目录 一&#xff1a;基础知识引导 数据库&#xff1a;information_schema里面记录着数据库的所有元信息 二&#xff0c;布尔盲注&#xff0c;时间盲注 &#xff08;1&#xff09;布尔盲注案例&#xff08;以sqli-labs第八关为例&#xff09;&#xff1a; &#xff08;2&am…

计算机网络-MPLS转发原理

在上一篇关于 MPLS 基础的文章中&#xff0c;我们了解了 MPLS 的基本概念、术语以及它在网络中的重要性。今天&#xff0c;我们将深入探讨 MPLS 转发的原理与流程&#xff0c;帮助大家更好地理解 MPLS 是如何在实际网络中工作的。 一、MPLS 转发概述 MPLS 转发的本质是将数据…

【设计模式】【行为型模式】职责链模式(Chain of Responsibility)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…

【H5自适应】高端科技类pbootcms网站模板 – 三级栏目、下载与招聘功能支持

(H5自适应)高端大气的科技类pbootcms网站模板 带三级栏目、下载和招聘功能 后台地址&#xff1a;您的域名/admin.php 后台账号&#xff1a;admin 后台密码&#xff1a;123456 为了提升系统安全&#xff0c;请将后台文件admin.php的文件名修改一下。修改之后&#xff0c;后台…

【Java 面试 八股文】框架篇

框架篇 1. Spring框架中的单例bean是线程安全的吗&#xff1f;2. 什么是AOP&#xff1f;3. 你们项目中有没有使用到AOP&#xff1f;4. Spring中的事务是如何实现的&#xff1f;5. Spring中事务失效的场景有哪些&#xff1f;6. Spring的bean的生命周期&#xff1f;7. Spring中的…

自动化UI测试 | 什么是测试驱动开发(TDD)和行为驱动开发(BDD)?有何区别?

TDD&#xff08;测试驱动开发&#xff09;和BDD&#xff08;行为驱动开发&#xff09;是两种独特的软件开发技术&#xff0c;它们在测试的内容和方式上有所不同。尽管名称相似&#xff0c;但服务于不同的目的。 什么是TDD&#xff1f; TDD代表测试驱动开发。它是一个过程&…

DeepSeek 助力 Vue 开发:打造丝滑的进度条

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

一场始于 Selector Error 的拯救行动:企查查数据采集故障排查记

时间轴呈现事故进程 17:00&#xff1a;开发人员小李正在尝试利用 Python 爬虫从企查查&#xff08;https://www.qcc.com&#xff09;抓取公司工商信息。原本一切正常&#xff0c;但突然发现信息采集失败&#xff0c;程序抛出大量选择器错误。17:15&#xff1a;小李发现&#x…

微信服务号推送消息

这里如果 没有 就需要点新的功能去申请一下 申请成功之后就可以设置模版消息 推送到用户接受的页面是 需要后端调用接口 传递token 发送给客户

24电子信息类研究生复试面试问题汇总 电子信息类专业知识问题最全!电子信息复试全流程攻略 电子信息考研复试真题汇总

你是不是在为电子信息考研复试焦虑&#xff1f;害怕被老师问到刁钻问题、担心专业面答不上来&#xff1f;别慌&#xff01;作为复试面试92分逆袭上岸的学姐&#xff0c;今天手把手教你拆解电子信息类复试通关密码&#xff01;看完这篇&#xff0c;让你面试现场直接开大&#xf…

嵌入式EasyRTC实时通话支持海思hi3516cv610,编译器arm-v01c02-linux-musleabi-gcc

EasyRTC已经完美支持海思hi3516cv610&#xff0c;编译器arm-v01c02-linux-musleabi-gcc&#xff0c;总体SDK大小控制在680K以内&#xff08;预计还能压缩100K上下&#xff09;&#xff1a; EasyRTC在hi3516cv610芯片上能双向通话、发送文字以及二进制指令&#xff0c;总体运行…

计算机视觉中图像的基础认知

一、图像/视频的基本属性 在计算机视觉中&#xff0c;图像和视频的本质是多维数值矩阵。图像或视频数据的一些基本属性。 宽度&#xff08;W&#xff09; 和 高度&#xff08;H&#xff09; 定义了图像的像素分辨率&#xff0c;单位通常是像素。例如&#xff0c;一张 1920x10…

Docker Desktop WebAPI《1》

方法1 》》生成 的文档不要动&#xff0c; 》》执行 Container&#xff08;Dockerfile&#xff09; 会生成镜像文件和容器 》》生成的镜像和容器 在 Docker Desktop 中可以查看 用VS 的 Container Dockerfile 调试 但把这个调试工工具 停止&#xff0c;WebAPi就不能访问了 …

《OpenCV》——特征提取与匹配方法

特征提取 特征提取是从原始数据中提取出能够代表数据本质特征和关键信息的过程&#xff0c;在很多领域都有广泛应用。原始数据往往包含大量的冗余信息&#xff0c;特征提取的目的是去除这些冗余&#xff0c;提取出最具代表性、最能区分不同类别或模式的特征&#xff0c;从而降…

如何使用DHTMLX Scheduler的拖放功能,在 JS 日程安排日历中创建一组相同的事件

DHTMLX Scheduler 是一个全面的调度解决方案&#xff0c;涵盖了与规划事件相关的广泛需求。假设您在我们的 Scheduler 文档中找不到任何功能&#xff0c;并且希望在我们的 Scheduler 文档中看到您的项目。在这种情况下&#xff0c;很可能可以使用自定义解决方案来实现此类功能。…

​矩阵元素的“鞍点”​

题意&#xff1a; 一个矩阵元素的“鞍点”是指该位置上的元素值在该行上最大、在该列上最小。 本题要求编写程序&#xff0c;求一个给定的n阶方阵的鞍点。 输入格式&#xff1a; 输入第一行给出一个正整数n&#xff08;1≤n≤6&#xff09;。随后n行&#xff0c;每行给出n个整数…