B站收藏夹视频统计:基于浏览器脚本的自动化盘点方案 📅 2026/8/26 10:59:17 1. 项目概述一个B站收藏夹的“自我盘点”作为一个重度B站用户我的收藏夹早就成了数字版的“松鼠症”仓库。从硬核技术教程到搞笑整活视频从深度纪录片到五分钟美食什么都往里扔。时间久了收藏夹里到底塞了多少宝贝我自己心里完全没数。每次想找以前看过的某个视频都得在几十甚至上百个分区里大海捞针效率极低。于是一个很自然的需求就冒出来了我到底收藏了多少视频能不能快速统计一下甚至做个简单的分类分析手动去数显然不现实。B站网页版虽然提供了收藏夹管理功能但并没有直接显示收藏视频总数的统计项。这时候作为一个有点技术背景的用户很自然地就想到了“自己动手丰衣足食”——写一个网页脚本Web Script。这个脚本的核心任务很简单自动调用B站的相关接口API获取我所有收藏夹的视频列表然后进行计数和简单分析。它不需要复杂的界面就在浏览器开发者工具的Console控制台里运行结果直接打印出来干净利落。这个项目我称之为“bilibili001”它本质上是一个轻量级的、面向特定场景的浏览器端JavaScript工具。它不涉及任何复杂的后端服务不存储用户数据所有的操作都在用户自己的浏览器环境中完成安全且私密。通过这个项目我们不仅能解决一个具体的实际问题更能深入理解如何与一个大型内容平台的Web API进行交互如何处理分页数据以及如何编写实用的、即拿即用的浏览器脚本。2. 核心思路与技术选型2.1 为什么选择网页脚本Web Script面对“统计B站收藏视频数”这个问题有好几种技术路径可选。比如可以写一个浏览器插件Extension拥有更稳定的生命周期和界面也可以用Python写一个爬虫脚本定期运行。但我最终选择了最轻量、最直接的网页脚本原因如下零部署、即时生效用户只需要打开B站收藏夹页面按F12打开开发者工具将脚本粘贴到Console中回车即可运行。无需安装插件、配置环境或运行独立程序门槛极低。环境天然适配B站的前端本身就是用JavaScript开发的其API调用也遵循Web标准如fetch或XMLHttpRequest。在浏览器环境中运行JavaScript脚本可以无缝利用页面已有的登录状态Cookie、Token省去了模拟登录这个最复杂的环节。灵活性高脚本可以根据需要随时修改、随时运行。今天想统计总数明天想按分区筛选稍微改几行代码就行非常适合快速原型和个性化需求。隐私安全所有数据请求和计算都发生在用户本地浏览器数据不会发送到任何第三方服务器最大程度保障了账号信息安全。注意这种方式的局限性在于依赖用户手动执行无法自动化定时任务。但对于“偶尔盘点一下”这种需求完全够用。2.2 关键技术点B站API与数据获取B站没有公开官方的、文档齐全的收藏夹API供第三方开发者使用。因此我们的脚本需要基于其网页端实际使用的接口进行“逆向工程”。这通常通过浏览器开发者工具的“网络”Network面板来完成。寻找目标接口打开B站网页版进入“我的收藏”页面同时打开Network面板然后滚动收藏夹列表。此时面板中会刷出许多网络请求我们需要从中找到那个负责加载收藏视频列表的请求。识别关键请求通常这类请求的URL会包含api.bilibili.com域名以及/x/v3/fav/resource/list这样的路径。请求方法为GET并且会携带一系列查询参数Query Parameters如media_id: 收藏夹ID。pn: 页码Page Number。ps: 每页大小Page Size。keyword: 搜索关键词可选。以及一些用于鉴权的csrfCross-Site Request Forgery令牌。分析响应结构点击这个请求查看它的“响应”Response内容。B站的API通常返回JSON格式的数据。我们需要关注的是其中的data对象里面会包含medias视频列表数组和page分页信息如总数total等关键字段。处理分页一个收藏夹的视频可能很多B站API一次只返回一页例如20条。因此我们的脚本必须实现分页逻辑循环请求每次递增pn参数直到获取所有数据。循环终止的条件可以是返回的medias数组为空或者当前页码超过了page.count总页数。2.3 工具与语言纯JavaScript (ES6)本项目完全使用现代JavaScriptES6及以上标准在浏览器环境中实现。核心将用到以下特性fetchAPI: 用于发起网络请求替代老旧的XMLHttpRequest语法更简洁支持Promise。async/await: 处理异步请求让代码逻辑更清晰避免“回调地狱”。模板字符串: 方便拼接URL和输出信息。箭头函数、const/let: 使代码更现代、严谨。我们不需要任何外部库如jQuery、Axios利用浏览器原生能力即可确保脚本的纯粹性和可移植性。3. 实操步骤从零编写统计脚本3.1 第一步环境准备与接口探查在开始写代码之前我们需要先“侦察”一下。使用Chrome或Edge浏览器登录B站进入https://space.bilibili.com/{你的UID}/favlist这个页面即“我的收藏”。按F12打开开发者工具切换到“网络”(Network)选项卡。在筛选器Filter中输入list或resource来缩小请求范围。刷新页面或滚动收藏夹列表观察出现的请求。找到一个形如https://api.bilibili.com/x/v3/fav/resource/list?media_idxxxpn1ps20...的请求。点击它在“标头”(Headers)标签页可以查看完整的请求URL和参数在“预览”(Preview)或“响应”(Response)标签页可以查看结构化的返回数据。记下这个请求的URL模板以及必要的参数。你会发现media_id收藏夹ID和csrf令牌通常在Cookie中是必需的。3.2 第二步编写核心数据获取函数基于探查结果我们编写一个异步函数用于获取单个收藏夹的某一页数据。/** * 获取指定收藏夹的某一页视频列表 * param {number|string} mediaId - 收藏夹ID * param {number} pageNum - 页码从1开始 * param {number} pageSize - 每页数量通常20 * returns {PromiseObject} - 返回API的响应数据 */ async function fetchFavListPage(mediaId, pageNum 1, pageSize 20) { // 构建请求URL参数直接从浏览器当前请求中复制过来确保包含csrf等必要参数 const url https://api.bilibili.com/x/v3/fav/resource/list?media_id${mediaId}pn${pageNum}ps${pageSize}ordermtimetype0tid0platformwebweb_location1550101; try { const response await fetch(url, { method: GET, credentials: include, // 关键携带当前站点的Cookie维持登录状态 headers: { Accept: application/json, text/plain, */*, // 可以添加一些常见的浏览器头避免被简单拦截 User-Agent: navigator.userAgent, } }); if (!response.ok) { throw new Error(网络请求失败: ${response.status}); } const result await response.json(); // B站API通常通过code字段表示状态0为成功 if (result.code ! 0) { throw new Error(API错误: ${result.message} (code: ${result.code})); } return result.data; // 返回核心数据部分 } catch (error) { console.error(获取第${pageNum}页数据时出错:, error); throw error; // 将错误向上抛出 } }关键点解析credentials: include这是整个脚本能运行的关键。它告诉浏览器在发起fetch请求时要携带当前域名bilibili.com下的Cookie。因为你是登录状态访问B站页面的所以这个请求就能自动使用你的登录凭证无需手动处理Token。result.code ! 0B站API的通用约定非0即表示某种错误需要处理。3.3 第三步实现分页获取与总数统计单个页面不够我们需要循环获取所有页面并累加视频数量。/** * 获取指定收藏夹的所有视频并统计总数 * param {number|string} mediaId - 收藏夹ID * returns {Promise{total: number, list: Array}} - 返回总数和视频列表 */ async function getAllFavVideos(mediaId) { let currentPage 1; const pageSize 20; let allVideos []; let totalCount 0; let hasMore true; console.log(开始获取收藏夹 ${mediaId} 的视频...); while (hasMore) { console.log(正在获取第 ${currentPage} 页...); try { const pageData await fetchFavListPage(mediaId, currentPage, pageSize); const videos pageData.medias || []; // 视频列表 const pageInfo pageData.page || {}; allVideos allVideos.concat(videos); // 如果是第一次请求可以从pageInfo中获取理论上的总数但可能不准确以实际拉取的为准 if (currentPage 1) { totalCount pageInfo.total || 0; console.log(该收藏夹理论视频总数: ${totalCount}); } // 判断是否还有下一页当前页返回的视频数小于pageSize或者当前页码超过总页数 if (videos.length pageSize) { hasMore false; console.log(已到最后一页第${currentPage}页本页仅有${videos.length}个视频。); } else { currentPage; // 添加一个短暂的延迟避免请求过快对服务器造成压力或触发风控 await new Promise(resolve setTimeout(resolve, 200)); } } catch (error) { console.error(获取过程中断于第 ${currentPage} 页。); hasMore false; // 出错也停止循环 // 可以选择将已获取的部分数据返回 // break; } } const actualTotal allVideos.length; console.log(获取完毕实际共获取到 ${actualTotal} 个视频。); // 对比理论总数和实际获取数 if (totalCount 0 actualTotal ! totalCount) { console.warn(注意实际获取数(${actualTotal})与API返回的理论总数(${totalCount})不符。可能部分视频因权限等原因未加载。); } return { total: actualTotal, list: allVideos }; }关键点解析循环条件以videos.length pageSize作为是否还有下一页的主要判断。这是最可靠的方式因为pageInfo中的总页数有时可能不准确。请求间隔在循环中增加了setTimeout延迟200毫秒。这是一个良好的实践可以避免在短时间内发出大量请求被服务器视为异常流量。错误处理在循环中捕获单页请求的错误并决定是否中止整个获取过程。这里选择出错即停止并打印警告。3.4 第四步获取当前页面收藏夹ID并执行我们如何得到mediaId呢最简单的方法就是从当前页面的URL或DOM中提取。观察B站收藏夹页面URL格式可能是https://space.bilibili.com/{uid}/favlist?fid{mediaId}。fid参数就是收藏夹ID。对于“全部收藏”这个默认视图可能需要特殊处理。更通用的方法是直接查看页面中第一个收藏夹的链接。我们可以写一个函数来自动获取当前页面正在浏览的收藏夹ID/** * 尝试从当前页面URL或DOM中获取收藏夹ID * returns {string|null} 收藏夹ID如果获取失败则返回null */ function getCurrentMediaId() { // 方法1: 从URL参数中获取 (fid) const urlParams new URLSearchParams(window.location.search); const fidFromUrl urlParams.get(fid); if (fidFromUrl) { console.log(从URL参数获取到收藏夹ID: ${fidFromUrl}); return fidFromUrl; } // 方法2: 从页面上的收藏夹列表第一个元素中获取 (更通用) // 假设页面有一个收藏夹列表其链接包含 “/favlist?fid” const favLink document.querySelector(a[href*/favlist?fid]); if (favLink) { const href favLink.getAttribute(href); const match href.match(/fid(\d)/); if (match match[1]) { console.log(从页面链接解析到收藏夹ID: ${match[1]}); return match[1]; } } // 方法3: 如果是“全部收藏”页面可能有一个默认的ID或者需要遍历所有收藏夹 // 这里可以提示用户手动输入 console.warn(无法自动获取收藏夹ID。请手动打开一个具体的收藏夹页面非“全部收藏”或手动输入收藏夹ID。); return null; }最后我们编写一个主函数将一切串联起来/** * 主执行函数 */ async function main() { console.clear(); console.log( B站收藏视频统计脚本 v1.0 ); const mediaId getCurrentMediaId(); if (!mediaId) { const manualId prompt(请手动输入你要统计的收藏夹ID数字:); if (!manualId || isNaN(manualId)) { console.error(未提供有效的收藏夹ID脚本终止。); return; } mediaId manualId; } try { const startTime Date.now(); const { total, list } await getAllFavVideos(mediaId); const endTime Date.now(); console.log(\n 统计完成); console.log( 收藏夹 ID: ${mediaId}); console.log( 视频总数: ${total} 个); console.log(⏱️ 耗时: ${((endTime - startTime) / 1000).toFixed(2)} 秒); // 简单分析示例按分区tid统计 const categoryCount {}; list.forEach(video { const tid video.tid || 0; categoryCount[tid] (categoryCount[tid] || 0) 1; }); console.log(\n 视频分区分布:); // 这里可以映射tid到分区名为了简化先输出tid Object.entries(categoryCount).forEach(([tid, count]) { console.log( 分区 ${tid}: ${count} 个视频); }); } catch (error) { console.error(脚本执行失败:, error); } } // 执行主函数 main();将上述所有代码块按顺序组合成一个完整的JS文件或者直接在开发者工具的Console中分段粘贴执行最后调用main()函数即可。4. 常见问题、优化与扩展4.1 实操中可能遇到的问题及解决方案fetch请求返回403或400错误原因最常见的原因是credentials: include未设置导致请求未携带Cookie身份验证失败。也可能是请求头不完整被服务器风控机制拦截。解决确保fetch的credentials选项设置为include。可以尝试在开发者工具中复制原始请求的Headers将必要的User-Agent、Referer等头信息添加到脚本的fetch请求中。获取的视频数量与页面显示不一致原因API返回的page.total有时是“理论值”可能包含了已失效、被删除或你无权查看的视频。我们的脚本以实际拉取到的列表为准这个数字更可靠。解决信任脚本统计的实际数量(allVideos.length)。如果差异巨大可能是分页逻辑有误检查循环终止条件。脚本运行缓慢或卡住原因收藏夹视频过多例如上千个循环请求次数多且每次等待响应需要时间。如果网络不佳或B站服务器响应慢总耗时可能很长。解决在循环中加入更明显的进度提示并考虑适当增加请求间隔如300-500毫秒避免给服务器造成负担。对于超大型收藏夹要有耐心。“全部收藏”无法统计原因“全部收藏”是聚合视图可能没有单一的media_id或者其数据获取接口不同。解决这是一个进阶需求。需要先调用另一个API如/x/v3/fav/folder/created/list获取用户创建的所有收藏夹ID列表然后遍历每个收藏夹调用我们的getAllFavVideos函数最后将结果累加。这会产生大量请求需谨慎操作并添加更长的延迟。4.2 脚本优化与功能扩展基础的统计功能完成后可以基于这个框架轻松扩展更多实用功能导出收藏列表将allVideos数组转换为JSON或CSV格式并提供下载链接。可以使用Blob对象和URL.createObjectURL实现浏览器端文件下载。function exportToJson(videoList) { const dataStr JSON.stringify(videoList, null, 2); const blob new Blob([dataStr], { type: application/json }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download bilibili_fav_videos_${Date.now()}.json; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }按分区、UP主、关键词筛选在获取完整列表后利用数组的filter方法进行筛选。例如找出所有“科技”分区tid对应特定数字或特定UP主的视频。去重检查收藏夹有时可能会重复收藏同一个视频。可以根据视频的bv_id或aid稿件ID进行去重统计。const uniqueVideos []; const idSet new Set(); allVideos.forEach(video { const id video.bvid || video.id; if (!idSet.has(id)) { idSet.add(id); uniqueVideos.push(video); } }); console.log(去重后视频数: ${uniqueVideos.length} (去除了${allVideos.length - uniqueVideos.length}个重复项));添加进度条和更友好提示使用console.log输出进度百分比提升等待体验。4.3 安全与合规使用提醒重要提示此脚本仅用于个人学习、数据统计和效率提升。尊重平台脚本的请求频率应保持在较低水平如每次请求间隔200毫秒以上模拟正常用户操作避免对B站服务器造成不必要的压力。遵守robots.txt虽然网页脚本不同于传统爬虫但仍应关注网站的robots.txt协议尊重平台的爬虫限制规定。数据用途获取的数据请仅用于个人分析切勿用于商业用途、批量抓取或任何可能侵犯版权、用户隐私的行为。账号安全脚本在你的浏览器环境中运行利用的是你已登录的会话。只要你不将脚本分享给不可信来源或访问恶意网站你的账号凭证就是安全的。但永远不要在任何第三方网站或工具中输入你的B站账号密码。通过这个“bilibili001”项目我们不仅解决了一个具体问题更实践了前端API调用、异步编程和简单数据处理。它像一把瑞士军刀虽然小巧但足够锋利能帮你理清数字世界的收藏混乱。下次当你面对一个看似没有现成工具的需求时不妨也打开开发者工具看看也许几行代码就能创造出一个专属于你的效率工具。