// checkActivity.js const axios = require('axios'); const cheerio = require('cheerio'); const notifier = require('node-notifier'); const url = 'https://www.szhdy.com/activities/default.html?method=activity&id=9'; const checkInterval = 300000; // 5分钟检查一次 const headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Connection': 'keep-alive', 'Upgrade-Insecure-Requests': '1', }; // Cookie 管理 let cookieJar = new Map(); let cookieExpiry = 0; function mergeCookiesFromHeaders(setCookieHeaders) { for (const header of setCookieHeaders) { const pair = header.split(';')[0]; const eqIdx = pair.indexOf('='); if (eqIdx > 0) { cookieJar.set(pair.substring(0, eqIdx).trim(), pair.substring(eqIdx + 1).trim()); } } } function getCookieString() { return Array.from(cookieJar.entries()).map(([k, v]) => `${k}=${v}`).join('; '); } async function getValidCookies() { if (cookieJar.size > 0 && Date.now() < cookieExpiry) { return getCookieString(); } console.log('[Cookie] 正在获取新的 Cookie...'); cookieJar.clear(); // 第一次请求拿 CDN Cookie(会返回 403,这是预期行为) const res = await axios.get(url, { headers, maxRedirects: 0, validateStatus: () => true, // 允许非 2xx 状态码 }); const setCookies = res.headers['set-cookie'] || []; mergeCookiesFromHeaders(setCookies); cookieExpiry = Date.now() + 3600 * 1000; console.log('[Cookie] 获取成功:', getCookieString()); return getCookieString(); } async function checkActivity() { try { const cookies = await getValidCookies(); const response = await axios.get(url, { headers: { ...headers, Cookie: cookies }, }); const html = response.data; // 如果响应内容过短,Cookie 可能失效,刷新重试 if (typeof html === 'string' && html.length < 1000) { console.log('[Cookie] 响应内容过短,刷新 Cookie 重试...'); cookieJar.clear(); cookieExpiry = 0; const retryCookies = await getValidCookies(); const retryRes = await axios.get(url, { headers: { ...headers, Cookie: retryCookies }, }); mergeCookiesFromHeaders(retryRes.headers['set-cookie'] || []); return parseAndCheck(retryRes.data); } // 合并新返回的 Cookie mergeCookiesFromHeaders(response.headers['set-cookie'] || []); return parseAndCheck(html); } catch (error) { console.error(`请求失败: ${error.message}`); } } function parseAndCheck(html) { const $ = cheerio.load(html); // 限定在活动主容器内检测,避免扫描其他区域 const $main = $('main.et-main.activities_bg'); if (!$main.length) { console.error('活动主容器未找到,请检查页面结构'); return; } // 提取产品详情的工具函数 const extractAttribute = ($product, label) => { const $container = $product.find('.form-container').filter((i, el) => { const labelText = $(el).find('.form-title h5').text().trim(); // 同时匹配中文/英文冒号和空格差异 return labelText.replace(/:|:/g, '').includes(label.replace(/:|:/g, '')); }); // 优先获取.form-text中的文本 let value = $container.find('.form-text').text().trim(); // 如果没有.form-text,尝试获取下拉选项中的文本 if (!value) { value = $container.find('.act-dropdown-option').text().trim(); } return value || '未知'; }; const extractProductDetails = (product) => { const $product = $(product); const name = $product.find('.cat-styleCtitle h1, .cat-styleBtitle h1').text().trim() || '未知服务器'; const core = extractAttribute($product, '核心:'); const memory = extractAttribute($product, '内存:'); const bandwidth = extractAttribute($product, '带宽:'); const duration = extractAttribute($product, '时长:'); // 同时提取价格和单位 const priceElement = $product.find('.main-price-current'); const priceUnitElement = $product.find('.price-current-unit'); const price = priceElement.text().trim() + (priceUnitElement.length ? priceUnitElement.text().trim() : ''); return { name, core, memory, bandwidth, duration, price }; }; // 格式化产品列表(精确空行格式) const formatProducts = (zoneTitle, products) => { if (products.length === 0) return ''; const productLines = products.map(product => { if ($(product).hasClass('cat-styleB-promotion-card') || $(product).hasClass('cat-styleC-promotion-card')) { const details = extractProductDetails(product); return `${details.name}\n${details.core} + ${details.memory}\n${details.bandwidth}\n${details.duration} + ${details.price}`; } return '未知产品'; }).join('\n\n'); // 产品之间空一行 // 严格按用户要求: // - 专区标题前空两行 // - 专区标题后空一行 // - 产品列表前空一行 return `\n\n- [${zoneTitle}] 可购买产品:\n\n${productLines}`; }; // 动态识别专区类型(解决ID变化问题) const activityZones = $main.find('.activityContent_bg'); let valueZoneProducts = []; let qualityZoneProducts = []; let valueZoneSoldOut = []; let qualityZoneSoldOut = []; const debugInfo = []; activityZones.each((i, zone) => { const $zone = $(zone); const title = $zone.find('.currency-title-a').text().trim(); // 支持两种产品结构:旧版(activity-row)和新版(cat-styleB/C-promotion-card) const oldProducts = $zone.find('.activity-row a.list-product-content'); const newProducts = $zone.find('.cat-styleB-promotion-card, .cat-styleC-promotion-card'); // 处理旧版产品结构 const oldValidProducts = oldProducts.filter((i, el) => { const href = $(el).attr('href'); const isNotAffiliate = !href.includes('fid=9'); const isNotSoldOut = !$(el).find('.list-product-desc').text().includes('已售'); return isNotAffiliate && isNotSoldOut; }); const oldSoldOutProducts = oldProducts.filter((i, el) => { const href = $(el).attr('href'); const isNotAffiliate = !href.includes('fid=9'); const isSoldOut = $(el).find('.list-product-desc').text().includes('已售'); return isNotAffiliate && isSoldOut; }); // 处理新版产品结构 const newValidProducts = newProducts.filter((i, el) => { return !$(el).find('.form-footer-butt').hasClass('disableButton'); }); const newSoldOutProducts = newProducts.filter((i, el) => { return $(el).find('.form-footer-butt').hasClass('disableButton'); }); // 合并新旧产品 const validProducts = [...oldValidProducts, ...newValidProducts]; const soldOutProducts = [...oldSoldOutProducts, ...newSoldOutProducts]; debugInfo.push({ title, total: oldProducts.length + newProducts.length, valid: validProducts.length, soldOut: soldOutProducts.length }); if (title.includes('性价比专区')) { valueZoneProducts = validProducts; valueZoneSoldOut = soldOutProducts; } if (title.includes('质量型专区')) { qualityZoneProducts = validProducts; qualityZoneSoldOut = soldOutProducts; } }); // 输出调试信息 console.log(`\n检测到${activityZones.length}个活动专区:`); debugInfo.forEach(info => { console.log(`- [${info.title}] 总产品: ${info.total}, 可购买: ${info.valid}, 已售罄: ${info.soldOut}`); }); // 状态判断逻辑 if (valueZoneProducts.length > 0 || qualityZoneProducts.length > 0) { let productInfo = ''; // 生成性价比专区产品列表(严格按空行要求) if (valueZoneProducts.length > 0) { productInfo += formatProducts('性价比专区!预算党优选云服务器!', valueZoneProducts); } // 生成质量型专区产品列表(两个专区之间空两行) if (qualityZoneProducts.length > 0) { if (productInfo) productInfo += '\n\n'; productInfo += formatProducts('质量型专区!需求稳定用户优选!', qualityZoneProducts); } console.log(productInfo); notifier.notify({ title: '活动产品上架提醒', message: `监控的活动产品已经上架!${productInfo.replace(/\\n/g, '\n')}`, sound: true, wait: true }); process.exit(0); } else if (valueZoneSoldOut.length > 0 || qualityZoneSoldOut.length > 0) { console.log('商品已售罄'); } else { console.log(`[${new Date().toLocaleString()}] 暂未上架,继续监控...`); } } // 立即执行一次 checkActivity(); // 定时执行 setInterval(checkActivity, checkInterval);