【问题标题】:Script gets stuck somewhere in it's execution process脚本卡在执行过程中的某个地方
【发布时间】:2021-09-13 18:49:00
【问题描述】:

我创建了一个脚本,使用requestcheerio 库从webpage 中抓取不同省份的链接,然后使用这些网址解析来自here 的不同办公室的链接。最后,使用这些 office 链接从 here 中抓取标题。

当我运行脚本时,我可以看到它相应地完成了它的工作,直到它卡在执行的某个地方。当它卡住时,它不会抛出任何错误。

以下是脚本所遵循的图像中的步骤:

  1. 首先,脚本从here抓取链接
  2. 其次,它从here抓取链接
  3. 最后,脚本从here 解析标题

这是我尝试过的:

const request = require('request');
const cheerio = require('cheerio');

const link = 'https://www.egyptcodebase.com/en/p/all';
const base_link = 'https://www.egyptcodebase.com/en/';

let getLinks = (link) => {
    const items = [];
    return new Promise((resolve, reject) => {
        request(link, function(error, response, html) {
            let $ = cheerio.load(html);
            if (error) return reject(error);
            try {
                $('.table tbody tr').each(function() {
                    items.push(base_link + $(this).find("a[href]").attr("href"));
                });
                resolve(items);
            } catch (e) {
                reject(e);
            }
        });
    });
};

let getData = (links) => {
    const nitems = [];
    const promises = links
        .map(nurl => new Promise((resolve, reject) => {
            request(nurl, function(error, response, html) {
                let $ = cheerio.load(html);
                if (error) return reject(error);
                try {
                    $('.table tbody tr').each(function() {
                        nitems.push(base_link + $(this).find("a[href]").attr("href"));
                    });
                    resolve(nitems);
                } catch (e) {
                    reject(e);
                }
            })
        }))
    return Promise.all(promises)
}

let FetchData = (links) => {
    const promises = links
        .map(turl => new Promise((resolve, reject) => {
            request(turl, function(error, response, html) {
                if (error) return reject(error);
                let $ = cheerio.load(html);
                try {
                    const title = $(".home-title > h2").eq(0).text();
                    console.log({
                        title: title,
                        itemLink: turl
                    });
                    resolve(title);
                } catch (e) {
                    reject(e);
                }
            })
        }))

    return Promise.all(promises)
}

(async function main() {
    const result = await getLinks(link);
    const resultSecond = await getData(result);
    const merged = resultSecond.flat(1);
    const resultFinal = await FetchData(merged);
    for (const title of resultFinal) {
        console.log(title);
    }

})().catch(console.error);

我怎样才能让脚本完成它的执行过程?

PS 虽然脚本看起来很大,但其中使用的函数几乎相同,除了选择器。

