Google 应用程序抓取脚本到 运行 regullary 直到提取所有网站的内页?
Google Apps scraping script to run regullary till all site's inner pages are extracted?
我做了一个抓取脚本,通过抓取一个一个地抓取任何网站的(url 待输入)内页,获取其他内部 url 并继续抓取所有页面并提取它们的纯文本(剥离 html)。
该脚本运行良好,但 google 脚本 运行 限制为 6 分钟,因此对于大型站点它不起作用(6 分钟后停止并且 google doc 文件中没有输出)。
function onOpen() {
DocumentApp.getUi() // Or DocumentApp or FormApp.
.createMenu('New scrape web docs')
.addItem('Enter Url', 'showPrompt')
.addToUi();
}
function showPrompt() {
var ui = DocumentApp.getUi();
var result = ui.prompt(
'Scrape whole website into text!',
'Please enter website url (with http(s)://):',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var url = result.getResponseText();
var links=[];
var base_url = url;
if (button == ui.Button.OK)
{
// gather initial links
var inner_links_arr = scrapeAndPaste(url, 1); // first run and clear the document
links = links.concat(inner_links_arr); // append an array to all the links
var new_links=[]; // array for new links
var processed_urls =[url]; // processed links
var link, current;
while (links.length)
{
link = links.shift(); // get the most left link (inner url)
processed_urls.push(link);
current = base_url + link;
new_links = scrapeAndPaste(current, 0); // second and consecutive runs we do not clear up the document
//ui.alert('Processed... ' + current + '\nReturned links: ' + new_links.join('\n') );
// add new links into links array (stack) if appropriate
for (var i in new_links){
var item = new_links[i];
if (links.indexOf(item) === -1 && processed_urls.indexOf(item) === -1)
links.push(item);
}
}
}
}
function scrapeAndPaste(url, clear) {
var text;
try {
var html = UrlFetchApp.fetch(url).getContentText();
// some html pre-processing
if (html.indexOf('</head>') !== -1 ){
html = html.split('</head>')[1];
}
if (html.indexOf('</body>') !== -1 ){ // thus we split the body only
html = html.split('</body>')[0] + '</body>';
}
// fetch inner links
var inner_links_arr= [];
var linkRegExp = /href="(.*?)"/gi; // regex expression object
var match = linkRegExp.exec(html);
while (match != null) {
// matched text: match[0]
if (match[1].indexOf('#') !== 0
&& match[1].indexOf('http') !== 0
//&& match[1].indexOf('https://') !== 0
&& match[1].indexOf('mailto:') !== 0
&& match[1].indexOf('.pdf') === -1 ) {
inner_links_arr.push(match[1]);
}
// match start: match.index
// capturing group n: match[n]
match = linkRegExp.exec(html);
}
text = getTextFromHtml(html);
outputText(url, text, clear); // output text into the current document with given url
return inner_links_arr; //we return all inner links of this doc as array
} catch (e) {
MailApp.sendEmail(Session.getActiveUser().getEmail(), "Scrape error report at "
+ Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd HH:mm:ss"),
"\r\nMessage: " + e.message
+ "\r\nFile: " + e.fileName+ '.gs'
+ "\r\nWeb page under scrape: " + url
+ "\r\nLine: " + e.lineNumber);
outputText(url, 'Scrape error for this page cause of malformed html!', clear);
}
}
function getTextFromHtml(html) {
return getTextFromNode(Xml.parse(html, true).getElement());
}
function getTextFromNode(x) {
switch(x.toString()) {
case 'XmlText': return x.toXmlString();
case 'XmlElement': return x.getNodes().map(getTextFromNode).join(' ');
default: return '';
}
}
function outputText(url, text, clear){
var body = DocumentApp.getActiveDocument().getBody();
if (clear){
body.clear();
}
else {
body.appendHorizontalRule();
}
var section = body.appendParagraph(' * ' + url);
section.setHeading(DocumentApp.ParagraphHeading.HEADING2);
body.appendParagraph(text);
}
我的想法是使用额外的电子表格来保存抓取的链接并在常规基础上自动重新启动脚本(使用 ScriptApp.newTrigger)。但是出现了一些障碍:
- 当通过触发器调用时,脚本只有 30 秒 运行 时间。
- 如果运行来自触发器,用户将无法与脚本进行交互!我是否应该再次使用电子表格单元格输入初始基数 url?
- 如何在脚本因 运行 限制时间(30 秒或 6 分钟)而停止之前将抓取的内容刷新到 google 文档文件中?
- 如果所有站点链接都已处理,如何停止通过触发器调用脚本?
为方便起见,您可以分别回答每个问题。
是否有更好的解决方案来抓取网站页面、抓取并将输出保存为一个文本文件?
AFAIK,你需要在触发之间至少间隔 6 分钟,然后它将 运行 再持续 6 分钟。
您可以一次请求所有 URL 并将它们保存在属性中,然后在触发器中调用属性。
你可以定期查看时间,知道它会运行只有6分钟,如果达到5分钟,全部粘贴然后设置触发器。
在属性中保存包含当前需要处理的链接的对象,然后当触发器调用脚本时,它只检索需要处理的 URL。
您可能无法将整个网站保存在属性中,因为它有 100kb 的限制,但您可以将每个页面拆分为不同的 属性,不知道这样是否会达到限制。
另一种方法是使用 HTMLService 或 setTimeout 异步进行检索调用 运行。我没有在 GAS 脚本中使用 setTimeout,但在 HTML Javascript.
中效果很好
我做了一个抓取脚本,通过抓取一个一个地抓取任何网站的(url 待输入)内页,获取其他内部 url 并继续抓取所有页面并提取它们的纯文本(剥离 html)。 该脚本运行良好,但 google 脚本 运行 限制为 6 分钟,因此对于大型站点它不起作用(6 分钟后停止并且 google doc 文件中没有输出)。
function onOpen() {
DocumentApp.getUi() // Or DocumentApp or FormApp.
.createMenu('New scrape web docs')
.addItem('Enter Url', 'showPrompt')
.addToUi();
}
function showPrompt() {
var ui = DocumentApp.getUi();
var result = ui.prompt(
'Scrape whole website into text!',
'Please enter website url (with http(s)://):',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var url = result.getResponseText();
var links=[];
var base_url = url;
if (button == ui.Button.OK)
{
// gather initial links
var inner_links_arr = scrapeAndPaste(url, 1); // first run and clear the document
links = links.concat(inner_links_arr); // append an array to all the links
var new_links=[]; // array for new links
var processed_urls =[url]; // processed links
var link, current;
while (links.length)
{
link = links.shift(); // get the most left link (inner url)
processed_urls.push(link);
current = base_url + link;
new_links = scrapeAndPaste(current, 0); // second and consecutive runs we do not clear up the document
//ui.alert('Processed... ' + current + '\nReturned links: ' + new_links.join('\n') );
// add new links into links array (stack) if appropriate
for (var i in new_links){
var item = new_links[i];
if (links.indexOf(item) === -1 && processed_urls.indexOf(item) === -1)
links.push(item);
}
}
}
}
function scrapeAndPaste(url, clear) {
var text;
try {
var html = UrlFetchApp.fetch(url).getContentText();
// some html pre-processing
if (html.indexOf('</head>') !== -1 ){
html = html.split('</head>')[1];
}
if (html.indexOf('</body>') !== -1 ){ // thus we split the body only
html = html.split('</body>')[0] + '</body>';
}
// fetch inner links
var inner_links_arr= [];
var linkRegExp = /href="(.*?)"/gi; // regex expression object
var match = linkRegExp.exec(html);
while (match != null) {
// matched text: match[0]
if (match[1].indexOf('#') !== 0
&& match[1].indexOf('http') !== 0
//&& match[1].indexOf('https://') !== 0
&& match[1].indexOf('mailto:') !== 0
&& match[1].indexOf('.pdf') === -1 ) {
inner_links_arr.push(match[1]);
}
// match start: match.index
// capturing group n: match[n]
match = linkRegExp.exec(html);
}
text = getTextFromHtml(html);
outputText(url, text, clear); // output text into the current document with given url
return inner_links_arr; //we return all inner links of this doc as array
} catch (e) {
MailApp.sendEmail(Session.getActiveUser().getEmail(), "Scrape error report at "
+ Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd HH:mm:ss"),
"\r\nMessage: " + e.message
+ "\r\nFile: " + e.fileName+ '.gs'
+ "\r\nWeb page under scrape: " + url
+ "\r\nLine: " + e.lineNumber);
outputText(url, 'Scrape error for this page cause of malformed html!', clear);
}
}
function getTextFromHtml(html) {
return getTextFromNode(Xml.parse(html, true).getElement());
}
function getTextFromNode(x) {
switch(x.toString()) {
case 'XmlText': return x.toXmlString();
case 'XmlElement': return x.getNodes().map(getTextFromNode).join(' ');
default: return '';
}
}
function outputText(url, text, clear){
var body = DocumentApp.getActiveDocument().getBody();
if (clear){
body.clear();
}
else {
body.appendHorizontalRule();
}
var section = body.appendParagraph(' * ' + url);
section.setHeading(DocumentApp.ParagraphHeading.HEADING2);
body.appendParagraph(text);
}
我的想法是使用额外的电子表格来保存抓取的链接并在常规基础上自动重新启动脚本(使用 ScriptApp.newTrigger)。但是出现了一些障碍:
- 当通过触发器调用时,脚本只有 30 秒 运行 时间。
- 如果运行来自触发器,用户将无法与脚本进行交互!我是否应该再次使用电子表格单元格输入初始基数 url?
- 如何在脚本因 运行 限制时间(30 秒或 6 分钟)而停止之前将抓取的内容刷新到 google 文档文件中?
- 如果所有站点链接都已处理,如何停止通过触发器调用脚本?
为方便起见,您可以分别回答每个问题。
是否有更好的解决方案来抓取网站页面、抓取并将输出保存为一个文本文件?
AFAIK,你需要在触发之间至少间隔 6 分钟,然后它将 运行 再持续 6 分钟。
您可以一次请求所有 URL 并将它们保存在属性中,然后在触发器中调用属性。
你可以定期查看时间,知道它会运行只有6分钟,如果达到5分钟,全部粘贴然后设置触发器。
在属性中保存包含当前需要处理的链接的对象,然后当触发器调用脚本时,它只检索需要处理的 URL。
您可能无法将整个网站保存在属性中,因为它有 100kb 的限制,但您可以将每个页面拆分为不同的 属性,不知道这样是否会达到限制。
另一种方法是使用 HTMLService 或 setTimeout 异步进行检索调用 运行。我没有在 GAS 脚本中使用 setTimeout,但在 HTML Javascript.
中效果很好