您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

productInfoExample.js 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. const ProductApiClient = require("../src/services/productApiClient");
  2. const axios = require("axios");
  3. // 本地服务端实例
  4. const localClient = new ProductApiClient({
  5. // 根据实际部署环境修改baseURL
  6. baseURL: "http://localhost:8991",
  7. });
  8. // 外网服务端实例
  9. const serverClient = {
  10. // baseURL: "http://192.168.1.107:8080", // 本地
  11. baseURL: "https://digital.sohomall.jp/prod-api", // 外网
  12. timeout: 10000 * 30, // 30秒
  13. params: {
  14. pageNum: 1,
  15. pageSize: 500,
  16. },
  17. };
  18. axios.defaults.baseURL = serverClient.baseURL;
  19. axios.defaults.timeout = serverClient.timeout;
  20. // 设置抓取配置
  21. const config = {
  22. platform: "amazon", // 平台
  23. needScreenshot: true, // 是否需要截图
  24. monitorFrequency: 8, // 监控频率(小时)
  25. goodsList: [], // 商品列表
  26. isRunning: false, // 是否正在执行抓取任务
  27. timer: null, // 定时器
  28. };
  29. /**
  30. * 获取商品信息
  31. * @param {Object} goods - 商品对象
  32. * @param {boolean} isRetry - 是否为重试操作
  33. * @returns {Promise<Object|null>} - 返回商品信息或null
  34. */
  35. async function fetchProductInfo(goods, isRetry = false) {
  36. try {
  37. console.log(`\n`);
  38. console.log(`3️⃣ ${isRetry ? "重试" : "开始"}抓取商品: ${goods.goodsSkuSn}`);
  39. const productInfo = await localClient.getProductInfo({
  40. url: goods.goodsSkuUrl,
  41. platform: goods.platform,
  42. needScreenshot: config.needScreenshot,
  43. });
  44. console.log(`\n`);
  45. console.log(`----------------抓取结果----------------`);
  46. console.log(`抓取商品: ${productInfo[0].title}`);
  47. console.log(`抓取SKU : ${goods.goodsSkuSn}`);
  48. console.log(`基准价格: ${goods.initPrice}`);
  49. console.log(`抓取价格: ${productInfo[0].price}`);
  50. console.log(`----------------------------------------`);
  51. console.log(`\n`);
  52. return productInfo;
  53. } catch (error) {
  54. console.error(
  55. `抓取失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`,
  56. error.message
  57. );
  58. return null;
  59. }
  60. }
  61. /**
  62. * 保存商品信息到服务器
  63. * @param {Object} goods - 商品对象
  64. * @param {Object} productInfo - 抓取到的商品信息
  65. * @returns {Promise<boolean>} - 是否保存成功
  66. */
  67. async function saveProductInfo(goods, productInfo) {
  68. try {
  69. console.log(
  70. `4️⃣ 开始保存商品信息: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`
  71. );
  72. const { title, price, sku, screenshotUrl } = productInfo[0];
  73. const res = await axios.post(
  74. serverClient.baseURL +
  75. "/system/operationWarnresult/receiveLatestGoodsInfo",
  76. {
  77. title,
  78. price: price.toString(),
  79. sku,
  80. url: goods.goodsSkuUrl,
  81. screenshotUrl: screenshotUrl,
  82. }
  83. );
  84. console.log(`\n`);
  85. console.log(res.data.success ? `✅️ ${goods.goodsSkuSn} 保存成功` : `❌️ ${goods.goodsSkuSn} 保存失败`);
  86. console.log(`\n`);
  87. return true;
  88. } catch (saveError) {
  89. console.log(`\n`);
  90. console.error(
  91. `保存失败: ${goods.goodsSkuSn} - ${new Date().toLocaleString()}`,
  92. saveError.message
  93. );
  94. console.log(`\n`);
  95. return false;
  96. }
  97. }
  98. /**
  99. * 处理单个商品的抓取和保存
  100. * @param {Object} goods - 商品对象
  101. * @returns {Promise<void>}
  102. */
  103. async function processProduct(goods) {
  104. // 第一次尝试抓取
  105. let productInfo = await fetchProductInfo(goods);
  106. // 如果第一次抓取成功,保存结果
  107. if (productInfo) {
  108. await saveProductInfo(goods, productInfo);
  109. return;
  110. }
  111. // 第一次失败,进行重试
  112. productInfo = await fetchProductInfo(goods, true);
  113. // 如果重试成功,保存结果
  114. if (productInfo) {
  115. await saveProductInfo(goods, productInfo);
  116. }
  117. // 重试失败,跳过该商品
  118. }
  119. /**
  120. * 获取抓取配置
  121. * @returns {Promise<void>}
  122. */
  123. async function fetchConfig() {
  124. try {
  125. console.log(`\n\n\n\n`);
  126. console.log(`1️⃣ 开始获取抓取配置`);
  127. const res = await axios.get(serverClient.baseURL + "/system/operationWarnconfig/noVerifyList", {
  128. params: serverClient.params,
  129. });
  130. console.log(`阈值: ${res.data.rows[0].priceChangeThreshold}`);
  131. console.log(`频率: ${res.data.rows[0].monitorFrequency}小时`);
  132. const { rows } = res.data;
  133. if (rows.length > 0) {
  134. config.monitorFrequency = rows[0].monitorFrequency;
  135. } else {
  136. config.monitorFrequency = 8; // 默认8小时
  137. }
  138. return true;
  139. } catch (error) {
  140. console.error(`获取抓取配置失败: ${new Date().toLocaleString()}`, error.message);
  141. console.log(`---------------------------------------------------------`);
  142. return false;
  143. }
  144. }
  145. /**
  146. * 获取商品列表并处理
  147. * @returns {Promise<void>}
  148. */
  149. async function fetchGoodsListAndProcess() {
  150. if (config.isRunning) {
  151. console.log(`上一次任务尚未完成,跳过本次执行: ${new Date().toLocaleString()}`);
  152. return;
  153. }
  154. config.isRunning = true;
  155. try {
  156. const res = await axios.get(serverClient.baseURL + "/system/operationGoods/noVerifyList", {
  157. params: {
  158. ...serverClient.params,
  159. isDisabled: 1,
  160. },
  161. });
  162. console.log(`\n`);
  163. console.log(`2️⃣ 开始获取商品列表, 共${res.data.rows.length}个商品`);
  164. const d = res.data.rows;
  165. d.forEach((row, index) => {
  166. console.log(`\n`);
  167. console.log(`--------------------------------`);
  168. console.log(`(${index + 1} / ${d.length})`);
  169. console.log(`商品名称: ${row.goodsSkuName}`);
  170. console.log(`商品SKU : ${row.goodsSkuSn}`);
  171. console.log(`基准价格: ${row.initPrice}`);
  172. console.log(`备注: ${row.remark}`);
  173. console.log(`--------------------------------`);
  174. console.log(`\n`);
  175. });
  176. const { rows } = res.data;
  177. config.goodsList = rows;
  178. // 使用for...of循环按顺序处理每个商品
  179. for (const goods of config.goodsList) {
  180. await processProduct(goods);
  181. }
  182. console.log("✌️ 所有商品抓取完成", new Date().toLocaleString());
  183. } catch (error) {
  184. console.error(`⛔️ 获取商品列表失败: ${new Date().toLocaleString()}`, error.message);
  185. } finally {
  186. config.isRunning = false;
  187. }
  188. }
  189. /**
  190. * 启动定时任务
  191. */
  192. async function startScheduler() {
  193. // 先获取配置
  194. await fetchConfig();
  195. // 立即执行一次
  196. await fetchGoodsListAndProcess();
  197. // 清除之前的定时器(如果存在)
  198. if (config.timer) {
  199. clearInterval(config.timer);
  200. }
  201. // 设置定时器,根据monitorFrequency的小时数定时执行
  202. const intervalMs = config.monitorFrequency * 60 * 60 * 1000; // 转换为毫秒
  203. console.log(`\n\n\n\n`);
  204. console.log(`设置定时任务,每 ${config.monitorFrequency} 小时执行一次,下次执行时间: ${new Date(Date.now() + intervalMs).toLocaleString()}`);
  205. console.log(`\n\n\n\n --------------------------------------------------------------------------------------------------------- \n\n\n\n`);
  206. config.timer = setInterval(async () => {
  207. console.log(`定时任务触发: ${new Date().toLocaleString()}`);
  208. // 重新获取配置(频率可能会改变)
  209. await fetchConfig();
  210. // 执行抓取处理
  211. await fetchGoodsListAndProcess();
  212. // 如果monitorFrequency发生变化,重新设置定时器
  213. const newIntervalMs = config.monitorFrequency * 60 * 60 * 1000;
  214. if (newIntervalMs !== intervalMs) {
  215. console.log(`监控频率已变更为 ${config.monitorFrequency} 小时,重新设置定时器`);
  216. clearInterval(config.timer);
  217. startScheduler(); // 重新启动调度器
  218. }
  219. }, intervalMs);
  220. // 添加防止程序崩溃的错误处理
  221. process.on('uncaughtException', (error) => {
  222. console.error(`未捕获的异常: ${new Date().toLocaleString()}`, error);
  223. // 尝试继续执行定时任务
  224. if (!config.timer) {
  225. startScheduler();
  226. }
  227. });
  228. }
  229. // 启动调度器
  230. startScheduler();