feat: 新增免费演出雷达工具与定投评估功能,优化周报提醒逻辑

1. 新增免费演出雷达工具:含秀动/猫眼/大麦平台抓取、本地代理鉴权配置
2. 新增建行定投评估模块:四大指数PE-TTM估值与定投档位建议
3. 新增推送告警白名单过滤功能,限制推送指定指数告警
4. 重构周报首页提醒逻辑,移至服务端处理并减少前端请求
5. 新增多套调试探针脚本与单元测试用例
This commit is contained in:
yangxiangyuan
2026-08-04 17:33:14 +08:00
parent af5d0f43a5
commit 6a7a2b089a
25 changed files with 3499 additions and 121 deletions
@@ -0,0 +1,136 @@
/**
* 探针脚本:验证「建行定投评估」所需数据源
* 目标:
* - 沪深300 / 中证500:PE-TTM + 近10年PE历史(算分位)
* - 标普500 / 纳指100:实时 PE-TTM(尽力找10年历史)
* 用法:node dev_test_scripts/debug/debug_dip_eval_sources.js
*/
const axios = require('axios')
const UA = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Referer': 'https://quote.eastmoney.com/' }
const cut = (s, n = 500) => String(s == null ? '' : s).replace(/\s+/g, ' ').slice(0, n)
// ---------- 1. 东方财富数据中心「估值分析」(A股指数每日 PE_TTM 历史) ----------
const probeEmValuation = async (code, pageSize = 3) => {
const url = `https://datacenter-web.eastmoney.com/api/data/v1/get?reportName=RPT_VALUEANALYSIS_DET&columns=ALL&filter=(SECURITY_CODE%3D%22${code}%22)&pageNumber=1&pageSize=${pageSize}&sortTypes=-1&sortColumns=TRADE_DATE&source=WEB&client=WEB`
try {
const res = await axios.get(url, { timeout: 10000, headers: UA, validateStatus: () => true })
const rows = res.data && res.data.result && res.data.result.data
if (!Array.isArray(rows) || !rows.length) return console.log(` [东财估值分析 ${code}] 无数据 status=${res.status} body=${cut(JSON.stringify(res.data), 300)}`)
const r = rows[0]
console.log(` [东财估值分析 ${code}] OK 最新=${r.TRADE_DATE} PE_TTM=${r.PE_TTM} PE静态=${r.PE_LAR} PE动=${r.PE_TTM_RATIO || r.DYNAMIC_PE} 字段=${Object.keys(r).join(',')}`)
} catch (e) {
console.log(` [东财估值分析 ${code}] 异常: ${e.message}`)
}
}
// ---------- 2. 东方财富 push2 实时字段(f162/f163/f164/f167) ----------
const probePush2 = async (secid) => {
const url = `https://push2.eastmoney.com/api/qt/stock/get?secid=${secid}&fields=f43,f57,f58,f162,f163,f164,f167&invt=2&fltt=2`
try {
const res = await axios.get(url, { timeout: 10000, headers: UA, validateStatus: () => true })
const d = res.data && res.data.data
if (!d) return console.log(` [push2 ${secid}] 无数据 body=${cut(JSON.stringify(res.data), 300)}`)
console.log(` [push2 ${secid}] OK 名称=${d.f58} 点位=${d.f43} f162=${d.f162} f163=${d.f163} f164=${d.f164} f167=${d.f167}`)
} catch (e) {
console.log(` [push2 ${secid}] 异常: ${e.message}`)
}
}
// ---------- 3. 腾讯美股指数 ----------
const probeTencent = async (symbols) => {
const iconv = require('iconv-lite')
const url = `http://qt.gtimg.cn/q=${symbols.join(',')}`
try {
const res = await axios.get(url, { timeout: 10000, responseType: 'arraybuffer', validateStatus: () => true })
const text = iconv.decode(Buffer.from(res.data), 'gb18030')
for (const line of String(text).split(/;\s*/).filter(Boolean)) {
console.log(` [腾讯] ${cut(line, 600)}`)
}
} catch (e) {
console.log(` [腾讯 ${symbols}] 异常: ${e.message}`)
}
}
// ---------- 4. 新浪美股指数 ----------
const probeSina = async (symbols) => {
const iconv = require('iconv-lite')
const url = `https://hq.sinajs.cn/list=${symbols.join(',')}`
try {
const res = await axios.get(url, { timeout: 10000, responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0', 'Referer': 'https://finance.sina.com.cn' }, validateStatus: () => true })
const text = iconv.decode(Buffer.from(res.data), 'gb18030')
for (const line of String(text).split(/\r?\n/).filter(Boolean)) {
console.log(` [新浪] ${cut(line, 600)}`)
}
} catch (e) {
console.log(` [新浪 ${symbols}] 异常: ${e.message}`)
}
}
// ---------- 5. multpl.com 标普500 PE(当前 + 月度历史) ----------
const probeMultpl = async () => {
try {
const res = await axios.get('https://www.multpl.com/s-p-500-pe-ratio', { timeout: 15000, headers: { 'User-Agent': UA['User-Agent'] }, validateStatus: () => true })
const m = String(res.data).match(/Current S&P 500 PE Ratio[\s\S]{0,400}?([0-9]+\.[0-9]+)/i)
console.log(` [multpl 当前PE] status=${res.status} 匹配=${m ? m[1] : '未匹配'} 片段=${cut(res.data.match(/Current S&P 500 PE Ratio[\s\S]{0,200}/i), 260)}`)
} catch (e) {
console.log(` [multpl 当前PE] 异常: ${e.message}`)
}
try {
const res = await axios.get('https://www.multpl.com/s-p-500-pe-ratio/table/by-month', { timeout: 15000, headers: { 'User-Agent': UA['User-Agent'] }, validateStatus: () => true })
const html = String(res.data)
const rows = html.match(/<tr>[\s\S]*?<\/tr>/g) || []
console.log(` [multpl 月度历史] status=${res.status} 表格行数=${rows.length} 示例=${cut(rows.slice(1, 4).join(' | '), 300)}`)
} catch (e) {
console.log(` [multpl 月度历史] 异常: ${e.message}`)
}
}
// ---------- 6. WSJ 指数页(NDX / SPX) ----------
const probeWsj = async (path) => {
const url = `https://www.wsj.com/market-data/quotes/index/${path}`
try {
const res = await axios.get(url, { timeout: 15000, headers: { 'User-Agent': UA['User-Agent'], 'Accept': 'text/html' }, validateStatus: () => true })
const html = String(res.data)
const m = html.match(/P\/E[\s\S]{0,120}?([0-9][0-9,]*\.?[0-9]*)/i)
console.log(` [WSJ ${path}] status=${res.status} len=${html.length} PE匹配=${m ? m[1] : '未匹配'}`)
} catch (e) {
console.log(` [WSJ ${path}] 异常: ${e.message}`)
}
}
// ---------- 7. 东财全球指数 push2(美股 secid=100.*) ----------
const probeEmGlobal = async () => {
await probePush2('100.INX') // 标普500
await probePush2('100.NDX') // 纳指100(猜测代码)
await probePush2('100.DJIA') // 道指对照
}
;(async () => {
console.log('=== A股指数:东财数据中心估值分析(近10年每日PE_TTM) ===')
await probeEmValuation('000300')
await probeEmValuation('000905')
console.log('\n=== A股指数:push2 实时字段 ===')
await probePush2('1.000300')
await probePush2('1.000905')
console.log('\n=== 美股指数:腾讯 ===')
await probeTencent(['usINX', 'usIXIC', 'usDJI', 'us.NDX'])
console.log('\n=== 美股指数:新浪 ===')
await probeSina(['gb_$inx', 'gb_$ixic', 'gb_$dji', 'gb_$ndx'])
console.log('\n=== 美股指数:东财全球指数 push2 ===')
await probeEmGlobal()
console.log('\n=== 标普500:multpl.com ===')
await probeMultpl()
console.log('\n=== 美股指数:WSJ ===')
await probeWsj('NDX')
await probeWsj('SPX')
console.log('\n探针结束')
})()