给小网站自制了bangumi数据解析套件
📝2862 个字
 | ⌛要看完怎么也得8分钟吧
背景概述
- 其实在不久的以前,就有过打算专门找个地方,添加一些关于自己兴趣爱好的展示橱窗。为此当初也确实新建了文件夹,甚至总入口都已经创建好了,不信你看连跳转链接都已经有了。只不过放的位置可能并不是那么起眼,所以很多人大概是没注意到过😂。不过也没关系,就算眼尖发现了,其实进去也只会是空荡荡的一片🤡。
反正有个懒屁股就是不想装修🤪。 - 那为啥现在又突然想起这茬事了呢?诶,主要这不已经年底了嘛😮,又到了是个平台,都会出个年度报告的时期了。所以俺自然也该追随一下时代的脚步,给自己稍微做个年度总结嘛。而作为一个资深凑打游戏的二次元,番剧和游戏自然是必不可少的兴趣爱好,所以要总结自然就得包含这俩块内容。
- 游戏其实还好说,毕竟基本都是在steam上的活动,所以G胖已经非常贴心地帮我汇总好了。但就是番剧这块内容,有点让人头大😖。主要还是因为国内的大环境所致,每次看番使用的都是各种小网站又或者小平台,反正肯定不会是小破站。所以观看数据自然就散乱的到处都是,也就注定没有哪个平台,能给出这份年度总结出来。
- 就在一筹莫展之际,突然发现了bangumi这个神奇的网站。其实也不是突然发现,就是以前网上冲浪的时候,时不时就会冒出一些相关的内容。当初就好些奇这到底是个什么样的网站,只不过也就只是好奇,并没咋真正了解过😂。所以这次抽空了解了一下,才发现这个网站不一般!
- 首先要说明的是,bangumi并不是番剧的资源网站,所以如果是为了寻找资源来的话,还是放弃这个网站吧。bangumi的最强大之处,主要还是在信息汇总上。没错,就这么个不起眼的网站,却汇总了大量近年来的番剧、游戏、音乐、书籍甚至是一些三次元相关的内容。除此之外,网站还提供了api接口,可以根据需要查询任何网站上的信息。
- 诶,这岂不是只要把我自己看过的番剧,在bangumi上标记一下,之后就可以通过调用对应的api接口,一次性获取到所有我想要的数据了。甚至我对每个番剧的评分、吐槽都可以包含在这份数据内。而既然有了数据,剩下无非就是怎么组织这份数据了。如此一来,设计一个专属的年度番剧展示橱窗,自然也是已经信手拈来了😎。
数据收集
- 那首先第一步,自然先是在bangumi上留下自己的足迹。这个基本没啥好说的,就注册个账号,然后一顿点一顿吐槽就完了😆。不过毕竟俺也是追过十几年番剧的人,这要我把所有番剧都标记上,多少还是有点赛博酷刑了🤪。所以这次也就偷个懒,先把今年的都标记上,好歹可以把今年的先汇总出来💪。
- 标记完成之后,剩下的就是纯代码的功夫了。但在这之前,首先需要打开的,还是bangumi的官方api文档。至少也得了解一下对应接口的使用方法,以及api的接入规范。文档上首先能看到的,是一个用于生成Access Token的网址,咱这次没用上,所以就pass了。然后就是关于User Agent的规范,内容不多,大致意思就是别用默认的信息,咱根据情况填写自己的信息即可。然后就可以开始翻找自己需要使用的接口啦。
- 因为咱这次主要收集的,是之前所有标记过的番剧信息,所以首先需要确认一下信息所处的位置在哪。经过一段时间的翻找,发现全部都显示在个人界面的收藏页签里面。然后通过查阅api文档,发现只要使用收藏分组里面的第一个接口,就可以获取账号收藏页签内的所有内容。稍微组织了一下代码格式,最后大致内容如下。
/* api参数: username: 用户名 subjectType: 条目类型 (别问,反正就是没有5😆) 1: 书籍 2: 动画 3: 音乐 4: 游戏 6: 三次元 type: 收藏类型 1: 想看 2: 看过 3: 在看 4: 搁置 5: 抛弃 limit: 分页参数,本次请求包含条目最大数量 offset: 分页参数,本次请求包含条目起始偏移量 */ const options = { hostname: 'api.bgm.tv', path: `/v0/users/${username}/collections?subject_type=${subjectType}&type=${type}&limit=${limit}&offset=${offset}`, headers: { 'User-Agent': `${userAgent}` } }; fetch(`https://${options.hostname}${options.path}`, { "headers": options.headers, }); - 如果参数没问题的话,应该就可以收到现在账号收藏页签下,所有的条目信息了。不过虽然本地能正常获取到数据,但后续经过测试之后,发现偶尔还是会出现一些访问异常的情况。感觉多半还是因为国内一些大伙耳熟能详的原因吧,要正常访问还是需要一些科技手段。
- 但自己用还好,放到小网站上就复杂了,毕竟不能要求所有小伙伴,都得带着科技与狠活来访问咱网站吧🤣。当然解决方法其实也很多,无非就是各种中间商中转一下数据,但感觉多少还是有点别扭。所以后来俺自己琢磨除了一个新方案。
- 首先是咱网站是部署在海外的边缘服务器上的,所以访问bangumi这种事情本来也不是啥难事。那既然咱自己访问没啥问题,不如直接在构建的时候,自动拉取一份信息,然后直接把数据缓存下来。之后网站上就可以直接使用咱自己缓存的数据,那肯定就不存在啥网络问题了😎。
- 当然这么做也存在一定的弊端,毕竟是缓存的数据,即时性几乎等于没有。但其实咱这个数据也不需要没事一直更新,所以就算和咱小作文的更新频率保持同步,其实也没啥太大问题,甚至还能给bangumi服务器省点压力。真要啥时候突然想立刻更新的话,其实也可以自己手动触发一下构建。而且这么做还有一个好处,就是咱们可以自行精简和重新组织数据结构。这对后续网页的访问速度以及功能制作,其实都有好处。
- 改起来也方便,无非就是把js从browser环境改成node环境,核心逻辑基本无需改动。不如说因为变成只在构建使用的node脚本,写起代码来反而更加放荡不羁了🤣。除了正常的加工和精简以外,还顺便给导出的数据进行了分组、排序,最后的数据格式大致如下:
//2025.json [ { "name_cn": "剧场版 鬼灭之刃 无限城篇 第一章 猗窝座再袭", "anime_type": 2, "short_summary": "鬼となった妹・禰?豆子を人間に戻すため鬼狩り...", "air_date": "2025-07-18", "user_rating": 8, "user_comment": "非要说啥缺点,就是作为一个剧场版,故事有很明显的拼接感,最终导致时长偏长,结尾也有点唐突", "image_url": "https://lain.bgm.tv/pic/cover/l/f6/0b/501958_VZs4W.jpg", "bgm_url": "https://bgm.tv/subject/501958" }, ... ] //summary.json { "username": "fantasyDM", "fetch_time": "2025-12-26T03:30:21.912Z", "total_count": 62, "year_statistics": [ { "year": "2025", "count": 62 } ] }
橱窗制作
- 嘛,解决了数据问题,接下来的步骤就简单了。首先就是在需要显示橱窗的地方,加个fetch脚本,根据配置需要展示的年份信息,获取到咱们构建时生成并缓存的数据,然后按照需要,重新把获取到的数据进行整理和筛选。大致代码如下:
const response = await fetch(this.dataPath); if (!response.ok) throw new Error('数据加载失败'); const data = await response.json(); // 筛选指定年份的数据 this.allData = data.filter(item => { if (!item.air_date) return false; const date = new Date(item.air_date); return date.getFullYear() === this.year; }); if (this.allData.length === 0) { this.timelineElement.innerHTML = '<div class="empty-message">该年份暂无收藏数据</div>'; this.statsElement.innerHTML = '<span class="stat-loading">暂无数据</span>'; return; } - 除此之外,毕竟咱们是为了做年度总结整的活,虽然不可能做到各大平台那样生龙活虎的,但基本的信息汇总还是不能少的,所以俺又专门做了一个简单的统计显示:
const total = data.length; const avgRating = data.filter(d => d.user_rating).length > 0 ? (data.reduce((sum, item) => sum + (item.user_rating || 0), 0) / data.filter(d => d.user_rating).length).toFixed(1) : 'N/A'; const typeCounts = data.reduce((acc, item) => { const type = ANIME_TYPE_MAP[item.anime_type] || 'Unknown'; acc[type] = (acc[type] || 0) + 1; return acc; }, {}); const topType = Object.entries(typeCounts).sort((a, b) => b[1] - a[1])[0]; this.statsElement.innerHTML = ` <span class="stat-item"> <span class="stat-icon">📊</span> <span>总计 <span class="stat-value">${total}</span> 部</span> </span> <span class="stat-item"> <span class="stat-icon">⭐</span> <span>平均 <span class="stat-value">${avgRating}</span> 分</span> </span> <span class="stat-item"> <span class="stat-icon">🎬</span> <span>最多 <span class="stat-value">${topType[0]}</span> (${topType[1]}部)</span> </span> `; - 剩下的无非就是橱窗本体的生成了,没啥特别的,就是根据之前缓存的json数据结构,获取到对应信息,最后再组装成对应的html即可。因为在整这部分功能代码的时候,俺总是按耐不住自己放荡不羁的小手🙈,导致最后代码变得又臭又长🤪。所以就不再单独贴出来了,咱们直接看最后实现的效果吧:
-
2025年度番剧汇总
(评分仅代表俺个人,实际观看体验可能因人而异)正在加载统计数据...下滑还有内容哦
后续安排
- 既然番剧的展示橱窗都已经做好了,那之后肯定得先把今年的年度总结先安排上了💪。不过毕竟打工人精力有限,所以其他维度的总结,就稍微复用一下每个平台自己的吧。如果后续还有时间,可以考虑一下再收集一些新维度的数据,整个新的展示橱窗✨。
- 同时,番剧的展示橱窗后续应该会在其他地方,单独开个新的大入口,可以包含进各个年份的番剧总结。酱紫至少可以从今年开始,把俺看过的番剧和评价都整理起来。至于以前看过的番剧,只能看有时间再慢慢补起来了。嘛,来日方长,只要bangumi不跑路,总有一天能补全的🤣。