在B站看课的进度助手

news2024/10/7 2:19:59

效果

在这里插入图片描述

代码

BilibiliVideoDurationCrawler

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BilibiliVideoDurationCrawler {
    private static final Pattern VIDEO_PART_PATTERN = Pattern.compile("\"part\":\"(.*?)\",\"duration\":(\\d+),");
    /**
     * 主函数:根据视频链接获取视频分P信息并打印每一集的观看进度
     * 参数:args - 传入的命令行参数数组
     */
    public static void main(String[] args) {
        

        // 根据视频链接获取视频分P信息的步骤
        String url = "https://www.bilibili.com/video/BV1834y1676P/";
        List<VideoPart> videoParts = new ArrayList<>();
        try {
            videoParts = getVideoPartsFromUrl(url); // 1. 获取网页源代码并爬取视频信息,转换为视频对象列表
        } catch (IOException | ParseException e) {
            System.err.println("获取视频信息失败:" + e.getMessage());
            return;
        }

        // 打印每一集所在进度的步骤
        if (!videoParts.isEmpty()) {
            for (int i = 0; i < videoParts.size(); i++) {
                String progress = getProgressStr(videoParts, i); // 获取指定集数的观看进度的字符串表示
                System.out.println("p" + (i + 1) + " " + videoParts.get(i).getPart() + " " + progress); // 打印集数、标题和进度
            }
        }
    }

    /**
     * 从给定的URL获取视频分段信息的列表。
     * @param url 需要解析的网页URL,预期包含视频分段的相关信息。
     * @return 返回一个包含视频分段及其持续时间的VideoPart对象列表。
     * @throws IOException 如果在连接或获取网页内容时发生IO异常。
     */
    public static List<VideoPart> getVideoPartsFromUrl(String url) throws IOException, ParseException {
        // 使用Jsoup连接指定URL并获取网页内容,模拟浏览器行为
        Document doc = Jsoup.connect(url).userAgent("Mozilla/5.0").get();

        // 选择网页中所有的<script>元素
        Elements elements = doc.select("script");

        // 用于临时存储匹配到的视频分段信息
        List<String> result = new ArrayList<>();

        // 遍历所有<script>元素,尝试匹配视频分段信息
        for (Element element : elements) {
            Matcher matcher = VIDEO_PART_PATTERN.matcher(element.html());
            while (matcher.find()) {
                // 将匹配到的分段信息以字符串形式添加到result列表中
                result.add("Part: " + matcher.group(1) + ", Duration: " + matcher.group(2));
            }
        }

        // 从result列表中解析出VideoPart对象并添加到videoParts列表中
        List<VideoPart> videoParts = new ArrayList<>();
        // 优化 totalDuration 的计算, 避免重复计算
        long totalDuration = 0;
        for (String str : result) {
            // 分割字符串以获取分段名称和持续时间
            String[] parts = str.split(", ");
            String part = parts[0].split(": ")[1];
            long duration = Long.parseLong(parts[1].split(": ")[1]);
            totalDuration += duration;
            // 创建VideoPart对象并添加到列表
            VideoPart videoPart = new VideoPart(part, duration);
            videoParts.add(videoPart);
        }
        VideoPart.setTotalDuration(totalDuration);
        return videoParts;
    }

    /**
     * 计算给定视频片段列表中前p个片段的进度百分比,并返回格式化后的字符串。
     *
     * @param videoParts 视频片段列表,每个片段包含持续时间。
     * @param p 计算进度时考虑的视频片段数量(从0开始)。
     * @return 返回计算出的进度百分比的字符串表示,保留两位小数。
     */
    public static String getProgressStr(List<VideoPart> videoParts, int p) {
        // 验证参数合法性
        if (videoParts == null || p < 0 || p >= videoParts.size()) {
            throw new IllegalArgumentException("Invalid video parts or index p");
        }

        // 计算所有视频片段的总长度
        long totalLength = VideoPart.getTotalDuration();

        // 计算前p个视频片段的长度总和
        long lengthBeforeP = 0;
        for (int i = 0; i <= p; i++) {
            lengthBeforeP += videoParts.get(i).getDuration();
        }

        // 根据前面计算的长度,计算并返回进度百分比,结果保留两位小数
        double progress = (double) lengthBeforeP / (totalLength == 0 ? 1 : totalLength) * 100;
        return String.format("%.2f%%", progress);
    }
}


