123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- 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<Object|null>} - 返回商品信息或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<boolean>} - 是否保存成功
- */
- 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<void>}
- */
- 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<void>}
- */
- 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<void>}
- */
- 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();
|