一次 AI 自动化实践:批量把小红书作品设为仅自己可见

有时候想把小红书历史作品统一改成“仅自己可见”,但平台通常没有提供批量设置入口。
这时候最省事的办法,不是手动一条条点,而是让 AI 帮你写一个页面自动化脚本,再由 AI 或你自己执行。

背景

抖音有作品批量管理,比如作品批量删除、批量设置为仅自己可见。

image-20260613084344330

但是,小红书的创作者中心是没有的。

image-20260613084522193

做法

可以让Codex全自动帮你把小红书的作品,自动帮你设置为某个状态。

Codex自动化做的事情:

  1. 打开小红书创作平台的作品管理页
  2. 自动遍历作品卡片
  3. 逐条打开权限设置
  4. 改成“仅自己可见”
  5. 自动滚动,直到全部处理完成

Codex产生的代码,发出来分享,当然,也可以按照后面的提示词,让AI重头干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// Run this in the DevTools console on:
// https://creator.xiaohongshu.com/new/note-manager
//
// It scrolls through all loaded notes, opens each note's privacy dialog,
// switches the note to "仅自己可见", and keeps going until it reaches the
// total count shown on the page.

(async () => {
const CONFIG = {
targetPrivacyText: "仅自己可见",
totalCountPattern: /全部\s*(\d+)/,
maxIdleRounds: 5,
modalTimeoutMs: 20000,
optionTimeoutMs: 10000,
scrollWaitMs: 1800,
postConfirmWaitMs: 500,
};

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const waitFor = async (fn, timeoutMs, errorMessage) => {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const value = fn();
if (value) return value;
await sleep(100);
}
throw new Error(errorMessage);
};

const getTotalCount = () => {
const match = document.body.innerText.match(CONFIG.totalCountPattern);
return match ? Number(match[1]) : null;
};

const getCards = () => [...document.querySelectorAll(".note-card")];
const getScrollContainer = () =>
document.querySelector(".content") || document.scrollingElement;

const getNoteId = (card) => {
const raw = card.getAttribute("data-impression");
if (!raw) return null;
try {
return JSON.parse(raw)?.noteTarget?.value?.noteId ?? null;
} catch {
return null;
}
};

const isAlreadyPrivate = (card) =>
card.querySelector(".permission_msg")?.textContent?.trim() === CONFIG.targetPrivacyText;

const getPrivacyTrigger = () =>
[...document.querySelectorAll(".d-select *")]
.find((el) => {
const text = el.textContent?.trim();
return (
[
"公开可见",
"仅自己可见",
"仅互关好友可见",
"部分人可见",
"部分人不可见",
].includes(text) && el.closest(".d-select")
);
})
?.closest(".d-select");

const getPrivacyOption = () =>
[...document.querySelectorAll(".d-options-wrapper .d-grid-item")]
.find((el) => el.textContent?.trim() === CONFIG.targetPrivacyText);

const getConfirmButton = () =>
[...document.querySelectorAll("button")]
.find((button) => button.textContent?.trim() === "确定");

const setNotePrivate = async (card) => {
const noteId = getNoteId(card);
if (!noteId) throw new Error("Missing note id");

if (isAlreadyPrivate(card)) {
return { noteId, changed: false };
}

const privacyBtn = card.querySelector(".note-card__actions .note-card__action-btn");
if (!privacyBtn) throw new Error(`Missing privacy button: ${noteId}`);

privacyBtn.click();

await waitFor(
() => getConfirmButton(),
CONFIG.modalTimeoutMs,
`Privacy dialog did not open: ${noteId}`
);

const trigger = await waitFor(
() => getPrivacyTrigger(),
CONFIG.optionTimeoutMs,
`Privacy selector missing: ${noteId}`
);
trigger.click();

const option = await waitFor(
() => getPrivacyOption(),
CONFIG.optionTimeoutMs,
`Privacy option missing: ${noteId}`
);
option.click();

const confirm = await waitFor(
() => getConfirmButton(),
CONFIG.optionTimeoutMs,
`Confirm button missing: ${noteId}`
);
confirm.click();

await waitFor(
() => !getConfirmButton(),
CONFIG.modalTimeoutMs,
`Privacy dialog did not close: ${noteId}`
);

await sleep(CONFIG.postConfirmWaitMs);
return { noteId, changed: true };
};

const total = getTotalCount();
if (!total) {
throw new Error("Could not read note total from the page");
}

const seen = new Set();
let changedCount = 0;
let skippedCount = 0;
let idleRounds = 0;

console.log(`[xhs-private] target total: ${total}`);

while (seen.size < total && idleRounds < CONFIG.maxIdleRounds) {
const cards = getCards();
let roundProgress = 0;

for (const card of cards) {
const noteId = getNoteId(card);
if (!noteId || seen.has(noteId)) continue;

const result = await setNotePrivate(card);
seen.add(noteId);
roundProgress += 1;

if (result.changed) {
changedCount += 1;
console.log(`[xhs-private] changed ${changedCount}: ${noteId}`);
} else {
skippedCount += 1;
console.log(`[xhs-private] already private ${skippedCount}: ${noteId}`);
}
}

const beforeCount = cards.length;
const scrollContainer = getScrollContainer();
if (scrollContainer) {
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}
await sleep(CONFIG.scrollWaitMs);
const afterCount = getCards().length;

if (roundProgress === 0 && afterCount === beforeCount) {
idleRounds += 1;
} else {
idleRounds = 0;
}

console.log(
`[xhs-private] seen=${seen.size}/${total}, loaded=${afterCount}, changed=${changedCount}, skipped=${skippedCount}, idleRounds=${idleRounds}`
);
}

const success = seen.size >= total;
const summary = {
success,
total,
seen: seen.size,
changed: changedCount,
skipped: skippedCount,
idleRounds,
};

console.log("[xhs-private] summary", summary);
return summary;
})().catch((error) => {
console.error("[xhs-private] failed", error);
throw error;
});

你可以直接把下面这段话发给 Codex、Hermes 或其他支持浏览器操作的 AI:

1
2
3
4
5
6
7
8
9
10
11
12
13
请打开这个页面,先不要直接写接口脚本。
先检查真实 DOM 结构,确认列表项、操作按钮、弹窗、目标选项和滚动容器。
然后写一个可以批量执行的页面脚本。

目标:
把当前列表里的全部作品批量改成“仅自己可见”。

要求:
1. 优先使用页面原生交互,不要先猜接口
2. 能自动滚动加载全部数据
3. 已经是目标状态的作品要跳过
4. 输出处理总数、成功数、跳过数
5. 最后把脚本保存成一个独立的 js 文件

如果你的目标不是“仅自己可见”,只要把提示词里的目标文案换掉即可。

总结

感叹现在的AI Agent真牛逼。真的要多试,“多偷懒”,多用。

一次 AI 自动化实践:批量把小红书作品设为仅自己可见

http://blog.wayss000.cn/2026/06/06/技术/用AI批量把小红书作品设为仅自己可见/

作者

大冬瓜头

发布于

2026-06-06

更新于

2026-06-13

许可协议