You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

productInfoExample.js 6.8KB

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