This Google Apps Script code to scrape historical data from from Yahoo Finance stopped working yesterday. It suddenly gives the error - No data (data.length == 0).
I think the bug is in the line 8 script while getting the JSON but I dont't have the necessary skill to fix it.
It woud be appreciate your help with issue.
function Scrapeyahoo(symbol) {
//Leemos de yahoo finance historical data
const s = encodeURI(symbol); // so that it works with a url
// turn it into an URL and call it
const url = 'https://finance.yahoo.com/quote/' +s +'/history?p=' +s;
const res = UrlFetchApp.fetch(url, { muteHttpExceptions: true }).getContentText();
const $ = Cheerio.load(res);
const data = $('script').toArray().reduce((ar, x) => {
const c = $(x).get()[0].children;
if (c.length > 0) {
const d = c[0].data.trim().match(/({"context"[\s\S\w]+);\n}\(this\)\);/);
if (d && d.length == 2) {
ar.push(JSON.parse(d[1]));
}
}
return ar;
}, []);
if (data.length == 0) throw new Error("No data.");
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
var key = Object.entries(data[0]).find(([k]) => !["context", "plugins"].includes(k))[1];
if (!key) return;
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const obj1 = data[0];
const obj2 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj1.context.dispatcher.stores, key)));
const ar = obj2.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
// ---
return ar
}
The original code was modified in December according to this solution, after it stopped working, but I can't find a solution for the issue now.
It seems that the specification for retrieving the key has been changed. In this case, var key = Object.entries(data[0]).find(([k]) => !["context", "plugins"].includes(k))[1]; doesn't return the correct key. By this, an error occurs at CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj1.context.dispatcher.stores, key)).
In the current stage, when I saw this script, the modified script is as follows.
Modified script:
function Scrapeyahoo(symbol) {
const s = encodeURI(symbol);
const url = 'https://finance.yahoo.com/quote/' +s +'/history?p=' +s;
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
var key = [...new Map(Object.entries(obj).filter(([k]) => !["context", "plugins"].includes(k)).splice(-4)).values()].join("");
if (!key) return;
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const obj1 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
const ar = obj1.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
return ar
}
In this case, Cheerio is not used.
Note:
In this sample, in order to load crypto-js, eval(UrlFetchApp.fetch(cdnjs).getContentText()) is used. But, if you don’t want to use it, you can also use this script by copying and pasting the script of https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js to the script editor of Google Apps Script. By this, the process cost can be reduced.
I can confirm that this method can be used for the current situation (January 27, 2023). But, when the specification in the data and HTML is changed in the future update on the server side, this script might not be able to be used. Please be careful about this.
Reference:
crypto-js
Related
I have a script to scrape Yahoo Historical Data but it looks like the decrypt service stopped working.
function Scrapeyahoo(symbol) {
//modificación del 27/1/23 hecha por Tanaike
// https://stackoverflow.com/questions/75250562/google-apps-script-stopped-scraping-data-from-yahoo-finance/75253348#75253348
const s = encodeURI(symbol);
const url = 'https://finance.yahoo.com/quote/' +s +'/history?p=' +s;
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
var key = [...new Map(Object.entries(obj).filter(([k]) => !["context", "plugins"].includes(k)).splice(-4)).values()].join("");
if (!key) return;
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const obj1 = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
const ar = obj1.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
return ar
}
I get the error Malformed UTF-8 data stringify in the line
JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key)));
A few weeks ago #Tanaike solved here a similar issue, but it looks like there has been new changes.
I ask for help with this problem.
Thanks in advance.
It seems that the specification for retrieving the key has been changed. In this case, vvar key = [...new Map(Object.entries(obj).filter(([k]) => !["context", "plugins"].includes(k)).splice(-4)).values()].join(""); doesn't return the correct key. And also, it seems that the logic for retrieving the valid key has been changed. But, unfortunately, I cannot still find the correct logic. So, in this answer, I would like to refer to this thread. In this thread, the valid keys are listed in a text file. When this is reflected in your script, it becomes as follows.
Modified script:
function Scrapeyahoo(symbol) {
const s = encodeURI(symbol);
const url = 'https://finance.yahoo.com/quote/' + s + '/history?p=' + s;
var html = UrlFetchApp.fetch(url).getContentText().match(/root.App.main = ([\s\S\w]+?);\n/);
if (!html || html.length == 1) return;
var obj = JSON.parse(html[1].trim());
const cdnjs = "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js";
eval(UrlFetchApp.fetch(cdnjs).getContentText());
const keyFile = "https://github.com/ranaroussi/yfinance/raw/main/yfinance/scrapers/yahoo-keys.txt";
const res = UrlFetchApp.fetch(keyFile);
const keys = res.getContentText().split("\n").filter(String);
let obj1 = keys.reduce((ar, key) => {
try {
const o = JSON.parse(CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(obj.context.dispatcher.stores, key.trim())));
ar.push(o);
} catch (e) {
// console.log(e.message)
}
return ar;
}, []);
if (obj1.length == 0) {
throw new Error("Specification at the server side might be changed. Please check it.");
}
obj1 = obj1[0];
const header = ["date", "open", "high", "low", "close", "adjclose", "volume"];
const ar = obj1.HistoricalPriceStore.prices.map(o => header.map(h => h == "date" ? new Date(o[h] * 1000) : (o[h] || "")));
return ar
}
When I tested this script with a sample value of CL=F as symbol, I confirmed that the script worked.
Note:
In this sample, in order to load crypto-js, eval(UrlFetchApp.fetch(cdnjs).getContentText()) is used. But, if you don’t want to use it, you can also use this script by copying and pasting the script of https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js to the script editor of Google Apps Script. By this, the process cost can be reduced.
I can confirm that this method can be used for the current situation (February 15, 2023). But, when the specification in the data and HTML is changed in the future update on the server side, this script might not be able to be used. Please be careful about this.
Reference:
crypto-js
Hi I'm just new in Google Apps Script. I want to modify this formula below to Google Apps Script
=arrayFORMULA(iferror(VLOOKUP(J3,{'WO SR 22/23'!P:P,'WO SR 22/23'!B:B},2,FALSE)))
The lookup value is in wsPetitionStatusReport sheet. I tried code below but it always return null. Can you help me with this? Thank you so much
function vLookUpVALUE() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const wsWOSR = ss.getSheetByName("WO SR 22/23")
const wsPetitionStatusReport = ss.getSheetByName("Petition Status Report ")
const searchVALUES = wsPetitionStatusReport.getRange("J3:J").getValues()
const wsWOSRDATcolO = wsWOSR.getRange("B3:P" + wsWOSR.getLastRow()).getValues();
const matchDATAcolO = searchVALUES.map(searchROWcolO => {
const matchROWcolO = wsWOSRDATcolO.find (r => r[0] == searchROWcolO[0])
return matchROWcolO ? [matchROWcolO[0]] : [null]
})
console.log(matchDATAcolO)
}
You can evaluate the array formula for the whole column by replacing J3 with an open-ended reference like J3:J:
=arrayformula(iferror(vlookup(J3:J, { 'WO SR 22/23'!P:P, 'WO SR 22/23'!B:B }, 2, false)))
To do the same with Apps Script, use something like this:
function vlookupLookAlike() {
const ss = SpreadsheetApp.getActive();
const source = {
values: ss.getRange('WO SR 22/23!B3:P').getValues(),
keyColumn: 14, // =column(P3) - column(B3)
dataColumn: 0,
};
source.keys = source.values.map(row => row[source.keyColumn]);
source.data = source.values.map(row => row[source.dataColumn]);
const target = {
range: ss.getRange('Petition Status Report !J3:J'), // trailing space
};
target.keys = target.range.getValues().flat();
target.results = target.keys.map(key => [key && source.data[source.keys.indexOf(key)]]);
target.range.offset(0, 5).setValues(target.results);
}
This is my code:
function pricesV2(){
var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
let myItems = new Map()
let myItem = new Map1()
json=eval('data')
json.forEach(function(elem){myItems.set(elem.id.toString(),elem.name)})
json.forEach(function(elem){myItem.set(elem.id.toString(),elem.examine)})
var url='https://prices.runescape.wiki/api/v1/osrs/latest'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','name','examine','high','low','lowTime', 'highTime'])
for (let p in eval('data.data')) {
try{result.push([p,myItems.get(p),myItem.get(p),data.data.item(p).high,data.data.item(p).low,convertTimestamp(data.data.item(p).lowTime),convertTimestamp(data.data.item(p).highTime)])}catch(e){}
}
return result
}
This is maybe important to know the variables of the API:
function prices(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/latest'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(['#','high','low','highTime','lowTime'])
for (let p in eval('data.data')) {
try{result.push([p,data.data.item(p).high,data.data.item(p).low,data.data.item(p).lowTime, ,data.data.item(p).highTime])}catch(e){}
}
return result
}
function naming(url){
//var url='https://prices.runescape.wiki/api/v1/osrs/mapping'
var data = JSON.parse(UrlFetchApp.fetch(url).getContentText())
var result = []
result.push(["id","name","examine","members","lowalch","limit","value","highalch"])
json=eval('data')
json.forEach(function(elem){
result.push([elem.id.toString(),elem.name,elem.examine,elem.members,elem.lowalch,elem.limit,elem.value,elem.highalch])
})
return result
}
These are 2 API combined (Importing API data via importJSON, solution did work out for 1 element, (element.name)). But when I want to add more from mapping it is giving an error. Could someone help me out? I want to combine all results in one table.
I believe your goal is as follows.
You want to integrate 2 returned data (JSON data) with the value of id.
From your reply of The colums doesn't need in this specific order., you are not required to check the order of columns.
You want to run the script as a custom function.
From your showing script, I thought that you might have wanted to use this script as a custom function.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please put a custom function =SAMPLE() to a cell. By this, the script is run.
function SAMPLE() {
const url1 = "https://prices.runescape.wiki/api/v1/osrs/mapping";
const url2 = "https://prices.runescape.wiki/api/v1/osrs/latest";
const [res1, res2] = [url1, url2].map(url => JSON.parse(UrlFetchApp.fetch(url).getContentText()));
const head = [...Object.keys(res1[0]), ...Object.keys(res2.data[Object.keys(res2.data)[0]])];
const obj1 = res1.reduce((o, e) => (o[e.id] = e, o), {});
const obj2 = Object.entries(res2.data).reduce((o, [k, v]) => (o[k] = v, o), {});
const keys = Object.keys(obj1).map(e => Number(e)).sort((a, b) => a - b);
const values = [head, ...keys.map(k => {
const o = Object.assign(obj1[k], obj2[k]);
return head.map(h => o[h] || "");
})];
return values;
}
Testing:
When this script is run, the following result is obtained.
Note:
If you want to set the specific order of the columns, please modify head in the above script.
When the custom function of =SAMPLE() is put to a cell, if an error occurs, please reopen Spreadsheet and test it again.
If you want to directly put the values to the Spreadsheet instead of the custom function, please modify the script.
References:
Custom Functions in Google Sheets
map()
reduce()
Added:
From the following 3 new questions,
Now how can I change like the top row to- > id, name, examine, members, lowalch, highalch, limit, high, low, lowtime, hightime? How can this be done in the function head, can't edit them individualy?
And also how can I format/convert highTime and lowTime to time (hh:mm:ss)?
From The colums doesn't need in this specific order., I didn't check the order of the column. In that case, as I have already mentioned in my answer, please modify head as follows. About your 2nd new question, in this case, please parse the unix time as follows.
So, when these new 2 questions are reflected in my sample script, it becomes as follows.
Sample script:
function SAMPLE() {
const url1 = "https://prices.runescape.wiki/api/v1/osrs/mapping";
const url2 = "https://prices.runescape.wiki/api/v1/osrs/latest";
const [res1, res2] = [url1, url2].map(url => JSON.parse(UrlFetchApp.fetch(url).getContentText()));
const head = ['id', 'name', 'examine', 'members', 'lowalch', 'highalch', 'limit', 'high', 'low', 'lowTime', 'highTime'];
const obj1 = res1.reduce((o, e) => (o[e.id] = e, o), {});
const obj2 = Object.entries(res2.data).reduce((o, [k, v]) => (o[k] = v, o), {});
const keys = Object.keys(obj1).map(e => Number(e)).sort((a, b) => a - b);
const timeZone = Session.getScriptTimeZone();
const values = [head, ...keys.map(k => {
const o = Object.assign(obj1[k], obj2[k]);
return head.map(h => o[h] ? (['lowTime', 'highTime'].includes(h) ? Utilities.formatDate(new Date(o[h] * 1000), timeZone, "HH:mm:ss") : o[h]) : "");
})];
return values;
}
Note:
About your following 3rd question,
How can this database also be added <prices.runescape.wiki/api/v1/osrs/volumes>?
I think that this is a new question. In this case, please post it as a new question.
=IF(ARRAYFORMULA(JOIN("-",TRIM(IMPORTXML("http://old.statarea.com/","//tr[3]/th[2]/b | //tr[3]/th[3]/b | //tr[3]/th[7]/b | //tr[3]/th[8]/b | //tr[3]/th[9]/b | //tr[3]/th[16]/b"))))="Host-Guest-1-X-2-2.5",
{IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[2]/a"),
IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[3]/a"),
ARRAYFORMULA(VALUE(TEXT(1/QUERY(SUBSTITUTE(IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[7]"),"HX",""),"Where Col1 is not null"),"0.00"))),
ARRAYFORMULA(VALUE(TEXT(1/QUERY(SUBSTITUTE(IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[8]"),"H2",""),"Where Col1 is not null"),"0.00"))),
ARRAYFORMULA(VALUE(TEXT(1/QUERY(SUBSTITUTE(IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[9]"),"HX",""),"Where Col1 is not null"),"0.00"))),
ARRAYFORMULA(VALUE(TEXT(1/QUERY(SUBSTITUTE(IMPORTXML(IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"),"//tr/td[16]"),"hc2",""),"Where Col1 is not null"),"0.00")))},
"Off")
Even earlier today it was working perfectly, showing all the data. But from now on this same error always appears, even if I try to import something very simple, like:
=IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href")
I would like to know if it is possible to reproduce this (the complete import that I put at the beginning of the question) import through Google App Script (GAS) and if someone could show me how it would look, that way there would be no more limit problem.
You want to convert the formula in your question to Google Apps Script.
The base URL is http://old.statarea.com/.
You want to retrieve the values by changing the data URL with IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href") and IMPORTXML("http://old.statarea.com/","//tr/td/a[5]/#href").
As the values, you want to retrieve td[7], td[8], td[9] and td[16].
For example, you want to calculate the retrieved value of 50% as 1 / 0.5, and want to use it as the value for putting to Spreadsheet.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Flow:
In this case, I retrieve the values you want by 3 steps.
Retrieve a part of values from the HTML using Parser which is a Google Apps Script library.
Parse the retrieved HTML with XmlService by removing the unnecessary values.
Retrieve the result values using XmlService.
Usage:
1. Install "Parser"
Please install a Google Apps Script library of "Parser".
2. Sample script 1:
This is a sample script. In this script, you can use this as the custom function. So please put a formula of =sample(5) to a cell.
function sample(placeOfUrl) {
// Retrieve URL.
var baseUrl = "http://old.statarea.com/";
var res1 = UrlFetchApp.fetch(baseUrl);
if (res1.getResponseCode() != 200) throw new Erro("URL cannot be used.");
const from = '<td style="padding-top: 10px; text-align: center;">';
const to = ' </td>';
const htmlData1 = (from + Parser.data(res1.getContentText()).from(from).to(to).build() + to).replace(/\ /g, "");
const xmlRoot = XmlService.parse(htmlData1).getRootElement();
const c = xmlRoot.getChildren()[placeOfUrl - 1];
if (!c) return;
const url = c.getAttribute("href").getValue();
// Parse HTML data.
const res2 = UrlFetchApp.fetch(url);
if (res2.getResponseCode() != 200) throw new Erro("URL for retrieving data cannot be used.");
const htmlData2 = res2.getContentText();
const parsedData1 = Parser.data(htmlData2).from('<table class="style_1" cellspacing="0" cellpadding="0" width="918" border="0">').to('</table>').build();
const parsedData2 = Parser.data(parsedData1).from("<tr>").to("</tr>").iterate();
const data = parsedData2
.filter(function(e) {return /^<td width="35" align="center">/.test(e)})
.map(function(e) {return "<content>" + e.match(/<td.+?\/td>/g).map(function(f) {return f.replace(/\ \;|<div.+?>|<\/div>|<img.+?>|<input.+?>|\&team_guest|<\/h.+?>|\&/g, "")}).join("") + "</content>"})
.join("");
const xmlRootContent = XmlService.parse("<root>" + data + "</root>").getRootElement();
// Retrieve result values.
const content = xmlRootContent.getChildren();
const values = content.reduce((ar1, e) => {
const temp = e.getChildren().reduce((ar2, f, j) => {
if (f) {
if (f.getChild("a")) {
const t = f.getChild("a").getValue()
if (t) ar2.push(t);
} else {
if (f.getAttribute("style")) {
const v = f.getValue();
if (v && [6, 7, 8, 15].includes(j)) {
ar2.push(Math.round((1 / (parseInt(v, 10) / 100)) * 100) / 100);
}
}
}
}
return ar2;
}, []);
ar1.push(temp);
return ar1;
}, []);
return values;
}
When =sample(4) is set, the data URL is the same with IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href").
When =sample(5) is set, the data URL is the same with IMPORTXML("http://old.statarea.com/","//tr/td/a[5]/#href").
Result:
3. Sample script 2:
Please copy and paste the following script to the script editor. In this case, the container-bound script is used. When you run the script at the script editor, the values are put to the Spreadsheet.
function myFunction() {
var placeOfUrl = "5"; // Here, you can change the URL for retrieving values.
// Retrieve URL.
var baseUrl = "http://old.statarea.com/";
var res1 = UrlFetchApp.fetch(baseUrl);
if (res1.getResponseCode() != 200) throw new Erro("URL cannot be used.");
const from = '<td style="padding-top: 10px; text-align: center;">';
const to = ' </td>';
const htmlData1 = (from + Parser.data(res1.getContentText()).from(from).to(to).build() + to).replace(/\ /g, "");
const xmlRoot = XmlService.parse(htmlData1).getRootElement();
const c = xmlRoot.getChildren()[placeOfUrl - 1];
if (!c) return;
const url = c.getAttribute("href").getValue();
// Parse HTML data.
const res2 = UrlFetchApp.fetch(url);
if (res2.getResponseCode() != 200) throw new Erro("URL for retrieving data cannot be used.");
const htmlData2 = res2.getContentText();
const parsedData1 = Parser.data(htmlData2).from('<table class="style_1" cellspacing="0" cellpadding="0" width="918" border="0">').to('</table>').build();
const parsedData2 = Parser.data(parsedData1).from("<tr>").to("</tr>").iterate();
const data = parsedData2
.filter(function(e) {return /^<td width="35" align="center">/.test(e)})
.map(function(e) {return "<content>" + e.match(/<td.+?\/td>/g).map(function(f) {return f.replace(/\ \;|<div.+?>|<\/div>|<img.+?>|<input.+?>|\&team_guest|<\/h.+?>|\&/g, "")}).join("") + "</content>"})
.join("");
const xmlRootContent = XmlService.parse("<root>" + data + "</root>").getRootElement();
// Retrieve result values.
const content = xmlRootContent.getChildren();
const values = content.reduce((ar1, e) => {
const temp = e.getChildren().reduce((ar2, f, j) => {
if (f) {
if (f.getChild("a")) {
const t = f.getChild("a").getValue()
if (t) ar2.push(t);
} else {
if (f.getAttribute("style")) {
const v = f.getValue();
if (v && [6, 7, 8, 15].includes(j)) {
ar2.push(Math.round((1 / (parseInt(v, 10) / 100)) * 100) / 100);
}
}
}
}
return ar2;
}, []);
ar1.push(temp);
return ar1;
}, []);
// Put values to Spreadsheet.
var sheetname = "Sheet5";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}
When var placeOfUrl = "4" is set, the data URL is the same with IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href").
When var placeOfUrl = "5" is set, the data URL is the same with IMPORTXML("http://old.statarea.com/","//tr/td/a[5]/#href").
Note:
I confirmed that in the GAS project in your shared Spreadsheet used V8. So above script also used V8. Please be careful this.
When the size of HTML data from "http://old.statarea.com/" is near 1 MB, your formula can be used. But when the size of HTML data from "http://old.statarea.com/" is near 2 MB, the error occurs. This has already been mentioned in your question.
In this case, it seems that the URL is changed. When the size of HTML data from "http://old.statarea.com/" is near 1 MB, var placeOfUrl = "4" is the same URL from IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"). But when the size of HTML data from "http://old.statarea.com/" is near 2 MB, var placeOfUrl = "5" is the same URL from IMPORTXML("http://old.statarea.com/","//tr/td/a[4]/#href"). But about this situation, I'm not sure whether this always occurs. I apologize for this.
When the specification of page of URL is changed, the script cannot be used. So please be careful this.
References:
Parser
XML Service
If I misunderstood your question and this was not the direction you want, I apologize.
I'm trying to create a custom Google action to turn on machines in a facility and log who is using them and for how long. How do I access Google sheets without using a service account?
So far, I've essentially just copy pasted from this. Specifically, lines 17, 20, 21, 23, 26, 28, 29, 30, 31, and 32. (I did also replace things like "client_email", which as far as I can tell will only work with a service account which I can't use for financial reasons.)
<code>
'use strict';
const functions = require('firebase-functions')
const {dialogflow} = require('actions-on-google')
const {google} = require('googleapis')
const {WebHookClient} = require('dialogflow-fulfillment')
const sheets = google.sheets('v4')
const WELCOME_INTENT = 'Default Welcome Intent'
const FALLBACK_INTENT = 'Default Fallback Intent'
const MACHINE_OFF_INTENT = 'Machine off'
const MACHINE_ON_INTENT = 'Machine on'
const MACHINE_TYPE_ENTITY = 'machine'
const MEMBER_NUM_ENTITY = 'number-sequence'
const app = dialogflow()
app.intent(FALLBACK_INTENT, (conv) => {
conv.ask("I didn't understand your request")
})
app.intent(MACHINE_OFF_INTENT, (conv) => {
const machine_type = conv.parameters[MACHINE_TYPE_ENTITY].toLowerCase();
const member_number = conv.parameters[MEMBER_NUM_ENTITY];
const spreadsheetID = "1n5VTSy5e8ger8FeKvKXymFMf2DGLK2lAKe_Qw2iG7Vc";
const appl = SpreadsheetApp;
const ss = appl.openById(spreadsheetID);
const sheet = ss.getSheetByName("Authorizations");
var engraverAuth = sheet.getRange("A1:A100");
var woodshopAuth = sheet.getRange("B1:B100");
var metalshopAuth = sheet.getRange("C1:C100");
var allAuth = sheet.getRange("D1:D100");
switch(machine_type){
case "laser engraver":
if (member_number.constructor == engraverAuth || member_number.constructor == allAuth) {
}
else {
conv.ask("You are not authorized to use that machine");
}
break;
case "woodshop":
if (member_number.constructor ==woodshopAuth || member_number.constructor == allAuth) {
}
else {
conv.ask("You are not authorized to use that machine");
}
break;
case "metalshop":
if (member_number.constructor ==metalshopAuth || member_number.constructor == allAuth) {
}
else {
conv.ask("You are not authorized to use that machine");
}
break;
default:
}
})
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app)
</code>
It just outputs error 500 (internal server error) which I guess is because I'm not authorizing it to use sheets? I'm new to all this and learning as I go so I'm certain there are several fundamental flaws going on here that would be obvious to most people, so help of any kind is appreciated.