VideoPart

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Date;

@Data
@AllArgsConstructor
/**
 * 视频部分信息类,用于描述视频的一个片段。
 */
public class VideoPart {
    /**
     * 构造方法,初始化视频片段的信息。
     * @param part 视频片段的标识。
     * @param duration 视频片段的持续时间,单位为秒。
     */
    public VideoPart(String part,long duration){
        this.part = part;
        this.duration = duration;
    }

    public static void setTotalDuration(long totalDuration) {
        VideoPart.totalDuration = totalDuration;
    }

    public static long getTotalDuration() {
        return totalDuration;
    }

    /**
     * 将持续时间转换为需要的时间对象。
     * 该方法将持续时间(秒)转换为Date对象,假设每秒为1000毫秒。
     */
    public void Duration2NeedTime(){
        this.needTime = new Date(duration*1000);
    }
    private String part; // 视频片段标识
    private long duration; // 视频片段持续时间,单位为秒
    private Date needTime; // 视频片段需要的时间,Date对象表示
    private double progress; // 视频片段的进度

    private static long totalDuration =-1; // 所有视频片段的总持续时间,初始值为-1表示未计算
}

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

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

相关文章

简单用Nodejs + express 编写接口

文章目录 get接口示范post接口示范注意点 准备工作可以看上一篇文章&#xff1a;文章链接》》 get接口示范 app.get(/, (req, res) > {res.send("Hello World"); })因为是get接口&#xff0c;所以可以直接在浏览器上请求&#xff08;端口地址接口名&#xff09;…

Java 二叉数(1)

一、认识树 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a; 有一个特殊的…

零基础入门NLP - 新闻文本分类比赛方案分享 nano- Rank1

nano- 康一帅 简介 环境 Tensorflow 1.14.0Keras 2.3.1bert4keras 0.8.4 文件说明 EDA&#xff1a;用于探索性数据分析。data_utils&#xff1a;用于预训练语料的构建。pretraining&#xff1a;用于Bert的预训练。train&#xff1a;用于新闻文本分类模型的训练。pred&a…

Celery使用异步、定时任务使用

一、什么是Celery 1.1、celery是什么 Celery是一个简单、灵活且可靠的&#xff0c;处理大量消息的分布式系统&#xff0c;专注于实时处理的异步任务队列&#xff0c;同时也支持任务调度。 Celery的架构由三部分组成&#xff0c;消息中间件&#xff08;message broker&#xf…

网络安全加密算法---对称加密

三位同学一组完成数据的对称加密传输。 三位同学分别扮演图中 A、B 和 KDC 三个角色&#xff0c;说明 KA、KB&#xff0c;KAB 和发送的数据Data 的内容。 给出图中 2 和 3 中的数据&#xff0c;以及 Data 加密后的密文。可以完成多轮角色互换的通信 过程。其中一轮过程要求 K…

【Entity Framework】聊聊EF中键

【Entity Framework】聊聊EF中键 文章目录 【Entity Framework】聊聊EF中键一、概述二、配置主键2.1 约定配置主键2.2 单个属性配置为实体主键2.3 组合主键 三、主键名称四、键类型和值五、备用键 一、概述 键用作每个实体实例的唯一标识符。EF中的大多数实体都有一个键&#…

Vue2电商前台项目(三):完成Search搜索模块业务

目录 一、请求数据并展示 1.写Search模块的接口 2.写Vuex中的search仓库&#xff08;三连环&#xff09; 3.组件拿到search仓库的数据 用getters简化仓库中的数据 4.渲染商品数据到页面 5.search模块根据不同的参数获取数据展示 &#xff08;1&#xff09;把派发actions…

layui中对table表格内容鼠标移入显示 tips内容

要在Layui中的表格中实现鼠标移入显示Tips&#xff0c;你可以使用Layui的事件监听和Tips组件。 有两种实现方式&#xff01; 第一种是&#xff0c;通过自定义鼠标事件显示 tips。在渲染 table 时&#xff0c;对 filed 进行重构&#xff0c;增加相应的选择器标识&#xff0c;一…

