在 LLM 出现之前,我常在谷歌上寻找各种编程问题的答案,经常就是搜到 CSDN 的技术文章,可见它出色的 SEO 效果。于是后期,我把公众号文章同步到了 CSDN,想借此提升文章的阅读量,并且也算是回馈社区。
开始的一段时间同步非常顺利,结果今年三月,一篇标题末尾含感叹号、问号和表情的文章触发了同步机制的 Bug,被疯狂复制出 60 多篇,累计展示和阅读突破十万。两次联系客服都没彻底解决,我只好先手动去掉公众号文章标题中的特殊符号,然后清理重复文章。发现 CSDN 并不支持批量删除,于是尝试了一下使用 Tampermonkey 脚本,一键全选、反选并批量删除。
js 代码用 o4-mini-high 制作,经过4轮对话,从开始不会展示按钮,不能删除文章,到最后成功点亮。
主要思路是模拟鼠标悬停触发下拉菜单,点击删除按钮。
-
等待文章渲染:轮询
.article-list-item-mp
,一旦出现就开始注入。 -
动态插入工具栏:在列表顶部添加“全选”“反选”“批量删除”按钮。
-
给每行加复选框:方便批量勾选。
-
删除流程:
-
优先点击“彻底删除”按钮;
-
找不到时,触发
mouseenter
打开下拉菜单,选中“删除”项并点击。
-
兼容渲染延迟:多处
setTimeout
/await
保证脚本稳定执行。
// ==UserScript==
// @name CSDN 批量删除文章(悬停版)
// @namespace http://tampermonkey.net/
// @version 0.6
// @description 支持“彻底删除”和鼠标悬停→“删除”两种模式的批量删除脚本。
// @match https://mpbeta.csdn.net/mp_blog/manage/article*
// @match https://blog.csdn.net/*/mp_blog/manage/article*
// @run-at document-idle
// @grant none
// ==/UserScript==
;(function(){
'use strict';
// 等到至少有一行文章出现
function waitForRow(){
if(document.querySelector('.article-list-item-mp')){
setup();
} else {
setTimeout(waitForRow, 300);
}
}
function setup(){
// 插入工具栏(如果已经插过就跳过)
if(document.getElementById('csdn-select-all')) return;
const container = document.querySelector('.article_manage_list') || document.body;
const bar = document.createElement('div');
bar.style.cssText = 'padding:8px;background:#f5f5f5;border-bottom:1px solid #ddd;';
bar.innerHTML = `
<button id="csdn-select-all">全选</button>
<button id="csdn-invert">反选</button>
<button id="csdn-batch-del" style="color:red">批量删除</button>
<span id="csdn-info" style="margin-left:16px;color:#666;"></span>
`;
container.prepend(bar);
// 给每行加 checkbox
function injectChecks(){
document.querySelectorAll('.article-list-item-mp').forEach(item=>{
if(item.querySelector('.csdn-checkbox')) return;
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.className = 'csdn-checkbox';
cb.style.margin = '0 6px 0 0';
item.querySelector('.list-item-mp-left').before(cb);
});
}
injectChecks();
document.getElementById('csdn-select-all').onclick = ()=> {
document.querySelectorAll('.csdn-checkbox').forEach(cb=>cb.checked = true);
};
document.getElementById('csdn-invert').onclick = ()=> {
document.querySelectorAll('.csdn-checkbox').forEach(cb=>cb.checked = !cb.checked);
};
document.getElementById('csdn-batch-del').onclick = async ()=>{
const info = document.getElementById('csdn-info');
const boxes = Array.from(document.querySelectorAll('.csdn-checkbox')).filter(cb=>cb.checked);
if(boxes.length===0){ alert('请先勾选文章'); return; }
if(!confirm(`确认删除 ${boxes.length} 篇文章?`)) return;
for(let i=0; i<boxes.length; i++){
const item = boxes[i].closest('.article-list-item-mp');
const ops = item.querySelectorAll('.item-info-oper-text');
// 1) 优先“彻底删除”
let btn = Array.from(ops).find(a=>a.textContent.trim()==='彻底删除');
if(btn){
btn.click();
} else {
// 2) 悬停触发下拉
const toggler = item.querySelector('.el_mcm-dropdown .el-dropdown-link');
toggler.dispatchEvent(new MouseEvent('mouseenter',{bubbles:true}));
// 等菜单出来
awaitnewPromise(r=>setTimeout(r, 400));
// 找所有可见的菜单项
const visible = Array.from(document.querySelectorAll('.el_mcm-dropdown-menu__item'))
.filter(li=>li.offsetParent !== null && li.textContent.includes('删除'));
if(visible.length){
// 最后一个应该是“删除”
const del = visible[visible.length-1];
del.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true}));
} else {
console.warn('没找到 “删除” 菜单项', item);
}
}
info.textContent = `已处理 ${i+1}/${boxes.length}`;
// 等后端响应
awaitnewPromise(r=>setTimeout(r, 1000));
}
info.textContent = '操作完成,建议刷新页面。';
};
// 监听新增行,补打 checkbox
new MutationObserver(injectChecks).observe(container, {childList:true, subtree:true});
}
waitForRow();
})();
使用指南
-
安装 Tampermonkey:Chrome/Edge/Firefox 均支持。
-
新建脚本:复制粘贴上面“完整脚本”,保存并启用。
-
打开管理页:刷新 CSDN 文章管理页(
/**/mp_blog/manage/article
),稍等片刻即可见工具栏。 -
批量操作:勾选文章行左侧的复选框,点击「批量删除」即可自动清理。
在没有LLM的时代,油猴代码都是一行一行码的,想想都是泪。。