stock-test.cjs•10.8 kB
#!/usr/bin/env node
// 股票API测试工具 - CommonJS版本,可以直接运行
// 测试 get-stock-history 和 get-market-index 两个接口
// API endpoints
const SEARCH_API = "http://searchapi.eastmoney.com/api/suggest/get";
const QUOTE_API = "http://push2.eastmoney.com/api/qt/stock/get";
const HISTORY_API = "http://push2his.eastmoney.com/api/qt/stock/kline/get";
const INDEX_API = "http://push2.eastmoney.com/api/qt/ulist.np/get";
// Common headers
const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
const COMMON_HEADERS = {
"User-Agent": USER_AGENT,
"Referer": "http://www.eastmoney.com/",
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
};
// 测试接口连通性
async function testApiConnectivity() {
console.log("🔍 测试API接口连通性...\n");
const apis = [
{ name: "股票历史数据接口", url: HISTORY_API },
{ name: "大盘指数接口", url: INDEX_API },
{ name: "股票搜索接口", url: SEARCH_API, params: "?input=茅台&type=14&token=D43BF722C8E33BDC906FB84D85E326E8&count=5" },
{ name: "股票行情接口", url: QUOTE_API }
];
for (const api of apis) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const testUrl = api.params ? api.url + api.params : api.url;
const response = await fetch(testUrl, {
method: 'GET',
headers: COMMON_HEADERS,
signal: controller.signal
});
clearTimeout(timeoutId);
if (response.ok) {
console.log(`✅ ${api.name}: 连通正常 (状态码: ${response.status})`);
} else {
console.log(`⚠️ ${api.name}: 响应异常 (状态码: ${response.status})`);
}
} catch (error) {
if (error.name === 'AbortError') {
console.log(`❌ ${api.name}: 连接超时 (5秒)`);
} else {
console.log(`❌ ${api.name}: 连接失败 - ${error.message}`);
}
}
}
console.log("");
}
// 搜索股票
async function searchStock(keyword) {
try {
const params = new URLSearchParams({
input: keyword,
type: "14",
token: "D43BF722C8E33BDC906FB84D85E326E8",
count: "5",
});
const response = await fetch(`${SEARCH_API}?${params}`, {
headers: COMMON_HEADERS,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.QuotationCodeTable.Status === 0 && data.QuotationCodeTable.Data) {
for (const item of data.QuotationCodeTable.Data) {
if (item.SecurityTypeName === "沪A" || item.SecurityTypeName === "深A") {
return [item.Code, item.Name];
}
}
}
return null;
} catch (error) {
console.error("搜索股票失败:", error);
return null;
}
}
// 获取股票市场代码
function getStockMarket(code) {
if (code.match(/^(000|002|300|301)/)) {
return [0, `0.${code}`];
} else if (code.match(/^(600|601|603|605|688)/)) {
return [1, `1.${code}`];
}
throw new Error(`不支持的股票代码格式:${code}`);
}
// 获取股票历史数据 (对应 get-stock-history)
async function getStockHistory(stockInput, period = "101", days = 30) {
try {
let stockCode = stockInput;
// 如果输入不是6位数字,尝试按名称搜索
if (!stockInput.match(/^\d{6}$/)) {
const searchResult = await searchStock(stockInput);
if (!searchResult) {
console.error(`未找到股票:${stockInput}`);
return null;
}
[stockCode] = searchResult;
console.log(`找到股票:${searchResult[1]}(${searchResult[0]})`);
}
// 获取市场代码和完整代码
const [market, fullCode] = getStockMarket(stockCode);
// 获取历史数据
const params = new URLSearchParams({
secid: fullCode,
klt: period, // 101=日K, 102=周K, 103=月K
fqt: "1", // 前复权
lmt: days.toString(),
end: "20500101",
iscca: "1",
fields1: "f1,f2,f3,f4,f5,f6",
fields2: "f51,f52,f53,f54,f55,f56,f57,f58,f59,f60,f61",
});
console.log(`📊 正在获取股票历史数据: ${stockCode} (${fullCode})`);
const response = await fetch(`${HISTORY_API}?${params}`, {
headers: COMMON_HEADERS,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.rc !== 0 || !data.data || !data.data.klines) {
throw new Error(data.msg || "获取历史数据失败");
}
const historyData = data.data.klines.map((kline) => {
const parts = kline.split(",");
return {
date: parts[0],
open: parseFloat(parts[1]),
close: parseFloat(parts[2]),
high: parseFloat(parts[3]),
low: parseFloat(parts[4]),
volume: parseInt(parts[5]) / 100, // 转换为手
amount: parseFloat(parts[6]) / 10000, // 转换为万元
changePercent: parseFloat(parts[8]),
};
});
return {
stockName: data.data.name,
stockCode: data.data.code,
data: historyData.reverse() // 最新的在前面
};
} catch (error) {
console.error("获取股票历史数据失败:", error);
return null;
}
}
// 获取大盘指数 (对应 get-market-index)
async function getMarketIndex() {
try {
const params = new URLSearchParams({
fltt: "2",
invt: "2",
fields: "f1,f2,f3,f4,f12,f13,f14",
secids: "1.000001,0.399001", // 上证指数和深证成指
});
console.log("📈 正在获取大盘指数数据...");
const response = await fetch(`${INDEX_API}?${params}`, {
headers: COMMON_HEADERS,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.rc !== 0 || !data.data || !data.data.diff) {
throw new Error(data.msg || "获取指数数据失败");
}
const indexData = data.data.diff.map((item) => ({
code: item.f12,
name: item.f14,
price: item.f2,
changePercent: item.f3,
change: item.f4,
volume: 0, // 新接口不返回成交量
amount: 0, // 新接口不返回成交额
time: new Date().toLocaleTimeString(),
}));
return indexData;
} catch (error) {
console.error("获取大盘指数失败:", error);
return null;
}
}
// 格式化历史数据
function formatHistoryData(result) {
if (!result || !result.data) {
return "无法获取历史数据";
}
const { stockName, stockCode, data } = result;
const formatPrice = (price) => price.toFixed(2);
const formatVolume = (volume) => volume.toFixed(2);
let output = `📈 ${stockName}(${stockCode}) 历史数据 (最近${data.length}个交易日)\n\n`;
// 显示最近10天的数据
const recentData = data.slice(0, Math.min(10, data.length));
output += "日期 开盘 收盘 最高 最低 成交量(手) 涨跌幅\n";
output += "─".repeat(65) + "\n";
for (const point of recentData) {
const changeColor = point.changePercent >= 0 ? "📈" : "📉";
output += `${point.date} ${formatPrice(point.open).padStart(6)} ${formatPrice(point.close).padStart(6)} ${formatPrice(point.high).padStart(6)} ${formatPrice(point.low).padStart(6)} ${formatVolume(point.volume).padStart(8)} ${changeColor}${point.changePercent.toFixed(2)}%\n`;
}
// 统计信息
const prices = data.map(d => d.close);
const maxPrice = Math.max(...prices);
const minPrice = Math.min(...prices);
const avgPrice = prices.reduce((a, b) => a + b, 0) / prices.length;
const totalVolume = data.reduce((sum, d) => sum + d.volume, 0);
output += `\n📊 统计信息:
最高价: ${formatPrice(maxPrice)}
最低价: ${formatPrice(minPrice)}
平均价: ${formatPrice(avgPrice)}
总成交量: ${formatVolume(totalVolume)}手
`;
return output;
}
// 格式化指数数据
function formatIndexData(data) {
if (!data || data.length === 0) {
return "无法获取指数数据";
}
const formatPrice = (price) => price.toFixed(2);
const formatVolume = (volume) => volume.toFixed(2);
let output = `📊 大盘指数信息\n\n`;
output += "指数名称 当前点位 涨跌幅 涨跌点 成交量(手) 成交额(万)\n";
output += "─".repeat(70) + "\n";
for (const index of data) {
const changeColor = index.changePercent >= 0 ? "📈" : "📉";
const changeSign = index.change >= 0 ? "+" : "";
output += `${index.name.padEnd(10)} ${formatPrice(index.price).padStart(8)} ${changeColor}${index.changePercent.toFixed(2)}%`.padEnd(20);
output += ` ${changeSign}${formatPrice(index.change).padStart(7)} ${formatVolume(index.volume).padStart(10)} ${formatVolume(index.amount).padStart(10)}\n`;
}
output += `\n🕐 更新时间: ${data[0]?.time || new Date().toLocaleTimeString()}`;
return output;
}
// 主函数 - 测试和示例
async function main() {
console.log("🚀 股票API测试工具启动\n");
// 1. 测试接口连通性
await testApiConnectivity();
// 2. 测试获取大盘指数
console.log("📈 测试获取大盘指数...");
try {
const indexData = await getMarketIndex();
if (indexData) {
console.log(formatIndexData(indexData));
} else {
console.log("❌ 获取大盘指数失败");
}
} catch (error) {
console.log("❌ 获取大盘指数出错:", error.message);
}
console.log("\n" + "=".repeat(80) + "\n");
// 3. 测试获取股票历史数据
console.log("📊 测试获取股票历史数据...");
const testStocks = ["600519", "000001", "贵州茅台"]; // 测试不同的输入格式
for (const stock of testStocks) {
console.log(`\n🔍 测试股票: ${stock}`);
try {
const historyResult = await getStockHistory(stock, "101", 10); // 获取最近10天数据
if (historyResult) {
console.log(formatHistoryData(historyResult));
} else {
console.log(`❌ 获取股票 ${stock} 历史数据失败`);
}
} catch (error) {
console.log(`❌ 获取股票 ${stock} 历史数据出错:`, error.message);
}
// 避免请求过于频繁
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log("\n✅ 测试完成!");
}
// 如果直接运行此文件,则执行测试
if (require.main === module) {
main().catch(console.error);
}
// 导出函数供其他模块使用
module.exports = {
testApiConnectivity,
getStockHistory,
getMarketIndex,
searchStock,
formatHistoryData,
formatIndexData
};