如何显示循环抓取的响应(cheerio)

问题描述 投票:0回答:1

我正在抓取这个网站以收集 2013 年的所有行,但是有 7 个页面,并且我的请求处于循环状态。收到全部 7 个回复后如何显示结果?如果我只是尝试 console.log rowTrack 数组,由于代码的异步性质,它会显示为空。理想情况下,我想按循环顺序运行请求,以便第一页的结果是数组的第一个元素等..

var request = require("request"),
cheerio = require("cheerio"),
rowTrack = [];

for (var i = 1; i <= 7; i++) {

var url = "http://www.boxofficemojo.com/alltime/world/?pagenum=" + i + "&p=.htm";
request(url, function(error, response, body) {
    if (!error) {
        var $ = cheerio.load(body),
            rows = $('table table tr');

        rows.each(function(j, element) {
            var select = $(element.children).text().split('\r\n')
            select.shift();
            select.pop();

            if (select[select.length - 1] == "2013") {

                rowTrack.push(select);

            }

        });
    }

});}

如何显示结果?

javascript node.js loops web-scraping cheerio
1个回答
0
投票

自从提出问题以来,您正在抓取的网站已经发生了一些变化。表格还在,但 URL 和分页有点不同。

JS 已转向承诺,并且

requests
包已被弃用。如今,有了承诺,你就会这样做:

const cheerio = require("cheerio"); // ^1.0.0-rc.12

const baseUrl =
  "https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
  
(async () => {
  const results = [];

  for (let i = 0; i < 6; i++) {
    const response = await fetch(`${baseUrl}&offset=${i * 100}`);
    const $ = cheerio.load(await response.text());
    results.push(...[...$("tr")]
      .map(e => [...$(e).find("td")].map(e => $(e).text()))
      .filter(e => e.at(-1) === "2013")
    );
  }

  console.log(results);
})();

上面的代码是串行运行的,但是你可以用

Promise.all
来并行化它:

const cheerio = require("cheerio");

const baseUrl =
  "https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";

(async () => {
  const results = await Promise.all(
    [...Array(6)].map(async (_, i) => {
      const response = await fetch(`${baseUrl}&offset=${i * 100}`);
      const $ = cheerio.load(await response.text());
      return [...$("tr")]
        .map(e => [...$(e).find("td")].map(e => $(e).text()))
        .filter(e => e.at(-1) === "2013");
    })
  );

  console.log(results.flat());
})();

Node 18 具有本机获取功能,但如果您遇到没有承诺的遗留情况,您可以将每个结果存储在数组中,并使用计数器来确定已完成的请求数量。当最后一个请求解决时,触发下一阶段的处理。

const cheerio = require("cheerio");
const request = require("request"); // ^2.88.2

const getRankings = done => {
  const results = [];
  const total = 6;
  let completed = 0;
  const baseUrl =
    "https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
  
  for (let i = 0; i < total; i++) {
    request(`${baseUrl}&offset=${i * 100}`, function (error, response, body) {
      if (error) {
        console.error(err);
      }
  
      const $ = cheerio.load(body);
      results[i] = [...$("tr")]
        .map(e => [...$(e).find("td")].map(e => $(e).text()))
        .filter(e => e.at(-1) === "2013");
  
      if (++completed === total) {
        done(results.flat());
      }
    });
  }
};

getRankings(results => {
  console.log(results);
});

上面的代码并行运行所有请求。要按顺序执行请求,您可以链接回调:

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

const getRankings = (done, results=[], total=6, i=0) => {
  const baseUrl =
    "https://www.boxofficemojo.com/chart/top_lifetime_gross/?area=XWW";
  request(`${baseUrl}&offset=${i * 100}`, (error, response, body) => {
    if (error) {
      console.error(err);
    }
  
    const $ = cheerio.load(body);
    results[i] = [...$("tr")]
      .map(e => [...$(e).find("td")].map(e => $(e).text()))
      .filter(e => e.at(-1) === "2013");
  
    if (i + 1 === total) {
      done(results.flat());
    }
    else {
      getRankings(done, results, total, i + 1);
    }
  });
}

getRankings(results => {
  console.log(results);
});

对失败请求的错误处理留作练习。我没有费心去适应现代 JS 习惯用法,如

.at(-1)
.flat()
等,以便在旧的 Node 版本上工作。 Cheerio 的
.toArray()
可以用来代替点差,
.at(-1)
可以用大致
const last = a => a[a.length-1];
重新创建,
.flat()
可以是
[].concat(...results)

© www.soinside.com 2019 - 2024. All rights reserved.