冯喜运:4.10晚间黄金原油走势分析

黄金消息技术面分析&#xff1a;美国CPI年率创半年新高&#xff0c;美国3月未季调CPI年率录得3.5%&#xff0c;高于预期的3.4%水平&#xff0c;为2023年9月以来最高水平。美国CPI高于预期&#xff0c;现货黄金短线下挫16美元。日线当前的指标macd依旧属于金叉放量运行&#xff…

unipush+个推实现消息推送

1.注册个推平台的帐号个推&#xff0c;专业的数据智能服务商-为垂直领域提供数据智能解决方案 2.应用列表中选择新增应用/服务 3.填写下应用信息4.创建好应用后在manifest.json中的sdkConfigs配置上写入appid、appkey、appsecret "sdkConfigs" : {"ad" :…

tailwindcss+vue3+vite+preline项目搭建

最近原子化样式比较火&#xff0c;用了一下确实还不错&#xff0c;也确实是用一些标准的样式能够使网页看起来比较统一&#xff0c;而且能够极大的减轻起名字的压力&#xff0c;有利有弊&#xff0c;就不一一细说了。 之前开发都是习惯于使用vitevue3来开发的&#xff0c;此次搭…

ITK 重采样 resample

Itk 重新采样有二多种情况&#xff0c;这里说二种情况 1. 输入参数 &#xff0c;和输出相关数据&#xff0c;输出范围&#xff0c;spacing &#xff1b; typedef itk::Image< float, 3 > itkFloatImageType;typedef itk::ResampleImageFilter < itkFloatImageType, i…

观察者模式与发布-订阅模式的对决

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

基础算法(算法竞赛、蓝桥杯)--堆排序

1、B站视频链接&#xff1a;A15 堆 堆排序_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】堆 - 洛谷 #include <iostream> using namespace std; int a[1000010],cnt; void up(int u){ //上浮if(u/2 && a[u/2]>a[u]) swap(a[u],a[u/2]), up(u/2); } void d…

练习题(2024/4/10)

1. 删除有序数组中的重复项 给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元…

Vue3---基础1(认识,创建)

变化 相对于Vue2&#xff0c;Vue3的变化&#xff1a; 性能的提升 打包大小减少 41% 初次渲染快 55%&#xff0c;更新渲染快133% 内存减少54% 源码的升级 使用 proxy 代替 defineProperty 实现响应式 重写虚拟 DOM 的实现和 Tree-shaking TypeScript Vue3就可以更好的支持TypeSc…

Harmony鸿蒙南向驱动开发-MMC

MMC&#xff08;MultiMedia Card&#xff09;即多媒体卡&#xff0c;是一种用于固态非易失性存储的小体积大容量的快闪存储卡。 MMC后续泛指一个接口协定&#xff08;一种卡式&#xff09;&#xff0c;能符合这种接口的内存器都可称作MMC储存体。主要包括几个部分&#xff1a;…

【java工具-灵活拉取数据库表结构和数据】

需求&#xff1a; 假设我们现在有一个需求&#xff0c;需要快速拉取数据库的某些表建表语句&#xff0c;和数据&#xff0c;平时做备份之类&#xff1b; 我这边自己写了个工具&#xff0c;不多废话&#xff0c;也不整虚的&#xff0c; 直接看代码&#xff1a; package com.…

SQL注入sqli_labs靶场第五、六题

第五题 根据报错信息&#xff0c;判断为单引号注入 没有发现回显点 方法&#xff1a;布尔盲注&#xff08;太耗时&#xff0c;不推荐使用&#xff09; 1&#xff09;猜解数据库名字&#xff1a;&#xff08;所有ASCII码值范围&#xff1a;0~127&#xff09; ?id1 and length…

Redis从入门到精通(四)Redis实战(一)短信登录

文章目录 前言第4章 Redis实战4.1 短信登录4.1.1 基于session实现短信登录4.1.1.1 短信登录逻辑梳理4.1.1.2 创建测试项目4.1.1.3 实现发送短信验证码功能4.1.1.4 实现用户登录功能4.1.1.5 实现登录拦截功能4.1.1.6 session共享问题 4.1.2 基于Redis实现短信登录4.1.2.1 Key-Va…