【问题讨论】:

    标签: javascript node.js web-scraping promise


    【解决方案1】:

    好的,所以在测试这段代码时,我马上遇到了两个问题:

    1. resultSecond,包含来自 getData() 的数据,返回一个类似数组的对象,而不是数组,所以我无法使用 平()。所以我创建了一个函数 toArray 将对象转换为数组,并在 resultSecond 之后添加了另一个名为 resultThird 的变量,并在 resultSecond,把它变成一个数组。

    2. flat() 在 Array 原型中不存在,所以我不得不手动添加它。

    解决了这些问题后,我能够运行您的代码,并且体验到了您所说的挂起。

    发生 ECONNRESET 错误,然后在挂起之前进行了大约几千个请求。 ECONNRESET 通常是由于未处理异步网络错误您请求的服务器决定终止连接。不确定请求模块将如何处理此类事件,但似乎该模块可能无法正确处理网络错误或终止连接。

    问题是您正在向此站点 API 发出 15,000 个请求,因此该 API 可能有一个速率限制器,查看请求的数量并终止其中的大部分,但允许几千个请求通过,但由于您是没有处理终止的连接——很可能是由于请求模块吞下了这些错误——它“挂”在那里,节点进程没有退出。

    所以我使用 async 模块将请求批处理为 300 个间隔,它的工作原理非常棒。没有终止连接,因为我没有达到速率限制。您可能会将间隔限制提高到 300 以上。

    但是,我建议不要使用 request 模块,而使用另一个 http 模块,例如 axios,它很可能会处理这些问题。当您执行大量异步请求时,您应该考虑使用异步。它有很多有用的方法。 Lmk 如果你需要更多解释异步模块在这里做什么,但我建议先阅读文档:https://caolan.github.io/async/v3/docs.html#mapLimit

    const request = require('request');
    const cheerio = require('cheerio');
    const _async = require('async');
    
    const link = 'https://www.egyptcodebase.com/en/p/all';
    const base_link = 'https://www.egyptcodebase.com/en/';
    
    const toArray = (obj) => {
      const arr = [];
      for (const prop in obj) {
        arr.push(obj[prop])
      }
      return arr;
    }
    
    Object.defineProperty(Array.prototype, 'flat', {
        value: function(depth = 1) {
          return this.reduce(function (flat, toFlatten) {
            return flat.concat((Array.isArray(toFlatten) && (depth>1)) ? toFlatten.flat(depth-1) : toFlatten);
          }, []);
        }
    });
    
    let getLinks = (link) => {
        const items = [];
        return new Promise((resolve, reject) => {
            request(link, function(error, response, html) {
                let $ = cheerio.load(html);
                if (error) return reject(error);
                try {
                    $('.table tbody tr').each(function() {
                        items.push(base_link + $(this).find("a[href]").attr("href"));
                    });
                    resolve(items);
                } catch (e) {
                    reject(e);
                }
            });
        });
    };
    
    let getData = (links) => {
        const nitems = [];
        const promises = links
            .map(nurl => new Promise((resolve, reject) => {
                request(nurl, function(error, response, html) {
                    let $ = cheerio.load(html);
                    if (error) return reject(error);
                    try {
                        $('.table tbody tr').each(function() {
                            nitems.push(base_link + $(this).find("a[href]").attr("href"));
                        });
                       return resolve(nitems);
                    } catch (e) {
                      return reject(e);
                    }
                })
            }))
        return Promise.all(promises)
    }
    
    let FetchData = (links) => {
      const limit = 300;
      return new Promise((resolve, reject) => {
        const itr = (col, cb) => {
          request(col, function(error, response, html) {
            if (error) cb(error)
            let $ = cheerio.load(html);
            try {
              const title = $(".home-title > h2").eq(0).text();
              console.log({
                  title: title,
                  itemLink: col
              });
              cb(null, title);
            } catch (e) {
              cb(e);
            }
          })
        }
        _async.mapLimit(links, limit, itr, function(err, results) {
          if (err) reject(err);
          return resolve(results);
        })
      })
    }
    
    (async function main() {
      const result = await getLinks(link);
      const resultSecond = await getData(result); 
      const resultThird = toArray(resultSecond);
      const merged = resultThird.flat(1);
      const resultFinal = await FetchData(merged);
      
      for (const title of resultFinal) {
          console.log("title: ", title);
      }
    })().catch(err => console.log(err))
    
    //good to listen to these
    process.on('uncaughtException', err => { console.log(err) });
    process.on('unhandledRejection', err => { console.log(err) });
    

    【讨论】:

    • 好的,我执行了你建议的脚本。好消息是脚本现在一路抛出错误,但在一段时间后又卡住了。这是脚本遇到的the error
    • 好的,如果cheerio.load() error 出现在FetchData() 中,那么这是一些设置错误,或者您可能已经达到了API 的速率限制,因为您没有对请求进行批处理。您是否在服务器上安装了async 并添加了我输入的编辑内容?
    猜你喜欢
    • 2015-12-31
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 2015-03-05
    • 1970-01-01
    • 1970-01-01
    • 2017-09-08
    • 1970-01-01
    相关资源
    最近更新 更多