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

这些脚本通过飞书API进行数据写入,并支持去重和定时任务调度。
2025-04-25 15:15:29 +08:00

362 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==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();
}
})();