const ProductApiClient = require("../src/services/productApiClient"); const axios = require("axios"); // 本地服务端实例 const localClient = new ProductApiClient({ // 根据实际部署环境修改baseURL baseURL: "http://localhost:8991", }); // 外网服务端实例 const serverClient = { // baseURL: "http://192.168.1.107:8080", // 本地 baseURL: "https://digital.sohomall.jp/prod-api", // 外网 timeout: 10000 * 30, // 30秒 params: { pageNum: 1, pageSize: 500, }, }; axios.defaults.baseURL = serverClient.baseURL; axios.defaults.timeout = serverClient.timeout; // 设置抓取配置 const config = { platform: "amazon", // 平台 needScreenshot: true, // 是否需要截图 monitorFrequency: 8, // 监控频率(小时) goodsList: [], // 商品列表 isRunning: false, // 是否正在执行抓取任务 timer: null, // 定时器 }; /** * 获取商品信息 * @param {Object} goods - 商品对象 * @param {boolean} isRetry - 是否为重试操作 * @returns {Promise} - 返回商品信息或null */ async function fetchProductInfo(goods, isRetry = false) { try { console.log(`\n`); console.log(`3️⃣ ${isRetry ? "重试" : "开始"}抓取商品: ${goods.goodsSkuSn}`); const productInfo = await localClient.getProductInfo({ url: goods.goodsSkuUrl, platform: goods.platform, needScreenshot: config.needScreenshot, }); console.log(`\n`); console.log(`----------------抓取结果----------------`); console.log(`抓取商品: ${productInfo[0].title}`); console.log(`抓取SKU : ${goods.goodsSkuSn}`); console.log(`基准价格: ${goods.initPrice}`); console.log(`抓取价格: ${productInfo[0].price}`); console.log(`----------------------------------------`); console.log(`\n`); return productInfo; } catch (error) { console.error( `抓取失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`, error.message ); return null; } } /** * 保存商品信息到服务器 * @param {Object} goods - 商品对象 * @param {Object} productInfo - 抓取到的商品信息 * @returns {Promise} - 是否保存成功 */ async function saveProductInfo(goods, productInfo) { try { console.log( `4️⃣ 开始保存商品信息: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}` ); const { title, price, sku, screenshotUrl } = productInfo[0]; const res = await axios.post( serverClient.baseURL + "/system/operationWarnresult/receiveLatestGoodsInfo", { title, price: price.toString(), sku, url: goods.goodsSkuUrl, screenshotUrl: screenshotUrl, } ); console.log(`\n`); console.log(res.data.success ? `✅️ ${goods.goodsSkuSn} 保存成功` : `❌️ ${goods.goodsSkuSn} 保存失败`); console.log(`\n`); return true; } catch (saveError) { console.log(`\n`); console.error( `保存失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`, saveError.message ); console.log(`\n`); return false; } } /** * 处理单个商品的抓取和保存 * @param {Object} goods - 商品对象 * @returns {Promise} */ async function processProduct(goods) { // 第一次尝试抓取 let productInfo = await fetchProductInfo(goods); // 如果第一次抓取成功,保存结果 if (productInfo) { await saveProductInfo(goods, productInfo); return; } // 第一次失败,进行重试 productInfo = await fetchProductInfo(goods, true); // 如果重试成功,保存结果 if (productInfo) { await saveProductInfo(goods, productInfo); } // 重试失败,跳过该商品 } /** * 获取抓取配置 * @returns {Promise} */ async function fetchConfig() { try { console.log(`\n\n\n\n`); console.log(`1️⃣ 开始获取抓取配置`); const res = await axios.get(serverClient.baseURL + "/system/operationWarnconfig/noVerifyList", { params: serverClient.params, }); console.log(`阈值: ${res.data.rows[0].priceChangeThreshold}`); console.log(`频率: ${res.data.rows[0].monitorFrequency}小时`); const { rows } = res.data; if (rows.length > 0) { config.monitorFrequency = rows[0].monitorFrequency; } else { config.monitorFrequency = 8; // 默认8小时 } return true; } catch (error) { console.error(`获取抓取配置失败: ${new Date().toLocaleString()}`, error.message); console.log(`---------------------------------------------------------`); return false; } } /** * 获取商品列表并处理 * @returns {Promise} */ async function fetchGoodsListAndProcess() { if (config.isRunning) { console.log(`上一次任务尚未完成,跳过本次执行: ${new Date().toLocaleString()}`); return; } config.isRunning = true; try { const res = await axios.get(serverClient.baseURL + "/system/operationGoods/noVerifyList", { params: { ...serverClient.params, isDisabled: 1, }, }); console.log(`\n`); console.log(`2️⃣ 开始获取商品列表, 共${res.data.rows.length}个商品`); const d = res.data.rows; d.forEach((row, index) => { console.log(`\n`); console.log(`--------------------------------`); console.log(`(${index + 1} / ${d.length})`); console.log(`商品名称: ${row.goodsSkuName}`); console.log(`商品SKU : ${row.goodsSkuSn}`); console.log(`基准价格: ${row.initPrice}`); console.log(`备注: ${row.remark}`); console.log(`--------------------------------`); console.log(`\n`); }); const { rows } = res.data; config.goodsList = rows; // 使用for...of循环按顺序处理每个商品 for (const goods of config.goodsList) { await processProduct(goods); } console.log("✌️ 所有商品抓取完成", new Date().toLocaleString()); } catch (error) { console.error(`⛔️ 获取商品列表失败: ${new Date().toLocaleString()}`, error.message); } finally { config.isRunning = false; } } /** * 启动定时任务 */ async function startScheduler() { // 先获取配置 await fetchConfig(); // 立即执行一次 await fetchGoodsListAndProcess(); // 清除之前的定时器(如果存在) if (config.timer) { clearInterval(config.timer); } // 设置定时器,根据monitorFrequency的小时数定时执行 const intervalMs = config.monitorFrequency * 60 * 60 * 1000; // 转换为毫秒 console.log(`\n\n\n\n`); console.log(`设置定时任务,每 ${config.monitorFrequency} 小时执行一次,下次执行时间: ${new Date(Date.now() + intervalMs).toLocaleString()}`); console.log(`\n\n\n\n --------------------------------------------------------------------------------------------------------- \n\n\n\n`); config.timer = setInterval(async () => { console.log(`定时任务触发: ${new Date().toLocaleString()}`); // 重新获取配置(频率可能会改变) await fetchConfig(); // 执行抓取处理 await fetchGoodsListAndProcess(); // 如果monitorFrequency发生变化,重新设置定时器 const newIntervalMs = config.monitorFrequency * 60 * 60 * 1000; if (newIntervalMs !== intervalMs) { console.log(`监控频率已变更为 ${config.monitorFrequency} 小时,重新设置定时器`); clearInterval(config.timer); startScheduler(); // 重新启动调度器 } }, intervalMs); // 添加防止程序崩溃的错误处理 process.on('uncaughtException', (error) => { console.error(`未捕获的异常: ${new Date().toLocaleString()}`, error); // 尝试继续执行定时任务 if (!config.timer) { startScheduler(); } }); } // 启动调度器 startScheduler();