以前投稿した GAS を使用した XPath 解析のスクリプト。
私は実際にこちらを使用して国内高配当株の管理を行なっています。
そのスプレッドシートと GAS 一式を公開します。
スプレッドシート
シートは次の通り
- 注文一覧
- ポートフォリオ
- 配当金構成比
- 設定
「注文一覧」に購入した証券コードと購入数、日付等を入力します。
「ポートフォリオ」の集計ボタンを押すと、GASによって「注文一覧」の情報をもとに、「ポートフォリオ」と「配当金構成比」のシートを更新します。
「設定」では GAS を動かす際に必要な設定を入れておきます。
スクリプト
スクリプト一式はこちらになります
class Util
{
static orgRound(value)
{
var elem = 0.01;
return Math.round(value / elem) * elem;
}
static roundRatio(value)
{
var elem = 0.01;
return Math.round((value * 100) / elem) * elem;
}
static toNumber(str)
{
if (str.length <= 0) return 0;
str = str.replace('円', '');
str = str.replace(',', '');
return parseInt(str);
}
static date(sheet, row, col)
{
var date = Utilities.formatDate(new Date(), 'JST', 'yyyy/MM/dd HH:mm');
sheet.set(row, col, date);
}
static checkLower(sheet, row, col, val)
{
if (sheet.get(row, col) <= val)
{
sheet.warn(row, col);
}
}
static checkUpper(sheet, row, col, val)
{
if (sheet.get(row, col) >= val)
{
sheet.warn(row, col);
}
}
}
class Sheet
{
constructor(name)
{
this._sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
}
set(row, col, val)
{
this._sheet.getRange(row, col).setValue(val);
}
get(row, col)
{
return this._sheet.getRange(row, col).getValue();
}
lastRow(row, col)
{
if (!row ||
!col)
{
return this._sheet.getLastRow();
}
return this.range(row, col).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()
}
range(row, col, rowNum, colNum)
{
if (!rowNum) rowNum = 1;
if (!colNum) colNum = 1;
return this._sheet.getRange(row, col, rowNum, colNum);
}
clear(row, col, rowNum, colNum)
{
if (!rowNum) rowNum = 1;
if (!colNum) colNum = 1;
var cell = this._sheet.getRange(row, col, rowNum, colNum);
cell.clearContent();
cell.clearFormat();
cell.setBackground(null);
}
findRow(col, str)
{
var endRow = this.lastRow();
for (var row = 1; row <= endRow; ++row)
{
if (this.get(row, col) == str)
{
return row;
}
}
return -1;
}
getUntilBlank(row, col)
{
var result = [];
var val = this.get(row, col);
while(val.length > 0)
{
result.push(val);
++row;
val = this.get(row, col);
}
return result;
}
warn(row, col)
{
var cell = this.range(row, col);
cell.setBackground("#ffcccc");
}
notice(row, col)
{
var cell = this.range(row, col);
//cell.setBackground("#ffccff");
cell.setBackground("#ffffcc");
}
}
function getTag(str, offset)
{
var res = {};
res.begin = str.indexOf('<', offset);
res.end = str.indexOf('>', res.begin) + 1;
res.tag = str.slice(res.begin, res.end);
res.name = res.tag.replace('/','').replace('<','').replace('>','').split(' ')[0];
res.isClose = res.tag.indexOf('</') >= 0;
res.isComment = res.tag.indexOf('<!') >= 0;
res.isSingle = res.tag.indexOf('/>') >= 0;
return res;
}
function searchEnd(content, begin)
{
var end = begin;
var list = [];
var offset = begin;
while (true)
{
var res = getTag(content, offset);
if (res.begin < 0) break;
offset = res.end;
if (res.isSingle) continue;
if (res.isComment) continue;
if (res.isClose)
{
while (list.length > 0)
{
var name = list.shift();
if (name == res.name) break;
}
}
else
{
list.unshift(res.name);
}
if (list.length <= 0)
{
end = offset;
break;
}
}
return end;
}
function removeParentTag(content)
{
var begin = content.indexOf('>') + 1;
var end = content.lastIndexOf('<');
return content.slice(begin, end);
}
function searchId(content, hierarchy)
{
var id = hierarchy.replace('*[@','').replace(']','');
var index = content.indexOf(id);
return content.lastIndexOf('<', index);
}
function searchTag(content, hierarchy)
{
var array = hierarchy.replace('[', ',').replace(']', '').split(',');
var tag = array[0];
var count = 1;
if (array.length > 1)
{
count = parseInt(array[1]);
}
var begin = 0;
var list = [];
var offset = 0;
while (true)
{
var res = getTag(content, offset);
if (res.begin < 0) break;
offset = res.end;
if (res.isSingle) continue;
if (res.isComment) continue;
if (res.isClose)
{
while (list.length > 0)
{
var name = list.shift();
if (name == res.name) break;
}
}
else
{
if (list.length <= 0)
{
if (tag == res.name)
{
--count;
}
}
list.unshift(res.name);
}
if (count <= 0)
{
begin = res.begin;
break;
}
}
return begin;
}
function searchBegin(content, hierarchy)
{
var index = hierarchy.indexOf('@');
if (index < 0)
{
return searchTag(content, hierarchy);
}
return searchId(content, hierarchy);
}
function extractText(content)
{
var offset = 0;
var res = getTag(content, offset);
if (res.begin < 0)
{
return content;
}
var begin = searchTag(content, res.name);
var end = searchEnd(content, begin);
var remove = content.slice(begin, end);
return content.replace(remove, '');
}
function narrow(content, hierarchy)
{
if (hierarchy.length <= 0) return content;
if (hierarchy.indexOf('text()') >= 0)
{
return extractText(content);
}
var begin = searchBegin(content, hierarchy);
var end = searchEnd(content, begin);
var target = content.slice(begin, end);
return removeParentTag(target);
}
function searchXPath(content, xpath)
{
var result = [];
for (var i in xpath)
{
var target = content;
var array = xpath[i].split('/');
for (var j in array)
{
if (array[j].length <= 0) continue;
target = narrow(target, array[j]);
}
result.push(target);
}
return result;
}
function searchXPathFromURL(url, xpath)
{
var content = UrlFetchApp.fetch(url).getContentText('UTF-8');
return searchXPath(content, xpath);
}
function Test_01()
{
var code = 8316;
var url = 'https://kabutan.jp/stock/?code=' + code;
var market = '//*[@id="stockinfo_i1"]/div[1]/span'; // market
var name = '//*[@id="stockinfo_i1"]/div[1]/h2/text()'; // name
var industry = '//*[@id="stockinfo_i2"]/div/a'; // industry
var price = '//*[@id="stockinfo_i1"]/div[2]/span[2]'; // price
var eps = '//*[@id="kobetsu_right"]/div[3]/table/tbody/tr[3]/td[4]'; // eps
var dividend = '//*[@id="kobetsu_right"]/div[3]/table/tbody/tr[3]/td[5]'; // dividend
var yield = '//*[@id="stockinfo_i3"]/table/tbody/tr[1]/td[3]/text()'; // yield
var xpath = [market, name, industry, price, eps, dividend, yield];
//var result = searchXPathFromURL(url, xpath);
var content = UrlFetchApp.fetch(url).getContentText('UTF-8');
var result = searchXPath(content, xpath);
for (var i in xpath)
{
console.log(xpath[i] + ' > ' + result[i]);
}
}
class Setting
{
constructor()
{
var sheet = new Sheet('設定');
var indexCol = 2;
var settingCol = 3;
var row = 0;
row = sheet.findRow(indexCol, '情報取得URL');
this.url = sheet.get(row, settingCol);
row = sheet.findRow(indexCol, '利回り');
this.yield = sheet.get(row, settingCol);
row = sheet.findRow(indexCol, '景気敏感業種');
this.businessIndustry = sheet.getUntilBlank(row, settingCol);
row = sheet.findRow(indexCol, '最低銘柄数');
this.companyLower = sheet.get(row, settingCol);
row = sheet.findRow(indexCol, '景気敏感業種の配当金構成比の上限');
this.businessUpper = sheet.get(row, settingCol);
row = sheet.findRow(indexCol, '特定業種の配当金構成比の上限');
this.indivisualIndustryUpper = sheet.get(row, settingCol);
row = sheet.findRow(indexCol, '個別企業の配当金構成比の上限');
this.indivisualCompanyUpper = sheet.get(row, settingCol);
}
}
class Order
{
constructor()
{
this._defineProperty('StartRow', 3);
this._defineProperty('CodeCol', 1);
this._defineProperty('CountCol', 2);
this._defineProperty('UnitPriceCol', 3);
this._defineProperty('DateCol', 4);
this._sheet = new Sheet('注文一覧');
this._array = {};
this._calc();
}
write(portfolio)
{
var array = this._array;
var keys = Object.keys(array);
for (var i = 0; i < keys.length; ++i)
{
var row = StartRow + i;
var number = i + 1;
var code = keys[i];
var data = array[code];
portfolio.set(row, NumberCol, number);
portfolio.set(row, CodeCol, code);
portfolio.set(row, NumCol, data.count);
portfolio.set(row, BuyTotalCol, data.total);
portfolio.set(row, BuyUnitCol, data.unit);
}
}
_calc()
{
var endRow = this._sheet.lastRow();
var array = {};
var warning = [];
for(var row = this.StartRow; row <= endRow; ++row)
{
var info = this._getInfo(row);
if (info.isValid == false)
{
warning.push(row);
continue;
}
var data = { count:0, total:0, unit:0 };
if (array[info.code])
{
data = array[info.code];
}
data.count += info.count;
data.total += info.count * info.unitPrice;
data.unit = data.total / data.count;
array[info.code] = data;
}
this._array = array;
//console.log(this._array);
if (warning.length > 0)
{
console.log('warning row:' + warning);
}
}
_defineProperty(name, val)
{
Object.defineProperty(this, name, { value: val });
}
_getInfo(row)
{
var info = {};
info.code = this._sheet.get(row, this.CodeCol);
info.count = this._sheet.get(row, this.CountCol);
info.unitPrice = this._sheet.get(row, this.UnitPriceCol);
info.isValid = true;
if (info.code.length <= 0 ||
info.count.length <= 0 ||
info.unitPrice.length <= 0)
{
info.isValid = false;
}
return info;
}
}
function getProfit(row, price)
{
var buyUnit = _portfolio.get(row, BuyUnitCol);
var profit = Util.toNumber(price) - buyUnit;
return profit;
}
function getProfitRatio(row, profit)
{
var buyUnit = _portfolio.get(row, BuyUnitCol);
var profitRatio = Util.orgRound(profit / buyUnit * 100);
return profitRatio;
}
function getStockData(row)
{
var code = _portfolio.get(row, CodeCol);
var url = _setting.url + code;
var market = '※ ここに XPath を入力 ※'; // market
var name = '※ ここに XPath を入力 ※'; // name
var industry = '※ ここに XPath を入力 ※'; // industry
var price = '※ ここに XPath を入力 ※'; // price
var eps = '※ ここに XPath を入力 ※'; // eps
var dividend = '※ ここに XPath を入力 ※'; // dividend
var yield = '※ ここに XPath を入力 ※'; // yield
var xpath = [market, name, industry, price, eps, dividend, yield];
var content = UrlFetchApp.fetch(url).getContentText('UTF-8');
var result = searchXPath(content, xpath);
var stockData = {};
stockData.market = result[0];
stockData.name = result[1];
stockData.industry = result[2];
stockData.price = result[3].replace('円', '');
stockData.eps = result[4];
stockData.dividend = result[5];
stockData.yield = result[6];
stockData.profit = getProfit(row, stockData.price);
stockData.profitRatio = getProfitRatio(row, stockData.profit);
return stockData;
}
function isCodeInvalid(row)
{
return (_portfolio.get(row, CodeCol).length <= 0);
}
function getInformation(row)
{
// check code
if (isCodeInvalid(row)) return;
// check buy
var buyUnit = _portfolio.get(row, BuyUnitCol);
if (buyUnit.length <= 0) return;
// check calcs
var isDone = true;
for(var col = CalcStartCol; col <= InfoEndCol; ++col)
{
var value = _portfolio.get(row, col);
if (value.toString().length > 0) continue;
isDone = false;
}
if (isDone) return;
var stockData = getStockData(row);
_portfolio.set(row, MarketCol, stockData.market);
_portfolio.set(row, NameCol, stockData.name);
_portfolio.set(row, IndustryCol, stockData.industry);
_portfolio.set(row, PriceCol, stockData.price);
_portfolio.set(row, ProfitCol, stockData.profit);
_portfolio.set(row, ProfitRatioCol, stockData.profitRatio);
_portfolio.set(row, EPSCol, stockData.eps);
_portfolio.set(row, DividendCol, stockData.dividend);
_portfolio.set(row, YieldCol, stockData.yield);
Util.checkLower(_portfolio, row, ProfitCol, 0);
Util.checkLower(_portfolio, row, ProfitRatioCol, 0);
Util.checkLower(_portfolio, row, YieldCol, _setting.yield);
}
function getInformationAll()
{
for (var row = StartRow; row <= _endRow; ++row)
{
getInformation(row);
}
}
function calcTotal(col)
{
var total = 0;
for (var row = StartRow; row <= _endRow; ++row)
{
var price = _portfolio.get(row, col);
total += price;
}
return total;
}
function calcPriceRatio()
{
var totalPrice = calcTotal(PriceCol);
for (var row = StartRow; row <= _endRow; ++row)
{
if (isCodeInvalid(row)) continue;
var price = _portfolio.get(row, PriceCol);
var priceRatio = price / totalPrice * 100;
_portfolio.set(row, PriceRatioCol, Util.orgRound(priceRatio));
}
}
function calcDividendTotal()
{
var totalDividend = 0;
for (var row = StartRow; row <= _endRow; ++row)
{
if (isCodeInvalid(row)) continue;
var num = _portfolio.get(row, NumCol);
var dividend = _portfolio.get(row, DividendCol);
var dividendTotal = num * dividend;
totalDividend += dividendTotal;
_portfolio.set(row, DividendTotalCol, dividendTotal);
}
return totalDividend;
}
function calcDividendRatio(totalDividend)
{
for (var row = StartRow; row <= _endRow; ++row)
{
if (isCodeInvalid(row)) continue;
var dividendTotal = _portfolio.get(row, DividendTotalCol);
var dividendRatio = dividendTotal / totalDividend * 100;
_portfolio.set(row, DividendRatioCol, Util.orgRound(dividendRatio));
Util.checkUpper(_portfolio, row, DividendRatioCol, _setting.indivisualCompanyUpper);
}
}
function calcYieldRatio()
{
for (var row = StartRow; row <= _endRow; ++row)
{
if (isCodeInvalid(row)) continue;
var dividend = _portfolio.get(row, DividendCol);
var buyUnit = _portfolio.get(row, BuyUnitCol);
var yieldRatio = dividend / buyUnit;
_portfolio.set(row, YieldRatioCol, Util.roundRatio(yieldRatio));
}
}
function setBuyRecommended(row, judge)
{
_portfolio.set(row, BuyRecommendedCol, judge);
_portfolio.range(row, BuyRecommendedCol).setHorizontalAlignment("right");
}
function buyRecommended()
{
for (var row = StartRow; row <= _endRow; ++row)
{
var profit = _portfolio.get(row, ProfitCol);
var yield = _portfolio.get(row, YieldCol);
var dividendRatio = _portfolio.get(row, DividendRatioCol);
if (yield > _setting.yield)
{
if (dividendRatio < _setting.indivisualCompanyUpper)
{
if (profit < 0)
{
setBuyRecommended(row, '◎');
continue;
}
setBuyRecommended(row, '〇');
continue;
}
if (profit < 0)
{
setBuyRecommended(row, '△');
continue;
}
}
setBuyRecommended(row, '×');
}
}
function clearPortfolio()
{
var rowNum = _portfolio.lastRow() - StartRow + 1;
var colNum = BuyRecommendedCol - NumberCol + 1;
_portfolio.clear(StartRow, NumberCol, rowNum, colNum);
_portfolio.clear(DateRow, DateCol);
}
const NumberCol = 1;
const CodeCol = 2;
const NumCol = 3;
const BuyTotalCol = 4;
const BuyUnitCol = 5;
const MarketCol = 6;
const NameCol = 7;
const IndustryCol = 8;
const PriceCol = 9;
const ProfitCol = 10;
const ProfitRatioCol = 11;
const EPSCol = 12;
const DividendCol = 13;
const YieldCol = 14;
const InfoEndCol = YieldCol;
const PriceRatioCol = 15;
const DividendTotalCol = 16;
const DividendRatioCol = 17;
const YieldRatioCol = 18;
const BuyRecommendedCol = 19;
const CalcStartCol = MarketCol;
const CalcRow = 1;
const StartRow = 3;
const DateRow = 1;
const DateCol = 7;
var _setting = new Setting();
var _order = new Order();
var _portfolio = new Sheet('ポートフォリオ');
var _constitution = new Sheet('配当金構成比');
var _endRow = StartRow;
var _totalRow = _endRow + 1;
function aggregate()
{
clearPortfolio();
_order.write(_portfolio);
_endRow = _portfolio.lastRow(StartRow, NumberCol);
_totalRow = _endRow + 1;
getInformationAll();
calcPriceRatio();
var totalDividend = calcDividendTotal();
calcDividendRatio(totalDividend);
calcYieldRatio();
buyRecommended();
Util.date(_portfolio, DateRow, DateCol);
}
function Aggregate()
{
aggregate();
constitution();
}
function calcIndustry()
{
var industryDataArray = {}
var endRow = _portfolio.lastRow(StartRow, NumberCol);
for (var row = StartRow; row <= endRow; ++row)
{
var industry = _portfolio.get(row, IndustryCol);
var price = _portfolio.get(row, PriceCol) * _portfolio.get(row, NumCol);
var buyTotal = _portfolio.get(row, BuyTotalCol);
var dividendTotal = _portfolio.get(row, DividendTotalCol);
var dividendRatio = _portfolio.get(row, DividendRatioCol);
var data = industryDataArray[industry];
if (data == undefined)
{
data = {};
data.count = 0;
data.price = 0;
data.buyTotal = 0;
data.dividendTotal = 0;
data.dividendRatio = 0;
}
data.count = data.count + 1;
data.price = data.price + price;
data.buyTotal = data.buyTotal + buyTotal;
data.dividendTotal = data.dividendTotal + dividendTotal;
data.dividendRatio = data.dividendRatio + dividendRatio;
industryDataArray[industry] = data;
}
var row = StartRow2;
for (var key in industryDataArray)
{
var data = industryDataArray[key];
_constitution.set(row, IndustryCol2, key);
_constitution.set(row, CountCol2, data.count);
_constitution.set(row, PriceCol2, data.price);
_constitution.set(row, BuyTotalCol2, data.buyTotal);
_constitution.set(row, DividendTotalCol2, data.dividendTotal);
_constitution.set(row, DividendRatioCol2, data.dividendRatio);
if (_setting.businessIndustry.indexOf(key) >= 0)
{
_constitution.notice(row, IndustryCol2);
}
Util.checkUpper(_constitution, row, DividendRatioCol2, _setting.indivisualIndustryUpper);
++row;
}
}
function calcIndustryTotal()
{
var startRow = StartRow2;
var endRow = _constitution.lastRow();
var total = { count:0, price:0, buyTotal:0, dividendTotal:0, dividendRatio:0 }
for (var row = startRow; row <= endRow; ++row)
{
total.count += _constitution.get(row, CountCol2);
total.price += _constitution.get(row, PriceCol2);
total.buyTotal += _constitution.get(row, BuyTotalCol2);
total.dividendTotal += _constitution.get(row, DividendTotalCol2);
total.dividendRatio += _constitution.get(row, DividendRatioCol2);
}
var totalRow = endRow + 2;
_constitution.set(totalRow, IndustryCol2, '合計');
_constitution.set(totalRow, CountCol2, total.count);
_constitution.set(totalRow, PriceCol2, total.price);
_constitution.set(totalRow, BuyTotalCol2, total.buyTotal);
_constitution.set(totalRow, DividendTotalCol2, total.dividendTotal);
_constitution.set(totalRow, DividendRatioCol2, total.dividendRatio);
Util.checkLower(_constitution, totalRow, CountCol2, _setting.companyLower);
var profitRow = totalRow + 1;
_constitution.set(profitRow, IndustryCol2, '損益');
_constitution.set(profitRow, BuyTotalCol2, total.price - total.buyTotal);
var profitRatioRow = profitRow + 1;
_constitution.set(profitRatioRow, IndustryCol2, '配当利回り');
_constitution.set(profitRatioRow, PriceCol2, Util.roundRatio(total.dividendTotal / total.price));
_constitution.set(profitRatioRow, BuyTotalCol2, Util.roundRatio(total.dividendTotal / total.buyTotal));
Util.checkLower(_constitution, profitRatioRow, PriceCol2, _setting.yield);
Util.checkLower(_constitution, profitRatioRow, BuyTotalCol2, _setting.yield);
var businessRatioRow = profitRatioRow + 1;
_constitution.set(businessRatioRow, IndustryCol2, '景気敏感業種比');
_constitution.notice(businessRatioRow, IndustryCol2);
var businessRatio = 0;
for (var row = startRow; row <= endRow; ++row)
{
var industry = _constitution.get(row, IndustryCol2);
if (_setting.businessIndustry.indexOf(industry) >= 0)
{
businessRatio += _constitution.get(row, DividendRatioCol2);
}
}
_constitution.set(businessRatioRow, DividendRatioCol2, businessRatio);
Util.checkUpper(_constitution, businessRatioRow, DividendRatioCol2, _setting.businessUpper);
}
function clearConstitution()
{
var rowNum = _constitution.lastRow() - StartRow2 + 1;
if (rowNum > 0)
{
_constitution.clear(StartRow2, StartCol2, rowNum, ColNum2);
}
_constitution.clear(DateRow2, DateCol2);
}
const StartRow2 = 3;
const IndustryCol2 = 2;
const CountCol2 = 3;
const PriceCol2 = 4;
const BuyTotalCol2 = 5;
const DividendTotalCol2 = 6;
const DividendRatioCol2 = 7;
const StartCol2 = IndustryCol2;
const EndCol2 = DividendRatioCol2;
const ColNum2 = EndCol2 - StartCol2 + 1;
const DateRow2 = 1;
const DateCol2 = 5;
function constitution()
{
clearConstitution();
calcIndustry();
calcIndustryTotal();
Util.date(_constitution, DateRow2, DateCol2);
}
「util.gs」はユーティリティメソッドをまとめています。
「sheet.gs」がシート管理用クラスです。
「xpath.gs」は html から xpath をもとに情報を抽出するクラスです。こちらが先のブログの本体です。
「setting.gs」は「設定」シートから設定情報を取得します。
「order.gs」は「注文一覧」シートから注文内容を収集します。
「portfolio.gs」は注文内容と設定情報をもとに「ポートフォリオ」シートを完成させます。
「constitution.gs」は完成した「ポートフォリオ」の情報をもとに総合的な情報をまとめ「配当金構成比」シートを完成させます。
こんな感じでスクリプトを並べています
補足
この国内高配当株の管理スプレッドシートは元ネタがあります。国内高配当株について調べている人は知っているかもしれません。
元ネタはエクセルでの管理でしたが、それをスプレッドシート化しました。追加でスクレイピングによる最新情報の取得、その他の機能を実装しています。
公開しているスプレッドシートと GAS はそのままだと動きません。取得 URL と XPath を適切に設定する必要があります。
実際に URL 、XPath 指定をして、プログラムを実行する場合は自己責任でお願いします。
運用
自分はこのスプレッドシートを1日数回実行し、「買い推奨」が「◎」や「〇」になった銘柄を1株買う、という運用をしています。
「買い推奨」判定についての詳細は、スクリプトを参照ください。大した判定はしていないです。
実績は公開しているスプレッドシートの「配当金構成比」シートを見ていただければ分かるかと。一応プラスです。
銘柄の選定については、「国内高配当株」等で Google 検索し、引っかかったサイトから候補をピックアップ、IRBank でステータス確認、総合的に問題なさそうであれば、1株購入してみてポートフォリオに追加、という手順です。
このあたりの選定方法も調べるといくつか詳しく説明してくださっているサイトがあるので探してみてください。
まとめ
このスプレッドシート自体は数か月くらいで実装できました。実際に実績を出せるか不安でしたが、1年弱運用してみて、総合的にプラスになっているので一応満足しています。
自分が本格的に株に手を出し始めたのが去年の頭くらいなので、手を出す時期が良かったというのもあるかとは思っています。
今回公開したスプレッドシートが皆さんのお役に立てれば幸いです。
不明な点、質問などありましたらコメント、フォームでお問い合わせください。
可能な限りお答えします。
コメント
突然ですみませんが、URL と XPathを使ってウェブ上で株価を取得したいんですが、公開してくれたGASで取得できますか?