// ==UserScript==
// @name 小红书广告数据查询与展示
// @namespace http://tampermonkey.net/
// @version 0.8
// @description 在当前页面插入悬浮元素,点击展开窗口并发送请求,将结果展示在表格中并提供Excel下载按钮
// @author You
// @match https://ad.xiaohongshu.com/aurora*
// @grant GM_xmlhttpRequest
// @connect ad.xiaohongshu.com
// @license MIT
// ==/UserScript==
(function () {
"use strict";
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
.loading {
width: 30px;
height: 30px;
border: 2px solid #000;
border-top-color: transparent;
border-radius: 100%;
animation: circle infinite 0.75s linear;
}
// 转转转动画
@keyframes circle {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
`);
function addStyleSheet(sheet) {
const allSheets = [...document.adoptedStyleSheets, sheet];
document.adoptedStyleSheets=allSheets
}
addStyleSheet(sheet)
// 存储请求返回的数据
let responseData = [];
function addBallAttributes(ball,text){
ball.style.cssText = `
position: fixed;
right: 10px;
bottom: 10px;
width: 60px;
height: 60px;
background-color: #0066CC;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: white;
z-index: 9999;
`;
ball.textContent = text;
}
// 创建隐藏的小球元素
const ball = document.createElement("div");
addBallAttributes(ball,"检查链接")
document.body.appendChild(ball);
// 创建悬浮窗口元素
const floatingWindow = document.createElement("div");
floatingWindow.style.cssText = `
position: fixed;
right: 20px;
top: 20px;
width: 50vw;
height: 80vh;
background-color: white;
border: 1px solid #0066CC;
border-radius: 8px;
display: none;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
z-index: 9999;
`; // 改成 cssom
document.body.appendChild(floatingWindow);
// 创建关闭按钮
const closeButton = document.createElement("div");
closeButton.style.cssText = `
position: absolute;
right: 10px;
top: 10px;
cursor: pointer;
font-size: 20px;
color: #0066CC;
`; // 改成 cssom
closeButton.innerHTML = "×";
floatingWindow.appendChild(closeButton);
// 创建按钮容器
const buttonContainer = document.createElement("div");
buttonContainer.style.cssText = `
margin-bottom: 20px;
`; // 改成 cssom
floatingWindow.appendChild(buttonContainer);
function addButtonAttributes(checkButton,text){
checkButton.textContent = text;//"检查";
checkButton.style.cssText = `
margin-right: 10px;
padding: 8px 16px;
background-color: #0066CC;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
`; // 改成 cssom
}
// 创建检查按钮
const checkButton = document.createElement("button");
addButtonAttributes(checkButton,"检查")
buttonContainer.appendChild(checkButton);
// 创建下载按钮
const downloadButton = document.createElement("button");
addButtonAttributes(downloadButton,"下载")
buttonContainer.appendChild(downloadButton);
// 创建链接统计文本容器
const linkStatsContainer = document.createElement("div");
linkStatsContainer.style.cssText = `
margin-bottom: 20px;
font-size: 14px;
color: #333333;
`; // 改成 cssom
floatingWindow.appendChild(linkStatsContainer);
// 创建表格容器
const tableContainer = document.createElement("div");
tableContainer.style.cssText = `
height: calc(100% - 100px);
overflow-y: auto;
overflow-x: auto;
`; // 改成 cssom
floatingWindow.appendChild(tableContainer);
// 创建进度条
const progressBar = document.createElement("progress");
progressBar.style.cssText = `
width: 100%;
margin-top: 10px;
display: none;
`; // 改成 cssom
progressBar.value = 0;
progressBar.max = 100;
floatingWindow.appendChild(progressBar);
// 显示小球
ball.style.display = "flex";
// 点击小球展开悬浮窗口
ball.addEventListener("click", function () {
floatingWindow.style.display = "block";
ball.style.display = "none";
});
// 点击关闭按钮
closeButton.addEventListener("click", function () {
floatingWindow.style.display = "none";
ball.style.display = "flex";
tableContainer.innerHTML = "";
linkStatsContainer.innerHTML = "";
responseData = []; // 清空存储的数据
});
// 点击检查按钮发送请求
checkButton.addEventListener("click", function () {
checkButton.disabled = true;
checkButton.style.backgroundColor ="rgb(105, 102, 102)"
const loading = document.createElement("div");
loading.className = "loading";
loading.id = "loading-indicator"; // 添加一个唯一ID
tableContainer.appendChild(loading);
sendRequest().then(() => {
checkButton.disabled = false;
checkButton.style.backgroundColor ="rgb(0, 102, 204)"
// 使用更安全的方式移除loading
const loadingElement = document.getElementById("loading-indicator");
if (loadingElement) {
loadingElement.remove();
}
});
});
// 点击下载按钮
downloadButton.addEventListener("click", function () {
if (responseData.length > 0) {
downloadExcel(responseData);
} else {
alert("没有可下载的数据!");
}
});
// 发送请求
async function sendRequest() {
let maxPageNum = 4;
let pageNum = 1;
let totalPage = 1;
let allData = [];
do {
const response = await fetch(
`https://ad.xiaohongshu.com/api/leona/rtb/creativity/list?pageNum=${pageNum}&pageSize=50`,
{
method: "POST",
headers: {
accept: "application/json, text/plain, */*",
"accept-language":
"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8",
priority: "u=1, i",
"sec-ch-ua":
'"Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
},
body: JSON.stringify({
startTime: new Date().toISOString().split("T")[0],
endTime: new Date().toISOString().split("T")[0],
pageNum: pageNum,
pageSize: 50,
}),
}
);
const data = await response.json();
console.log(data.data.list);
allData = allData.concat(data.data.list);
totalPage = data.data.totalPage;
pageNum++;
} while (pageNum <= totalPage || pageNum <= maxPageNum);
try {
// 保存响应数据
responseData = allData;
// 统计链接重复次数
const clickUrlCounts = {};
const expoUrlCounts = {};
allData.forEach((item) => {
if (item.clickUrls && item.clickUrls[0]) {
const clickUrl = extractUrlParam(item.clickUrls[0]);
clickUrlCounts[clickUrl] = (clickUrlCounts[clickUrl] || 0) + 1;
}
if (item.expoUrls && item.expoUrls[0]) {
const expoUrl = extractUrlParam(item.expoUrls[0]);
expoUrlCounts[expoUrl] = (expoUrlCounts[expoUrl] || 0) + 1;
}
});
// 读取option 本地持久化的存储
const storedOption = localStorage.getItem('data');
const option = JSON.parse(storedOption);
option.forEach((item) => {
const link = item.link;
const relink = link.match(/e=[^&]+/);
if (relink) {
item.relink = relink[0];
} else {
item.relink = link
}
});
console.log(option);
if (storedOption) {
Object.entries(clickUrlCounts).forEach(([url, count]) => {
if (url) {
const div = document.createElement("div");
div.style.cssText = `
margin-bottom: 5px;
`; // 改成 cssom
div.textContent = `点击链接:${url} 有${count}条`;
const relink = url.match(/e=.*?(&|$)/)[0];
if (option.some(item => item.relink === relink)) {
div.textContent += ` 配置符合:${option.find(item => item.relink === relink).name}`;
} else {
div.textContent += " 无配置符合";
}
linkStatsContainer.appendChild(div);
}
});
Object.entries(expoUrlCounts).forEach(([url, count]) => {
if (url) {
const div = document.createElement("div");
div.style.cssText = `
margin-bottom: 5px;
`; // 改成 cssom
div.textContent = `曝光链接:${url} 有${count}条`;
const relink = url.match(/e=.*?(&|$)/)[0];
if (option.some(item => item.relink === relink)) {
div.textContent += ` 配置符合:${option.find(item => item.relink === relink).name}`;
} else {
div.textContent += " 无配置符合";
}
linkStatsContainer.appendChild(div);
}
});
} else {
Object.entries(clickUrlCounts).forEach(([url, count]) => {
if (url) {
const div = document.createElement("div");
div.style.cssText = `
margin-bottom: 5px;
`; // 改成 cssom
div.textContent = `点击链接:${url} 有${count}条`;
div.textContent += " 无配置符合";
linkStatsContainer.appendChild(div);
}
});
Object.entries(expoUrlCounts).forEach(([url, count]) => {
if (url) {
const div = document.createElement("div");
div.style.cssText = `
margin-bottom: 5px;
`; // 改成 cssom
div.textContent = `曝光链接:${url} 有${count}条`;
div.textContent += " 无配置符合";
linkStatsContainer.appendChild(div);
}
});
}
createTable(allData);
} catch (error) {
console.error("解析响应数据时出错:", error);
}
}
// 提取URL参数
function extractUrlParam(url) {
const match = url.match(/https:\/\/magellan.alimama.com\/(.*?)&/);
return match ? match[1] : "";
}
// 创建表格
function createTable(data) {
const table = document.createElement("table");
table.style.cssText = `
width: 100%;
border-collapse: collapse;
background-color: white;
`; // 改成 cssom
// 创建表头
const thead = document.createElement("thead");
thead.style.cssText = `
background-color: #0066CC;
`; // 改成 cssom
const headerRow = document.createElement("tr");
const headers = ["创建时间", "创意名", "创意ID", "点击链接", "曝光链接"];
headers.forEach((headerText) => {
const th = document.createElement("th");
th.style.cssText = `
border: 1px solid #0066CC;
padding: 12px;
color: white;
font-weight: bold;
`; // 改成 cssom
th.textContent = headerText;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// 创建表体
const tbody = document.createElement("tbody");
data.forEach((item, index) => {
const row = document.createElement("tr");
row.style.cssText = `
background-color: ${index % 2 === 0 ? "#F5F8FA" : "white"};
`; // 改成 cssom
const createTime = item.creativityCreateTime;
const creativityName = item.creativityName;
const creativityId = item.creativityId;
const clickUrl = item.clickUrls
? JSON.stringify(item.clickUrls.map(extractUrlParam)).replace(
/,/g,
"\n"
)
: "";
const expoUrl = item.expoUrls
? JSON.stringify(item.expoUrls.map(extractUrlParam)).replace(/,/g, "\n")
: "";
const values = [
createTime,
creativityName,
creativityId,
clickUrl,
expoUrl,
];
values.forEach((value) => {
const td = document.createElement("td");
td.style.cssText = `
border: 1px solid #E5E5E5;
padding: 12px;
color: #333333;
`; // 改成 cssom
td.textContent = value;
row.appendChild(td);
});
tbody.appendChild(row);
});
table.appendChild(tbody);
// 清空表格容器并插入新表格
tableContainer.innerHTML = "";
tableContainer.appendChild(table);
}
// 下载Excel
function downloadExcel(data) {
// 添加BOM头,解决中文乱码问题
const BOM = "\uFEFF";
const headers = ["创建时间", "创意名", "创意ID", "点击链接", "曝光链接"];
const csvContent = [headers.join(",")];
data.forEach((item) => {
const createTime = item.creativityCreateTime || "";
const creativityName = item.creativityName || "";
const creativityId = item.creativityId || "";
const clickUrl =
item.clickUrls && item.clickUrls[0]
? JSON.stringify(item.clickUrls.map(extractUrlParam)).replace(
/,/g,
"\n"
)
: "";
const expoUrl =
item.expoUrls && item.expoUrls[0]
? JSON.stringify(item.expoUrls.map(extractUrlParam)).replace(
/,/g,
"\n"
)
: "";
// 处理CSV中的特殊字符
const escapeCsvValue = (value) => {
if (typeof value !== "string") return value;
if (
value.includes(",") ||
value.includes('"') ||
value.includes("\n")
) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
};
const values = [
createTime,
creativityName,
creativityId,
clickUrl,
expoUrl,
].map(escapeCsvValue);
csvContent.push(values.join(","));
});
const csv = BOM + csvContent.join("\n");
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
const timestamp = new Date().toLocaleString().replace(/[/:]/g, "-");
link.setAttribute("href", url);
link.setAttribute("download", `data_${timestamp}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
function option(){
const div = document.createElement('div');
div.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
width: 500px;
height: 300px;
overflow-x: auto;
overflow-y: auto;
background: white;
border: 1px solid rgb(0, 102, 204);
border-radius: 8px;
`; // 改成 cssom
const table = document.createElement('table');
table.style.cssText = `
width: 100%;
border-collapse: collapse;
`; // 改成 cssom
const thead = document.createElement('thead');
const tr = document.createElement('tr');
const th1 = document.createElement('th');
th1.textContent = '链接';
tr.appendChild(th1);
const th2 = document.createElement('th');
th2.textContent = '名字';
tr.appendChild(th2);
thead.appendChild(tr);
table.appendChild(thead);
const tbody = document.createElement('tbody');
const storedData = localStorage.getItem('data');
if (storedData) {
const data = JSON.parse(storedData);
data.forEach((item) => {
const row = document.createElement('tr');
const td1 = document.createElement('td');
const linkInput = document.createElement('input');
linkInput.type = 'text';
linkInput.value = item.link;
td1.appendChild(linkInput);
row.appendChild(td1);
const td2 = document.createElement('td');
const nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.value = item.name;
td2.appendChild(nameInput);
row.appendChild(td2);
tbody.appendChild(row);
});
} else {
const row = document.createElement('tr');
const td1 = document.createElement('td');
const linkInput = document.createElement('input');
linkInput.type = 'text';
td1.appendChild(linkInput);
row.appendChild(td1);
const td2 = document.createElement('td');
const nameInput = document.createElement('input');
nameInput.type = 'text';
td2.appendChild(nameInput);
row.appendChild(td2);
tbody.appendChild(row);
}
table.appendChild(tbody);
const addButton = document.createElement('button');
addButton.textContent = '+';
addButton.onclick = () => {
const newRow = document.createElement('tr');
const newTd1 = document.createElement('td');
const newLinkInput = document.createElement('input');
newLinkInput.type = 'text';
newTd1.appendChild(newLinkInput);
newRow.appendChild(newTd1);
const newTd2 = document.createElement('td');
const newNameInput = document.createElement('input');
newNameInput.type = 'text';
newTd2.appendChild(newNameInput);
newRow.appendChild(newTd2);
if (tbody.children.length > 0) {
tbody.insertBefore(newRow, tbody.children[0]);
} else {
tbody.appendChild(newRow);
}
};
table.appendChild(addButton);
const saveButton = document.createElement('button');
saveButton.textContent = '保存';
saveButton.onclick = () => {
const rows = tbody.rows;
const data = [];
for (let i = 0; i < rows.length; i++) {
const link = rows[i].cells[0].children[0].value;
const name = rows[i].cells[1].children[0].value;
data.push({ link, name });
}
localStorage.setItem('data', JSON.stringify(data));
div.style.display = 'none'; // 点击保存后隐藏当前元素
};
table.appendChild(saveButton);
div.appendChild(table);
document.body.appendChild(div);
}
//======================改名
// 创建打开卡片的按钮
const openButton = document.createElement('button');
addButtonAttributes(openButton, "修改单元内计划字符");
// 为打开按钮添加点击事件监听器
openButton.addEventListener('click', function () {
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
#overlay {
position: fixed;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
transform: translate(-50%, -50%);
}
#card {
position: fixed;
top: 50%;
left: 50%;
width: 500px; /* 设置卡片的宽度 */
height: 300px; /* 设置卡片的高度 */
background-color: white;
border: 1px solid #ccc;
padding: 20px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 2147483647;
transform: translate(-50%, -50%);
}
`);
const allSheets = [...document.adoptedStyleSheets, sheet];
document.adoptedStyleSheets=allSheets
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'overlay';
// 创建卡片
const card = document.createElement('div');
card.id = 'card';
// 创建关闭按钮
const closeButton = document.createElement('span');
closeButton.textContent = 'x';
closeButton.style.position = 'absolute';
closeButton.style.top = '10px';
closeButton.style.right = '10px';
closeButton.style.cursor = 'pointer';
closeButton.addEventListener('click', function () {
document.body.removeChild(overlay);
document.body.removeChild(card);
});
// 创建第一个输入框及其标签
const oldCharLabel = document.createElement('label');
oldCharLabel.textContent = '旧字符';
const oldCharInput = document.createElement('input');
oldCharInput.type = 'text';
// 创建第二个输入框及其标签
const newCharLabel = document.createElement('label');
newCharLabel.textContent = '新字符';
const newCharInput = document.createElement('input');
newCharInput.type = 'text';
// 创建替换按钮
const replaceButton = document.createElement('button');
replaceButton.textContent = '替换';
replaceButton.addEventListener('click', async function () {
const oldChar = oldCharInput.value;
const newChar = newCharInput.value;
await mainSend(oldChar, newChar)
// 这里可以执行其他脚本,例如替换页面上的文本
console.log(`将 "${oldChar}" 替换为 "${newChar}"`);
});
// 将元素添加到卡片中
card.appendChild(closeButton);
card.appendChild(oldCharLabel);
card.appendChild(oldCharInput);
card.appendChild(newCharLabel);
card.appendChild(newCharInput);
card.appendChild(replaceButton);
// 将遮罩层和卡片添加到页面中
document.body.appendChild(overlay);
document.body.appendChild(card);
});
async function mainSend(old1,new1) {
const n = location.href.match(/[0-9]{1,20}/g)
const r = await fetch("https://ad.xiaohongshu.com/api/leona/rtb/creativity/list", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8",
"priority": "u=1, i",
"sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Microsoft Edge\";v=\"133\", \"Chromium\";v=\"133\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-b3-traceid": "37e8ffc9e25f798c"
},
"referrer": "https://ad.xiaohongshu.com/aurora/ad/manage/103584356/192658846/creativity",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `{\"campaignId\":${n[0]},\"unitId\":${n[1]},\"startTime\":\"2025-02-27\",\"endTime\":\"2025-02-27\",\"pageNum\":1,\"pageSize\":50}`,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
const j = await r.json()
const total = j.data.list.length;
let completed = 0;
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
#progressBar {
width: 0;
height: 20px;
background-color: #54FF9F;
position: fixed;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
z-index: 2147483647;
}
`);
addStyleSheet(sheet)
// document.adoptedStyleSheets = [sheet];
const progressBar = document.createElement('div');
progressBar.id = 'progressBar';
progressBar.innerText = "进度";
document.body.appendChild(progressBar);
for (let index = 0; index < j.data.list.length; index++) {
const element = j.data.list[index];
const oldName = element.creativityName
const regex = new RegExp(old1, 'g');
const newName = oldName.replace(regex,new1)
await reanmeSend(element.creativityId,newName)
completed++;
const progress = (completed / total) * 100;
progressBar.style.width = `${progress}%`;
}
document.body.removeChild(progressBar);
alert('完成');
}
async function reanmeSend(id,name) {
return fetch("https://ad.xiaohongshu.com/api/leona/rtb/creativity/batch/update/name", {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/json;charset=UTF-8",
"priority": "u=1, i",
"sec-ch-ua": "\"Not(A:Brand\";v=\"99\", \"Microsoft Edge\";v=\"133\", \"Chromium\";v=\"133\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-b3-traceid": "dee287ce9b6526cc"
},
//"referrer": "https://ad.xiaohongshu.com/aurora/ad/manage/103596579/192712174/creativity",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `{\"creativityId\":${id},\"creativityName\":\"${name}\"}`,
"method": "POST",
"mode": "cors",
"credentials": "include"
});
}
//======================
// 创建配置按钮
const optionButton = document.createElement("button");
addButtonAttributes(optionButton,"配置")
optionButton.onclick = option;
buttonContainer.appendChild(optionButton);
buttonContainer.appendChild(openButton);
})();