“神话曲”这个词在术力口圈子里是有明确门槛的Niconico 上播放量突破 1000 万次的歌曲才能被归入这个档位播放量到 100 万是“传说曲”到 1000 万之前的大量歌曲其实长期处于“看得见目标、但不知道哪一天达标”的状态。这次我们来看“N站术力口准神话曲月刊 8 月第十期”这个选题背后的数据逻辑。如果只把它当成一期歌曲推荐那就忽略了它真正值得拆解的部分它本质上是一个“视频数据爬取 榜单生成 里程碑追踪”的轻量数据工程。与其等着别人整理月度榜单不如自己搭一套脚本让计算机定期帮我们筛出那些播放量接近 1000 万、处在“准神话”区间的术力口歌曲并生成一份可阅读的月刊报告。这篇文章会从数据采集、筛选规则、月度报告生成、执行排错这几个角度展开。无论你是想给自己做一个追榜工具还是想理解“准神话曲月刊”这类内容是怎样被批量生产出来的都可以按下面的流程跑一遍。整套工程不需要 GPU不需要大显存CPU 加几 GB 内存就够用核心成本在于数据源是否稳定、筛选逻辑是否合理以及任务调度是否足够健壮。1. 项目定位与核心能力“准神话曲月刊”的定位并不是一个传统意义上的 Web 应用而更像是一个“月度数据快照生成器”。它以 Niconico 上的术力口歌曲为观察对象把播放量处在接近 1000 万区间、并且仍有较快增长趋势的歌曲筛选出来形成一期榜单。如果要把这套东西产品化建议先把它拆成四个模块模块作用技术选型建议数据采集获取歌曲元数据、播放量、发布时间、UP 主信息Python HTTP 抓取或官方/第三方开放接口数据存储保存历史快照供后续对比增长趋势SQLite / CSV轻量级足够筛选与排序计算“是否接近神话曲”及增长趋势Pandas 或纯 Python报告生成输出月度榜单页面或 Markdown 文档Jinja2 / Markdown 模板这类工具最大的好处是运行成本极低。它不需要 GPU 资源也不依赖高阶的深度学习模型主要消耗在 HTTP 请求和数据处理上。一台普通的 Linux 云主机、Windows 主机甚至树莓派都能承担每月一次甚至每天一次的任务。1.1 “准神话曲”的筛选思路一个歌曲要进入“准神话”观察区间通常可以设定两到三个维度的条件这里给出一个可调参数以实际歌曲分布为准播放量阈值播放量已经超过 900 万但尚未突破 1000 万。近期增长近 30 天新增播放量仍保持在较高水平例如日均增长超过 2000表明歌曲仍有持续热度。播放占比播放占比如异常高说明这首歌是“长尾型”热门曲目可能在将来某个节点突破神话线。这些条件不是固定的。选择标准可以根据榜单目的自由调整如果月刊定位是“最接近 1000 万的歌曲”那播放量区间可以放宽到 800 万以上如果月刊想突出“未来的神话曲”那需要更严格的增长过滤条件。2. 使用场景与合规边界这类项目的使用场景很清晰主要集中在内容观察、数据分析和二次创作辅助。场景是否推荐说明自用追榜关注喜欢的 UP 主或 P 主推荐低频抓取个人使用影响小制作公开榜单或数据分析文章需谨慎涉及数据来源、素材版权需确认引用边界抓取视频评论、弹幕等内容不推荐属于用户生成内容个人信息和版权风险高抓取原始音频或视频文件不推荐涉及版权方利益必须取得授权搞大规模爬虫去干扰目标站点不推荐违反平台服务协议可能造成封禁术力口歌曲本身存在大量二次创作与翻唱在制作“准神话曲月刊”这类内容时需要第一时间确认信息来源是否允许聚合。Niconico 的页面内容、缩略图、歌曲信息和播放数据都受到平台服务和相关版权约束。在实际搭建之前请先确认三个边界:数据只用于个人或小范围技术验证避免做商业化视频聚合平台。不以任何方式绕过平台的反爬机制不模拟用户登录去抓取非公开数据不批量抓取弹幕和评论区。发布报告时如果包含歌曲封面、歌词、音频片段先取得作者或版权方许可或者只用“文字描述 数据表格”的方式。合规边界不是额外负担。把数据源限定在“允许公开访问的元数据”范围内既能让工具跑得更干净也能避免后续被平台封禁或产生版权纠纷。3. 环境准备与前置条件“准神话曲月刊”这类数据工程项目对运行环境要求很低。基础环境只需要 Python 3.9 以上版本以及一个可安装第三方库的虚拟环境。建议在正式开始前先准备以下内容Python 版本3.9、3.10、3.11 均可不建议使用过于古老的 3.6。操作系统Windows 10/11、Ubuntu 20.04、macOS 均可。依赖包requests、pandas、jinja2、sqlite3Python 内置。磁盘空间日志、数据库和报告占用极小预留 500MB 足够。网络环境能正常访问目标数据源站点即可如果目标站点在中国大陆访问不稳定可以设置合理的请求超时不要让脚本无限等待。下面创建一个干净的虚拟环境mkdir niconico-monthly-report cd niconico-monthly-report python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate然后安装依赖pip install requests pandas jinja2这一套工具已经能覆盖大部分场景。如果你的歌曲数据源是某个已封装好的 Python SDK可以再额外安装对应的 SDK但建议先保持最小依赖确认数据源能连通后再扩展。4. 数据存储结构与首版基准月度榜单必须依赖历史数据否则你只能看到当月的播放量无法判断歌曲是否在增长。所以建议从第一次运行就开始落库哪怕一开始只有五条测试数据也比“只抓当前量级、不存历史”要好。推荐使用 SQLite单文件、零配置、方便备份。建立一个简单的表结构CREATE TABLE songs ( id INTEGER PRIMARY KEY AUTOINCREMENT, song_title TEXT NOT NULL, uploader_name TEXT, publish_date TEXT, duration_seconds INTEGER, play_count INTEGER, comment_count INTEGER, mylist_count INTEGER, url TEXT, snapshot_date TEXT NOT NULL ); CREATE INDEX idx_songs_snapshot ON songs(snapshot_date);每条歌曲数据都对应一个snapshot_date代表“这个播放量是在哪一天抓到的”。保存历史快照非常重要。如果你在每个月初只把当时的榜单打印出来而不写进数据库一两个月后想回顾“某首歌上个月增长了 80 万播放”就会发现数据已经丢失整个月刊的“追踪”属性也就消失了。初始化数据库的代码如下import sqlite3 from datetime import date conn sqlite3.connect(vocaloid_trend.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS songs ( id INTEGER PRIMARY KEY AUTOINCREMENT, song_title TEXT NOT NULL, uploader_name TEXT, publish_date TEXT, duration_seconds INTEGER, play_count INTEGER, comment_count INTEGER, mylist_count INTEGER, url TEXT, snapshot_date TEXT NOT NULL ) ) conn.commit() today date.today().isoformat() conn.execute(INSERT INTO songs (song_title, play_count, snapshot_date) VALUES (测试歌曲, 9500000, ?), (today,)) conn.commit()这个例子只是为了验证数据库能正常写入。实际脚本里“测试歌曲”的位置应该替换成从数据源拿到的真实歌曲信息。5. 歌曲数据采集请求封装与通用模板数据采集是整个项目的核心也是最容易出问题的部分。不同数据源有不同的请求方式这里给出一套通用的采集类模板实际使用时把API_ENDPOINT和目标字段替换成你对接的数据源即可。import requests import time HEADERS { User-Agent: Mozilla/5.0 (compatible; MonthlyReportBot/1.0) } def fetch_song_list(page_size50, max_pages3): 通用数据源拉取函数。 真实项目里需要替换成目标数据源的实际接口地址和参数。 songs [] # 示例端点仅用于演示请求结构不可直接使用 api_url https://api.example-video-service.test/v1/music/ranking for page in range(1, max_pages 1): params { page: page, page_size: page_size, category: vocaloid, sort: play_count_desc } try: resp requests.get(api_url, headersHEADERS, paramsparams, timeout10) resp.raise_for_status() data resp.json() for item in data.get(songs, []): songs.append({ song_title: item.get(title), uploader_name: item.get(uploader), publish_date: item.get(publish_date), play_count: item.get(play_count), comment_count: item.get(comment_count), mylist_count: item.get(mylist_count), url: item.get(url), }) except requests.exceptions.Timeout: print(f第 {page} 页请求超时跳过) except requests.exceptions.HTTPError as e: print(f第 {page} 页请求失败{e}) break time.sleep(1) # 控制请求频率避免对数据源造成压力 return songs注意fetch_song_list是一个模板不是可以直接对接 Niconico 的成品。原因在于各数据源的鉴权方式、字段名、翻页逻辑都不一样。拿到一个数据源后你要先看它的接口文档或公共页面结构再调整params和字段解析逻辑。如果数据源不提供公开 API需要靠普通 HTML 页面解析那就引入BeautifulSoup或parsel做选择器提取此时抓取频率必须进一步调低并且在脚本里输出清晰的日志。6. 筛选“准神话曲”的完整逻辑先把歌曲数据同步到 SQLite再做二次计算。推荐的做法是当天采集到的全部歌曲先入库存一份筛选条件不在采集阶段做死保证后续调整阈值时历史数据不丢失。按月报目标筛选过程大致分为三步。6.1 第一步计算上次采集到本次采集的播放量增量SELECT current.song_title, current.play_count AS current_play, previous.play_count AS previous_play, current.play_count - previous.play_count AS play_increase, julianday(current.snapshot_date) - julianday(previous.snapshot_date) AS days_diff FROM songs current JOIN songs previous ON current.song_title previous.song_title WHERE current.snapshot_date ? AND previous.snapshot_date ?这一步会让你获得两期快照数据的对比。有了play_increase和days_diff就能算出日均增量这是判断歌曲“是否仍然有机会朝神话线继续爬升”的重要指标。6.2 第二步应用过滤阈值使用 Python 进行二次过滤import sqlite3 THRESHOLD_PLAY 9_000_000 # 播放量超过 900 万 THRESHOLD_DAILY 3_000 # 日均增长超过 3000可按实际情况调整 def find_quasi_myth_songs(snapshot_date): conn sqlite3.connect(vocaloid_trend.db) conn.row_factory sqlite3.Row rows conn.execute( SELECT song_title, uploader_name, play_count, mylist_count FROM songs WHERE snapshot_date ? ORDER BY play_count DESC , (snapshot_date,)).fetchall() result [] for row in rows: if row[play_count] THRESHOLD_PLAY: continue result.append({ song_title: row[song_title], uploader_name: row[uploader_name], play_count: row[play_count], mylist_count: row[mylist_count], tag: 准神话候选 }) return result阈值的设定很关键。如果把阈值定在 900 万很多几年才涨几万的老歌会被卷进来如果加上“日均增长 3000”的条件就会筛掉那些已经停止增长的作品留下更有“未来可能达标”潜力的歌曲。6.3 第三步生成可调参数配置建议把筛选条件写进配置文件不让团队里的非技术人员改代码。这里用最简单的 YAML# config.yaml play_count_min: 9000000 play_count_max: 9999999 min_daily_growth: 3000 months_window: 12 fetch_page_size: 50 fetch_max_pages: 5 snapshot_frequency: monthly在 Python 里读配置import yaml with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) print(config[play_count_min])如果不想引入 YAML 依赖直接用json也可以。核心是“参数与代码分离”让每期月刊的执行者可以在不改变逻辑的前提下调整入榜标准。7. 生成月刊报告与批量输出筛选得到榜单后剩下的事情就是把数据变成可读报告。这里推荐输出两种格式直接打印在控制台的摘要。生成一份 Markdown 文件方便后续发到博客或内部文档。7.1 Markdown 报告模板from jinja2 import Template report_template Template( # 术力口准神话曲月刊 生成日期{{ generate_date }} ## 入围歌曲 {% for song in songs %} - {{ loop.index }}. 《{{ song.song_title }}》 - UP 主{{ song.uploader_name }} - 播放量{{ song.play_count }} - 收藏数{{ song.mylist_count }} {% endfor %} 注本榜单仅为技术演示用途数据字段需要依实际来源补充。 ) songs [ {song_title: 示例歌曲, uploader_name: 示例P主, play_count: 9_500_000, mylist_count: 320_000}, ] output report_template.render( generate_date2025-08-10, songssongs, ) with open(monthly_report.md, w, encodingutf-8) as f: f.write(output) print(output)7.2 控制台批量输出如果只是巡检完全不需要生成文件在控制台里输出一个列表就够了python generate_report.py输出效果类似 准神话曲候选 1. 示例歌曲A | 950.2万播放 | 日均1.2万 | 距离神话线49.8万 2. 示例歌曲B | 930.8万播放 | 日均0.8万 | 距离神话线69.2万当你看到“距离神话线”只有几十万而日均增长长期稳定基本可以预判如果没有意外情况这首歌将在某个时间点突破 1000 万。8. 月度任务自动化定时执行与调度月刊意味着不需要实时更新频率通常很低。可以用系统自带的任务调度来执行不需要额外搭建 Celery 这类分布式任务队列。8.1 Linux cron 配置# 每月 1 日早上 8 点执行 0 8 1 * * cd /path/to/niconico-monthly-report /usr/bin/python3 fetch_and_report.py logs/report_$(date \%Y\%m\%d).log 218.2 Windows 任务计划程序可以把整条逻辑写成一个.bat文件再到“任务计划程序”中设定每月触发。echo off cd /d D:\niconico-monthly-report call venv\Scripts\activate.bat python fetch_and_report.py logs\report_%date:~0,4%%date:~5,2%%date:~8,2%.log 21执行频率不需要太高每月一次就是最低限度的“月刊”如果想观察趋势曲线可以改成每周一次甚至每天一次。但需要注意数据源承载能力如果只是个人项目不建议启动高并发抓取。9. 数据校验、资源占用与失败恢复非实时数据工具最怕的不是速度慢而是数据不准。如果你的数据库里同一个歌曲名因为大小写、全半角差别出现了多行记录那最终报告会非常混乱。建议在月度任务里加入三个质量校验步骤当月快照写入前先检查是否存在同一天的快照避免重复入库。对播放量做环比校验如果一首歌本月播放量比上月下降超过 2%且不是下架或隐藏就要检查字段解析是否出错。每次执行后生成日志摘要至少包含“成功抓取条数”“失败条目数”“新增入库数”。下面是一段简单的去重校验示例def is_snapshot_exists(cursor, snapshot_date): row cursor.execute( SELECT COUNT(*) FROM songs WHERE snapshot_date ?, (snapshot_date,) ).fetchone() return row[0] 0如果任务已经运行过可以跳过当天任务避免重复数据污染统计。9.1 CPU 与内存占用由于这类工具只是发 HTTP 请求和跑 DataFrame 级的数据处理运行时占用通常很低。普通情况下 Python 进程内存占用在 150MB 以内抓取大量数据时可能到 300MB 左右。对于一台 2C4G 的轻量云主机来说完全是闲置负载。不需要 GPU不需要推理框架。所谓“资源占用观察”重点应该放在网络请求超时、磁盘写满、日志膨胀这几项而不是显存。9.2 失败恢复抓取过程中最常见的失败是单页请求超时。建议给每一页请求设置重试机制def fetch_with_retry(session, url, params, retries3): for attempt in range(retries): try: resp session.get(url, paramsparams, timeout15) resp.raise_for_status() return resp.json() except Exception as e: print(f第 {attempt 1} 次尝试失败: {e}) time.sleep(2) return None数据源不稳定时与其频繁切换代理和服务不如先把超时时间和重试次数控制好。低频任务是容忍失败的这次没抓到下次再抓回来就行。10. 接口 API 化把榜单服务暴露给前端或 Bot如果不想每次都手动运行脚本而是希望有一个 Web 页面或聊天 Bot 能随时调取最近一期“准神话曲”榜单可以把数据查询层封装成一个轻量 HTTP API。这里给一个使用 Flask 实现的通用例子实际接口路径需要按自己的服务调整from flask import Flask, jsonify import sqlite3 app Flask(__name__) DB_PATH vocaloid_trend.db app.route(/api/quasi-myth) def get_quasi_myth_songs(): latest_date None conn sqlite3.connect(DB_PATH) cur conn.cursor() cur.execute(SELECT MAX(snapshot_date) FROM songs) row cur.fetchone() latest_date row[0] if row else None if not latest_date: return jsonify({error: no data yet, songs: [], snapshot_date: None}) songs cur.execute( SELECT song_title, uploader_name, play_count, mylist_count FROM songs WHERE snapshot_date ? ORDER BY play_count DESC , (latest_date,)).fetchall() result [ {title: s[0], uploader: s[1], play_count: s[2], mylist_count: s[3]} for s in songs ] return jsonify({snapshot_date: latest_date, songs: result}) if __name__ __main__: app.run(host127.0.0.1, port8000, debugFalse)启动后访问http://127.0.0.1:8000/api/quasi-myth只监听 127.0.0.1 的意思是仅允许本机访问。如果要把服务提供给局域网或公网请务必加鉴权或者用 nginx 反代加一层 Token 校验不要直接把一个可读数据库接口暴露在公网裸奔。11. 常见问题与排查清单这里整理高频问题直接从真实运行中总结可能性更大的故障点问题现象可能原因排查方式解决方案脚本启动后没有任何输出任务被系统定时调度执行但 Python 环境变量不对手动到项目目录执行脚本检查 venv 是否激活路径是否写绝对路径抓到的播放量全是 0 或空接口字段名被改解析代码取到错误字段打印原始 JSON 字段到目标站点检查字段映射更新解析逻辑多次运行后数据库大量重复没有检查当日快照是否已存在查看同 snapshot_date 的条数入库前做去重判断请求经常超时网络不稳定或请求频率过高观察请求日志的状态码和耗时增加请求间隔、降低并发、设置更灵活的重试播放量环比突然下跌歌曲被下架、改名或解析的是错误页面人工核对具体歌曲链接排除异常数据或标记歌曲状态生成的榜单只有两三首歌阈值设得太严格检查候选库中歌曲总量放宽播放量阈值或日均增长阈值端口 8000 无法访问端口占用或只监听 127.0.0.1查看端口占用和防火墙规则更换端口或调整监听范围多数问题不是来自算法复杂度而是来自“数据源页面改版”和“任务环境变化”。处理办法也很朴素全脚本加日志、关键数据入库、阈值全部参数化。12. 最佳实践与工程建议把这套“准神话曲月刊”做成可持续运行的数据项目有几条经验值得保留第一从第一天就开始建库不要等项目“完善”后再存数据。历史快照是这类榜单最值钱的部分错过一期就补不回来。第二抓数据时不要一上来就追求全量。先验证两三个歌曲条目能正确入库再跑全量。测试时使用有限的max_pages参数避免循环失控。第三报告模板和筛选逻辑分开。每期月刊可能有不同主题比如“本期关注新投稿”或“本期回顾老曲重生”只要数据和筛选项继续沉淀在数据库里模板随时可以换来换去。第四注意素材合规。在公开发布“N站术力口准神话曲月刊”这类榜单时最稳妥的信息呈现方式是“文字榜单 官方链接 数据说明”不搬运完整音频、视频和封面不批量展示弹幕评论。榜单本身是对公开数据的二次整理但仍要尊重目标站点和原作者权利。第五做好日志和输出目录规划。建议文件结构如下niconico-monthly-report/ ├── config.yaml ├── fetch_and_report.py ├── vocaloid_trend.db ├── output/ │ ├── monthly_report_2025_08.md │ └── monthly_report_2025_09.md ├── logs/ │ ├── 2025_08_01.log │ └── 2025_09_01.log └── venv/输出报告按月份保留数据库定期备份日志不需要长期堆积三个月前的日志可以压缩归档。到这步一套“准神话曲追踪基础设施”就算是真正跑起来了。如果你自己平时就在追术力口歌曲最值得先做的一件事不是把榜单排得好看而是先把播放量历史记录留下来。记录攒够了后面每一期月刊都只是做一次 SQL 查询加一次模板渲染而已。
