Javascript 使用节点js在foor循环中同步多个请求

Javascript 使用节点js在foor循环中同步多个请求,javascript,node.js,web-scraping,request,x-ray,Javascript,Node.js,Web Scraping,Request,X Ray,我是从Javascript开始的,我需要帮助来弄清楚如何在循环for循环时使代码同步。 基本上,我所做的是在for循环中发出多个POST请求,然后使用库删除数据,最后将结果保存到Mongo数据库。 输出正常,但它以无序方式出现,并突然挂起,我必须使用ctrl+C强制关闭。这是我的功能: function getdata() { const startYear = 1996; const currentYear = 1998; // new Date().getFullYear()

我是从Javascript开始的,我需要帮助来弄清楚如何在循环for循环时使代码同步。 基本上,我所做的是在for循环中发出多个POST请求,然后使用库删除数据,最后将结果保存到Mongo数据库。 输出正常,但它以无序方式出现,并突然挂起,我必须使用ctrl+C强制关闭。这是我的功能:

  function getdata() {
  const startYear = 1996;
  const currentYear = 1998; // new Date().getFullYear()

for (let i = startYear; i <= currentYear; i++) {
for (let j = 1; j <= 12; j++) {
  if (i === startYear) {
    j = 12;
  }

  // Form to be sent
  const form = {
    year: `${i}`,
    month: `${j}`,
    day: '01',
  };

  const formData = querystring.stringify(form);
  const contentLength = formData.length;

  // Make HTTP Request
  request({
    headers: {
      'Content-Length': contentLength,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
    body: formData,
    method: 'POST',
  }, (err, res, html) => {

    if (!err && res.statusCode === 200) {

      // Scrapping data with X-Ray
      x(html, '#divID0 > table > tr', {
        date: '.block90w',
        lat: 'td:nth-child(2)',
        lon: 'td:nth-child(3)',
        prof: 'td:nth-child(4)',
        mag: 'td:nth-child(5)',
        local: 'td:nth-child(6)',
        degree: 'td:nth-child(7)',
      })((error, obj) => {

        const result = {
          date: obj.date,
          lat: obj.lat.replace(',', '.'),
          lon: obj.lon.replace(',', '.'),
          prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'),
          mag: obj.mag.replace(',', '.'),
          local: obj.local,
          degree: obj.degree,
        };

        // console.log(result);

        upsertEarthquake(result); // save to DB

      });

    }


  });

  }
  }
  }
函数getdata(){ 常数startYear=1996; const currentYear=1998;//新日期().getFullYear() for(让i=startYear;i{ 常数结果={ 日期:obj.date, 横向:对象横向替换(',','), lon:obj.lon.replace(',','), prof:obj.prof=='-'?null:obj.prof.replace(',','.'), mag:obj.mag.replace(',','), 本地:obj.local, 学位:obj学位, }; //控制台日志(结果); Upsert地震(结果);//保存到数据库 }); } }); } } }
我想我必须使用承诺或回调,但我不知道如何做到这一点,我已经尝试使用async await,但没有成功。如果需要提供任何其他信息,请告诉我,谢谢。

您正在循环中调用请求

异步函数是在主线程逻辑结束后获取结果(即在回调函数中接收响应)的函数

这样,如果我们有:

for (var i = 0; i < 12; i++) {
    request({
        data: i
    }, function(error, data) {
        // This is the request result, inside a callback function
    });
}
在这里你可以看到逻辑。您有一个名为
callNext
的函数,如果不需要更多调用,它将进行下一次调用或调用
requestend


当在
callNext
内部调用
request
时,它将等待接收回调(这将在将来某个时候异步发生),将处理收到的数据,然后在回调中告诉他再次调用
callNext

而不是循环。您可以使用开始和结束年份创建一个数组,然后将该数组映射到您请求的配置,然后将结果映射到x射线返回的值(x射线返回一个数组,因此无需回调)。然后使用返回承诺的函数将刮取结果放入mongodb中

如果某个对象被拒绝,则创建一个
Fail
类型的对象,并使用该对象进行解析

使用Promise并行启动所有请求、x射线和mongo。使用Promise限制活动请求的数量

下面是代码中的内容:

//you can get library containing throttle here:
//  https://github.com/amsterdamharu/lib/blob/master/src/index.js
const lib = require('lib');
const Fail = function(details){this.details=details;};
const isFail = o=>(o&&o.constructor)===Fail;
const max10 = lib.throttle(10);
const range = lib.range;
const createYearMonth = (startYear,endYear)=>
  range(startYear,endYear)
  .reduce(
    (acc,year)=>
      acc.concat(
        range(1,12).map(month=>({year,month}))
      )
    ,[]
  );
const toRequestConfigs = yearMonths =>
  yearMonths.map(
    yearMonth=>{
      const formData = querystring.stringify(yearMonth);
      return {
        headers: {
          'Content-Length': formData.length,
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
        body: formData,
        method: 'POST',
      };
    }
  );
const scrape = html =>
  x(
    html, 
    '#divID0 > table > tr', 
    {
      date: '.block90w',
      lat: 'td:nth-child(2)',
      lon: 'td:nth-child(3)',
      prof: 'td:nth-child(4)',
      mag: 'td:nth-child(5)',
      local: 'td:nth-child(6)',
      degree: 'td:nth-child(7)'
    }
  );
const requestAsPromise = config =>
  new Promise(
    (resolve,reject)=>
      request(
        config,
        (err,res,html)=>
          (!err && res.statusCode === 200) 
            //x-ray returns a promise:
            // https://github.com/matthewmueller/x-ray#xraythencb
            ? resolve(html)
            : reject(err)
      )
  );
const someMongoStuff = scrapeResult =>
  //do mongo stuff and return promise
  scrapeResult;
const getData = (startYear,endYear) =>
  Promise.all(
    toRequestConfigs(
      createYearMonth(startYear,endYear)
    )
    .map(
      config=>
        //maximum 10 active requests
        max10(requestAsPromise)(config)
        .then(scrape)
        .then(someMongoStuff)
        .catch(//if something goes wrong create a Fail type object
          err => new Fail([err,config.body])
        )
    )
  )
//how to use:
getData(1980,1982)
.then(//will always resolve unless toRequestConfigs or createYearMonth throws
  result=>{
    //items that were successfull
    const successes = result.filter(item=>!isFail(item));
    //items that failed
    const failed = result.filter(isFail);
  }
)
抓取经常发生的情况是,目标站点在y周期内不允许您发出超过x个请求,并且开始将您的IP列入黑名单,如果您超过此期限,则拒绝服务

假设您希望限制为每5秒10个请求,那么您可以将上述代码更改为:

const max10 = lib.throttlePeriod(10,5000);

代码的其余部分是相同的

您有
sync for…循环
,其中有
异步方法

解决这一问题的干净方法是使用

ES2017
async/await
语法

假设您希望在
upsertSevention(result)
之后停止每个迭代,您应该更改代码以获得类似的结果

function async getdata() {
    const startYear = 1996;
    const currentYear = 1998; // new Date().getFullYear()

    for (let i = startYear; i <= currentYear; i++) {
        for (let j = 1; j <= 12; j++) {
            if (i === startYear)
                j = 12; 

            // Form to be sent
            const form = {
                year: `${i}`,
                month: `${j}`,
                day: '01',
            };

            const formData = querystring.stringify(form);
            const contentLength = formData.length;
            //Make HTTP Request
            await new Promise((next, reject)=> { 
                request({
                    headers: {
                        'Content-Length': contentLength,
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
                    body: formData,
                    method: 'POST',
                }, (err, res, html) => {
                    if (err || res.statusCode !== 200)
                        return next() //If there is an error jump to the next

                    //Scrapping data with X-Ray
                    x(html, '#divID0 > table > tr', {
                        date: '.block90w',
                        lat: 'td:nth-child(2)',
                        lon: 'td:nth-child(3)',
                        prof: 'td:nth-child(4)',
                        mag: 'td:nth-child(5)',
                        local: 'td:nth-child(6)',
                        degree: 'td:nth-child(7)',
                    })((error, obj) => {
                        const result = {
                            date: obj.date,
                            lat: obj.lat.replace(',', '.'),
                            lon: obj.lon.replace(',', '.'),
                            prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'),
                            mag: obj.mag.replace(',', '.'),
                            local: obj.local,
                            degree: obj.degree,
                        }
                        //console.log(result);
                        upsertEarthquake(result); // save to DB
                        next() //This makes jump to the next for... iteration
                    })

                }) 
            }
        }
    }
}

我试图实现它,但是输出仍然是无序的,尽管它不再挂起。你可以在这里看到我所做的@MiguelFerreira你明白了。有了这些信息,您创建了一种连接异步回调的方法,这很酷,但我仍然看到了一个问题。您在另一个异步请求中有一个异步请求。在调用
x
请求
回调中,该回调也有一个异步回调。您希望在所有异步回调完成后执行下一个请求。我对你的粘贴库做了一些调整:在你得到你想要的结果之后,检查我调用
getdata
的地方。此时,您已完成并可以继续执行下一个请求。请注意,如果出现错误或其他原因导致您不调用
x
函数,它将无法继续执行下一个请求。想想这是不是你真正想要的东西。也许你想重复这个请求,或者在这种情况下从头再开始。这取决于你。只需添加一个
else
,并在“请求链”中断时添加您想要运行的代码。感谢jorge花费时间和精力,您的解决方案非常简单,而且非常适合您+1!编辑:我不能投票,因为我还有@MiguelFerreira哈哈,没问题。很高兴它成功了:-)非常感谢您的时间和努力,但jorge回答得更快,而且解决方案更简单。@MiguelFerreira没问题,如果您需要帮助实现此解决方案,因为您了解节流并行与刮取的关系,或者如果您因为一个失败而丢失所有成功处理的项,或者如果您了解命令式编程和声明式编程之间的区别,请告诉我。
function async getdata() {
    const startYear = 1996;
    const currentYear = 1998; // new Date().getFullYear()

    for (let i = startYear; i <= currentYear; i++) {
        for (let j = 1; j <= 12; j++) {
            if (i === startYear)
                j = 12; 

            // Form to be sent
            const form = {
                year: `${i}`,
                month: `${j}`,
                day: '01',
            };

            const formData = querystring.stringify(form);
            const contentLength = formData.length;
            //Make HTTP Request
            await new Promise((next, reject)=> { 
                request({
                    headers: {
                        'Content-Length': contentLength,
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    uri: 'https://www.ipma.pt/pt/geofisica/sismologia/',
                    body: formData,
                    method: 'POST',
                }, (err, res, html) => {
                    if (err || res.statusCode !== 200)
                        return next() //If there is an error jump to the next

                    //Scrapping data with X-Ray
                    x(html, '#divID0 > table > tr', {
                        date: '.block90w',
                        lat: 'td:nth-child(2)',
                        lon: 'td:nth-child(3)',
                        prof: 'td:nth-child(4)',
                        mag: 'td:nth-child(5)',
                        local: 'td:nth-child(6)',
                        degree: 'td:nth-child(7)',
                    })((error, obj) => {
                        const result = {
                            date: obj.date,
                            lat: obj.lat.replace(',', '.'),
                            lon: obj.lon.replace(',', '.'),
                            prof: obj.prof == '-' ? null : obj.prof.replace(',', '.'),
                            mag: obj.mag.replace(',', '.'),
                            local: obj.local,
                            degree: obj.degree,
                        }
                        //console.log(result);
                        upsertEarthquake(result); // save to DB
                        next() //This makes jump to the next for... iteration
                    })

                }) 
            }
        }
    }
}
if (err || res.statusCode !== 200)
    return reject(err)