Raspador web para correos electrónicos y enlaces -- javascript campo con object-oriented campo con design-patterns campo con node.js campo con web-scraping camp codereview Relacionados El problema

web scraper for emails and links


4
vote

problema

Español

He creado una clase para raspar las URL, analizar y validar los correos electrónicos y obtener enlaces internos.

¿Cómo puedo lograr los principios sólidos en esta clase escritos en JavaScript para hacer un raspador web?

  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) => {             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);         }     }   

Obtenga todos los enlaces internos de un sitio web para rasparlos

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

Uso de una regulación para encontrar todos los correos electrónicos en el DOM

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

Validate todos los correos electrónicos que se encuentran en el DOM

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

Escriba los correos electrónicos en un archivo

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

Añadir correo electrónico a los correos electrónicos de la propiedad

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

Esto ejecuta el raspador

  const Scraper = require('./Scraper'); const scraper = new Scraper('any url'); (async () => {   try {     const dades = await scraper.executeScraper();    } catch (e) {     console.log(e)   } })()   
Original en ingles

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) => {             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^="/"]');         Array.from(links).filter(link => 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 => {             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) => {             fs.writeFile('./emails.txt', Array.from(this.emails), err => {                 if (err) reject('Could not write file');                 resolve('success');             });         });     } 

Add email to the propertie emails

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

This execute the scraper

const Scraper = require('./Scraper'); const scraper = new Scraper('any url'); (async () => {   try {     const dades = await scraper.executeScraper();    } catch (e) {     console.log(e)   } })() 
              
         
         

Lista de respuestas

4
 
vote

Elijo concentrarme en el principio de la responsabilidad única en esta respuesta.

Para acercarme al principio de la responsabilidad única, creo que parte de la funcionalidad debe ser movida alrededor. He descompuesto a la clase y eliminé todo el estado que se comparte en la clase (pregunta retórica: ¿es la clase responsable del estado o la funcionalidad? Si es "ambos", seguramente eso viola SRP?!). Estado compartido como este puede ser difícil de razonar y llevar a una clase cada vez mayor a medida que la funcionalidad evoluciona con el tiempo.

¿Qué tal un objeto scraper , con un método run que acepta una URL y devuelve el resultado para una URL (y sus sub-URL)?

Uso:

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

Configuramos un objeto result3 que luego pasamos para rellenar, antes de que se devuelva:

  9988776655544334  

Definimos una función scrape , responsable de coordinar el raspado de una 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 }   

FUNCIÓN fetchDOM es responsable de recuperar el DOM de la red para una URL:

  9988776655544338  

9988776655544339 es responsable de devolver una matriz de enlaces internos, que se encuentran dentro del DOM suministrado:

  99887766555443310  

run11 es responsable de devolver la matriz de correos electrónicos, encontrados dentro del DOM suministrado:

  run2  

run3 Splits Una matriz de correos electrónicos en dos conjuntos: run4 y run5 :

  run6  

La escritura de un archivo es una responsabilidad totalmente separada. Tal vez cree un objeto run7 para encapsularlo:

  run8  

Pondría cada una de estas funciones y objetos en un archivo separado. Cada uno tiene su propia responsabilidad después de todo.

 

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 "both", 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^="/"]')     return Array.from(links).map(link=>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=>{         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)=>{             fs.writeFile('./emails.txt', emails, err=>{                 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.

 
 
3
 
vote

Error: No hay asignación al llamar filter

Hay lugares donde Array.filter() se llama pero no asignado a nada.

  Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href')));   

y esto en addEmails()

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

Sugerencias

Manejo de casos de respuesta

El código en fetchUrlAndGetDom() MANEJA Respuestas de 200 y 404 . ¿Qué pasa con los otros numerosos casos ?

El else En ese if / Array.filter()0 La secuencia es superflua, ya que el bloque Array.filter()1 contiene un bloque < > Array.filter()2 Declaración.

Declaraciones variables

Se recomienda para el uso de Array.filter()3 en lugar de Array.filter()4 para todas las variables como puede causa errores . Cuando determine la asignación de reesignación (principalmente para variables de bucle / iterador), use Array.filter()5 . Algunas variables ya están declaradas con Array.filter()6 , pero otros podrían declararse con Array.filter()7 en lugar de Array.filter()8 - e.g. Array.filter()9 EN Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 0 , Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 1 EN Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 2 , etc.

denominación variable

Algunas variables son nombradas inapropiadamente. Por ejemplo, el método Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 3 pasa una matriz al método Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 4 :

Const Emails = Await This.FetchInternallinks (LinkSarray);

Sin embargo, la firma es:

  Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 5  

no solo es Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 6 asignado a la función (que parece superflua), el parámetro es 99887776655443327 , pero se pasa una matriz y se trata como una matriz (ya que el 99887776655443328 El método se le llama). Un mejor nombre para ese argumento sería Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 9 .

Nodelist para Array

