利用JavaScript与DOM操作实现Steam游戏库批量清理
1. 项目缘起:当“喜加一”变成数字垃圾
作为一名Steam老玩家,我敢说在座的各位,谁的库里没躺着几十上百个“喜加一”游戏?从Epic每周送,到Steam各种限时免费领取,再到各种慈善包里的零元购,我们总是不由自主地点下那个“添加到账户”的按钮。那一刻的快乐是真实的——不花钱就能拥有一个游戏,哪怕它可能是个像素风、评价褒贬不一、甚至你听都没听过的独立小品。
但时间一长,问题就来了。你的Steam游戏库会变得臃肿不堪。当你真正想找一款游戏来玩时,视线会被大量你永远不会打开的免费游戏淹没。更关键的是,这些游戏会出现在你的“所有游戏”列表、好友的“最近游玩”动态里,甚至影响Steam的推荐算法。它们就像数字空间的“杂物”,占据了宝贵的视觉和心智带宽。
手动清理?那简直是噩梦。Steam客户端和网页版都没有提供批量移除游戏的功能。你需要进入每一个游戏的商店页面,点击“管理”,再选择“从账户中移除”。面对成百上千的免费游戏,这个过程的繁琐程度足以让任何人放弃。于是,这些“数字垃圾”就永远留在了你的库里。
有没有一种高效、一键式的方法来解决这个问题?答案是肯定的。通过浏览器开发者工具(F12控制台)运行一段简单的JavaScript代码,我们就能实现批量、自动化地清理这些不再需要的免费游戏。这不仅是技术上的一个小技巧,更是对个人数字资产的一次有效“断舍离”。
2. 核心原理:如何与Steam网页端“对话”
在动手写代码之前,我们必须理解我们操作的对象——Steam的网页客户端——是如何工作的。我们并非在破解或入侵Steam,而是利用其公开的网页接口,模拟用户操作,实现自动化。
2.1 网页的“骨架”:DOM与事件
现代网页(包括Steam)的界面是由一种叫做DOM(文档对象模型)的树状结构构成的。每一个按钮、每一段文字、每一个链接,在DOM中都是一个“节点”。当我们点击网页上的“从账户中移除”按钮时,背后发生的是:浏览器检测到鼠标点击事件,找到对应的DOM节点,并执行该节点上绑定的JavaScript函数,这个函数会向Steam的服务器发送一个请求。
我们的JS代码要做的,就是直接找到这些代表“移除”按钮的DOM节点,然后以编程方式“触发”它们的点击事件,绕过手动点击的步骤。这就像是你雇了一个机器人,它能够精准地识别页面上的特定按钮并替你按下去。
2.2 Steam账户页面的结构分析
要实现精准操作,我们需要知道按钮在哪里。以Steam网页版“账户明细”页的游戏列表为例:
- 访问路径:登录Steam后,点击右上角用户名 -> “账户明细” -> 在“商店和消费历史记录”部分找到“查看许可和产品序列号激活”。这个页面列出了你账户中的所有游戏。
- 页面结构:在这个列表中,每个游戏条目通常包含游戏名称、添加日期等信息。最关键的是,对于大多数免费领取的游戏(非免费游玩,而是永久拥有),其条目右侧会有一个“移除”链接或按钮。
- 目标定位:通过浏览器的“检查元素”功能(右键点击“移除”按钮,选择“检查”),我们可以发现这个“移除”按钮的HTML代码特征。例如,它可能是一个带有特定
class(如remove_link)的<a>标签,或者其链接地址(href)中包含特定的关键词(如RemoveLicense)。
我们的脚本核心任务就是:在页面中找出所有符合“免费游戏”且带有“移除”按钮特征的DOM元素,然后循环触发它们的点击事件。
注意:Steam会不定期更新其网页前端代码,包括类名和结构。因此,一段代码可能不会永远有效。本文提供的是一种方法和当前(基于近期分析)有效的选择器,你需要具备根据页面实际情况进行调整的能力。
2.3 安全边界与风险提示
在开始之前,必须明确安全边界:
- 仅操作网页元素:此方法完全在浏览器中运行,只与你当前打开的Steam页面交互,不涉及账号密码、不调用未公开的API、不进行任何形式的“破解”。
- 模拟用户操作:代码所做的每一个“点击”,都和你手动点击在Steam服务器看来没有本质区别。
- 无法撤销:通过此方法移除的游戏,将永久从你的账户库中消失。虽然你可以重新购买或再次领取(如果依然免费),但原来的激活记录会被清除。请务必谨慎操作,建议先对少数几个不重要的游戏进行测试。
- 遵守服务条款:大规模自动化操作可能违反Steam的服务条款。虽然个人小批量、低频率的清理通常不会被追究,但理论上存在风险。请负责任地使用。
3. 实战代码:从单次点击到批量清理
理解了原理,我们就可以着手编写代码了。我们将从最简单的版本开始,逐步构建一个健壮、可用的批量清理脚本。
3.1 基础版本:找到并点击所有“移除”按钮
这是最直接的思路。我们假设Steam页面上所有可移除的许可都对应一个具有特定特征的链接。
// 基础版本:简单粗暴地点击所有‘移除’链接 (function() { // 尝试寻找带有‘remove_link’类的链接,这是Steam曾用过的一种常见类名 let removeLinks = document.querySelectorAll('a.remove_link'); if (removeLinks.length === 0) { console.log('未找到标准的“remove_link”链接。尝试其他选择器...'); // 尝试寻找链接文本包含“移除”的按钮 removeLinks = Array.from(document.querySelectorAll('a')).filter(link => link.textContent.includes('移除')); } console.log(`找到了 ${removeLinks.length} 个可能的移除链接。`); if (removeLinks.length === 0) { alert('未找到任何可移除的游戏链接。请确保你正在正确的页面(账户许可页面),且页面已完全加载。'); return; } // 逐一触发点击事件 removeLinks.forEach((link, index) => { console.log(`正在点击第 ${index + 1} 个链接: ${link.textContent}`); // 使用 setTimeout 是为了让操作有一点间隔,避免请求过于密集 setTimeout(() => { link.click(); }, index * 500); // 间隔500毫秒 }); console.log('已开始自动点击流程。请留意页面弹出的确认对话框!'); })();代码解读与实操要点:
document.querySelectorAll('a.remove_link'):这是核心选择器。它会在整个页面中寻找所有<a>标签且其class属性包含remove_link的元素。如果Steam更新了前端,这个类名可能失效。- 备选方案:如果上述选择器找不到,代码会尝试寻找所有链接文本中包含“移除”二字的
<a>标签。这提高了兼容性。 setTimeout:这是关键技巧。如果不加延迟,所有点击请求会在瞬间同时发出,这可能导致Steam服务器端限制或页面行为异常。间隔500毫秒(0.5秒)是一个比较保守且安全的设置,模拟了人类操作的速度。- 如何运行:
- 打开浏览器(Chrome/Firefox/Edge均可),登录你的Steam账户。
- 进入“账户明细”->“查看许可和产品序列号激活”页面。
- 按下
F12键打开开发者工具。 - 切换到
Console(控制台)标签页。 - 将上面的代码完整地复制粘贴到控制台底部的输入行(
>符号后面)。 - 按下
Enter键执行。
执行后,控制台会输出找到的链接数量,并开始自动点击。此时,你的浏览器会弹出第一个确认移除的对话框!脚本无法自动点击这个对话框的“确定”按钮(出于安全考虑,浏览器禁止非用户交互的弹窗自动确认)。你需要手动点击“确定”。之后,脚本会继续点击下一个链接,并再次弹出对话框,如此循环。
基础版本的局限性:需要人工干预每个确认对话框,对于清理大量游戏来说,依然繁琐。
3.2 进阶版本:自动处理确认对话框
要真正实现“一键清理”,我们必须解决确认对话框的问题。这需要更深入地与页面交互。
// 进阶版本:自动处理确认对话框(需要配合浏览器设置或用户首次授权) (function() { // 首先,我们需要“欺骗”一下浏览器,让它允许我们自动处理后续的confirm对话框。 // 注意:这通常只需要在页面生命周期内执行一次。 window.confirm = function(message) { console.log(`拦截到确认对话框: "${message}"。自动选择“确定”。`); return true; // 返回 true 相当于点击了“确定” }; // 同样,先寻找移除链接 let removeLinks = document.querySelectorAll('a.remove_link'); if (removeLinks.length === 0) { removeLinks = Array.from(document.querySelectorAll('a')).filter(link => link.textContent.trim() === '移除'); } if (removeLinks.length === 0) { alert('未找到移除链接。'); return; } console.log(`找到了 ${removeLinks.length} 个移除链接,开始自动处理...`); // 由于我们重写了confirm,现在可以连续点击了 let index = 0; function clickNext() { if (index >= removeLinks.length) { console.log('所有链接已处理完毕。'); // 可选:恢复默认的confirm行为 // delete window.confirm; return; } let link = removeLinks[index]; console.log(`处理第 ${index + 1}/${removeLinks.length} 个: ${link.closest('tr')?.querySelector('.gameListRowItemName')?.textContent || '未知游戏'}`); // 触发点击 link.click(); index++; // 设置一个延迟,让服务器和页面有时间响应 setTimeout(clickNext, 800); // 间隔可以稍长,确保稳定性 } // 开始执行 clickNext(); })();代码解读与核心技巧:
window.confirm = function(...) { return true; }:这是本脚本的“灵魂”。它重写了浏览器内置的confirm函数。当Steam的代码调用confirm(“您确定要从账户中移除这款游戏吗?”)时,我们的函数会拦截这个调用,直接返回true,从而自动确认对话框。这是一个关键技巧,但请注意其影响范围仅限于当前标签页的当前页面生命周期。- 更精准的选择器:进阶版中,我们尝试寻找链接文本精确等于“移除”的按钮(
textContent.trim() === '移除'),这比“包含”更精确,能避免误点其他链接。 - 递归函数
clickNext:使用递归而非简单的forEach循环,是为了更好地控制流程。每次点击后,等待800毫秒,再处理下一个,形成了一个稳定的操作链。 - 游戏名称提取:
link.closest('tr')?.querySelector('.gameListRowItemName')?.textContent这行代码尝试从当前链接所在的表格行(tr)中,找到游戏名称的单元格。这能让我们在控制台看到正在处理哪个游戏,便于监控。选择器.gameListRowItemName是Steam页面中游戏名称常见的类名,可能需要根据实际情况调整。
实操中的坑与解决方案:
- 首次运行可能失败:有些浏览器(特别是新版Chrome)出于安全策略,对于
window.confirm的重写可能在第一次调用时无效,或者会触发警告。如果遇到这种情况,一个变通的方法是:先手动点击第一个“移除”链接,并在弹出的确认对话框中勾选“不要再问我”或类似的选项(如果Steam提供的话)。这样,后续的对话框可能就不再弹出,或者我们的重写就能生效了。 - 选择器失效:如果控制台输出“未找到移除链接”,说明页面结构已变。你需要使用开发者工具的“元素检查”(Elements)功能,右键点击一个“移除”按钮,查看它最新的HTML结构和类名,然后更新代码中的
querySelectorAll参数。 - 网络延迟与错误:如果服务器响应慢,或者某个请求失败,可能会导致脚本中断。更健壮的版本应该加入错误处理(
try...catch)和重试机制,但为了代码简洁,本文的示例未包含。对于重要操作,建议分批进行(例如,每次处理20个游戏)。
3.3 安全增强与筛选版本:只清理免费游戏
前面的版本会尝试移除页面上所有带“移除”按钮的许可,这可能包括你付费购买的游戏!这非常危险。我们必须增加筛选逻辑,只针对“免费喜加一”游戏。
如何区分?通常,在许可列表页面,免费游戏所在的行会有一些特征,比如没有价格信息,或者“购买类型”一栏显示为“免费”或“礼包”。我们需要通过DOM分析来找到这个特征。
// 安全增强版本:尝试只筛选出免费游戏的移除链接 (function() { // 重写confirm函数 window.confirm = function(msg) { console.log(`自动确认: ${msg}`); return true; }; // 首先,找到所有包含游戏信息的行。假设每行是一个<tr>元素,且有一个特定的类,例如‘gameListRow’(需要根据实际情况调整) let gameRows = document.querySelectorAll('table.gameListTable tr:not(:first-child)'); // 排除表头行 let freeGameRemoveLinks = []; gameRows.forEach(row => { // 尝试找到“移除”链接 let removeLink = row.querySelector('a.remove_link') || Array.from(row.querySelectorAll('a')).find(a => a.textContent.trim() === '移除'); if (!removeLink) return; // 如果没有移除链接,跳过这行 // **关键筛选逻辑**:判断这行是否为免费游戏 // 方法1:查找包含“免费”文本的单元格 let rowText = row.textContent.toLowerCase(); if (rowText.includes('免费') || rowText.includes('free')) { freeGameRemoveLinks.push(removeLink); console.log(`识别为免费游戏: ${row.querySelector('.gameListRowItemName')?.textContent}`); } // 方法2:查找购买类型列(需要你知道列的位置,例如第4个<td>) // let purchaseTypeCell = row.children[3]; // 假设第4列是购买类型 // if (purchaseTypeCell && purchaseTypeCell.textContent.toLowerCase().includes('free')) { // freeGameRemoveLinks.push(removeLink); // } }); if (freeGameRemoveLinks.length === 0) { alert('未筛选到符合条件的免费游戏移除链接。请检查页面结构或筛选逻辑。'); return; } console.log(`筛选出 ${freeGameRemoveLinks.length} 个免费游戏的移除链接。`); // 询问用户是否继续 if (!confirm(`即将自动移除 ${freeGameRemoveLinks.length} 款筛选出的免费游戏。此操作不可逆!\n\n(此确认由脚本生成,点击确定后开始自动流程。)`)) { console.log('用户取消了操作。'); return; } // 开始自动处理 let index = 0; function processNext() { if (index >= freeGameRemoveLinks.length) { console.log('所有免费游戏已处理完毕。'); return; } let link = freeGameRemoveLinks[index]; console.log(`正在移除免费游戏 [${index + 1}/${freeGameRemoveLinks.length}]...`); link.click(); index++; setTimeout(processNext, 1000); // 间隔1秒更稳妥 } processNext(); })();这个版本的重大改进:
- 先筛选,后操作:不再是找到所有链接就点,而是先遍历每一行游戏数据,通过文本内容(如包含“免费”或“free”)来判断是否为免费游戏。这是防止误删付费游戏的关键!
- 二次确认:脚本开始前,会用
confirm(这是我们重写前的那个,会被我们的函数拦截)向用户展示一个汇总提示,列出即将移除的游戏数量,让用户做最终决定。 - 更保守的间隔:将点击间隔增加到1000毫秒(1秒),进一步降低对服务器造成压力的风险,也更模拟真人操作。
重要警告:即使有这个筛选逻辑,也并非100%安全。Steam页面结构复杂多变,免费游戏的标识方式也可能不同。最安全的方式是,在运行全自动脚本前,先手动检查前几个被筛选出来的游戏是否确实是你不想要的免费游戏。你可以修改脚本,在console.log中打印出游戏名,先运行一次看看输出列表是否正确。
4. 高阶应用与边界情况处理
掌握了基础脚本后,我们可以探讨一些更复杂但实用的场景和问题。
4.1 处理分页与动态加载
Steam的许可列表如果游戏数量很多,可能会分页显示。我们的脚本默认只对当前页面有效。要清理所有页面的游戏,需要处理分页。
思路:
- 脚本首先清理当前页。
- 清理完成后,寻找“下一页”按钮(可能是一个链接或带有特定类的元素)。
- 自动点击“下一页”按钮。
- 等待新页面加载完成(使用
setTimeout或监听DOM变化)。 - 在新页面上重新执行清理逻辑。
这涉及到更复杂的页面状态管理和等待机制,代码量会大增。一个更简单的替代方案是:Steam许可页面有时可以通过修改URL参数来显示所有项目(如&p=all),但这并非官方稳定功能。最务实的方法是:手动点击“下一页”,然后在每一页上分别运行一次我们的批量清理脚本。
4.2 制作浏览器书签工具
每次都要打开控制台粘贴代码很麻烦。我们可以将代码制作成一个“书签工具”。
新建一个浏览器书签。
在网址(URL)一栏,填入以下内容:
javascript:(function(){/* 将上面‘安全增强版本’的整个代码复制到这里,并压缩成一行 */})()- 注意:需要将代码中的所有换行符和多余空格去掉,压缩成一行。可以使用在线的JS压缩工具。
- 例如,一个极简版本的代码可能是:
javascript:(function(){window.confirm=function(){return true;}; let links=document.querySelectorAll('a.remove_link'); let i=0; function c(){if(i<links.length){links[i].click();i++;setTimeout(c,800);}} c();})()(警告:此极简版本没有筛选逻辑,会点击所有移除链接,非常危险!仅作示例。)
保存书签,命名为“Steam清理工具”。
以后需要清理时,只需打开Steam许可页面,点击一下这个书签即可运行脚本。
4.3 应对Steam前端变更
这是最大的不确定性。Steam的前端工程师某天更新了类名或页面结构,我们的脚本就会失效。
应对策略:
- 保持代码简洁,逻辑清晰:我们的脚本核心是“找到按钮 -> 点击按钮”。只要这个逻辑在,只需要更新“找到按钮”的方法。
- 掌握调试技能:学会使用开发者工具的“元素检查”(Elements)和“控制台”(Console)是必备技能。当脚本不工作时,打开检查工具,看看“移除”按钮的HTML代码变成了什么样子,然后相应地修改
querySelectorAll中的选择器。 - 关注社区:Reddit的
/r/Steam或一些游戏技术论坛,有时会有玩家分享最新的脚本或选择器。
4.4 道德与替代方案考量
最后,我们必须从更广阔的视角看待这个问题。
- 对Steam的影响:频繁的自动化请求会给Steam服务器带来不必要的负载。虽然个人清理的影响微乎其微,但这是需要考虑的。尽量选择在非高峰时段操作,并设置较长的操作间隔(如2秒以上)。
- 真正的需求:我们真的需要彻底移除这些游戏吗?Steam有一个“隐藏”游戏的功能(在游戏库中右键游戏 -> “管理” -> “隐藏此游戏”)。隐藏后,游戏不会出现在你的主库和公开资料中,但你仍然拥有它。对于绝大多数“可能未来某天会想玩”的免费游戏,隐藏是比移除更优的选择。移除是不可逆的,而隐藏是可恢复的。
- 第三方工具风险:网络上可能存在一些声称能一键清理的第三方软件或浏览器插件。对这些工具要保持高度警惕,它们可能会窃取你的Steam账户凭证(Cookie、会话令牌)。通过浏览器控制台运行自写的JS代码,是风险最低的方式,因为代码完全在你的控制下,运行在你的浏览器沙盒环境中,不涉及向第三方发送数据。
因此,在按下“确定”前,不妨再想想:我清理的是“数字垃圾”,还是“未来的可能性”?对于确定永远不想碰的游戏,用本文的方法高效清理;对于只是暂时不想看到的,使用Steam自带的“隐藏”功能,或许是更平衡的数字生活管理策略。技术给了我们选择的权力,而如何行使这个权力,取决于我们对自己数字生活的理解和规划。
