yahoo-finance-api icon indicating copy to clipboard operation
yahoo-finance-api copied to clipboard

Statistics tab

Open 8ctopus opened this issue 1 year ago • 10 comments

Hallo Christian,

I've just started playing around with your package and like it. Thank you! I've managed to get basic quote information such as trailing PE, however I fail to access the information that is found on the statistics page such as payout ratio. Looking at past issues, I have found #35 which is marked as completed, however I fail to fi /nd any of it in the source code.

Also the query link shared, fails with

{
    "finance": {
        "result": null,
        "error": {
            "code": "Unauthorized",
            "description": "Invalid Crumb"
        }
    }
}

Update: I actually figured it out, adding &crumb=mdeVssfeRhi to the request url works. I got the crumb value from a browser session on yahoo finance.

8ctopus avatar Nov 11 '23 11:11 8ctopus

To get the crumb, you need to provide two things: cookies and user agent:

curl 'https://query2.finance.yahoo.com/v1/test/getcrumb' \
  -H 'cookie: GUC=AQEBCAFlULBleUIhXwTg&s=AQAAAC76j7jx&g=ZU9e-w; A1=d=AQABBK0Lr2ICEI-bBud4SuIDLaB4bqaMNbAFEgEBCAGwUGV5Zc3ibmUB_eMBAAcIrQuvYqaMNbA&S=AQAAAggpViKy189d2OkdWsFnK_Y; A3=d=AQABBK0Lr2ICEI-bBud4SuIDLaB4bqaMNbAFEgEBCAGwUGV5Zc3ibmUB_eMBAAcIrQuvYqaMNbA&S=AQAAAggpViKy189d2OkdWsFnK_Y; A1S=d=AQABBK0Lr2ICEI-bBud4SuIDLaB4bqaMNbAFEgEBCAGwUGV5Zc3ibmUB_eMBAAcIrQuvYqaMNbA&S=AQAAAggpViKy189d2OkdWsFnK_Y; gpp=DBAA; gpp_sid=-1; gam_id=y-rpk1JRJE2uL6EDgVwC0_in.GgdLv339c~A; axids=gam=y-rpk1JRJE2uL6EDgVwC0_in.GgdLv339c~A&dv360=eS00Mm1IMHpoRTJ1RXhhNVlTUGtSVWRPdkZCWExrbjFkNH5B; tbla_id=ba643052-93dc-4ae8-a277-ee1cb7f7dab5-tuctc48e47a; cmp=t=1699788664&j=0&u=1---; PRF=t%3DZURN.SW%252BSREN.SW%26newChartbetateaser%3D0%252C1700910092680' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'

From the cookies, only A1 is necessary. So this is the smallest request I could find:

curl 'https://query2.finance.yahoo.com/v1/test/getcrumb' \
  -H 'cookie: A1=d=AQABBK0Lr2ICEI-bBud4SuIDLaB4bqaMNbAFEgEBCAGwUGV5Zc3ibmUB_eMBAAcIrQuvYqaMNbA&S=AQAAAggpViKy189d2OkdWsFnK_Y' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'

And to get A1 cookie:

curl -v -s 'https://finance.yahoo.com/quote/ZURN.SW' \
  -H 'accept-language: en-US,en;q=0.9,fr;q=0.8' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0' \
   1> /dev/null

Here's the response to the last request:

