How can I serially download multiple files with NighmareJs?

674 views Asked by At

I'm very new to NodeJs and NightmareJs. I need to download several files from the same page using the nightmare-inline-download plugin. So far my code below can download the first file. But I can't figure out how to download all the files linked to the page, that is, how to loop around click(selector).download() in the proper way. Moreover, how will I yield upon the looped downloads to get all the downloaded file names?

Note the HTML <a> tags I need to click: <a target="_blank" class="download-link">Download</a>. There is no href attribute; clicking the tag triggers a script that starts the download.

The website allows starting only one download at the time.

Here's my code so far:

var Nightmare = require('nightmare');
require('nightmare-inline-download')(Nightmare);
var nightmare = Nightmare({ show: false });
nightmare
  .goto(pageUrl)
  .evaluate({
    var links = document.querySelectorAll('.download-link');
    for(var i = 0, i < links.length; i++) {
      links[i].setAttribute('download-this', 'true');
    }
  })
  .click('[download-this="true"]') // will select just the first element
  .download()
  .end()
  .then(() => {
    console.log('done');
  });
1

There are 1 answers

0
stepse On

Answering my own question. After reading this, this and this several times, I figured how to combine selectors and promises to loop over click().download(). The key is to give each download link in evaluate() its own unique id, then return an array of the ids. After that .then can reduce the array to a list of promises, where each promise clicks and downloads the element selected by unique id. A final .then kicks off the downloads. The code becomes:

var Nightmare = require('nightmare');
require('nightmare-inline-download')(Nightmare);
var nightmare = Nightmare({ show: false });
nightmare
  .goto(pageUrl)
  .evaluate({
    var links = document.querySelectorAll('.download-link');
    var ids = [];
    for(var i = 0, i < links.length; i++) {
      links[i].setAttribute('download-this', i);
      ids.push(i);
    }
    return ids
  })
  .then(function (ids) {
    return ids.reduce(function (accumulator, id) {
      return accumulator.then(function (results) {
        nightmare
          .click('[download-this=["' + id + '"]')
          .download();
        results.push(id);
        return results; // ids of downloaded files
      })
    }, Promise.resolve([]))
  })
  .then(function (results) {
    console.log('results', results);
    return nightmare.end()
  })
  .catch(function (error) {
    console.error('Error:', error);
    return nightmare.end()
  });

Now, if I need to print information about each download, instead of returning the downloaded file ids, I add a .then after download() to return the info about the completed download. This bit of code I took from this test script, which in retrospect is similar to the script that I'm presenting in this answer! So, the relevant code changes this way, from

    nightmare
      .click('[download-this=["' + id + '"]')
      .download();
    results.push(id);
    return results; // ids of downloaded files

to

    return nightmare
      .click('[download-this=["' + id + '"]')
      .download()
      .then(info => {
        results.push(info);
        return results; // info about downloaded files
      });