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.
Related
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
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
I'm trying to use the new Google Analytics Data API to pull in some very simple data from our GA4 property and into a spreadsheet.
This all works great.
However I now want to apply some Dimension Filters so that It returns only those rows that match the filter.
This is the code I've added but I think the format is wrong.
const dimensionfilter = AnalyticsData.newFilterExpression();
dimensionfilter.filter.fieldName = 'pageTitle';
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT';
There are no examples in Apps script for adding DimensionFilter etc
Has anyone done this? Has anyone got any very simple examples.
Many thanks in advance.
Here is my full code
function runReport() {
const propertyId = '29045017783';
try {
const metric = AnalyticsData.newMetric();
metric.name = 'screenPageViews';
const pagetitle = AnalyticsData.newDimension();
pagetitle.name = 'pageTitle';
const pagepath = AnalyticsData.newDimension();
pagepath.name = 'pagePath';
const dateRange = AnalyticsData.newDateRange();
dateRange.startDate = '2022-05-01';
dateRange.endDate = 'today';
const dimensionfilter = AnalyticsData.newFilterExpression();
dimensionfilter.filter.fieldName = 'pageTitle';
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT';
const request = AnalyticsData.newRunReportRequest();
request.dimensions = [pagetitle, pagepath];
request.metrics = [metric];
request.dateRanges = dateRange;
request.limit=10;
request.dimensionFilter = dimensionfilter;
const report = AnalyticsData.Properties.runReport(request,'properties/' + propertyId);
if (!report.rows) {
Logger.log('No rows returned.');
return;
}
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
// Append the headers.
const dimensionHeaders = report.dimensionHeaders.map(
(dimensionHeader) => {
return dimensionHeader.name;
});
const metricHeaders = report.metricHeaders.map(
(metricHeader) => {
return metricHeader.name;
});
const headers = [...dimensionHeaders, ...metricHeaders];
sheet.appendRow(headers);
// Append the results.
const rows = report.rows.map((row) => {
const dimensionValues = row.dimensionValues.map(
(dimensionValue) => {
return dimensionValue.value;
});
const metricValues = row.metricValues.map(
(metricValues) => {
return metricValues.value;
});
return [...dimensionValues, ...metricValues];
});
sheet.getRange(2, 1, report.rows.length, headers.length)
.setValues(rows);
Logger.log('Report spreadsheet created: %s',
spreadsheet.getUrl());
} catch (e) {
// TODO (Developer) - Handle exception
Logger.log('Failed with error: %s', e.error);
}
}
This is how you can apply Dimension Filter
const dimensionfilter = AnalyticsData.newFilterExpression()
dimensionfilter.filter = AnalyticsData.newFilter()
dimensionfilter.filter.fieldName = 'pageTitle'
dimensionfilter.filter.stringFilter = AnalyticsData.newStringFilter()
dimensionfilter.filter.stringFilter.value = 'MYPAGETITLETEXT'
Edit: For multiple filters
To combine multiple filters with OR condition
const pageTitles = [
'MYPAGETITLETEXT1',
'MYPAGETITLETEXT2'
]
const dimensionfilter = AnalyticsData.newFilterExpression()
dimensionfilter.orGroup = AnalyticsData.newFilterExpressionList()
dimensionfilter.orGroup.expressions = []
for (const pageTitle of pageTitles) {
const filterExpression = AnalyticsData.newFilterExpression()
filterExpression.filter = AnalyticsData.newFilter()
filterExpression.filter.fieldName = 'pageTitle'
filterExpression.filter.stringFilter = AnalyticsData.newStringFilter()
filterExpression.filter.stringFilter.value = pageTitle
dimensionfilter.orGroup.expressions.push(filterExpression)
}
You can refer to this document to understand How method signatures are determined
I'm trying to use this function (which I found on a website) to link google form questions with a spread sheet but I keep getting error in line 4
Error Attempted to execute myFunction, but could not save.
function getDataFromGoogleSheets () {
const ss = SpreadsheetApp.openById('1xYldb3csabrznjvapWPlIPAkEVQAyKlLQGzl810mG3Y');
const sheet = ss.getSheetByName("clinic");
const [header, ...data] = sheet.getDataRange().getDisplayValues();
const choices = {};
header.forEach(function(title, index) {
choices[title] = data.map((row) => row[index]).filter((e) => e!=="");
});
return choices;
}
function populateGoogleForms() {
const GOOGLE_FORM_ID = '1jg4vooSWG4BY7z_9tMQV5aVaTOGeauC3ad3vsfvhGgk';
const googleForm = FormApp.openById(GOOGLE_FORM_ID);
const items = googleForm.getItems();
const choices = getDataFromGoogleSheets;//<======== Missing () in function call
items.forEach(function(item){
const itemTitle = item.getTitle();
if(itemTitle in choices){
const itemType = item.getType();
switch (itemType) {
case FormApp.ItemType.CHECKBOX:
item.asCheckboxItem().setChoiceValues(choices[itemTitle]);
break;
case FormApp.ItemType.LIST:
item.asListItem().setChoiceValues(choices[itemTitle]);
break;
case FormApp.ItemType.MULTIPLE_CHOICE:
item.asMultipleChoiceItem().setChoiceValues(choices[itemTitle]);
break;
default:
Logger.log('ignore question',itemTitle)
}
}
});
ss.toast('Google Form Updated !!');
}
This const choices = getDataFromGoogleSheets; needs to be this const choices = getDataFromGoogleSheets();
It's a common error with using the new editor. My guess is that the people that wrote the new editor don't ever use it. If they did, they'd be a lot more inclined to change it.
=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.