Trigger-运行 Google Sheets 脚本从 Yahoo Finance 批量获取 URL 数据
Trigger-run a Google Sheets script to fetch URL data from Yahoo Finance in batches
序曲 - 这很长 post 但主要是因为有很多图片可以阐明我的问题:)
我一直在从 Yahoo! 中提取公司数据金融,最初只针对少数股票,但目前针对数百只股票(很快就会达到数千只)。我目前正在实时提取这些数据,每次我加载价差 sheet 时,每个股票代码都会提取一个 url。这带来了三个问题:
- sheet 需要很长时间才能加载,因为它向 Yahoo!
发出数百个请求
- 我经常在 Google 端受到速率限制(每天最多 20k url 提取)
- 我可能会在 Yahoo! 上受到速率限制财务方面
因此我正在寻找更好的方法:
- 我将 运行 一个 google 脚本,每天为每个代码获取一次数据,而不是在每次传播时为每个代码调用 Yahoosheet
考虑以下简化示例 sheet(选项卡 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()
只是 return 一个数组。实际上,yahoo()
函数从 Yahoo! 中提取 JSON
数据。在 运行 脚本之后,传播sheet 看起来像:
到目前为止一切顺利。但是,如果列表不是 3,而是 5000 个代码,运行按原样设置脚本将使我快速限制速率(或者等待传播sheet 加载很长时间)。因此,我想到了以下几点:
- 每次脚本 运行,它只会提取 5 个代码的数据。该脚本将 运行 每分钟一次。 (就此线程而言,每分钟 5 个代码)
- 脚本将跟踪上次为每个代码下载数据的时间
假设列表当前如下所示:
假设今天是5月31日,脚本是运行:
从列表顶部开始,脚本应该要更新今天尚未更新的 5 个代码:
BLK
第2行今天已经更新,省略
AAPL
昨天最后更新,因此它是第一个通过 Yahoo! 查询的代码
- 等等
现在第 2 行到第 9 行已更新数据。第二次脚本是 运行,它应该更新接下来的五个。再次从顶部开始,寻找前 5 个代码(1)没有“最后 运行”日期或(2)今天之前的 运行 日期:
如您所见,第 11 至 15 行现在也已更新。 TSLA
被跳过了,因为(不管什么原因)今天已经更新了。
这里又是同一个列表,只是多了 2 个代码。如果脚本在6月1日运行几次,结果会是这样:
- 3 批 5 个代码
- 一批 2 个代码
如果 Yahoo!财务服务始终会为每个代码提供 return 数据。然而,它不会。例如因为:
- 它没有某个代码的数据
- 请求超时
我认为我需要一个解决方案来跟踪下载数据时的错误。假设脚本在 6 月 2 日再次 运行 几次(由 Google 脚本中每分钟一次的触发器触发),并得到以下结果:
我们看到两个代码(JPM
和 ORCL
)无法更新数据。两者都在错误栏中标出,由待编写的脚本填写。
假设我们在 6 月 3 日再次 运行 脚本。这一天,JPM
数据下载完美,但 ORCL
再次产生错误。雅虎!没有 return 任何数据。 error
列更新为 2
。
如果代码连续 2 次尝试 (error = 2
) 未获取数据 return,则应永远跳过它。我会在某个时候手动填写,或者查看我是否输入了一个不存在的代码。
保留错误下载的地址可以防止脚本卡住。没有它,如果列表顶部有 5 个代码不断抛出错误,脚本将永远不会超出这 5 个。它将尝试为这些代码一遍又一遍地尝试从 Yahoo 下载数据。
在最后一张图片中,我们看到脚本在 6 月 4 日 运行 的结果。我再次为每 运行/ 分钟更新的批次(5 个代码)着色。
我已尽力解释我是如何考虑从 Yahoo! 构建防错下载的。金融。在我传播的其余部分sheet,每当我需要来自公司的元数据时,我可以简单地从这个 db
选项卡中获取它,而不是查询 Yahoo!一遍又一遍。
我的问题是我的脚本技能有限。我没有监督如何开始构建它。有人可以吗:
- 给我一个开始的(伪)代码或
- 反馈我的想法。我在这里遗漏了什么可能会出现问题的东西吗?
PS。我知道每次脚本 运行 时,我仍在进行 5 次 url 提取。有人向我建议我应该将这 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] Example spreadsheet here.
[EDIT3] 示例中的当前脚本传播sheet:
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] 运行ning 脚本 4 次后的结果:
[EDIT5] B
和 C
列中的现有数据被覆盖
在 运行 脚本之前:
在 运行 脚本之后:
[EDIT6]:
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
];
}
我相信你的目标如下。
- 通过从“A”列检索值,您想要 运行 函数
yahoo
,并希望将“B”列更新为“F”。
- 通过检查“E”栏的日期,你想执行
yahoo
的功能。
- 从您的问题和显示的图片来看,“E”列的值是日期对象。而且,你要查看年月日。
- 在这种情况下,您希望每个 运行ning 仅执行 5 次
yahoo
的函数。
- 当
yahoo
发生错误时,您想对“F”列进行计数。当没有错误发生时,您想将 null
设置为“F”列。
- 您想通过 time-driven 触发器执行脚本。
- 您可以自行安装time-driven触发器。
当我看到你的脚本时,没有包含实现上述目标的脚本。并且, setValues
用于循环。这样的话,工艺成本就会变高
那么,在您的情况下,为了实现您的目标,下面的示例脚本怎么样?
示例脚本:
这个脚本可以直接用脚本编辑器运行。所以,在你 运行 这个脚本使用 time-driven 触发器之前,我想推荐测试这个脚本。
在您测试该脚本并确认输出情况后,请为该函数安装time-driven触发器。这样,当您将 time-driven 触发器安装到此函数时,脚本将被触发器 运行。
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)
的实际脚本。所以,请注意这一点。
从你的问题和展示的图片来看,我了解到你想查看年月日。请注意这一点。
参考:
已添加:
根据您的以下附加问题,
also, I have added to the example sheet a second tab (db2) if I could ask you to have a brief look. Here, I have added 2 columns in between ticker and the rest of the data that yahoo() is returning. Assume that I want to fill in other data here. Would it be possible to adjust your script so that it leaves these columns alone, so only works on columns A and D to 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
函数都已修改。而且,为了使用您提供的 Spreadsheet 的第二个选项卡,sheet 名称也更改为 db2
。请注意这一点。
序曲 - 这很长 post 但主要是因为有很多图片可以阐明我的问题:)
我一直在从 Yahoo! 中提取公司数据金融,最初只针对少数股票,但目前针对数百只股票(很快就会达到数千只)。我目前正在实时提取这些数据,每次我加载价差 sheet 时,每个股票代码都会提取一个 url。这带来了三个问题:
- sheet 需要很长时间才能加载,因为它向 Yahoo! 发出数百个请求
- 我经常在 Google 端受到速率限制(每天最多 20k url 提取)
- 我可能会在 Yahoo! 上受到速率限制财务方面
因此我正在寻找更好的方法:
- 我将 运行 一个 google 脚本,每天为每个代码获取一次数据,而不是在每次传播时为每个代码调用 Yahoosheet
考虑以下简化示例 sheet(选项卡 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()
只是 return 一个数组。实际上,yahoo()
函数从 Yahoo! 中提取 JSON
数据。在 运行 脚本之后,传播sheet 看起来像:
到目前为止一切顺利。但是,如果列表不是 3,而是 5000 个代码,运行按原样设置脚本将使我快速限制速率(或者等待传播sheet 加载很长时间)。因此,我想到了以下几点:
- 每次脚本 运行,它只会提取 5 个代码的数据。该脚本将 运行 每分钟一次。 (就此线程而言,每分钟 5 个代码)
- 脚本将跟踪上次为每个代码下载数据的时间
假设列表当前如下所示:
假设今天是5月31日,脚本是运行:
从列表顶部开始,脚本应该要更新今天尚未更新的 5 个代码:
BLK
第2行今天已经更新,省略AAPL
昨天最后更新,因此它是第一个通过 Yahoo! 查询的代码
- 等等
现在第 2 行到第 9 行已更新数据。第二次脚本是 运行,它应该更新接下来的五个。再次从顶部开始,寻找前 5 个代码(1)没有“最后 运行”日期或(2)今天之前的 运行 日期:
如您所见,第 11 至 15 行现在也已更新。 TSLA
被跳过了,因为(不管什么原因)今天已经更新了。
这里又是同一个列表,只是多了 2 个代码。如果脚本在6月1日运行几次,结果会是这样:
- 3 批 5 个代码
- 一批 2 个代码
如果 Yahoo!财务服务始终会为每个代码提供 return 数据。然而,它不会。例如因为:
- 它没有某个代码的数据
- 请求超时
我认为我需要一个解决方案来跟踪下载数据时的错误。假设脚本在 6 月 2 日再次 运行 几次(由 Google 脚本中每分钟一次的触发器触发),并得到以下结果:
我们看到两个代码(JPM
和 ORCL
)无法更新数据。两者都在错误栏中标出,由待编写的脚本填写。
假设我们在 6 月 3 日再次 运行 脚本。这一天,JPM
数据下载完美,但 ORCL
再次产生错误。雅虎!没有 return 任何数据。 error
列更新为 2
。
如果代码连续 2 次尝试 (error = 2
) 未获取数据 return,则应永远跳过它。我会在某个时候手动填写,或者查看我是否输入了一个不存在的代码。
保留错误下载的地址可以防止脚本卡住。没有它,如果列表顶部有 5 个代码不断抛出错误,脚本将永远不会超出这 5 个。它将尝试为这些代码一遍又一遍地尝试从 Yahoo 下载数据。
在最后一张图片中,我们看到脚本在 6 月 4 日 运行 的结果。我再次为每 运行/ 分钟更新的批次(5 个代码)着色。
我已尽力解释我是如何考虑从 Yahoo! 构建防错下载的。金融。在我传播的其余部分sheet,每当我需要来自公司的元数据时,我可以简单地从这个 db
选项卡中获取它,而不是查询 Yahoo!一遍又一遍。
我的问题是我的脚本技能有限。我没有监督如何开始构建它。有人可以吗:
- 给我一个开始的(伪)代码或
- 反馈我的想法。我在这里遗漏了什么可能会出现问题的东西吗?
PS。我知道每次脚本 运行 时,我仍在进行 5 次 url 提取。有人向我建议我应该将这 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] Example spreadsheet here.
[EDIT3] 示例中的当前脚本传播sheet:
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] 运行ning 脚本 4 次后的结果:
[EDIT5] B
和 C
列中的现有数据被覆盖
在 运行 脚本之前:
在 运行 脚本之后:
[EDIT6]:
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
];
}
我相信你的目标如下。
- 通过从“A”列检索值,您想要 运行 函数
yahoo
,并希望将“B”列更新为“F”。- 通过检查“E”栏的日期,你想执行
yahoo
的功能。- 从您的问题和显示的图片来看,“E”列的值是日期对象。而且,你要查看年月日。
- 在这种情况下,您希望每个 运行ning 仅执行 5 次
yahoo
的函数。 - 当
yahoo
发生错误时,您想对“F”列进行计数。当没有错误发生时,您想将null
设置为“F”列。
- 通过检查“E”栏的日期,你想执行
- 您想通过 time-driven 触发器执行脚本。
- 您可以自行安装time-driven触发器。
当我看到你的脚本时,没有包含实现上述目标的脚本。并且, setValues
用于循环。这样的话,工艺成本就会变高
那么,在您的情况下,为了实现您的目标,下面的示例脚本怎么样?
示例脚本:
这个脚本可以直接用脚本编辑器运行。所以,在你 运行 这个脚本使用 time-driven 触发器之前,我想推荐测试这个脚本。
在您测试该脚本并确认输出情况后,请为该函数安装time-driven触发器。这样,当您将 time-driven 触发器安装到此函数时,脚本将被触发器 运行。
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)
的实际脚本。所以,请注意这一点。从你的问题和展示的图片来看,我了解到你想查看年月日。请注意这一点。
参考:
已添加:
根据您的以下附加问题,
also, I have added to the example sheet a second tab (db2) if I could ask you to have a brief look. Here, I have added 2 columns in between ticker and the rest of the data that yahoo() is returning. Assume that I want to fill in other data here. Would it be possible to adjust your script so that it leaves these columns alone, so only works on columns A and D to 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
函数都已修改。而且,为了使用您提供的 Spreadsheet 的第二个选项卡,sheet 名称也更改为db2
。请注意这一点。