楼主
使用模型:deeepseek v4 flash 、 grok 4.5
需求是:使用autox.js的语法写一个小红书批量取关的脚本,先点击已关注的按钮:select....,它会弹出取消关注就点击一下,id("0_resource_name_obfuscated").className("android.widget.TextView").text("不再关注").findOne(),然后如果当前页面没有已关注的按钮,就向下滑动
使用的是 dsh + dsh tui

debug 至少 8 轮以上死活写不出来,一直在向下滑动

grok4.5 第一轮就可以取关一个用户,对话 2 轮修复了复杂的逻辑流程后即可运行


分享一下脚本吧,用的是autox.js
https://github.com/autox-community/AutoX
/**
* 小红书批量取关 - AutoX.js(layout 精简版,Rhino 兼容)
*
* 控件:
* TextView "已关注" → 父级 FrameLayout(clickable) 才是按钮
* 整行 LinearLayout 可点会进主页,不要点
*
* 确认:
* A. "不再关注"
* B. "取消关注" 可能弹两次
*
* 进入关注列表后运行;音量下键停止。
*/
"auto";
auto.waitFor();
console.show();
sleep(200);
var PKG = "com.xingin.xhs";
var MAX = 500;
var GAP = 700;
var CONFIRM_MS = 2500;
var EMPTY_SCROLL = 3;
var stop = false;
var ok = 0;
var fail = 0;
var empty = 0;
try {
events.observeKey();
events.onKeyDown("volume_down", function () {
stop = true;
toast("已停止");
});
} catch (e0) {
console.log("音量键监听不可用: " + e0);
}
function log(m) {
console.log("[" + new Date().toLocaleTimeString() + "] " + m);
}
/** 找「已关注」对应的可点击小 FrameLayout */
function findFollowBtns() {
var tvs = text("已关注").packageName(PKG).find();
var out = [];
var h = device.height;
var w = device.width;
var i, tv, b, btn, p2, bb;
for (i = 0; i < tvs.length; i++) {
tv = tvs[i];
b = tv.bounds();
if (b.bottom - b.top < 40) continue;
if (b.top < 200 || b.bottom > h - 80) continue;
if (b.left < w * 0.6) continue;
btn = tv.parent();
if (!btn) continue;
if (!btn.clickable()) {
p2 = btn.parent();
if (p2 && p2.clickable()) {
btn = p2;
}
}
bb = btn.bounds();
if (bb.right - bb.left > w * 0.4) continue;
if (!btn.clickable()) continue;
out.push(btn);
}
out.sort(function (a, b) {
return a.bounds().top - b.bounds().top;
});
return out;
}
function tap(node) {
if (!node) return false;
try {
if (node.click()) return true;
} catch (e1) {
// fallthrough
}
var b = node.bounds();
return click(b.centerX(), b.centerY());
}
function confirmExists() {
return text("不再关注").exists() || text("取消关注").exists();
}
function findConfirm() {
var node = text("不再关注").findOnce();
if (node) return node;
node = text("取消关注").findOnce();
if (node) return node;
return null;
}
/** 点确认,最多 3 次(覆盖双次取消关注) */
function tapConfirms() {
var clicked = 0;
var deadline = Date.now() + CONFIRM_MS;
var node;
var round;
while (Date.now() < deadline && !stop) {
node = findConfirm();
if (node) {
log("确认「" + (node.text() || "") + "」");
tap(node);
clicked++;
sleep(450);
break;
}
sleep(100);
}
if (!clicked) return false;
for (round = 0; round < 2; round++) {
node = text("不再关注").findOne(800);
if (!node) {
node = text("取消关注").findOne(800);
}
if (!node) break;
log("再确认「" + (node.text() || "") + "」");
tap(node);
clicked++;
sleep(450);
}
return clicked > 0;
}
function scrollList() {
var w = device.width;
var h = device.height;
swipe(w / 2, h * 0.72, w / 2, h * 0.38, 400);
sleep(900);
}
function ensureList() {
if (confirmExists()) return true;
if (text("已关注").packageName(PKG).exists()) return true;
if (textContains("我的关注").exists()) return true;
log("不在列表,back");
back();
sleep(600);
return text("已关注").exists() || textContains("我的关注").exists();
}
// ---------- main ----------
log("请停在关注列表页,开始取关");
toast("批量取关中,音量下停止");
sleep(800);
if (currentPackage() !== PKG) {
app.launchPackage(PKG);
sleep(2000);
log("请手动进入关注列表…");
var waitUntil = Date.now() + 30000;
while (Date.now() < waitUntil && !stop && !text("已关注").exists()) {
sleep(400);
}
}
while (!stop && ok < MAX) {
if (confirmExists()) {
if (tapConfirms()) {
ok++;
log("成功 #" + ok + "(清弹层)");
sleep(GAP);
continue;
}
}
ensureList();
var btns = findFollowBtns();
if (!btns.length) {
empty++;
log("无按钮,下滑 " + empty + "/" + EMPTY_SCROLL);
if (empty >= EMPTY_SCROLL) break;
scrollList();
continue;
}
empty = 0;
var btn = btns[0];
var bb = btn.bounds();
log("点已关注 #" + (ok + 1) + " " + bb.left + "," + bb.top + "-" + bb.right + "," + bb.bottom);
if (!tap(btn)) {
fail++;
sleep(400);
continue;
}
sleep(300);
if (tapConfirms()) {
ok++;
log("取关成功 #" + ok);
sleep(GAP);
} else {
fail++;
log("无确认弹层,可能进了主页");
ensureList();
sleep(400);
if (fail % 3 === 0) {
scrollList();
}
}
}
try {
events.removeAllKeyDownListeners();
} catch (e2) {
// ignore
}
log("结束:成功 " + ok + " 失败 " + fail);
toast("完成 成功" + ok + " 失败" + fail);这下真成小难梁了 😅
最后由 RrOrange 编辑于 2026-08-17 19:48