Get data from webpage using Google Apps Script and Yahoo Query Language - google-apps-script

Using Google Apps Script, I've written the following function to extract a piece of information to a spreadsheet:
function myFunction(symbol, elemento) {
var url = "http://www.example.com/query?symbol=" + symbol;
switch (elemento) {
case 'one':
var xpath='//*[#id="sectionTitle"]/h1';
break;
case 'two':
var xpath='//*[#id="headerQ"]/div[1]/div/span[2]'
break;
}
var query = "select * from html where url = '" + url + "' and xpath = '" + xpath + "'";
var yql = "https://query.yahooapis.com/v1/public/yql?format=json&q=" + encodeURIComponent(query);
var response = UrlFetchApp.fetch(yql);
var json = JSON.parse(response.getContentText());
switch(elemento){
case 'one':
return json.query.results.h1;
break;
case 'two':
return ponto(json.query.results.span.content);
break;
}
}
Now, this works OK when typing the function into a cell, but "sometimes" I get the error #ERROR! in a cell with the note:
TypeError: Can't read "h1" property of null. (line 54).
Deleting that cell and typing the function again usually works.
Why is this function volatile (ie: it does work, but only sometimes)?

You will need to do some error checking with your fetch. Any request over the internet may fail.
If no results are found the results object value will be null. I put in a quick backoff, but you may need to play with the numbers to suit your needs.
var response = UrlFetchApp.fetch(yql);
var json = JSON.parse(response.getContentText());
var backoff = 1;
while((json.query.results == null || response.getResponseCode() != 200)){
Utilities.sleep((Math.pow(2,backoff)*1000) + (Math.round(Math.random() * 1000)));
response = UrlFetchApp.fetch(yql);
json = JSON.parse(response.getContentText());
backoff++;
}

Related

How to return google sheet values using doPost using x-www-form-urlencoded?

