'Historical Yahoo Finance API On Fritz Again?

I've been successfully using the Yahoo Finance API cookie/crumb technique to get historical stock quotes for many years. On April 28th, 2022, it broke, and excessive exercise of vocabulary has failed to fix it.

The technique is to lookup an illegible stock symbol, because part of the returned cookie can be used to obtain real historical data on successive uses of the historical Yahoo API. I've tried it with illegible and legitimate stock symbols. I'm using Excel 2019 VBA, and the debug seems to hang on the ".waitForResponse (10)" instruction. It's part of a loop, and it hangs on the first instance. The code is shown below. It successfully writes a zero on ws1.S10, but it fails to do anything after the "Next cook" instruction. Did Yahoo intentionally break the Finance API again, or did Microsoft "improve" Excel? Or, more likely, did I do something stump stupid, like turn on the computer? Thanks!

Sub HistUp()

Dim resultFromYahoo, csv_rows() As String
Dim objRequest
Dim resultArray As Variant
Dim eagle, nColumns, cook, iRows, iCols As Integer
Dim CSV_Fields As Variant
Dim ticker, tickerURL, cookie, crumb As String
Dim HistQuote, HistDiv, DefaultKey As String
Dim Curr, StartPer As String
Dim fox, sheep, bear, elk, wolf, raccoon, snake As Integer
Dim julian, ricky, bubbles As Double
Dim crumbStartPos, crumbEndPos, Lastrow1, Lastrow2 As Long

Set wb = ThisWorkbook
Set ws1 = wb.Worksheets(1)
Set ws2 = wb.Worksheets(2)
Set ws3 = wb.Worksheets(3)
Set ws4 = wb.Worksheets(4)
Set ws5 = wb.Worksheets(5)

Application.EnableEvents = False
Application.DisplayAlerts = False
eagle = ActiveSheet.Index
wb.Worksheets("Warn").Select
wb.Worksheets("Warn").Range("A1").Select
DoEvents

'getCookieCrumb
For cook = 0 To 5  'ask for a valid crumb 6 times
    ws1.Range("S10") = cook
    Set objRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
    With objRequest
        .Open "GET", "https://finance.yahoo.com/lookup?s=turpitude", False
        .setRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
        .send
        .waitForResponse (10)
        cookie = Split(.getResponseHeader("Set-Cookie"), ";")(0)
        crumbStartPos = InStrRev(.ResponseText, """crumb"":""") + 9
        crumbEndPos = crumbStartPos + 11
        crumb = Mid(.ResponseText, crumbStartPos, crumbEndPos - crumbStartPos)
    End With
    
    If Len(crumb) = 11 Then 'a valid crumb is 11 characters long
        Exit For
    End If
Next cook


Solution 1:[1]

I have the same problem. Looks like Yahoo changed the Yahoo finance API. When I comment out the line: cookie = Split(.getResponseHeader("Set-Cookie"), ";")(0) the code seems to work as before.

Then also comment out '.setRequestHeader "Cookie", Cookie when you send the request. Rest of my code:

'CONSTRUCT THE URL:
'interval=1d or 1wk or 1mo, events=dividends or events=history (prices) or events=splits
    WebRequestURL = "https://query1.finance.yahoo.com/v7/finance/download/" & StockSymbol & _
        "?period1=" & UnixStartDate & "&period2=" & UnixEndDate & _
        "&interval=" & UrlInterval & "&events=" & UrlEvents & "&crumb=" & Crumb

'FETCH THE DATA:
    With WebRequest
        .Open "GET", WebRequestURL, False
        '.setRequestHeader "Cookie", Cookie
        .Send
        .waitForResponse (10)
    End With

Solution 2:[2]

I have recently experienced the same problem, after trying the VBA on a different laptop the issue is also the same. The macro I have been using is similar and only recently stopped working.. I'm unsure how to fix the problem using the posts above

Solution 3:[3]

jp90025, thanks - commenting out the 2 line has worked for me!

Solution 4:[4]

As people have commented, the "Set-Cookie" header is no longer in the response, however the crumbstore is still there, so I would recommend checking to see if the header exists, and if not continue without setting that header or including the crumb.

Here is more robust code that allows you to select if you want price history, dividends, or split. You can also select a date range, by converting the dates to POSIX. It can also accommodate a proxy server. Feel free to comment on any improvements I can make to this code.

