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
@@ -0,0 +1,361 @@
// ==UserScript==
// @name 抖音商家体验分监控
// @namespace https://bbs.tampermonkey.net.cn/
// @version 0.4.0
// @description 每天更新前一日的体验分数据
// @author wanxi
// @crontab 0 8-19 * * *
// @grant GM_xmlhttpRequest
// ==/UserScript==
const CONFIG = {
id: "cli_a6f25876ea28100d",
secret: "raLC56ZLIara07nKigpysfoDxHTAeyJf",
appId: "GyUUbEzuxajfU4sGieIcNKxvnXd",
tableId1: "tblyXVQVqwfcyaoK", // 维度表
tableId2: "tbl8JEts30wvgWv3", // 指标表
logTableId: "tbl6eZJpt9GkZjWO", // 运行记录表
dimensionDict: [
{
维度: "商品体验",
指标: ["商品差评率", "商品品质退货率"],
},
{
维度: "物流体验",
指标: ["运单配送时效达成率", "24小时支付-揽收率", "发货问题负向反馈率"],
},
{
维度: "服务体验",
指标: [
"仅退款自主完结时长",
"退货退款自主完结时长",
"飞鸽平均响应时长",
"飞鸽不满意率",
"平台求助率",
"售后拒绝率",
],
},
],
urls: {
overview:
"https://fxg.jinritemai.com/governance/shop/experiencescore/getOverviewByVersion?exp_version=8.0&source=1",
analysisScore:
"https://fxg.jinritemai.com/governance/shop/experiencescore/getAnalysisScore?new_dimension=true&time=30&filter_by_industry=true&number_type=30&exp_version=8.0",
tenantAccessToken:
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
bitableRecords: (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`,
},
};
let logs = [];
let result = "Success";
function log(message) {
logs.push(message);
console.log(message);
}
async function sendHttpRequest(
method,
url,
body = null,
headers = {
"Content-Type": "application/json",
}
) {
return 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 checkIfRecordExists(accessToken, appId, tableId, date) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.bitableSearchRecords(appId, tableId),
JSON.stringify({
field_names: ["数据时间"],
filter: {
conjunction: "and",
conditions: [
{
field_name: "数据时间",
operator: "is",
value: [date],
},
],
},
}),
{
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
}
);
return response.data.total > 0;
} catch (error) {
throw new Error(`Failed to check if record exists in Bitable: ${error}`);
}
}
async function updateBitableRecords(accessToken, appId, tableId, items) {
try {
const response = await sendHttpRequest(
"POST",
CONFIG.urls.bitableRecords(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}`);
}
}
function getDimension(indicator) {
for (const dimension of CONFIG.dimensionDict) {
if (dimension.指标.includes(indicator)) {
return dimension.维度;
}
}
return null;
}
function isYesterday(date) {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
return date.toDateString() === yesterday.toDateString();
}
async function fetchAndUploadData() {
try {
// 获取TenantAccessToken
log("Fetching tenant access token...");
const accessToken = await fetchTenantAccessToken(CONFIG.id, CONFIG.secret);
log("Tenant access token fetched successfully.");
/*
// 获取昨天的日期
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
const yesterdayStr = yesterday.toISOString().split('T')[0];
*/
// 检查昨天的数据是否已经存在
log("Checking if yesterday's data already exists...");
//https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/record-filter-guide
const recordExists = await checkIfRecordExists(
accessToken,
CONFIG.appId,
CONFIG.tableId1,
"Yesterday"
);
if (recordExists) {
log("Yesterday's data already exists. Skipping data upload.");
return;
}
// 细分指标里面有current_date,先请求细分指标
log("Fetching analysis score data...");
const analysisData = await sendHttpRequest(
"GET",
CONFIG.urls.analysisScore
);
log("analysisData: " + JSON.stringify(analysisData));
if (!analysisData || !analysisData.data) {
throw new Error("Invalid analysisData structure");
}
const currentDate = new Date(Date.parse(analysisData.data.current_date));
if (!isYesterday(currentDate)) {
log("Current date is not yesterday. Skipping data upload.");
return;
}
const analysisScore = analysisData.data.shop_analysis.map((item) => ({
指标: item.title,
维度: getDimension(item.title),
数据时间: new Date(currentDate).getTime(),
数值无单位: item.value.value_figure,
环比: item.compare_with_self.rise_than_yesterday,
超越同行: item.surpass_peers.value_figure,
等级: item.level,
}));
log("Analysis score data fetched successfully.");
// 将指标分写入多维表格
log("Uploading analysis score data to bitable...");
for (const item of analysisScore) {
const itemsToWrite = {
fields: item,
};
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.tableId2,
itemsToWrite
);
}
log("Analysis score data uploaded successfully.");
// 维度分
log("Fetching overview data...");
const overviewData = await sendHttpRequest("GET", CONFIG.urls.overview);
log("overviewData: " + JSON.stringify(overviewData));
if (!overviewData || !overviewData.data) {
throw new Error("Invalid overviewData structure");
}
const scores = [
{
维度: "商品体验分",
得分: overviewData.data.goods_score.value,
较前一日: overviewData.data.goods_score.rise_than_yesterday,
数据时间: new Date(currentDate).getTime(),
},
{
维度: "物流体验分",
得分: overviewData.data.logistics_score.value,
较前一日: overviewData.data.logistics_score.rise_than_yesterday,
数据时间: new Date(currentDate).getTime(),
},
{
维度: "服务体验分",
得分: overviewData.data.service_score.value,
较前一日: overviewData.data.service_score.rise_than_yesterday,
数据时间: new Date(currentDate).getTime(),
},
{
维度: "商家体验分",
得分: overviewData.data.experience_score.value,
较前一日: overviewData.data.experience_score.rise_than_yesterday,
数据时间: new Date(currentDate).getTime(),
},
];
log("Overview data fetched successfully.");
log("Uploading overview data to bitable...");
for (const item of scores) {
const itemsToWrite = {
fields: item,
};
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.tableId1,
itemsToWrite
);
}
log("Overview 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 logData1 = {
fields: {
表名: "商家体验分-维度",
表格id: CONFIG.tableId1,
最近运行时间: runTime,
运行结果: result,
日志: logs.join("\n"),
},
};
const logData2 = {
fields: {
表名: "商家体验分-指标",
表格id: CONFIG.tableId2,
最近运行时间: runTime,
运行结果: result,
日志: logs.join("\n"),
},
};
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.logTableId,
logData1
);
await updateBitableRecords(
accessToken,
CONFIG.appId,
CONFIG.logTableId,
logData2
);
log("Run result logged successfully.");
} catch (error) {
log("Failed to log run result: " + error.message);
}
}
(async function () {
try {
await fetchAndUploadData();
} catch (error) {
log("Script execution failed: " + error.message);
} finally {
await logRunResult();
}
})();