[英]Trigger-run a Google Sheets script to fetch URL data from Yahoo Finance in batches
Prelude - 這是一個相當長的帖子,但主要是因為有很多圖片來澄清我的問題:)
我一直在從 Yahoo! 提取公司數據! 金融,最初只針對幾只股票,但目前針對數百只股票(很快將達到數千只)。 我目前正在實時提取這些數據,每次加載電子表格時每個股票代碼都有一個 urlFetch。 這提出了三個問題:
因此,我正在尋找更好的方法:
考慮以下簡化的示例表(tab db
):
當前腳本如下:
function trigger() {
const db = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('db');
const tickers = db.getRange('A2:A' + db.getLastRow()).getValues().flat();
for (var row = 0; row < tickers.length; row++) {
var data = yahoo(tickers[row]);
db.getRange(row + 2, 2, 1, 3).setValues(data);
}
}
function yahoo(ticker) {
return [ticker, "SOME", "DATA", "MORE"]
}
請注意,出於測試目的, yahoo()
只是返回一個數組。 實際上, yahoo()
函數從 Yahoo! 中提取JSON
數據。 運行腳本后,電子表格如下所示:
到目前為止,一切都很好。 但是,如果列表不是 3 個而是 5000 個代碼長,那么按原樣運行腳本會讓我快速限制速率(或者等待很長時間才能加載電子表格)。 因此,我想到了以下幾點:
假設列表當前如下所示:
假設今天是 5 月 31 日,運行腳本:
從列表頂部開始,腳本應該要更新今天尚未更新的 5 個代碼:
BLK
今天已經更新,所以省略AAPL
最后一次更新是昨天,所以它是第一個被 Yahoo! 查詢的股票代碼。現在第 2 到 9 行已更新數據。 第二次運行腳本時,它應該更新接下來的五個。 再次從頂部開始,查找前 5 個代碼(1)沒有“最后運行”日期或(2)今天之前的運行日期:
如您所見,第 11 到 15 行現在也更新了。 TSLA
被跳過,因為(無論出於何種原因)它今天已經更新。
這里又是同一個列表,只是多了 2 個代碼。 如果腳本在 6 月 1 日運行幾次,結果將是這樣的:
如果 Yahoo! 財務服務將始終返回每個代碼的數據。 但是,它不會。 例如因為:
我相信我需要一個解決方案來在下載數據時跟蹤錯誤。 假設腳本在 6 月 2 日再次運行幾次(由 Google 腳本中每分鍾一次的觸發器觸發),結果如下:
我們看到兩個代碼( JPM
和ORCL
)的數據無法更新。 兩者都標記在錯誤列中,由待編寫的腳本填充。
假設我們在 6 月 3 日再次運行腳本。 這一天, JPM
數據正在完美下載,但ORCL
再次出現錯誤。 雅虎! 沒有返回任何數據。 error
列更新為2
。
如果代碼連續 2 次嘗試未返回數據( error = 2
),則應永遠跳過它。 我會在某個時候手動填寫它,或者查看我是否輸入了一個不存在的代碼。
保留錯誤下載可以防止腳本卡住。 如果沒有它,如果列表頂部有 5 個不斷拋出錯誤的代碼,腳本將永遠不會超過這 5 個。它會嘗試一遍又一遍地嘗試從 Yahoo 下載這些代碼的數據。
在最后一張圖片中,我們看到了腳本在 6 月 4 日運行的結果。 我再次為每運行/分鍾更新的批次(5 個代碼)着色。
我盡力解釋了我是如何考慮從 Yahoo! 構建防錯下載的。 金融。 在我的電子表格的其余部分,每當我需要來自公司的元數據時,我都可以簡單地從這個db
選項卡中獲取它,而不是查詢 Yahoo! 一遍又一遍。
我的問題是我的腳本技能有限。 我不是在監督如何開始構建它。 有人可以請:
PS。 我知道每次運行腳本時我仍在進行 5 次 urlfetches。 有人建議我將這 5 個一起批處理(這將防止我至少在 Google 方面受到速率限制)。 這是一個好主意,但我發現很難理解它是如何工作的,所以我寧願首先有一個可以工作並且我可以遵循的腳本。 在以后的階段,我一定會升級/提高效率:)
如果您一直閱讀到這里,非常感謝。 任何幫助是極大的贊賞!
[EDIT1]:實際上, yahoo()
看起來像這樣:
function yahoo(ticker) {
const url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/' + encodeURI(ticker) + '?modules=price,assetProfile,summaryDetail';
let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
}
let fwdPE = object.quoteSummary.result[0]?.summaryDetail?.forwardPE?.fmt || '-';
let sector = object.quoteSummary.result[0]?.assetProfile?.sector || '-';
let mktCap = object.quoteSummary.result[0]?.price?.marketCap?.fmt || '-';
return [[fwdPE, sector, mktCap]];
}
[EDIT2] 此處的電子表格示例。
[EDIT3] 示例電子表格中的當前腳本:
function trigger() {
const max = 5; // From your question, maximum execution of "yahoo" is 5.
const today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd");
const db = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('db');
const range = db.getRange('A2:F' + db.getLastRow());
const { values } = range.getValues().reduce((o, r) => {
const [ticker, b, c, d, e, f] = r;
if (o.c < max && (e.toString() == "" || Utilities.formatDate(e, Session.getScriptTimeZone(), "yyyyMMdd") != today)) {
try {
o.c++;
o.values.push([...yahoo(ticker), today, null]);
} catch (_) {
o.values.push([ticker, b, c, d, today, ["", "0"].includes(f.toString()) ? 1 : f + 1]);
}
} else {
o.values.push(r);
}
return o;
}, { values: [], c: 0 });
range.setValues(values);
}
function yahoo(ticker) {
const url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/' + encodeURI(ticker) + '?modules=price,assetProfile,summaryDetail';
let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
}
let fwdPE = object.quoteSummary.result[0]?.summaryDetail?.forwardPE?.fmt || '-';
let sector = object.quoteSummary.result[0]?.assetProfile?.sector || '-';
let mktCap = object.quoteSummary.result[0]?.price?.marketCap?.fmt || '-';
return [[ticker, fwdPE, sector, mktCap]];
}
[EDIT4] 運行腳本 4 次后的結果:
[EDIT5] B
和C
列中的現有數據被覆蓋
[編輯6]:
function trigger() {
const max = 5; // From your question, maximum execution of "yahoo" is 5.
const todayObj = new Date();
const today = Utilities.formatDate(todayObj, Session.getScriptTimeZone(), "yyyyMMdd");
const db = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('db2');
const range = db.getRange('A2:AO' + db.getLastRow());
const { values } = range.getValues().reduce((zo, zr) => {
const [ticker, b, c, d, e, f, g, h, i, j, k, l, m, n, r, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, an, ao] = zr;
if (zo.zc < max && (g.toString() == "" || Utilities.formatDate(an, Session.getScriptTimeZone(), "yyyyMMdd") != today)) {
try {
zo.zc++;
zo.values.push([ticker, b, c, ...yahoo(ticker), todayObj, null]);
} catch (_) {
zo.values.push([ticker, b, c, d, e, f, g, h, i, j, k, l, m, n, r, o, p, q, r, s, t, u, v, w, x, y, z, aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, todayObj, ["", "0"].includes(an.toString()) ? 1 : ao + 1]);
}
} else {
zo.values.push(zr);
}
return zo;
}, { values: [], zc: 0 });
range.setValues(values);
}
function yahoo(ticker) {
const url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/' + encodeURI(ticker) + '?modules=summaryDetail,financialData,defaultKeyStatistics';
let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
}
// misc
let marketCap = object.quoteSummary.result[0]?.summaryDetail?.marketCap?.raw || '-';
let dividendRate = object.quoteSummary.result[0]?.summaryDetail?.dividendRate?.raw || '-';
let dividendYield = object.quoteSummary.result[0]?.summaryDetail?.dividendYield?.raw || '-';
let payoutRatio = object.quoteSummary.result[0]?.summaryDetail?.payoutRatio?.raw || '-';
let fiveYAvgDivYield = object.quoteSummary.result[0]?.summaryDetail?.fiveYearAvgDividendYield?.raw || '-';
let insidersPercentHeld = object.quoteSummary.result[0]?.majorHoldersBreakdown?.insidersPercentHeld?.raw || '-';
let institutionsPercentHeld = object.quoteSummary.result[0]?.majorHoldersBreakdown?.institutionsPercentHeld?.raw || '-';
// dates
let earningsDate = object.quoteSummary.result[0]?.calendarEvents?.earnings?.earningsDate[0]?.raw || '-';
let exDividendDate = object.quoteSummary.result[0]?.calendarEvents?.exDividendDate?.raw || '-';
let dividendDate = object.quoteSummary.result[0]?.calendarEvents?.dividendDate?.raw || '-';
// earnings
let totalRevenue = object.quoteSummary.result[0]?.financialData?.totalRevenue?.raw || '-';
let revenueGrowth = object.quoteSummary.result[0]?.financialData?.revenueGrowth?.raw || '-';
let revenuePerShare = object.quoteSummary.result[0]?.financialData?.revenuePerShare?.raw || '-';
let ebitda = object.quoteSummary.result[0]?.financialData?.ebitda?.raw || '-';
let grossProfits = object.quoteSummary.result[0]?.financialData?.grossProfits?.raw || '-';
let earningsGrowth = object.quoteSummary.result[0]?.financialData?.earningsGrowth?.raw || '-';
let grossMargins = object.quoteSummary.result[0]?.financialData?.grossMargins?.raw || '-';
let ebitdaMargins = object.quoteSummary.result[0]?.financialData?.ebitdaMargins?.raw || '-';
let operatingMargins = object.quoteSummary.result[0]?.financialData?.operatingMargins?.raw || '-';
let profitMargins = object.quoteSummary.result[0]?.financialData?.profitMargins?.raw || '-';
// cash
let totalCash = object.quoteSummary.result[0]?.financialData?.totalCash?.raw || '-';
let freeCashflow = object.quoteSummary.result[0]?.financialData?.freeCashflow?.raw || '-';
let opCashflow = object.quoteSummary.result[0]?.financialData?.operatingCashflow?.raw || '-';
let cashPerShare = object.quoteSummary.result[0]?.financialData?.totalCashPerShare?.raw || '-';
// debt
let totalDebt = object.quoteSummary.result[0]?.financialData?.totalDebt?.raw || '-';
let debtToEquity = object.quoteSummary.result[0]?.financialData?.debtToEquity?.raw || '-';
// ratios
let quickRatio = object.quoteSummary.result[0]?.financialData?.quickRatio?.raw || '-';
let currentRatio = object.quoteSummary.result[0]?.financialData?.currentRatio?.raw || '-';
let trailingEps = object.quoteSummary.result[0]?.defaultKeyStatistics?.trailingEps?.raw || '-';
let forwardEps = object.quoteSummary.result[0]?.defaultKeyStatistics?.forwardEps?.raw || '-';
let pegRatio = object.quoteSummary.result[0]?.defaultKeyStatistics?.pegRatio?.raw || '-';
let priceToBook = object.quoteSummary.result[0]?.defaultKeyStatistics?.priceToBook?.raw || '-';
let returnOnAssets = object.quoteSummary.result[0]?.financialData?.returnOnAssets?.raw || '-';
let returnOnEquity = object.quoteSummary.result[0]?.financialData?.returnOnEquity?.raw || '-';
let enterpriseValue = object.quoteSummary.result[0]?.defaultKeyStatistics?.enterpriseValue?.raw || '-';
let bookValue = object.quoteSummary.result[0]?.defaultKeyStatistics?.bookValue?.raw || '-';
return [
marketCap, dividendRate, dividendYield, payoutRatio, fiveYAvgDivYield, insidersPercentHeld, institutionsPercentHeld,
earningsDate, exDividendDate, dividendDate,
totalRevenue, revenueGrowth, revenuePerShare, ebitda, grossProfits, earningsGrowth, grossMargins, ebitdaMargins, operatingMargins, profitMargins,
totalCash, freeCashflow, opCashflow, cashPerShare,
totalDebt, debtToEquity,
quickRatio, currentRatio, trailingEps, forwardEps, pegRatio, priceToBook, returnOnAssets, returnOnEquity,
enterpriseValue, bookValue
];
}
我相信你的目標如下。
yahoo
,並且想要將“B”列更新為“F”。
yahoo
的功能。
yahoo
的功能。yahoo
發生錯誤時,您希望計算列“F”。 當沒有發生錯誤時,您希望將“F”列設置為null
。 當我看到你的腳本時,沒有包含實現上述目標的腳本。 並且, setValues
在循環中使用。 在這種情況下,處理成本會變高。
那么,在您的情況下,為了實現您的目標,下面的示例腳本怎么樣?
這個腳本可以直接用腳本編輯器運行。 因此,在您使用時間驅動觸發器運行此腳本之前,我建議您測試此腳本。
測試此腳本並確認輸出情況后,請在函數中安裝時間驅動觸發器。 這樣,當您將時間驅動觸發器安裝到此函數時,腳本將由觸發器運行。
function trigger() {
const max = 5; // From your question, maximum execution of "yahoo" is 5.
const todayObj = new Date();
const today = Utilities.formatDate(todayObj, Session.getScriptTimeZone(), "yyyyMMdd");
const db = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
const range = db.getRange('A2:F' + db.getLastRow());
const { values } = range.getValues().reduce((o, r) => {
const [ticker, b, c, d, e, f] = r;
if (o.c < max && (e.toString() == "" || Utilities.formatDate(e, Session.getScriptTimeZone(), "yyyyMMdd") != today)) {
try {
o.c++;
o.values.push([...yahoo(ticker), todayObj, null]);
} catch (_) {
o.values.push([ticker, b, c, d, todayObj, ["", "0"].includes(f.toString()) ? 1 : f + 1]);
}
} else {
o.values.push(r);
}
return o;
}, { values: [], c: 0 });
range.setValues(values);
}
function yahoo(ticker) {
const url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/' + encodeURI(ticker) + '?modules=price,assetProfile,summaryDetail';
let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
}
let fwdPE = object.quoteSummary.result[0]?.summaryDetail?.forwardPE?.fmt || '-';
let sector = object.quoteSummary.result[0]?.assetProfile?.sector || '-';
let mktCap = object.quoteSummary.result[0]?.price?.marketCap?.fmt || '-';
return [ticker, fwdPE, sector, mktCap];
}
運行此腳本時,我認為您的上述目標可能能夠實現。 所以,
從您的實際yahoo
中,返回的值與您的第一個腳本不同。 所以,我也修改了它。
不幸的是,我無法想象yahoo(ticker)
的實際腳本。 因此,為了檢查錯誤,我使用了 try-catch。 在這種情況下,它假設當未檢索到值時, yahoo(ticker)
中發生錯誤。 請注意這一點。
我無法理解您的yahoo(ticker)
實際腳本。 所以,請注意這一點。
從您的問題和顯示圖像中,我了解到您想檢查年、月和日。 請注意這一點。
根據您的以下附加問題,
另外,如果我可以請您簡要看一下,我已經在示例表中添加了第二個選項卡 (db2)。 在這里,我在代碼和 yahoo() 返回的其余數據之間添加了 2 列。 假設我要在這里填寫其他數據。 是否可以調整您的腳本以使其不理會這些列,因此僅適用於 A 和 D 到 H 列?
我知道您想將空的 2 列“B”和“C”添加到結果數組中。 在這種情況下,請測試以下示例腳本。
function trigger() {
const max = 5; // From your question, maximum execution of "yahoo" is 5.
const todayObj = new Date();
const today = Utilities.formatDate(todayObj, Session.getScriptTimeZone(), "yyyyMMdd");
const db = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('db2');
const range = db.getRange('A2:H' + db.getLastRow());
const { values } = range.getValues().reduce((o, r) => {
const [ticker, b, c, d, e, f, g, h] = r;
if (o.c < max && (g.toString() == "" || Utilities.formatDate(g, Session.getScriptTimeZone(), "yyyyMMdd") != today)) {
try {
o.c++;
o.values.push([ticker, b, c, ...yahoo(ticker), todayObj, null]);
} catch (_) {
o.values.push([ticker, b, c, d, e, f, todayObj, ["", "0"].includes(f.toString()) ? 1 : h + 1]);
}
} else {
o.values.push(r);
}
return o;
}, { values: [], c: 0 });
range.setValues(values);
}
function yahoo(ticker) {
const url = 'https://query2.finance.yahoo.com/v10/finance/quoteSummary/' + encodeURI(ticker) + '?modules=price,assetProfile,summaryDetail';
let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
}
let fwdPE = object.quoteSummary.result[0]?.summaryDetail?.forwardPE?.fmt || '-';
let sector = object.quoteSummary.result[0]?.assetProfile?.sector || '-';
let mktCap = object.quoteSummary.result[0]?.price?.marketCap?.fmt || '-';
return [fwdPE, sector, mktCap];
}
trigger
和yahoo
功能都進行了修改。 而且,為了使用您提供的電子表格的第二個選項卡,工作表名稱也更改為db2
。 請注意這一點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.