'New Yahoo Data Method
'sEvent:  history, div, split
'sInterval: 1d,1wk,1mo
Public Function GetNewYahooData(sSymbol As String, sStart As String, sEnd As String, sEvent As String, sInterval As String, sProxy As String) As String
On Error GoTo Error_Message
    
    Const sTestURL = "https://query1.finance.yahoo.com/v7/finance/download/"
    Const sBaseURL = "https://finance.yahoo.com/quote/^GSPC"
    'This assumes the crumb appears like this:  "CrumbStore":{"crumb":"taEvjA8DFqs"}
    Const sCrumbStart = """CrumbStore"":{""crumb"":"""
    Const sCrumbEnd = """"
    Const sTickerReplace = "TTTT"
    Const sPeriod1Replace = "pppppppp"
    Const sPeriod2Replace = "qqqqqqqq"
    Const sEventReplace = "eeeeeeee"
    Const sCrumbReplace = "cccccccc"
    Const sIntervalReplace = "iiiiiiii"
    
    Dim sReturn As String
    Dim sTemURL As String
    Dim sCookie As String
    Dim sCrumb As String
    Dim dtStart As Date
    Dim dtEnd As Date
    Dim lngCrumbStart As Long
    Dim lngCrumbEnd As Long
    Dim objRequest As WinHttp.WinHttpRequest
    Dim sContentType As String
    Dim bolHaveCrumb As Boolean
    
    dtStart = CDate(sStart)
    dtEnd = CDate(sEnd)
    bolHaveCrumb = False
    
    'Perform a Yahoo financial lookup on SP500 to get the crumb
     Set objRequest = New WinHttp.WinHttpRequest
     With objRequest
        If Len(sProxy) > 0 Then .SetProxy 2, sProxy, ""
        .Open "GET", sBaseURL, False
        .SetRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
        .sEnd
        .WaitForResponse (10)
        sReturn = .ResponseText
        
        If InStr(1, sReturn, sCrumbStart) > 0 Then
            lngCrumbStart = InStr(1, sReturn, sCrumbStart) + 23
            lngCrumbEnd = InStr(lngCrumbStart, sReturn, sCrumbEnd)
            sCrumb = Mid(sReturn, lngCrumbStart, lngCrumbEnd - lngCrumbStart)
            sCookie = .GetAllResponseHeaders
            
            If InStr(sCookie, "Set-Cookie") > 0 Then
                sCookie = Split(.GetResponseHeader("Set-Cookie"), ";")(0)
                bolHaveCrumb = True
            Else
                bolHaveCrumb = False
            End If
        End If
    End With
    
    'This is currently https://query1.finance.yahoo.com/v7/finance/download/TTTT?period1=pppppppp&period2=qqqqqqqq&interval=iiiiiiii&events=eeeeeeee&crumb=cccccccc
    sTemURL = gYAHOO_HIS_URL
    sTemURL = Replace(sTemURL, sIntervalReplace, sInterval)
    sTemURL = Replace(sTemURL, sTickerReplace, sSymbol)
    sTemURL = Replace(sTemURL, sPeriod1Replace, toPOSIX(dtStart))
    sTemURL = Replace(sTemURL, sPeriod2Replace, toPOSIX(dtEnd))
    sTemURL = Replace(sTemURL, sEventReplace, sEvent)
    
    If bolHaveCrumb Then
        sTemURL = Replace(sTemURL, sCrumbReplace, sCrumb)
    Else
        sTemURL = Replace(sTemURL, "&" & sCrumbReplace, "")
    End If
    
    Set objRequest = New WinHttp.WinHttpRequest
    With objRequest
        If Len(sProxy) > 0 Then .SetProxy 2, sProxy, ""
        .Open "GET", sTemURL, False
        If bolHaveCrumb Then .SetRequestHeader "Cookie", sCookie
        .sEnd
        .WaitForResponse (10)
        sContentType = .GetResponseHeader("Content-Type")
        sReturn = StrConv(.ResponseText, vbUnicode)
        sReturn = StrConv(sReturn, vbFromUnicode)
    End With
    
    If Len(sReturn) > 0 Then
        GetNewYahooData = sReturn
    Else
        GetNewYahooData = ""
    End If
    Exit Function
    
Error_Message:
    MsgBox err.Description, vbCritical, "Yahoo Price Retrieval"
    GetNewYahooData = ""
End Function


'Helper function to convert a date into its POSIX representation
Public Function toPOSIX(dt As Date) As Long
On Error GoTo err
    toPOSIX = DateDiff("s", "1/1/1970", dt)
    Exit Function
err:
MsgBox err.Description, vbOKOnly, "toPOSIX"
End Function

Also sometimes the first time you make the request, it might not return anything, so I usually try 5 times before giving up.

'Sometimes we won't get a response on the first try, so try 5 times
 While Not YahooEnd
    gHTMLFileHis = GetNewYahooData(txtSymbol.Text, txtStartDate.Text, txtEndDate.Text, sEvent, sInterval, txtproxy.Text)
    i = i + 1
    If Len(gHTMLFileHis) = 0 Then
        YahooSuccess = False
    Else
        arrRows = Split(gHTMLFileHis, vbLf)
        arrRow = Split(arrRows(0), ",")
        If arrRow(0) = StrConv("Date", vbUnicode) Or arrRow(0) = "Date" Then
            YahooSuccess = True
            YahooEnd = True
        End If
        If i = 5 Then YahooEnd = True
    End If
  Wend

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jp90025
Solution 2 Joe
Solution 3 user19020960
Solution 4 RESTfulInSeattle