Hay algunos lugares donde addEmails()0 se llama para poner elementos de un nodelista a una matriz. Algunos de los pueden simplificarse usando la Spread Syntax < / a>. Por ejemplo, en lugar de:

  addEmails()1  

puede ser:

  addEmails()2  

Simplificación de expresiones regulares

la clase de caracteres addEmails()3 se puede utilizar en lugar de addEmails()4 . Como se mencionó en un comentario, Validar una dirección de correo electrónico con una expresión regular no es una hazaña fácil . Hay paquetes que podrían usarse en NODEJS - E.G. validador de correo electrónico , validate-ignora-direcciones-nodo-js , etc.

 

Bug: no assignment when calling filter

There are places where Array.filter() is called but not assigned to anything.

Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 

and this in addEmails()

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

Suggestions

Handling Response cases

The code in fetchUrlAndGetDom() handles responses of 200 and 404. What about the other numerous cases?

The else in that if/else sequence is superfluous, since the if block contains a return statement.

Variable declarations

It is recommended to default to using const instead of let for all variables as it can cause bugs. When you determine re-assignment is necessary (mostly for loop/iterator variables) then use let. Some variables are already declared with const but others could be declared with const instead of let - e.g. htmlData in fetchUrlAndGetDom(), links in getInternalLinks(), etc.

Variable naming

Some variables are inappropriately named. For example, the method executeScraper passes an array to the method fetchInternalLinks:

const emails = await this.fetchInternalLinks(linksArray);

Yet the signature is:

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

Not only is result assigned to the function (which seems superfluous), the parameter is internalLink, yet it is passed an array and treated as an array (since the .map() method is called on it). A better name for that argument would be internalLinks.

NodeList to array

There are a few places where Array.from() is called to put elements of a NodeList to an array. Some of those can be simplified by using the spread syntax. For example, instead of:

Array.from(links).filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 

It can be:

[...links].filter(link => Scraper.internalLinks.add(link.getAttribute('href'))); 

Simplifying Regular Expressions

The Character class \w can be used instead of [A-Za-z0-9_]. As was mentioned in a comment, validating an email address with a regular expression is not an easy feat. There are packages that could be used in NodeJS - e.g. email-validator, validate-email-address-node-js, etc.

 
 

Relacionados problema

1  Scraper de Python + Selenium para obtener resultados usando la búsqueda inversa  ( Python selenium scraper to grab results using reverse search ) 
He escrito algún código en Python en combinación con Selenium para raspar el resultado poblado de un sitio web después de realizar una búsqueda inversa. Mi ...

6  TRIVAGO Hotels Precio Checker  ( Trivago hotels price checker ) 
He decidido escribir mi primer proyecto en Python. Me gustaría escuchar alguna opinión de usted. Descripción del script: Generar URLS TrivAGO para hote...

2  Utilización de apiss de vapor y raspado web  ( Utilization of steam apis and web scraping ) 
alguna información de fondo aquí: Este es un pequeño proyecto divertido que hice utilizando las API de vapor y el raspado web Esta es la primera vez que ...

9  Un raspador web que busca palabras predefinidas en artículos de noticias  ( A web scraper that looks for pre defined words in news articles ) 
Sigo siendo bastante nuevo en Python y Web-rasping, pero un colega me preguntó si podía construir un raspador web que podría ser utilizado por un Think Tank, ...

3  Raspador de noticias de Google para buscar enlaces con historias similares  ( Google news scraper to fetch links with similar stories ) 
El siguiente código lleva una URL o el título a un artículo de noticias existente. busca en Google News usando el título. recoge todos los enlaces de ...

4  Raspando html usando sopa hermosa  ( Scraping html using beautiful soup ) 
He escrito un script usando una hermosa sopa para raspar un poco de html y hacer algunas cosas y producir HTML de vuelta. Sin embargo, no estoy convencido con...

6  BFS / DFS Web Crawler  ( Bfs dfs web crawler ) 
He construido un rastreador web que comienza en una URL de origen y rastrea la web con un método BFS o DFS. Todo está funcionando bien, pero la actuación es h...

5  Cómo obtener información de los países de un sitio web que no está utilizando una verbanización consistente  ( Getting information of countries out of a website that isnt using consistent ve ) 
de este sitio web Necesitaba agarrar la información para cada país e insértelo en una hoja de cálculo de Excel. Mi plan original era usar mi programa y ...

11  ¿Es esta la forma en ello a Web-Scrape a una imagen de portada de libros?  ( Is this the clojure way to web scrape a book cover image ) 
¿Hay una manera de escribir esto mejor o más de manera de engaño? Especialmente la última parte con with-open y el let . ¿Debo poner el formulario 9988776...

2  PHP Crawler para recoger comentarios sobre los artículos  ( Php crawler to collect comments on articles ) 
Tengo código que analiza las páginas web encuentra comentarios y guarda información sobre comentarios en DB. Tengo una matriz donde se almacenan todas las pág...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos