// сложный метод, в ходе которого мы вытаскиваем данные по финансовым показателям financialData с gurufocus.com public string GetFinancials(string ticker) { WebPage page = GetWebPage($"https://www.gurufocus.com/financials/{ticker}"); int numberOfQuarters = 5; int numberOfYears = 4; int quarterlyOffset = 6; int yearOffset = 1; var finYearGurufocusModel = new FinYearGurufocusModel(); var finQuartGurufocusModel = new FinQuartGurufocusModel(); var tdFiscalPer = page.Html.CssSelect("td").FirstOrDefault(x => x.Attributes["title"]?.Value == "Fiscal Period"); if (tdFiscalPer != null) { var trFiscPer = tdFiscalPer.ParentNode; var tdFiscalPeriods = trFiscPer.CssSelect("td").Where(x => x.Attributes["title"]?.Value != null); // вытаскиваем квартальные даты var quarters = new string[numberOfQuarters]; for (int i = 0; i < quarters.Length; i++) { string dateQuarterly = tdFiscalPeriods.ToList()[i + quarterlyOffset]?.GetAttributeValue("title", ""); dateQuarterly = dateQuarterly.Substring(0, 5); // берем первые 5 символов (MMMyy), т.к. только они значащие string monthQuart = DateTime.ParseExact(dateQuarterly, "MMMyy", CultureInfo.InvariantCulture).Month.ToString(); string yearQuart = DateTime.ParseExact(dateQuarterly, "MMMyy", CultureInfo.InvariantCulture).Year.ToString(); quarters[i] = monthQuart + "/" + yearQuart; } finQuartGurufocusModel.Quarterly = quarters; // берем первый период, чтобы знать с какого года вести отсчет (всего можем предоставить данные за 4 года) string startDate = tdFiscalPeriods.ToList()[yearOffset].GetAttributeValue("title", ""); finYearGurufocusModel.startYear = DateTime.ParseExact(startDate, "MMMyy", CultureInfo.InvariantCulture).Year; } bool noMarketableSecurities = false; foreach (var param in financialData) { var tds = page.Html.CssSelect("td"); var tdParam = tds.FirstOrDefault(x => x.Attributes["title"]?.Value == param); if (tdParam != null) { var trParam = tdParam.ParentNode; List <HtmlAgilityPack.HtmlNode> listValues = trParam .CssSelect("td") .Where(x => x.Attributes["title"]?.Value != null) .ToList(); var dYearValues = new double[numberOfYears]; var dQuartValues = new double[numberOfQuarters]; var style = NumberStyles.Number | NumberStyles.AllowCurrencySymbol; var culture = new CultureInfo("en-GB"); // заполняем годовые данные for (int i = 0; i < numberOfYears; i++) { Double.TryParse( listValues[i + yearOffset].Attributes["title"].Value, style, culture, out dYearValues[i]); } // заполняем квартальные данные for (int i = 0; i < numberOfQuarters; i++) { // здесь смещение для получения квартальных данных уже должно быть на 1 меньше, // т.к. показатель TTM в listValues уже не попадает (см. ссылку на gurufocus и разбирай таблицу) Double.TryParse( listValues[i + (quarterlyOffset - 1)].Attributes["title"].Value, style, culture, out dQuartValues[i]); } // округляем все данные до 1 знака после запятой dYearValues = dYearValues.Select(d => Math.Round(d, 1)).ToArray(); dQuartValues = dQuartValues.Select(d => Math.Round(d, 1)).ToArray(); // копируем ссылки на получившиеся массивы с данными в соответствующие поля моделей switch (param) { case "Revenue": finYearGurufocusModel.RevenueData = dYearValues; finQuartGurufocusModel.RevenueData = dQuartValues; break; case "Net Income": finYearGurufocusModel.IncomeData = dYearValues; finQuartGurufocusModel.IncomeData = dQuartValues; break; case "Cash, Cash Equivalents, Marketable Securities": finYearGurufocusModel.CashData = dYearValues; finQuartGurufocusModel.CashData = dQuartValues; break; case "Cash and cash equivalents": // для некоторых акций "Cash, Cash Equivalents, Marketable Securities" отсутствует, // в таких случаях CashData формируется путем сложения "Cash and cash equivalents" с "Short-term investments" if (finYearGurufocusModel.CashData == null) { finYearGurufocusModel.CashData = dYearValues; finQuartGurufocusModel.CashData = dQuartValues; noMarketableSecurities = true; } break; case "Short-term investments": if (noMarketableSecurities) { for (int i = 0; i < numberOfYears; i++) { finYearGurufocusModel.CashData[i] += dYearValues[i]; finQuartGurufocusModel.CashData[i] += dQuartValues[i]; } // записываем данные последнего квартала finQuartGurufocusModel.CashData[numberOfQuarters - 1] += dQuartValues[numberOfQuarters - 1]; } break; case "Long-Term Debt & Capital Lease Obligation": // показатель DebtData формируется путем сложения // "Long-Term Debt & Capital Lease Obligation" с "Short-Term Debt & Capital Lease Obligation" finYearGurufocusModel.DebtData = dYearValues; finQuartGurufocusModel.DebtData = dQuartValues; break; case "Short-Term Debt & Capital Lease Obligation": for (int i = 0; i < numberOfYears; i++) { finYearGurufocusModel.DebtData[i] += dYearValues[i]; finQuartGurufocusModel.DebtData[i] += dQuartValues[i]; } // записываем данные последнего квартала finQuartGurufocusModel.DebtData[numberOfQuarters - 1] += dQuartValues[numberOfQuarters - 1]; break; } } } string result = string.Empty; JsonSerializerOptions jsonOptions = new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; result = "{\"yearData\":" + JsonSerializer.Serialize <FinYearGurufocusModel>(finYearGurufocusModel, jsonOptions) + "," + "\"quarterlyData\":" + JsonSerializer.Serialize <FinQuartGurufocusModel>(finQuartGurufocusModel, jsonOptions) + "}"; return(result); }
public IActionResult GetFinancials(string ticker) { ScrapingBrowser browser = new ScrapingBrowser(); WebPage page; try { page = browser.NavigateToPage(new Uri($"https://www.gurufocus.com/financials/{ticker}")); } catch { return(BadRequest()); } string[] financialData = new string[] { "Revenue", "Net Income", "Cash, Cash Equivalents, Marketable Securities", "Cash and cash equivalents", "Short-term investments", "Long-Term Debt & Capital Lease Obligation", "Short-Term Debt & Capital Lease Obligation" }; FinYearGurufocusModel finYearGurufocusModel = new FinYearGurufocusModel(); FinQuartGurufocusModel finQuartGurufocusModel = new FinQuartGurufocusModel(); var tdFiscalPer = page.Html.CssSelect("td").FirstOrDefault(x => x.Attributes["title"]?.Value == "Fiscal Period"); if (tdFiscalPer != null) { var trFiscPer = tdFiscalPer.ParentNode; var tdFiscalperiods = trFiscPer.CssSelect("td").Where(x => x.Attributes["title"]?.Value != null); // вытаскиваем кварталы string[] quarters = new string [5]; for (int i = 0; i < quarters.Length; i++) { string dateQuarterly = tdFiscalperiods.ToList()[i + 6]?.GetAttributeValue("title", ""); dateQuarterly = dateQuarterly.Substring(0, 5); // берем первые 5 символов (MMMyy), т.к. только они значащие string monthQuart = DateTime.ParseExact(dateQuarterly, "MMMyy", CultureInfo.InvariantCulture).Month.ToString(); string yearQuart = DateTime.ParseExact(dateQuarterly, "MMMyy", CultureInfo.InvariantCulture).Year.ToString(); quarters[i] = monthQuart + "/" + yearQuart; } finQuartGurufocusModel.Quarterly = quarters; // берем первый период, чтобы знать с какого года вести отсчет (всего можем предоставить данные за 4 года) string startDate = tdFiscalperiods.ToList()[1].GetAttributeValue("title", ""); finYearGurufocusModel.startYear = DateTime.ParseExact(startDate, "MMMyy", CultureInfo.InvariantCulture).Year; } bool noMarketSec = false; foreach (var param in financialData) { var tds = page.Html.CssSelect("td"); var td = tds.FirstOrDefault(x => x.Attributes["title"]?.Value == param); if (td != null) { var tr = td.ParentNode; var financialValues = tr.CssSelect("td").Where(x => x.Attributes["title"]?.Value != null); List <HtmlAgilityPack.HtmlNode> listValues = financialValues.ToList(); double[] dYearValues = new double[4]; double[] dQuartValues = new double[5]; NumberStyles style = NumberStyles.Number | NumberStyles.AllowCurrencySymbol; CultureInfo culture = CultureInfo.CreateSpecificCulture("en-GB"); for (int i = 0; i < dYearValues.Length; i++) { Double.TryParse(listValues[i + 1].Attributes["title"].Value, style, culture, out dYearValues[i]); } for (int i = 0; i < dQuartValues.Length; i++) { Double.TryParse(listValues[i + 5].Attributes["title"].Value, style, culture, out dQuartValues[i]); } dYearValues = dYearValues.Select(d => Math.Round(d, 1)).ToArray(); dQuartValues = dQuartValues.Select(d => Math.Round(d, 1)).ToArray(); switch (param) { case "Revenue": finYearGurufocusModel.RevenueData = dYearValues; finQuartGurufocusModel.RevenueData = dQuartValues; break; case "Net Income": finYearGurufocusModel.IncomeData = dYearValues; finQuartGurufocusModel.IncomeData = dQuartValues; break; case "Cash, Cash Equivalents, Marketable Securities": finYearGurufocusModel.CashData = dYearValues; finQuartGurufocusModel.CashData = dQuartValues; break; case "Cash and cash equivalents": if (finYearGurufocusModel.CashData == null) { finYearGurufocusModel.CashData = dYearValues; finQuartGurufocusModel.CashData = dQuartValues; noMarketSec = true; } break; case "Short-term investments": if (noMarketSec) { for (int i = 0; i < finYearGurufocusModel.CashData?.Length; i++) { finYearGurufocusModel.CashData[i] += dYearValues[i]; finQuartGurufocusModel.CashData[i] += dQuartValues[i]; } finQuartGurufocusModel.CashData[4] += dQuartValues[4]; } break; case "Long-Term Debt & Capital Lease Obligation": finYearGurufocusModel.DebtData = dYearValues; finQuartGurufocusModel.DebtData = dQuartValues; break; case "Short-Term Debt & Capital Lease Obligation": for (int i = 0; i < finYearGurufocusModel.DebtData?.Length; i++) { finYearGurufocusModel.DebtData[i] += dYearValues[i]; finQuartGurufocusModel.DebtData[i] += dQuartValues[i]; } finQuartGurufocusModel.DebtData[4] += dQuartValues[4]; break; } } } string result; JsonSerializerOptions jsonOptions = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; result = JsonSerializer.Serialize <FinYearGurufocusModel>(finYearGurufocusModel, jsonOptions); result = "{\"yearData\":" + JsonSerializer.Serialize <FinYearGurufocusModel>(finYearGurufocusModel, jsonOptions) + "," + "\"quarterlyData\":" + JsonSerializer.Serialize <FinQuartGurufocusModel>(finQuartGurufocusModel, jsonOptions) + "}"; JsonDocument doc = JsonDocument.Parse(result); return(Ok(doc.RootElement)); }