You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

YFinance Flask Web应用无法显示非美国市场股票价格问题求助

YFinance Flask Web应用无法显示非美国市场股票价格问题求助

我最近做了一个用yfinance、Flask和JavaScript搭建的股票价格监控网页,美国市场的股票(比如AAPL、MSFT)都能正常显示实时价格和涨跌幅,但非美国市场的股票就出问题了——我看浏览器的网络请求明明已经拿到了数据,但前端页面就是不显示任何内容,也没有报错提示。我怀疑是数据格式或者前端选择器的问题,但自己排查了半天没找到根源,有没有大佬能帮我看看?

我的代码结构

我把代码分成了后端Flask接口和前端JavaScript逻辑两部分,具体代码如下:

Flask 后端代码

import yfinance as yf
from flask import request, render_template, jsonify, Flask

app = Flask(__name__, template_folder='templates')

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/get_stock_data', methods=['POST'])
def get_stock_data():
    ticker = request.get_json()['ticker']
    data = yf.Ticker(ticker).history(period='1y')
    
    if data.empty:
        return jsonify({"error": "No data found for the given ticker."})
    
    return jsonify({
        "currentPrice": data.iloc[-1].Close,
        "openPrice": data.iloc[-1].Open
    })

if __name__ == '__main__':
    app.run(debug=True)

前端JavaScript代码

var tickers = JSON.parse(localStorage.getItem('tickers')) || [];
var lastPrices = {};
var counter = 10;

function startUpdateCycle() {
    updatePrices();
    setInterval(function() {
        counter--;
        $('#counter').text(counter);
        if (counter <= 0) {
            updatePrices();
            counter = 10;
        }
    }, 1000);
}

$(document).ready(function() {
    tickers.forEach(function(ticker) {
        addTickerToGrid(ticker);
    });

    $('#tickers').on('click', '.remove-btn', function() {
        var tickerToRemove = $(this).data('ticker');
        tickers = tickers.filter(t => t !== tickerToRemove);
        localStorage.setItem('tickers', JSON.stringify(tickers));
        $('#'+tickerToRemove).remove();
    });

    updatePrices();

    $('#add-ticker-form').submit(function(e) {
        e.preventDefault();
        var newTicker = $('#new-ticker').val().toUpperCase();
        if (!tickers.includes(newTicker)) {
            tickers.push(newTicker);
            localStorage.setItem('tickers', JSON.stringify(tickers))
            addTickerToGrid(newTicker);
        }
        $('new-ticker').val('');
        updatePrices();
    });

    $('#tickers-grid').on('click', '.remove-btn', function() {
        var tickerToRemove = $(this).data('ticker');
        tickers = tickers.filter(t => t !== tickerToRemove);
        localStorage.setItem('tickers', JSON.stringify(tickers));
        $('#'+tickerToRemove).remove();
    });

    startUpdateCycle();
});

function addTickerToGrid(ticker) {
    $('#tickers-grid').append(`<div id="${ticker}" class="stock-box"><h2>${ticker}</h2><p id="${ticker}-price"></p><p id="${ticker}-pct"></p><button class="remove-btn" data-ticker="${ticker}">Remove</button></div>`);
}

function updatePrices() {
    tickers.forEach(function(ticker) {
        $.ajax({
            url: '/get_stock_data',
            type : 'POST',
            data: JSON.stringify({ticker: ticker}),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (data) {
                var changePercent = ((data.currentPrice - data.openPrice) / data.openPrice) * 100;
                var colorClass;

                if (changePercent <= -2) {
                    colorClass = 'dark-red'
                } else if (changePercent <= 0) {
                    colorClass = 'red'
                } else if (changePercent == 0) {
                    colorClass = 'gray'
                } else if (changePercent <= 2) {
                    colorClass = 'green'
                } else {
                    colorClass = 'dark-green'
                }

                $('#'+ticker+'-price').text('$'+data.currentPrice.toFixed(2));
                $('#'+ticker+'-pct').text(changePercent.toFixed(2)+'%');
                
                $('#'+ticker+'-price').removeClass("dark-red red gray green dark-green").addClass(colorClass);
                $('#'+ticker+'-pct').removeClass("dark-red red gray green dark-green").addClass(colorClass);

                var flashClass;
                if (lastPrices[ticker] > data.currentPrice) {
                    flashClass = 'red-flash';
                } else if (lastPrices[ticker] < data.currentPrice) {
                    flashClass = 'green-flash';
                } else {
                    flashClass = 'gray-flash';
                }

                lastPrices[ticker] = data.currentPrice;
                $('#'+ticker).addClass(flashClass);
                setTimeout(function() {
                    $('#'+ticker).removeClass(flashClass);
                },  1000);
            }
        });
    });
}

