feat: 新增多个脚本用于监控抖音直播、店铺评价、售后及体验分数据

新增了多个脚本文件,用于监控抖音直播间的弹幕、店铺评价、售后数据及商家体验分。这些脚本通过飞书多维表格进行数据存储,并支持定时任务自动更新数据。具体包括:
1. 直播间弹幕监控脚本
2. 店铺评价监控脚本
3. 售后数据监控脚本
4. 商家体验分监控脚本
5. 竞品、行业及跨行业热门千川素材获取脚本

这些脚本通过飞书API进行数据写入,并支持去重和定时任务调度。
This commit is contained in:
intelligrow
2025-04-25 15:15:29 +08:00
commit 3c3ecf8947
10 changed files with 3689 additions and 0 deletions
+285
View File
@@ -0,0 +1,285 @@
// ==UserScript==
// @name 抖音店铺评价监控
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.2.0
// @description 每小时获取前2小时的店铺评价数据,并去重后上传到飞书多维表格
// @author wanxi
// @crontab 1 * * * *
// @grant GM_xmlhttpRequest
// @connect open.feishu.cn
// @connect fxg.jinritemai.com
// ==/UserScript==
const CONFIG = {
id: "cli_a6f25876ea28100d",
secret: "raLC56ZLIara07nKigpysfoDxHTAeyJf",
appId: "GyUUbEzuxajfU4sGieIcNKxvnXd",
tableId: "tblfMlqE1lKBEnR4", // 表格ID
logTableId: "tbl6eZJpt9GkZjWO", // 运行记录表
urls: {
tenantAccessToken:
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
bitableUpdateRecords: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records`,
bitableSearchRecords: (appId, tableId) =>
`https://open.feishu.cn/open-apis/bitable/v1/apps/${appId}/tables/${tableId}/records/search`,
fetchComments: (page, pageSize, startTime, endTime) =>
`https://fxg.jinritemai.com/product/tcomment/commentList?rank=0&content_search=0&reply_search=0&appeal_search=0&comment_time_from=${startTime}&comment_time_to=${endTime}&pageSize=${pageSize}&page=${page}`,
},
};
let logs = [];
let result = "Success";
function log(message) {
logs.push(message);
console.log(message);
}
// 重试函数
const retry = async (fn, retries = 3, delay = 1000) => {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i < retries - 1) {
console.warn(`Retrying... (${i + 1}/${retries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
};
async function sendHttpRequest(
method,
url,
body = null,
headers = { "Content-Type": "application/json" }
) {
return retry(
() =>
new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url,
data: body,
headers: headers,
onload: function (response) {
if (response.status >= 200 && response.status < 300) {
try {
const jsonResponse = JSON.parse(response.responseText);
if (jsonResponse.msg) {
log(jsonResponse.msg);
}
resolve(jsonResponse);
} catch (e) {
reject(`Failed to parse JSON: ${e}`);
}
} else {
reject(`Error: ${response.status} ${response.statusText}`);
}
},
onerror: function (error) {
reject(`Network Error: ${error}`);
},
});
})
);
}
async function fetchTenantAccessToken(id, secret) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.tenantAccessToken,
JSON.stringify({
app_id: id,
app_secret: secret,
})
);
return response.tenant_access_token;
} catch (error) {
throw new Error(`Error fetching Tenant Access Token: ${error}`);
}
}
//更新
async function updateBitableRecords(accessToken, appId, tableId, items) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.bitableUpdateRecords(appId, tableId),
JSON.stringify(items),
{
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
}
);
log("Updated record: " + JSON.stringify(response));
return response;
} catch (error) {
throw new Error(`Failed to update record in Bitable: ${error}`);
}
}
async function checkIfRecordExists(accessToken, appId, tableId, commentId) {
const url = CONFIG.urls.bitableSearchRecords(appId, tableId);
const headers = {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
};
const body = JSON.stringify({
field_names: ["评价id"],
filter: {
conjunction: "and",
conditions: [
{
field_name: "评价id",
operator: "is",
value: [commentId],
},
],
},
automatic_fields: false,
});
try {
const response = await sendHttpRequest("POST", url, body, headers);
return response.data.total > 0;
} catch (error) {
console.error("Error querying Bitable:", error);
throw new Error(`Failed to query Bitable: ${error.message}`);
}
}
async function fetchComments(page, pageSize = 50, startTime, endTime) {
const url = CONFIG.urls.fetchComments(page, pageSize, startTime, endTime);
const headers = {
accept: "application/json, text/plain, */*",
};
try {
const response = await sendHttpRequest("GET", url, null, headers);
return response;
} catch (error) {
console.error("Error fetching comments:", error);
throw error;
}
}
async function fetchAllComments() {
let allComments = [];
let page = 0;
let hasMore = true;
//秒级时间戳
const endTime = Math.floor(Date.now() / 1000);
const startTime = endTime - 2 * 3600;
//const startTime = 1721516400;
//const endTime = 1721523600;
while (hasMore) {
const data = await fetchComments(page, 50, startTime, endTime);
if (data.code !== 0) {
result = "Failed";
throw new Error(`Error fetching comments: ${data.msg}`);
}
if (data.data && data.data.length > 0) {
allComments = allComments.concat(data.data);
page++;
} else {
hasMore = false;
}
}
return allComments;
}
async function fetchAndUploadComments() {
try {
log("Fetching comments data...");
const comments = await fetchAllComments();
log("Comments data fetched successfully.");
log("Fetching tenant access token...");
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
log("Tenant access token fetched successfully.");
log("Uploading comments data to bitable...");
for (const comment of comments) {
const exists = await checkIfRecordExists(
accessToken,
CONFIG.appId,
CONFIG.tableId,
comment.id
);
if (!exists) {
const fields = {
评价id: comment.id,
评价时间: comment.comment_time * 1000,
商品id: comment.product_id,
店铺评分: comment.rank_shop,
物流评分: comment.rank_logistic,
商品评分: comment.rank_product,
综合评分: comment.rank,
评价标签: comment.tags.rank_info.name,
SKU: comment.sku,
店铺名: "夸迪官方旗舰店",
订单id: comment.order_id,
商品名称: comment.product.name,
评价内容: comment.content,
};
const itemsToWrite = { fields: fields };
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.tableId,
itemsToWrite
);
}
}
log("Comments data uploaded successfully.");
} catch (error) {
log("Error: " + error.message);
result = "Failed";
throw error;
}
}
async function logRunResult() {
try {
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
const runTime = new Date().getTime();
const logData = {
fields: {
表名: "评价",
表格id: CONFIG.tableId,
最近运行时间: runTime,
运行结果: result,
日志: logs.join("\n"),
},
};
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.logTableId,
logData
);
log("Run result logged successfully.");
} catch (error) {
log("Failed to log run result: " + error.message);
}
}
(async function () {
try {
await fetchAndUploadComments();
} catch (error) {
log("Script execution failed: " + error.message);
} finally {
await logRunResult();
}
})();