web scraper for emails and links I created a class to scrape URLS, parse and validate emails and get internal links.
How can I achieve the SOLID principles in this class written in Javascript to make a web scraper?
const axios = require('axios');
const fs = require('fs')
const {
    JSDOM
} = require('jsdom');
class Scraper {
    static dangerDomains = ['loan', 'work', 'biz', 'racing', 'ooo', 'life', 'ltd', 'png'];
    static emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi;
    static domObject = null;
    static internalLinks = new Set();
    static externalLinks = new Set();
    constructor(url) {
        this.url = url;
        this.emails = new Set();
        this.dangerEmails = new Set();
    }

    async executeScraper() {
        const fetchedData = await this.fetchUrlAndGetDom(this.url);
        const links = await this.getInternalLinks();
        const linksArray = Array.from(Scraper.internalLinks);
        const emails = await this.fetchInternalLinks(linksArray);
        this.addEmails(emails);
        this.validateEmails();
        await this.writeFile();
    }

    const result = async fetchInternalLinks(internalLink) {
        const result = await Promise.all(internalLink.map((link) =&gt; {
            return this.fetchUrlAndGetDom(`${this.url}${link}`);
        }));
        return result;
    }

    async fetchUrlAndGetDom(url) {
        try {
            const response = await axios(url);
            if (response.status === 200) {
                let htmlData = await response.data;
                Scraper.domObject = await new JSDOM(htmlData);
                let dades = this.searchForEmails();
                return dades;
            } else if (response.status === 404) {
                console.log('this page doesnt exists')
                return process.exit(1);
            }
        } catch (error) {
            console.log(error)
            return process.exit(1);
        }
    }


Get all the internal links from a website to scrape them

    getInternalLinks() {
        let links = Scraper.domObject.window.document.body;
        links = links.querySelectorAll('a[href^=&quot;/&quot;]');
        Array.from(links).filter(link =&gt; Scraper.internalLinks.add(link.getAttribute('href')));
    }
    
 


Use of a regex to find all the emails in the dom

    searchForEmails() {
        let emailsInDom = Scraper.domObject.window.document.body.innerHTML;
        let emails = emailsInDom.toString().match(Scraper.emailRegex)
        return emails;
    }


Validate all the emails found in the dom

    validateEmails() {
        const validatedEmails = Array.from(this.emails).filter(email =&gt; {
            let domainName = email.split('@')[1].split('.')[1];
            if (Scraper.dangerDomains.includes(domainName)) {
                this.dangerEmails.add(email);
                this.emails.delete(email);
            } else return email;
        });
    }


Write the emails in a file

    async writeFile() {
        return new Promise((resolve, reject) =&gt; {
            fs.writeFile('./emails.txt', Array.from(this.emails), err =&gt; {
                if (err) reject('Could not write file');
                resolve('success');
            });
        });
    }


Add email to the propertie emails

    addEmails(emailsInDom) {
        Array.from(emailsInDom).filter(el =&gt; {
            if(el !== null) {
                el.filter(el =&gt; this.emails.add(el))
      }
    });
    }
}


This execute the scraper

const Scraper = require('./Scraper');
const scraper = new Scraper('any url');
(async () =&gt; {
  try {
    const dades = await scraper.executeScraper();

  } catch (e) {
    console.log(e)
  }
})()
I choose to focus on the Single Responsibility Principle in this answer.
To move closer to the Single Responsibility Principle, I think some of the functionality needs to be moved around. I have decomposed the class and removed all state that is shared on the class (rhetorical question: is the class responsible for state or functionality? If it is &quot;both&quot;, then surely that violates SRP?!). Shared state like this can get tough to reason about and lead to an ever-larger class as the functionality evolves over time.
How about a scraper object, with one method run that accepts a URL and returns the result for a URL (and its sub-URLs)?
Usage:
import scraper from './scraper.js'

const { emails: { valid, dangerous }, links: { internal } } = await scraper.run('http://www.example.com')

We configure a result object which we then pass around to populate, before it is returned:
const scraper = {
    async run(url) {
        const result = {
            emails: { valid: [], dangerous: [] },
            links: { internal: [] }
        }
        return scrape(url, result)
    }
}

We define a scrape function, responsible for coordinating the scraping of a URL:
async function scrape(url, result, visited=new Set()) {
    if (visited.has(url)) {
        return result
    }

    visited.add(url)
    const dom = await fetchDom(url)
    const internalLinks = findInternalLinks(dom)
    const { valid, dangerous } = validateEmails(findEmails(dom))

    for (let link of internalLinks) {
        await scrape(link, result, visited)
    }

    result.links.internal.push(...internalLinks)
    result.emails.valid.push(...valid)
    result.emails.dangerous.push(...dangerous)

    return result
}

Function fetchDOM is responsible for retrieving the DOM from the network for a URL:
async function fetchDOM(url) {
    try {
        const { status, data } = await axios(url)
        switch (status) {
            case 200:
                return new JSDOM(await data)
            case 404:
                console.log('this page doesnt exists')
                return process.exit(1)
        }
    } catch (error) {
        console.log(error)
        return process.exit(1)
    }
}

findInternalLinks is responsible for returning an array of internal links, found within the supplied DOM:
function findInternalLinks({ window: { document: body } }) {
    let links = body.querySelectorAll('a[href^=&quot;/&quot;]')
    return Array.from(links).map(link=&gt;link.getAttribute('href'))
}

findEmails is responsible for returning the an array of emails, found within the supplied DOM:
const EMAIL_REGEX = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi

function findEmails({ window: { document: { body: { innerHTML: html } } }) {
    return html.toString().match(EMAIL_REGEX)
}

validateEmails splits an array of emails into two sets: valid and dangerous:
const DANGEROUS_DOMAINS = ['loan', 'work', 'biz', 'racing', 'ooo', 'life', 'ltd', 'png']

function validateEmails(emails) {
    const dangerous = []
    const valid = Array.from(emails).filter(email=&gt;{
        let domainName = email.split('@')[1].split('.')[1]
        if (DANGEROUS_DOMAINS.includes(domainName)) {
            dangerous.add(email)
            return false
        } else
            return true
        })
    return { valid, dangerous }
}

The writing of a file is a wholly separate responsibility. Perhaps create an emailFileWriter object to encapsulate it:
const emailFileWriter = {
    write(emails) {
        return new Promise((resolve,reject)=&gt;{
            fs.writeFile('./emails.txt', emails, err=&gt;{
                if (err) {
                    reject('Could not write file')
                }
                resolve('success')
            })
        })
    }
}

I would put each of these functions and objects in a separate file. Each has its own responsibility after all.