问题现象

  • 美国市场股票(如AAPL、GOOGL):添加后能正常显示当前价格、涨跌幅,更新也正常。
  • 非美国市场股票(如港股0700.HK、德国BMW.DE):网络请求能成功返回数据(在浏览器开发者工具的Network面板里能看到currentPrice和openPrice的数值),但页面上对应的股票卡片里的价格和涨跌幅始终是空的,没有任何内容显示。

我自己排查的点

  1. 确认非美国股票的ticker格式正确(比如0700.HK、BMW.DE),yfinance能拿到数据,后端接口也返回了正确的JSON数据。
  2. 检查前端的success回调是否执行:在回调里加console.log(data),能看到正确的currentPrice和openPrice数值,说明回调是正常触发的。

现在实在不知道哪里出问题了,求大佬们指点!


问题根源与解决方案

看了你的代码和问题描述,我立刻发现了几个核心问题,其中第一个就是导致非美国股票不显示的关键:

1. jQuery选择器无法识别含特殊字符的ID

非美国股票的ticker通常包含.(比如0700.HK、BMW.DE),而.在jQuery/CSS选择器中是类选择器的标识。举个例子:
你创建的价格元素id是0700.HK-price,但你用$('#0700.HK-price')去选择时,jQuery会把它解析为「寻找id为0700且class为HK-price的元素」,完全匹配不到实际的元素,自然无法设置价格文本。

修复方案(二选一)

  • 方案A:使用属性选择器替代ID选择器
    updatePrices函数中,把所有通过id选择元素的代码改成属性选择器,比如:

    // 替换原有的价格设置代码
    $('[id="'+ticker+'-price"]').text('$'+data.currentPrice.toFixed(2));
    $('[id="'+ticker+'-pct"]').text(changePercent.toFixed(2)+'%');
    
    // 替换样式设置代码
    $('[id="'+ticker+'-price"]').removeClass("dark-red red gray green dark-green").addClass(colorClass);
    $('[id="'+ticker+'-pct"]').removeClass("dark-red red gray green dark-green").addClass(colorClass);
    
    // 替换闪烁效果的代码
    $('[id="'+ticker+'"]').addClass(flashClass);
    setTimeout(function() {
        $('[id="'+ticker+'"]').removeClass(flashClass);
    },  1000);
    

    属性选择器会完全匹配id的字符串内容,不会解析特殊字符。

  • 方案B:对ticker的特殊字符进行转义/替换
    在创建元素时,把ticker里的特殊字符(比如.)替换为下划线_,同时保留原始ticker在data属性中:

    function addTickerToGrid(ticker) {
        // 替换特殊字符为下划线,生成安全的ID
        var safeId = ticker.replace(/[.#$&+,:;=?@'<>]/g, '_');
        $('#tickers-grid').append(`<div id="${safeId}" class="stock-box" data-ticker="${ticker}"><h2>${ticker}</h2><p id="${safeId}-price"></p><p id="${safeId}-pct"></p><button class="remove-btn" data-ticker="${ticker}">Remove</button></div>`);
    }
    

    然后在updatePrices函数中,通过元素的data-ticker获取原始ticker,用safeId来选择元素。

2. 潜在的JSON序列化问题

yfinance返回的价格是numpy的数值类型(比如numpy.float64),虽然Flask的jsonify通常能处理,但为了避免跨环境的序列化问题,建议在后端转成Python原生的float类型:

return jsonify({
    "currentPrice": float(data.iloc[-1].Close),
    "openPrice": float(data.iloc[-1].Open)
})

3. 前端输入框清空的小BUG

原代码中提交表单后清空输入框的代码是$('new-ticker').val('');,缺少了ID选择器的#,应该改为:

$('#new-ticker').val('');

额外提示:非美国股票的ticker格式

确保你输入的非美国股票ticker是yfinance支持的格式,比如:

  • 港股:0700.HK(不能只输0700)
  • 德国股票:BMW.DE
  • 中国A股:比如贵州茅台是600519.SS(上交所)、宁德时代是300750.SZ(深交所)

按照上面的方法修改后,非美国股票的价格应该就能正常显示了!


备注:内容来源于stack exchange,提问作者Salih

火山引擎 最新活动