< HTTP/1.1 200 OK
< referrer-policy: no-referrer-when-downgrade
< strict-transport-security: max-age=31536000
< x-frame-options: SAMEORIGIN
< content-security-policy: frame-ancestors 'self' https://*.yahoo.com https://*.engadget.com https://*.pnr.ouryahoo.com https://pnr.ouryahoo.com https://*.search.aol.com https://*.onesearch.com https://*.publishing.oath.com https://*.aol.com; sandbox allow-downloads allow-forms allow-modals allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-presentation;
< cache-control: private, no-store, no-cache, max-age=0
< content-type: text/html; charset=utf-8
< vary: Accept-Encoding
< date: Mon, 13 Nov 2023 05:04:59 GMT
< x-envoy-upstream-service-time: 479
< server: ATS
< x-envoy-decorator-operation: finance-nodejs--mtls-production-ir2.finance-k8s.svc.yahoo.local:4080/*
< Age: 0
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expect-CT: max-age=31536000, report-uri="http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only"
< X-XSS-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< Set-Cookie: A1=d=AQABBHuuUWUCECcV2H5ND6rsZYO4zo0HI2gFEgEBAQH_UmVbZc1o0CMA_eMAAA&S=AQAAAumF6MTURk-DLOB53xHM7Bk; Expires=Tue, 12 Nov 2024 11:04:59 GMT; Max-Age=31557600; Domain=.yahoo.com; Path=/; SameSite=Lax; Secure; HttpOnly
< Set-Cookie: A3=d=AQABBHuuUWUCECcV2H5ND6rsZYO4zo0HI2gFEgEBAQH_UmVbZc1o0CMA_eMAAA&S=AQAAAumF6MTURk-DLOB53xHM7Bk; Expires=Tue, 12 Nov 2024 11:04:59 GMT; Max-Age=31557600; Domain=.yahoo.com; Path=/; SameSite=None; Secure; HttpOnly
< Set-Cookie: A1S=d=AQABBHuuUWUCECcV2H5ND6rsZYO4zo0HI2gFEgEBAQH_UmVbZc1o0CMA_eMAAA&S=AQAAAumF6MTURk-DLOB53xHM7Bk; Domain=.yahoo.com; Path=/; SameSite=Lax; Secure

8ctopus avatar Nov 12 '23 11:11 8ctopus

The feature was discussed in #35, but it never got implemented. If you'd like to see that feature, I'm open to PRs.

For getting a crumb value, there should already be a function in the library for that purpose.

scheb avatar Nov 16 '23 20:11 scheb

I tried to code the request that gets the A1-Cookie in in c#. My response was correct but it didn't contain a A1-Cookie

RobertSchiefele avatar Dec 17 '23 13:12 RobertSchiefele

@RobertSchiefele Just tested my code today to see if anything changed and I found out that it is still working. So there is some issue on your side.

8ctopus avatar Dec 18 '23 10:12 8ctopus

@8ctopus I've tried the curl command for the cookie and it's not present. Here is what I get. Can you try again on your side to see if you still have it or can see a problem on my side? Thank you

curl -v -s 'https://finance.yahoo.com/quote/ZURN.SW' \                                                                                                   ✔  3s 
  -H 'accept-language: en-US,en;q=0.9,fr;q=0.8' \
  -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0' \
   1> /dev/null
* Connected to finance.yahoo.com (2a00:1288:7c:800::4001) port 443 (#0)
* ALPN: offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
} [322 bytes data]
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
{ [122 bytes data]
* (304) (IN), TLS handshake, Unknown (8):
{ [19 bytes data]
* (304) (IN), TLS handshake, Certificate (11):
{ [4371 bytes data]
* (304) (IN), TLS handshake, CERT verify (15):
{ [79 bytes data]
* (304) (IN), TLS handshake, Finished (20):
{ [36 bytes data]
* (304) (OUT), TLS handshake, Finished (20):
} [36 bytes data]
* SSL connection using TLSv1.3 / AEAD-AES128-GCM-SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=US; ST=California; L=Sunnyvale; O=Oath Holdings Inc.; CN=*.fantasysports.yahoo.com
*  start date: Dec 12 00:00:00 2023 GMT
*  expire date: Jan 31 23:59:59 2024 GMT
*  subjectAltName: host "finance.yahoo.com" matched cert's "*.yahoo.com"
*  issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert SHA2 High Assurance Server CA
*  SSL certificate verify ok.
* using HTTP/2
* h2 [:method: GET]
* h2 [:scheme: https]
* h2 [:authority: finance.yahoo.com]
* h2 [:path: /quote/ZURN.SW]
* h2 [accept: */*]
* h2 [accept-language: en-US,en;q=0.9,fr;q=0.8]
* h2 [user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0]
* Using Stream ID: 1 (easy handle 0x147809800)
> GET /quote/ZURN.SW HTTP/2
> Host: finance.yahoo.com
> Accept: */*
> accept-language: en-US,en;q=0.9,fr;q=0.8
> user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
>
< HTTP/2 200
< referrer-policy: no-referrer-when-downgrade
< strict-transport-security: max-age=31536000
< x-frame-options: SAMEORIGIN
< content-security-policy: frame-ancestors 'self' https://*.yahoo.com https://*.engadget.com https://*.pnr.ouryahoo.com https://pnr.ouryahoo.com https://*.search.aol.com https://*.onesearch.com https://*.publishing.oath.com https://*.aol.com; sandbox allow-downloads allow-forms allow-modals allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-presentation;
< cache-control: private, no-store, no-cache, max-age=0
< content-type: text/html; charset=utf-8
< vary: Accept-Encoding
< date: Thu, 21 Dec 2023 10:50:02 GMT
< x-envoy-upstream-service-time: 662
< server: ATS
< x-envoy-decorator-operation: finance-nodejs--mtls-production-ir2.finance-k8s.svc.yahoo.local:4080/*
< age: 5
< expect-ct: max-age=31536000, report-uri="http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only"
< x-xss-protection: 1; mode=block
< x-content-type-options: nosniff
<
{ [8372 bytes data]
* Connection #0 to host finance.yahoo.com left intact

Vladutu avatar Dec 21 '23 10:12 Vladutu

@Vladutu I tried the YahooManager.cs class from @8ctopus and it worked fine on my side. Thanks to the author.

RobertSchiefele avatar Dec 21 '23 11:12 RobertSchiefele

@RobertSchiefele what YahooManager.cs are you refering to? Maybe I'm not looking where I should but the yahoo-finance-api is PHP project, not c#. I saw that he was doing a call to https://fc.yahoo.com to get the cookie (which I was also doing) but that doesn't work anymore, the website is not responding (I got 502 connection timeout)

Vladutu avatar Dec 21 '23 12:12 Vladutu

@Vladutu sorry I got this Classs from another site, cannot remember where. But anyway here is the code

using Microsoft.Extensions.Caching.Memory; using System.Net;

namespace My.YahooFinanceAPI { public class YahooManager { private static readonly object yahooCredentialsLocker = new object();

    private const string STOCK_MARKET_URL_SUMMARY = "https://query1.finance.yahoo.com/v10/finance/quoteSummary/{0}?formatted=true&lang=en-US&region=US&modules=assetProfile%2CbalanceSheetHistory%2CbalanceSheetHistoryQuarterly%2CcalendarEvents%2CcashflowStatementHistory%2CcashflowStatementHistoryQuarterly%2CdefaultKeyStatistics%2Cearnings%2CearningsHistory%2CearningsTrend%2CesgScores%2CfinancialData%2CfundOwnership%2CincomeStatementHistory%2CincomeStatementHistoryQuarterly%2CindexTrend%2CindustryTrend%2CinsiderHolders%2CinsiderTransactions%2CinstitutionOwnership%2CmajorDirectHolders%2CmajorHoldersBreakdown%2CnetSharePurchaseActivity%2Cprice%2CrecommendationTrend%2CsecFilings%2CsectorTrend%2CsummaryDetail%2CsummaryProfile%2CupgradeDowngradeHistory%2Cpageviews%2Cquotetype&ssl=true";
    
    private const string YahooFcUrl = "https://fc.yahoo.com";
    private const string YahooGetCrumbUrl = "https://query2.finance.yahoo.com/v1/test/getcrumb";
    private const string CacheKeyCookieContainer = "YahooCookieContainer";
    private const string CacheKeyCrumb = "YahooCrumb";

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IMemoryCache _memCache;

    public YahooManager(IHttpClientFactory httpClientFactory, IMemoryCache memCache)
    {
        _httpClientFactory = httpClientFactory;
        _memCache = memCache;
    }


    public async Task<string> GetYahooSymbolSummary(string symbol)
    {
        return await WebRequestGet(string.Format(STOCK_MARKET_URL_SUMMARY, symbol));
    }

    public async Task<string> WebRequestGet(string url)
    {
        (CookieContainer cookie, string crumb) credentials = new(null, null);
        lock (yahooCredentialsLocker)
        {
            int tryCounter = 0;
            while ((credentials.crumb == null || credentials.cookie == null) && tryCounter < 10)
            {
                Task.Delay(1000 * tryCounter).Wait();
                credentials = GetYahooCookie().ConfigureAwait(false).GetAwaiter().GetResult();
                tryCounter++;
            }//while
        }//lock

        string sAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36";
        var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url + "&crumb=" + credentials.crumb)
        {
            Headers =
                    {
                        { "Cookie",  credentials.cookie.GetCookieHeader(new Uri(YahooFcUrl))},
                        { "User-Agent",sAgent }
                    }
        };
        var httpClient = _httpClientFactory.CreateClient();
        var webResponse = await httpClient.SendAsync(httpRequestMessage);
        //webResponse.EnsureSuccessStatusCode();
        string responseContent = await webResponse.Content.ReadAsStringAsync();
        return responseContent;
    }//WebRequestGet

    private async Task<(CookieContainer cookie, string crumb)> GetYahooCookie()
    {
        CookieContainer m_cookieContainer = null;
        string m_crumb = null;
        _memCache.TryGetValue(CacheKeyCookieContainer, out m_cookieContainer);
        _memCache.TryGetValue(CacheKeyCrumb, out m_crumb);

        if (m_cookieContainer == null || m_crumb == null)
        {
            string sAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36";

            m_cookieContainer = new CookieContainer();
            var uri = new Uri(YahooFcUrl);
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, YahooFcUrl)
            {
                Headers =
                    {
                        { "User-Agent", sAgent }
                    }
            };
            var httpClient = _httpClientFactory.CreateClient();
            var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
            IEnumerable<string> cookies;
            httpResponseMessage.Headers.TryGetValues("Set-Cookie", out cookies);
            if (cookies == null)
                return (null, null);

            foreach (var cookieValue in cookies)
                m_cookieContainer.SetCookies(uri, cookieValue);
            httpResponseMessage.Dispose();

            httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, YahooGetCrumbUrl)
            {
                Headers =
                    {
                        { "Cookie",  m_cookieContainer.GetCookieHeader(uri) },
                        { "User-Agent",sAgent}
                    }
            };
            httpClient = _httpClientFactory.CreateClient();
            var crumbResponse = await httpClient.SendAsync(httpRequestMessage);
            crumbResponse.EnsureSuccessStatusCode();
            string responseContent = await crumbResponse.Content.ReadAsStringAsync();
            m_crumb = responseContent.Trim('"');

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetSlidingExpiration(TimeSpan.FromSeconds(60*5))
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(60*10))
                .SetPriority(CacheItemPriority.Normal);
            _memCache.Set(CacheKeyCookieContainer, m_cookieContainer, cacheEntryOptions);
            _memCache.Set(CacheKeyCrumb, m_crumb, cacheEntryOptions);
        }//endif
        return (m_cookieContainer, m_crumb);
    }//GetYahooCookie


}

}

RobertSchiefele avatar Dec 21 '23 14:12 RobertSchiefele

@RobertSchiefele thank you. I will have a look.

Vladutu avatar Dec 21 '23 18:12 Vladutu

@RobertSchiefele Seems it's doing the same using "https://fc.yahoo.com" which for some reason doesn't work anymore for me. I even asked a friend to try it out (thought maybe my IP was blocked) but it was the same thing

Vladutu avatar Dec 21 '23 18:12 Vladutu