I try to use google sheets to write and read some data using post requests,
the writing part works, but it never returns any value back.
function doPost(e) { return handleResponse(e); }
function handleResponse(e) {
// Get public lock, one that locks for all invocations
// (https://gsuite-developers.googleblog.com/2011/10/concurrency-and-google-apps-script.html)
var lock = LockService.getPublicLock();
// Allow the write process up to 2 seconds
lock.waitLock(2000);
try {
// Generate a (not very good) UUID for this submission
var submissionID = e.parameter.id || 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
// Open the spreadsheet document and select the right sheet page
var sheetName = e.parameter.sheet_name|| 'Sheet1';
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("key"));
var sheet = doc.getSheetByName(sheetName);
//get information out of post request
var action = e.parameter.action || 'save';
var pName = e.parameter.name;
var rowNumber = findRow(pName,sheetName);
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(headRow, 1, 1, sheet.getLastColumn()).getValues()[0];
// check for action is loading
if(action == 'load'){
//check if the name has data
if (rowNumber){
//loads all the give values out of the parameters
var answer = [];
Logger.log('hadders: ' + headers);
for (i in headers) {
if (e.parameter[headers[i].toLowerCase()] !== undefined) {
var val = sheet.getRange(rowNumber, 1, 1,sheet.getLastColumn()).getValues()[0][i];
answer.push(val);
}
}
Logger.log('answer: '+ answer);
// Return result in JSON
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
;
}else{
// return error name wasn't found in sheet.
return ContentService
.createTextOutput("can't find Name")
.setMimeType(ContentService.MimeType.TEXT)
;
}
}
The logger returns all the right values,
but logging the return value from this function ends up in an empty object.
I tried just making my own return object like:
return ContentService
.createTextOutput({body={parameter={answer=JSON.stringify(answer)}}})
.setMimeType(ContentService.MimeType.TEXT)
;
I know that I need to use &= instead of ,: but it still returned nothing.
In your script, how about modifying as follows?
From:
return ContentService
.createTextOutput({body:{parameter:{answer}}})
.setMimeType(ContentService.MimeType.JSON)
To:
return ContentService
.createTextOutput(JSON.stringify({body:{parameter:{answer}}}))
.setMimeType(ContentService.MimeType.JSON)
In the case of createTextOutput({body:{parameter:{answer}}}), the object cannot be directly put. So I thought that it is required to convert it to the string.
Note:
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".

Error handling - retry urlfetch on error until success

I've looked at all the relevant questions here (such as this), but still cannot make sense of this VERY simple task.
So, trying to verify numbers using the NumVerify API. We're still on the free license on APILAYER so we're getting the following error from time to time
Request failed for https://apilayer.net returned code 500
I'd like to add a loop so that the script will try again until a proper response is received.
Here is a snippet based on several answers here:
function numverifylookup(mobilephone) {
console.log("input number: ",mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&number="+mobilephone+"&country_code=IL";
try {
var response = UrlFetchApp.fetch(lookupUrl);
if (response) {//Check for truthy value
var json = response.getContentText();
} else {
Utilities.sleep(2000);
continue;//If "get" returned a falsy value then continue
}
} catch(e) {
continue;//If error continue looping
}
var data = JSON.parse(response);
Sadly, still not working due to the following error:
Continue must be inside loop. (line 10
Any thoughts?
I think it's actually better to solve this using muteHTTPexepctions but couldn't quite make it work.
Thanks!
I think I got this to work as below:
function numverify(mobilephone);
console.log("input number: ",mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXX&number="+mobilephone+"&country_code=IL";
var i = 0;
var trycount = 1;
var errorcodes = "";
while (i != 1) {
var response = UrlFetchApp.fetch(lookupUrl, {muteHttpExceptions: true });
var responsecode = response.getResponseCode();
var errorcodes = errorcodes + "," + responsecode;
if (responsecode = 200) {//Check for truthy value
var json = response.getContentText();
var i = 1
} else {
var trycount = trycount + 1;
Utilities.sleep(2000);
}
}
var data = JSON.parse(response);
var valid = data.valid;
var localnum = data.local_format;
var linetype = data.line_type;
console.log(data," ",valid," ",localnum," ",linetype," number of tries= ",trycount," responsecodes= ", errorcodes);
var answer = [valid,localnum,linetype];
return answer;
}
I'll circle back in case it still doesn't work.
Thanks for helping!
You cannot use continue to achieve what you want, instead you can / need to call the function again:
function numverifylookup(mobilephone) {
console.log("input number: ", mobilephone);
var lookupUrl = "https://apilayer.net/api/validate?access_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&number=" + mobilephone + "&country_code=IL";
try {
var response = UrlFetchApp.fetch(lookupUrl);
if (response) {//Check for truthy value
var json = response.getContentText();
} else {
Utilities.sleep(2000);
numverifylookup(mobilephone);
}
} catch (e) {
Utilities.sleep(2000);
numverifylookup(mobilephone);//If error rerun the function
}
var data = JSON.parse(response);
}
As you can draw from the documentation the statement continue can only be used inside of loops, like e.g. the for loop.

Return link to redirect url

I'm trying to get the url of the final destination of a specific website, but all the templates I've found to use as a function in my spreadsheet, only return the initial link:
https://stackoverflow.com/a/50733029
function getRedirect(url) {
var response = UrlFetchApp.fetch(url, {'followRedirects': false, 'muteHttpExceptions': false});
var redirectUrl = response.getHeaders()['Location']; // undefined if no redirect, so...
var responseCode = response.getResponseCode();
if (redirectUrl) { // ...if redirected...
var nextRedirectUrl = getRedirect(redirectUrl); // ...it calls itself recursively...
Logger.log(url + " is redirecting to " + redirectUrl + ". (" + responseCode + ")");
return nextRedirectUrl;
}
else { // ...until it's not
Logger.log(url + " is canonical. (" + responseCode + ")");
return url;
}
}
This is the model where I put:
=getRedirect("https://c.newsnow.co.uk/A/1067471289?-833:12")
In the spreadsheet it returns:
https://c.newsnow.co.uk/A/1067471289?-833:12
I would like to collect the link to after redirect:
https://sports.ndtv.com/football/europa-league-bruno-fernandes-double-helps-manchester-united-thrash-real-sociedad-gareth-bale-stars-for-tottenham-2373767
When I saw the HTML of the URL https://c.newsnow.co.uk/A/1067471289?-833:12, I thought that in this case, the value of https://sports.ndtv.com/football/europa-league-bruno-fernandes-double-helps-manchester-united-thrash-real-sociedad-gareth-bale-stars-for-tottenham-2373767 might be able to be directly retrieved using IMPORTXML and a xpath. The sample formula is as follows.
Sample formula:
=IMPORTXML(A1,"//a/#href")
In this case, please put the URL of https://c.newsnow.co.uk/A/1067471289?-833:12 to the cell "A1".
Result:
Using Google Apps Script:
When you want to use Google Apps Script, you can also use the following script. In this case, please put a custom formula of =SAMPLE("https://c.newsnow.co.uk/A/1067471289?-833:12") to a cell.
function SAMPLE(url) {
const res = UrlFetchApp.fetch(url).getContentText();
const v = res.match(/url: '([\s\S\w]+?)'/);
return v && v.length == 2 ? v[1].trim() : "Value cannot be retrieved.";
}
Note:
In this sample formula and xpath is for the URL of https://c.newsnow.co.uk/A/1067471289?-833:12. So when you use this for other URLs, it might not be able to be used. So please be careful this.
Reference:
IMPORTXML

How to test my google script URL as deploy web app for two data?

I would like to discuss with anyone here regarding my problem.
My problem is that, I cannot successfully test my code.gs in google script with two data when I deploy it as web app. When I test it only one data it say 'Ok', but when I try to test adding a second data it say 'unsupported parameter'.
When I deploy the script as web app, the link is as below:
https://script.google.com/macros/s/AKfycbyXlCLBDNzJGXWNkrEHtWP0jaxnpvX0dPUnXjwilioUd7up-SU/exec
how can I test it?
Is it like,
[1]
https://script.google.com/macros/s/AKfycbyXlCLBDNzJGXWNkrEHtWP0jaxnpvX0dPUnXjwilioUd7up-SU/exec?BBTTempData=32.56&RTtempData=25.6
or
[2]
https://script.google.com/macros/s/AKfycbyXlCLBDNzJGXWNkrEHtWP0jaxnpvX0dPUnXjwilioUd7up-SU/exec?BBTTempData=32.56/RTtempData=25.6
My code is just like below:
function doGet(e) {
Logger.log( JSON.stringify(e) ); // view parameters
var result = 'Ok'; // assume success
if (e.parameter == undefined) {
result = 'No Parameters';
}
else {
var id = '<mygooglespreadsheet_id>'; //docs.google.com/spreadsheetURL/d
var sheet = SpreadsheetApp.openById(id).getActiveSheet();
var newRow = sheet.getLastRow() + 1;
var rowData = [];
rowData[0] = new Date(); // Timestamp in column A
for (var param in e.parameter) {
Logger.log('In for loop, param='+param);
var value = stripQuotes(e.parameter[param]);
//Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'BBTTempData': //Parameter
rowData[1] = value; //Value in column B
break;
case 'RTtempData':
rowData[2] = value;
break;
default:
result = "unsupported parameter";
}
}
Logger.log(JSON.stringify(rowData));
// Write new row below
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
// Return result of operation
return ContentService.createTextOutput(result);
}
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
The code, I get it in YouTube https://www.youtube.com/watch?v=tWTv4-QUQ0E.
A discussion is good for me.
Thanks.
Using 2 parameters you should use BBTTempData=32.56&RTtempData=25.6 I've tested this and it works fine for me. Perhaps you need to re-deploy the web app to get it to work if you've been making changes to the code.

Case statement won't match text from spreadsheet column in Google Apps script (script inside)

I have a google form and the corresponding results spreadsheet. I have a google apps script on the sheet that is intended activate on form submit. It reads the entry, puts formats the filled in columns into an email, and sends that email out to a specified address. All that works as intended.
However, there is a case statement that is supposed to set the target e-mail address & subject line based on the text in one of the columns. I can't get it to read anything but the default case. The text in the column (the 'campus' column) seems to match the case statements? Anyone have advice? Thanks!
function SendGoogleForm(e) {
if (MailApp.getRemainingDailyQuota() < 1) return;
var s = SpreadsheetApp.getActiveSheet();
var columns = s.getRange(1, 1, 1, s.getLastColumn()).getValues()[0];
var email = "";
var subject = "";
var message = "";
switch (e.namedValues["Campus"]){
case "Lower School":
email = "xxxx#*****.org";
subject = "LS Ticket Created";
message = "Campus column is: " + e.namedValues["Campus"] + "\n";
message += "Case is: Lower School"+"\n";
break;
case "Upper School":
email = "yyyy#*****.org";
subject = "US Ticket Created";
message = "Campus column is: " + e.namedValues["Campus"] + "\n";
message += "Case is: Upper School"+"\n";
break;
case "S*******":
email = "zzzz#*****.org";
subject = "SC Ticket Created";
message = "Campus column is: " + e.namedValues["Campus"] + "\n";
message += "Case is: S*******"+"\n";
break;
default:
email = "xxxx#*****.org";
subject = "General Ticket Created";
message = "Campus column is: " + e.namedValues["Campus"] + "\n";
message += "Case is: Default"+"\n";
break;
}
for (var keys in columns) {
var key = columns[keys];
if (e.namedValues[key] && (e.namedValues[key] !== "")) {
message += key + ' :: ' + e.namedValues[key] + "\n\n";
}
}
MailApp.sendEmail(email, subject, message);
}
e.namedValues returns the values in an Array, rather than as simple strings. This allows it to support multiple values for a single field.
This means you need to reference the first item in the array in your switch statement, at index 0:
switch (e.namedValues["Campus"][0]){
...
}
Otherwise you are using Switch correctly.
Regarding your comment, I'm not sure why the IF would be working when the Switch isn't, it should also require you to access the Array element. It must be that the IF statement is implicitly converting the Array to a string for the sake of the comparison, while the switch statement doesn't have that behaviour.
See e.namedValues here, with the values shown in Array notation:
https://developers.google.com/apps-script/guides/triggers/events
The problem is that when you are reading the parameters from the URL (Also applies to "namedvalues") you need to parse to int or string or the appropriate type, beyond that the switch/case usage is correct.
Options:
1. var input = 1; //int
2. var input = "myvalue"; //string
3. var input = parseInt(e.parameters.input);
//parsed int from querystring. http://.../?input=1
4. var input = e.parameters.input.toString();
//parsed string from querystring. http://.../?input=myvalue
5. var input = e.parameters.input;
//Not parsed int or string from query string.
//http://.../?input=1 or http://.../?input=myvalue
//This option will not work unless you parse to the appropriate type.
switch (input){
case 1:
//This works for option 1 and 3;
break;
case "myvalue":
//This works for option 2 and 4;
break;
default:
//Option 5 will fall here;
break;
}
References:
URL Parameters