UNPKG

node-red-contrib-sina-finance

Version:
278 lines (249 loc) 12.9 kB
module.exports = function(RED) { "use strict"; var http = require('http'); var iconv = require('iconv-lite'); function SinaStockNode(config) { RED.nodes.createNode(this, config); var node = this; this.stockCodes = config.stockCodes || ""; this.outputLanguage = config.outputLanguage || "en"; // 格式化股票数据为更友好的JSON格式 function formatStockData(stockInfoArray, codeArray) { var result = []; // 根据语言选择键名 const keyNames = node.outputLanguage === "zh" ? { code: "股票代码", name: "股票名称", open: "开盘价", previousClose: "昨收价", price: "当前价格", high: "最高价", low: "最低价", bid: "竞买价", ask: "竞卖价", volume: "成交量", amount: "成交额", bidVolume1: "买一量", bidPrice1: "买一价", bidVolume2: "买二量", bidPrice2: "买二价", bidVolume3: "买三量", bidPrice3: "买三价", bidVolume4: "买四量", bidPrice4: "买四价", bidVolume5: "买五量", bidPrice5: "买五价", askVolume1: "卖一量", askPrice1: "卖一价", askVolume2: "卖二量", askPrice2: "卖二价", askVolume3: "卖三量", askPrice3: "卖三价", askVolume4: "卖四量", askPrice4: "卖四价", askVolume5: "卖五量", askPrice5: "卖五价", date: "日期", time: "时间" } : { code: "code", name: "name", open: "open", previousClose: "previousClose", price: "price", high: "high", low: "low", bid: "bid", ask: "ask", volume: "volume", amount: "amount", bidVolume1: "bidVolume1", bidPrice1: "bidPrice1", bidVolume2: "bidVolume2", bidPrice2: "bidPrice2", bidVolume3: "bidVolume3", bidPrice3: "bidPrice3", bidVolume4: "bidVolume4", bidPrice4: "bidPrice4", bidVolume5: "bidVolume5", bidPrice5: "bidPrice5", askVolume1: "askVolume1", askPrice1: "askPrice1", askVolume2: "askVolume2", askPrice2: "askPrice2", askVolume3: "askVolume3", askPrice3: "askPrice3", askVolume4: "askVolume4", askPrice4: "askPrice4", askVolume5: "askVolume5", askPrice5: "askPrice5", date: "date", time: "time" }; for (var i = 0; i < stockInfoArray.length; i++) { var stockNode = stockInfoArray[i].split(','); if (stockNode.length < 32) { node.log("警告: 股票数据格式不完整: " + stockInfoArray[i]); continue; } var stockData = {}; // 使用传入的股票代码作为code值 stockData[keyNames.code] = codeArray[i]; stockData[keyNames.name] = stockNode[0]; stockData[keyNames.open] = parseFloat(stockNode[1]); stockData[keyNames.previousClose] = parseFloat(stockNode[2]); stockData[keyNames.price] = parseFloat(stockNode[3]); stockData[keyNames.high] = parseFloat(stockNode[4]); stockData[keyNames.low] = parseFloat(stockNode[5]); stockData[keyNames.bid] = parseFloat(stockNode[6]); stockData[keyNames.ask] = parseFloat(stockNode[7]); stockData[keyNames.volume] = parseInt(stockNode[8]); stockData[keyNames.amount] = parseFloat(stockNode[9]); stockData[keyNames.bidVolume1] = parseInt(stockNode[10]); stockData[keyNames.bidPrice1] = parseFloat(stockNode[11]); stockData[keyNames.bidVolume2] = parseInt(stockNode[12]); stockData[keyNames.bidPrice2] = parseFloat(stockNode[13]); stockData[keyNames.bidVolume3] = parseInt(stockNode[14]); stockData[keyNames.bidPrice3] = parseFloat(stockNode[15]); stockData[keyNames.bidVolume4] = parseInt(stockNode[16]); stockData[keyNames.bidPrice4] = parseFloat(stockNode[17]); stockData[keyNames.bidVolume5] = parseInt(stockNode[18]); stockData[keyNames.bidPrice5] = parseFloat(stockNode[19]); stockData[keyNames.askVolume1] = parseInt(stockNode[20]); stockData[keyNames.askPrice1] = parseFloat(stockNode[21]); stockData[keyNames.askVolume2] = parseInt(stockNode[22]); stockData[keyNames.askPrice2] = parseFloat(stockNode[23]); stockData[keyNames.askVolume3] = parseInt(stockNode[24]); stockData[keyNames.askPrice3] = parseFloat(stockNode[25]); stockData[keyNames.askVolume4] = parseInt(stockNode[26]); stockData[keyNames.askPrice4] = parseFloat(stockNode[27]); stockData[keyNames.askVolume5] = parseInt(stockNode[28]); stockData[keyNames.askPrice5] = parseFloat(stockNode[29]); stockData[keyNames.date] = stockNode[30]; stockData[keyNames.time] = stockNode[31]; result.push(stockData); } return result; } // 直接调用新浪API获取数据 function directApiCall(codes) { return new Promise((resolve, reject) => { // 构建API URL var list = codes.split(','); var url = 'http://hq.sinajs.cn/list=' + codes; node.log("API URL: " + url); var options = { headers: { 'referer': 'https://finance.sina.com.cn/' } }; http.get(url, options, function(res) { var chunks = []; res.on('data', function(chunk) { chunks.push(chunk); }); res.on('end', function() { var buffer = Buffer.concat(chunks); var str = iconv.decode(buffer, 'GBK'); // 新浪API返回的是GBK编码 node.log("API返回数据长度: " + str.length); if (str.length < 20) { node.log("API返回数据内容: " + str); // 如果数据很少,直接显示 } else { node.log("API返回数据摘要: " + str.substring(0, 100) + "..."); } var stockInfoArray = []; var foundCodeArray = []; // 解析返回的数据 list.forEach(function(code) { var pattern = new RegExp('var hq_str_' + code + '="(.+?)";'); var match = str.match(pattern); if (match && match[1]) { var stockInfo = match[1]; node.log("找到股票 " + code + " 数据: " + stockInfo.substring(0, 20) + "..."); stockInfoArray.push(stockInfo); foundCodeArray.push(code); } else { node.warn("未找到股票 " + code + " 的数据"); } }); resolve({data: stockInfoArray, codes: foundCodeArray}); }); res.on('error', function(err) { node.error("API请求错误: " + err.message); reject(err); }); }).on('error', function(err) { node.error("HTTP请求错误: " + err.message); reject(err); }); }); } // 获取股票数据 function getStockData() { if (node.stockCodes) { node.log("正在获取股票数据,股票代码: " + node.stockCodes); // 确保股票代码前缀正确 let codes = node.stockCodes.split(',').map(code => { code = code.trim(); // 如果代码不包含前缀,添加默认前缀 if (!code.startsWith('sh') && !code.startsWith('sz') && !code.startsWith('bj')) { // 根据代码规则添加前缀 if (code.startsWith('6')) { return 'sh' + code; } else if (code.startsWith('0') || code.startsWith('3')) { return 'sz' + code; } else if (code.startsWith('4') || code.startsWith('8')) { return 'bj' + code; } } return code; }).join(','); node.log("格式化后的股票代码: " + codes); // 直接调用API获取数据 directApiCall(codes).then(function(result) { var stockInfoArray = result.data; var foundCodeArray = result.codes; if (!stockInfoArray || stockInfoArray.length === 0) { node.warn("API调用未返回股票数据"); node.status({fill:"red", shape:"dot", text:"获取数据失败"}); return; } var formattedData = formatStockData(stockInfoArray, foundCodeArray); node.log("API调用:格式化后的数据条数: " + formattedData.length); var msg = { payload: formattedData }; node.status({fill:"green", shape:"dot", text:"获得 " + formattedData.length + "条数据"}); node.send(msg); }).catch(function(err) { node.error("API调用出错: " + err); node.status({fill:"red", shape:"dot", text:"API错误"}); }); } else { node.status({fill:"yellow", shape:"ring", text:"未配置股票代码"}); } } // 节点输入触发更新 node.on('input', function(msg) { // 如果消息中有股票代码,则使用消息中的代码 if (msg.stockCodes) { node.stockCodes = msg.stockCodes; node.log("从输入消息stockCodes属性获取股票代码: " + node.stockCodes); } else if (msg.payload && typeof msg.payload === 'string' && msg.payload.trim() !== '') { node.stockCodes = msg.payload; node.log("从输入消息获取股票代码: " + node.stockCodes); } // 如果消息中有输出语言设置,则使用消息中的设置 if (msg.outputLanguage) { node.outputLanguage = msg.outputLanguage === "zh" ? "zh" : "en"; node.log("从输入消息获取输出语言设置: " + node.outputLanguage); } getStockData(); }); // 节点关闭时清理 this.on('close', function() { node.status({}); }); // 初始状态 node.status({fill:"blue", shape:"ring", text:"准备就绪"}); } RED.nodes.registerType("sina-stock", SinaStockNode); }