1. 认识Tampermonkey与网易云音乐下载需求
第一次接触Tampermonkey(俗称油猴插件)是在五年前,当时为了批量下载某论坛的附件资源。这个浏览器扩展的神奇之处在于,它能让我们用JavaScript代码"改造"任何网页。今天我们就用它来解决一个实际需求:在网易云音乐网页版下载免费歌曲。
你可能遇到过这种情况:听到一首喜欢的歌想下载,却发现需要开通会员。其实平台上有大量免费歌曲是可以合法下载的,只是官方没有提供直接下载按钮。通过分析网页结构,我发现这些免费歌曲的音频文件其实就藏在网页代码里,就像藏在迷宫中的宝藏,只需要正确的工具和路径就能获取。
与QQ音乐不同,网易云音乐的DOM结构更加动态化。歌曲信息不会直接显示在audio标签里,而是通过异步加载。这就好比你去餐厅点餐,QQ音乐是直接把菜端上桌,而网易云音乐需要服务员(JavaScript)去后厨现做。我们需要找到正确的"点餐方式"才能获取到音频文件。
2. 环境准备与基础分析
2.1 安装Tampermonkey插件
在Chrome商店搜索Tampermonkey安装即可,这里有个小技巧:建议安装Beta版本,它对ES6语法支持更好。安装后浏览器右上角会出现猴子图标,点击"添加新脚本"就会打开编辑器界面。我习惯先写好基础框架:
// ==UserScript== // @name 网易云音乐免费下载 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 下载网易云音乐免费歌曲 // @author 你的名字 // @match https://music.163.com/* // @grant GM_download // @run-at document-end // ==/UserScript==特别注意@match参数要包含网易云音乐的所有子页面,@grant声明我们要使用的API权限。
2.2 分析网易云音乐页面结构
打开网易云音乐网页版播放任意免费歌曲,按F12打开开发者工具。关键点来了:
- 切换到Elements面板,搜索
audio标签。你会发现它没有src属性——这是因为音频地址是动态加载的 - 在Network面板筛选
media类型请求,播放歌曲时会看到.mp3的请求,这就是我们要的音频地址 - 观察发现歌曲名藏在
<title>标签和<meta>标签里,但更可靠的是从.tit类名的元素获取
经过多次测试,我发现音频URL的规律:总是包含.mp3后缀,并且域名是music.126.net。这个发现很关键,它让我们可以绕过复杂的DOM操作直接定位资源。
3. 核心代码实现
3.1 动态监听音频地址
由于网易云音乐采用动态加载,我们需要用MutationObserver监听DOM变化。这就像在餐厅安装监控,每当服务员端出新菜(音频元素)我们就能立即发现:
function setupObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { const audioNodes = document.querySelectorAll('audio'); audioNodes.forEach((audio) => { if (audio.src.includes('.mp3') && !audio.dataset.processed) { audio.dataset.processed = 'true'; setupDownloadButton(audio.src); } }); } }); }); observer.observe(document.body, { childList: true, subtree: true }); }这段代码做了三件事:
- 创建观察者监视body内所有DOM变化
- 当发现新增节点时检查是否有audio标签
- 确认是MP3资源且未被处理过时触发按钮创建
3.2 创建下载按钮
接下来我们改造播放控件区域,添加下载按钮。网易云的播放器控件都在.m-playbar元素内,找到合适的位置插入按钮很重要:
function setupDownloadButton(audioUrl) { const songName = document.querySelector('.f-thide').innerText; const controlBar = document.querySelector('.btns'); if (!controlBar || document.getElementById('downloadBtn')) return; const downloadBtn = document.createElement('a'); downloadBtn.id = 'downloadBtn'; downloadBtn.className = 'btn'; downloadBtn.innerHTML = '<i class="icn-dl"></i>下载'; downloadBtn.style.marginLeft = '15px'; downloadBtn.style.cursor = 'pointer'; downloadBtn.addEventListener('click', () => { downloadFile(audioUrl, `${songName}.mp3`); }); controlBar.appendChild(downloadBtn); }这里有几个实用技巧:
- 使用平台原有的CSS类名(如
btn)保持样式统一 - 添加图标使按钮更专业
- 限制重复创建按钮的判断逻辑
3.3 实现下载功能
下载功能使用Tampermonkey提供的GM_download API,但需要处理几个常见问题:
function downloadFile(url, filename) { // 处理网易云音乐的反盗链 const finalUrl = url.replace(/^http:/, 'https:') + '?x-oss-process=audio'; GM_download({ url: finalUrl, name: filename, saveAs: true, onerror: (error) => { console.error('下载失败:', error); alert(`下载失败: ${error.error}`); }, onload: () => { console.log('下载完成'); } }); }特别注意:
- 强制使用HTTPS避免混合内容问题
- 添加OSS处理参数绕过部分限制
- 完善的错误处理机制
4. 高级优化与调试技巧
4.1 处理动态加载延迟
网易云音乐有时会延迟加载音频资源,我们需要多重保障:
// 初始检查 setTimeout(() => { const audio = document.querySelector('audio'); if (audio && audio.src) setupDownloadButton(audio.src); }, 2000); // 启动观察者 setupObserver(); // 监听路由变化 window.addEventListener('hashchange', () => { setTimeout(checkForAudio, 1000); });这种三层检测机制确保:
- 页面加载后立即检查
- 动态变化实时监控
- 单页面应用路由切换时重新检查
4.2 样式隔离与冲突解决
为防止CSS冲突,建议给自定义元素添加独特前缀:
downloadBtn.style.cssText = ` margin-left: 15px; cursor: pointer; background-color: #f00 !important; border-radius: 4px !important; padding: 0 12px !important; `;使用!important覆盖平台默认样式,但要注意适度使用。
4.3 跨域问题解决方案
如果遇到跨域问题,可以尝试以下方法:
const finalUrl = new URL(url); finalUrl.searchParams.set('_t', Date.now());通过添加时间戳参数,有时能绕过简单的缓存限制。
5. 完整代码与使用说明
将所有代码组合起来,最终的完整脚本如下:
// ==UserScript== // @name 网易云音乐免费下载增强版 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 支持网易云音乐网页版免费歌曲下载 // @author 技术爱好者 // @match https://music.163.com/* // @grant GM_download // @grant GM_notification // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 下载功能实现 function downloadFile(url, filename) { const finalUrl = new URL(url); finalUrl.protocol = 'https:'; finalUrl.searchParams.set('_t', Date.now()); GM_download({ url: finalUrl.href, name: cleanFilename(filename), saveAs: true, onerror: (error) => { GM_notification({ title: '下载失败', text: error.error, timeout: 3000 }); } }); } // 清理非法文件名字符 function cleanFilename(name) { return name.replace(/[\\/:*?"<>|]/g, '') + '.mp3'; } // 创建下载按钮 function createDownloadButton(url) { const songName = document.querySelector('.f-thide')?.innerText || '未知歌曲'; const controlBar = document.querySelector('.btns'); if (!controlBar || document.getElementById('myDownloadBtn')) return; const btn = document.createElement('a'); btn.id = 'myDownloadBtn'; btn.className = 'btn'; btn.innerHTML = '<i class="icn-dl"></i>下载'; btn.style.cssText = ` margin-left: 15px; cursor: pointer; background-color: #f00 !important; border-radius: 4px !important; padding: 0 12px !important; `; btn.addEventListener('click', () => downloadFile(url, songName)); controlBar.appendChild(btn); } // 检查音频元素 function checkForAudio() { const audio = document.querySelector('audio'); if (audio?.src?.includes('.mp3')) { createDownloadButton(audio.src); } } // 启动观察者 function setupObserver() { const observer = new MutationObserver(() => { checkForAudio(); }); observer.observe(document.body, { childList: true, subtree: true }); } // 初始化 setTimeout(checkForAudio, 1000); setupObserver(); window.addEventListener('hashchange', () => { setTimeout(checkForAudio, 800); }); })();使用说明:
- 安装脚本后刷新网易云音乐页面
- 播放任意免费歌曲
- 等待1-2秒,播放控件区域会出现红色下载按钮
- 点击按钮即可保存MP3文件
常见问题处理:
- 如果按钮不出现,尝试刷新页面
- 下载失败时检查控制台错误信息
- 某些特殊歌曲可能受版权保护无法下载
6. 法律与道德注意事项
在开发和使用这类脚本时,必须明确以下几点:
- 仅适用于平台明确标注为"免费"的歌曲
- 不得破解或下载VIP专享内容
- 下载的资源仅限个人使用,禁止传播
- 尊重音乐人的劳动成果,支持正版
技术本身是中性的,关键在于如何使用。我个人的原则是:用技术解决问题,但绝不侵犯他人合法权益。在实际使用中,这个脚本帮我保存了很多网络条件不好时想听的歌曲,也希望它能帮到你。