Costruire uno strumento di validazione per destinatari di uccelli asincroni in blocco

Zachary Samuels

26 mag 2022

Email

1 min read

Costruire uno strumento di validazione per destinatari di uccelli asincroni in blocco

Punti Chiave

    • L'autore ha realizzato uno strumento di convalida dei destinatari in bulk per convalidare milioni di indirizzi email in modo efficiente utilizzando l'API di convalida dei destinatari di Bird.

    • Node.js si è dimostrato più veloce e scalabile rispetto a Python grazie alla sua I/O non bloccante e alla mancanza di limitazioni del GIL.

    • Lo strumento legge i file CSV in modo asincrono, chiama l'API di convalida per ogni email e scrive i risultati in un nuovo CSV in tempo reale.

    • Questo approccio evita colli di bottiglia nella memoria e migliora il throughput a circa 100.000 convalide in meno di un minuto.

    • Miglioramenti futuri potrebbero includere una gestione dei retry migliore, un'interfaccia utente intuitiva, o la migrazione verso ambienti serverless per la scalabilità.

Punti salienti del Q&A

  • Qual è lo scopo dello strumento di convalida dei destinatari asincroni in blocco?

    Convalida grandi volumi di indirizzi e-mail integrandosi direttamente con l'API di Validazione dei Destinatari di Bird, restituendo rapidamente risultati verificati senza caricamenti manuali.

  • Perché Python è stato inizialmente utilizzato e poi sostituito da Node.js?

    Il Global Interpreter Lock (GIL) di Python limitava la concorrenza, mentre Node.js permetteva un'esecuzione veramente asincrona, risultando in chiamate API parallele molto più veloci.

  • Come gestisce lo strumento file di grandi dimensioni senza esaurire la memoria?

    Invece di caricare tutti i dati contemporaneamente, lo script elabora ogni riga del CSV singolarmente—inviando la richiesta di convalida e scrivendo immediatamente i risultati in un nuovo file CSV.

  • Quale problema risolve lo strumento per gli sviluppatori?

    Consente la convalida delle liste email su larga scala, superando il limite di 20 MB del validatore basato sull'interfaccia utente di SparkPost ed eliminando la necessità di caricare manualmente più file.

  • Quanto è veloce la versione finale del programma?

    Circa 100.000 convalide completate in 55 secondi, rispetto a oltre un minuto utilizzando la versione UI.

  • Quali problemi sono stati riscontrati sui sistemi Windows?

    Il pooling delle connessioni del client HTTP di Node.js ha causato errori “ENOBUFS” dopo molte richieste concorrenti, che sono stati risolti configurando il riutilizzo delle connessioni di axios.

  • Quali miglioramenti futuri sono suggeriti?

    Aggiungere la gestione degli errori e i tentativi, creare un'interfaccia front-end o implementare lo strumento come una funzione Azure serverless per una migliore scalabilità e resilienza.

Per chi cerca un programma semplice e veloce che prenda un CSV, chiami l'API di convalida del destinatario e restituisca un CSV, questo programma è per te.

Quando si costruiscono applicazioni email, gli sviluppatori devono spesso integrare più servizi e API. Comprendere i fondamenti delle API email nell'infrastruttura cloud fornisce le basi per costruire strumenti robusti come il sistema di validazione in blocco che creeremo in questa guida.

Una delle domande che riceviamo occasionalmente è: come posso convalidare in blocco liste email con la convalida dei destinatari? Qui ci sono due opzioni: una è caricare un file tramite l'interfaccia SparkPost per la convalida, e l'altra è effettuare chiamate individuali per ogni email all'API (poiché l'API è per la convalida di una singola email).

La prima opzione funziona bene ma ha una limitazione di 20Mb (circa 500.000 indirizzi). E se qualcuno avesse una lista email contenente milioni di indirizzi? Potrebbe significare suddividere in migliaia di caricamenti di file CSV.

Poiché caricare migliaia di file CSV sembra un po' assurdo, ho preso in considerazione quel caso d'uso e ho cominciato a chiedermi quanto velocemente potessi far funzionare l'API. In questo post del blog spiegherò cosa ho provato e come sono arrivato a un programma che poteva effettuare 100.000 convalide in 55 secondi (mentre nell'interfaccia ho ottenuto circa 100.000 convalide in 1 minuto e 10 secondi).

Approccio

Convalide Testate

Tempo per Completare

Throughput Approssimativo

Strumento Node.js asincrono in blocco

100.000

55 secondi

~1.818 convalide/sec

Caricamento UI SparkPost

100.000

1 min 10 sec

~1.428 convalide/sec

E mentre questo richiederebbe ancora circa 100 ore per essere completato con circa 654 milioni di convalide, questo script può essere eseguito in background risparmiando tempo significativo.

La versione finale di questo programma può essere trovata qui.

Quando si costruiscono applicazioni email, gli sviluppatori devono spesso integrare più servizi e API. Comprendere i fondamenti delle API email nell'infrastruttura cloud fornisce le basi per costruire strumenti robusti come il sistema di validazione in blocco che creeremo in questa guida.

Una delle domande che riceviamo occasionalmente è: come posso convalidare in blocco liste email con la convalida dei destinatari? Qui ci sono due opzioni: una è caricare un file tramite l'interfaccia SparkPost per la convalida, e l'altra è effettuare chiamate individuali per ogni email all'API (poiché l'API è per la convalida di una singola email).

La prima opzione funziona bene ma ha una limitazione di 20Mb (circa 500.000 indirizzi). E se qualcuno avesse una lista email contenente milioni di indirizzi? Potrebbe significare suddividere in migliaia di caricamenti di file CSV.

Poiché caricare migliaia di file CSV sembra un po' assurdo, ho preso in considerazione quel caso d'uso e ho cominciato a chiedermi quanto velocemente potessi far funzionare l'API. In questo post del blog spiegherò cosa ho provato e come sono arrivato a un programma che poteva effettuare 100.000 convalide in 55 secondi (mentre nell'interfaccia ho ottenuto circa 100.000 convalide in 1 minuto e 10 secondi).

Approccio

Convalide Testate

Tempo per Completare

Throughput Approssimativo

Strumento Node.js asincrono in blocco

100.000

55 secondi

~1.818 convalide/sec

Caricamento UI SparkPost

100.000

1 min 10 sec

~1.428 convalide/sec

E mentre questo richiederebbe ancora circa 100 ore per essere completato con circa 654 milioni di convalide, questo script può essere eseguito in background risparmiando tempo significativo.

La versione finale di questo programma può essere trovata qui.

Quando si costruiscono applicazioni email, gli sviluppatori devono spesso integrare più servizi e API. Comprendere i fondamenti delle API email nell'infrastruttura cloud fornisce le basi per costruire strumenti robusti come il sistema di validazione in blocco che creeremo in questa guida.

Una delle domande che riceviamo occasionalmente è: come posso convalidare in blocco liste email con la convalida dei destinatari? Qui ci sono due opzioni: una è caricare un file tramite l'interfaccia SparkPost per la convalida, e l'altra è effettuare chiamate individuali per ogni email all'API (poiché l'API è per la convalida di una singola email).

La prima opzione funziona bene ma ha una limitazione di 20Mb (circa 500.000 indirizzi). E se qualcuno avesse una lista email contenente milioni di indirizzi? Potrebbe significare suddividere in migliaia di caricamenti di file CSV.

Poiché caricare migliaia di file CSV sembra un po' assurdo, ho preso in considerazione quel caso d'uso e ho cominciato a chiedermi quanto velocemente potessi far funzionare l'API. In questo post del blog spiegherò cosa ho provato e come sono arrivato a un programma che poteva effettuare 100.000 convalide in 55 secondi (mentre nell'interfaccia ho ottenuto circa 100.000 convalide in 1 minuto e 10 secondi).

Approccio

Convalide Testate

Tempo per Completare

Throughput Approssimativo

Strumento Node.js asincrono in blocco

100.000

55 secondi

~1.818 convalide/sec

Caricamento UI SparkPost

100.000

1 min 10 sec

~1.428 convalide/sec

E mentre questo richiederebbe ancora circa 100 ore per essere completato con circa 654 milioni di convalide, questo script può essere eseguito in background risparmiando tempo significativo.

La versione finale di questo programma può essere trovata qui.

Il mio primo errore: usare Python

Python è uno dei miei linguaggi di programmazione preferiti. Eccelle in molti aspetti ed è incredibilmente semplice. Tuttavia, un'area in cui non eccelle è nei processi concorrenti. Anche se Python ha la capacità di eseguire funzioni asincrone, ha quello che è conosciuto come il Global Interpreter Lock di Python o GIL.

“Il Global Interpreter Lock di Python o GIL, in parole semplici, è un mutex (o un blocco) che consente a un solo thread di mantenere il controllo dell'interprete Python.

Ciò significa che solo un thread può essere in uno stato di esecuzione in qualsiasi momento. L'impatto del GIL non è visibile agli sviluppatori che eseguono programmi a thread singolo, ma può essere un collo di bottiglia delle prestazioni nel codice vincolato dalla CPU e multi-thread.

Poiché il Global Interpreter Lock (GIL) consente solo a un thread di eseguire alla volta, anche su sistemi multi-core, ha guadagnato una reputazione come caratteristica

Python è uno dei miei linguaggi di programmazione preferiti. Eccelle in molti aspetti ed è incredibilmente semplice. Tuttavia, un'area in cui non eccelle è nei processi concorrenti. Anche se Python ha la capacità di eseguire funzioni asincrone, ha quello che è conosciuto come il Global Interpreter Lock di Python o GIL.

“Il Global Interpreter Lock di Python o GIL, in parole semplici, è un mutex (o un blocco) che consente a un solo thread di mantenere il controllo dell'interprete Python.

Ciò significa che solo un thread può essere in uno stato di esecuzione in qualsiasi momento. L'impatto del GIL non è visibile agli sviluppatori che eseguono programmi a thread singolo, ma può essere un collo di bottiglia delle prestazioni nel codice vincolato dalla CPU e multi-thread.

Poiché il Global Interpreter Lock (GIL) consente solo a un thread di eseguire alla volta, anche su sistemi multi-core, ha guadagnato una reputazione come caratteristica

Python è uno dei miei linguaggi di programmazione preferiti. Eccelle in molti aspetti ed è incredibilmente semplice. Tuttavia, un'area in cui non eccelle è nei processi concorrenti. Anche se Python ha la capacità di eseguire funzioni asincrone, ha quello che è conosciuto come il Global Interpreter Lock di Python o GIL.

“Il Global Interpreter Lock di Python o GIL, in parole semplici, è un mutex (o un blocco) che consente a un solo thread di mantenere il controllo dell'interprete Python.

Ciò significa che solo un thread può essere in uno stato di esecuzione in qualsiasi momento. L'impatto del GIL non è visibile agli sviluppatori che eseguono programmi a thread singolo, ma può essere un collo di bottiglia delle prestazioni nel codice vincolato dalla CPU e multi-thread.

Poiché il Global Interpreter Lock (GIL) consente solo a un thread di eseguire alla volta, anche su sistemi multi-core, ha guadagnato una reputazione come caratteristica

Il mio secondo errore: cercare di leggere il file nella memoria

La mia idea iniziale era la seguente:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e controllare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione dei destinatari. In quarto luogo, attendere i risultati e caricarli in una variabile. E infine, esportare questa variabile in un file CSV.

Questo ha funzionato molto bene per file più piccoli. Il problema è sorto quando ho provato a elaborare 100.000 email. Il programma si è bloccato intorno a 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era il caricamento di tutti i risultati in una variabile (e quindi esaurendo rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Versione 1 (NON RACCOMANDATO).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, mentre ogni riga viene letta in modo asincrono, chiamare l'API di validazione dei destinatari ed esportare i risultati in un file CSV.

Così, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere alcun dato in memoria a lungo termine. Ho anche rimosso il controllo della sintassi dell'email dopo aver parlato con il team di validazione dei destinatari, poiché mi hanno informato che la validazione dei destinatari ha già controlli integrati per verificare se un'email è valida o meno.

La mia idea iniziale era la seguente:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e controllare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione dei destinatari. In quarto luogo, attendere i risultati e caricarli in una variabile. E infine, esportare questa variabile in un file CSV.

Questo ha funzionato molto bene per file più piccoli. Il problema è sorto quando ho provato a elaborare 100.000 email. Il programma si è bloccato intorno a 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era il caricamento di tutti i risultati in una variabile (e quindi esaurendo rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Versione 1 (NON RACCOMANDATO).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, mentre ogni riga viene letta in modo asincrono, chiamare l'API di validazione dei destinatari ed esportare i risultati in un file CSV.

Così, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere alcun dato in memoria a lungo termine. Ho anche rimosso il controllo della sintassi dell'email dopo aver parlato con il team di validazione dei destinatari, poiché mi hanno informato che la validazione dei destinatari ha già controlli integrati per verificare se un'email è valida o meno.

La mia idea iniziale era la seguente:

Flowchart illustrating the process of validating a CSV list of emails, starting with ingestion, format checking, asynchronous API validation, result aggregation, and concluding with outputting to a CSV file.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e controllare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione dei destinatari. In quarto luogo, attendere i risultati e caricarli in una variabile. E infine, esportare questa variabile in un file CSV.

Questo ha funzionato molto bene per file più piccoli. Il problema è sorto quando ho provato a elaborare 100.000 email. Il programma si è bloccato intorno a 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era il caricamento di tutti i risultati in una variabile (e quindi esaurendo rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Versione 1 (NON RACCOMANDATO).


Flowchart illustrating an email processing workflow, showing steps from ingesting a CSV list of emails to outputting results to a CSV file, with asynchronous validation via an API.


Innanzitutto, acquisire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, mentre ogni riga viene letta in modo asincrono, chiamare l'API di validazione dei destinatari ed esportare i risultati in un file CSV.

Così, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere alcun dato in memoria a lungo termine. Ho anche rimosso il controllo della sintassi dell'email dopo aver parlato con il team di validazione dei destinatari, poiché mi hanno informato che la validazione dei destinatari ha già controlli integrati per verificare se un'email è valida o meno.

Analizzando il codice finale

Dopo aver letto e convalidato gli argomenti del terminale, eseguo il seguente codice. Prima leggo il file CSV delle email e conto ogni riga. Ci sono due scopi per questa funzione: 1) mi consente di riportare accuratamente i progressi del file [come vedremo più avanti], e 2) mi consente di fermare un timer quando il numero di email nel file equivale alle convalide completate. Ho aggiunto un timer in modo da poter eseguire dei benchmark e assicurarmi di ottenere buoni risultati.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 Poi chiamo la funzione validateRecipients. Nota che questa funzione è asincrona. Dopo aver convalidato che il file di input e il file di output sono CSV, scrivo una riga di intestazione e avvio un timer del programma utilizzando la libreria JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

Lo script seguente è realmente il nucleo del programma, quindi lo dividerò e spiegherò cosa sta succedendo. Per ogni riga del file di input:

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

Poi, sulla risposta

  • Aggiungi l'email al JSON (per poter stampare l'email nel CSV)

  • Convalida se il motivo è nullo, e se sì, popola un valore vuoto (questo è affinché il formato CSV sia coerente, poiché in alcuni casi il motivo è fornito nella risposta)

  • Imposta le opzioni e le chiavi per il modulo json2csv.

  • Converti il JSON in CSV e produci l'output (utilizzando json2csv)

  • Scrivi i progressi nel terminale

  • Infine, se il numero di email nel file = convalide completate, ferma il timer e stampa i risultati


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

Un'ultima questione che ho trovato è che mentre ciò funzionava molto bene su Mac, ho riscontrato il seguente errore utilizzando Windows dopo circa 10.000 convalide:

Errore: connect ENOBUFS XX.XX.XXX.XXX:443 – Locale (undefined:undefined) con email XXXXXXX@XXXXXXXXXX.XXX

Dopo aver fatto ulteriori ricerche, sembra essere un problema con il pool di connessione cliente HTTP di NodeJS che non riutilizza le connessioni. Ho trovato questo articolo di Stackoverflow sul problema e, dopo ulteriori ricerche, ho trovato una buona configurazione predefinita per la libreria axios che ha risolto questo problema. Non sono ancora certo del perché questo problema si presenti solo su Windows e non su Mac.

Dopo aver letto e convalidato gli argomenti del terminale, eseguo il seguente codice. Prima leggo il file CSV delle email e conto ogni riga. Ci sono due scopi per questa funzione: 1) mi consente di riportare accuratamente i progressi del file [come vedremo più avanti], e 2) mi consente di fermare un timer quando il numero di email nel file equivale alle convalide completate. Ho aggiunto un timer in modo da poter eseguire dei benchmark e assicurarmi di ottenere buoni risultati.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 Poi chiamo la funzione validateRecipients. Nota che questa funzione è asincrona. Dopo aver convalidato che il file di input e il file di output sono CSV, scrivo una riga di intestazione e avvio un timer del programma utilizzando la libreria JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

Lo script seguente è realmente il nucleo del programma, quindi lo dividerò e spiegherò cosa sta succedendo. Per ogni riga del file di input:

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

Poi, sulla risposta

  • Aggiungi l'email al JSON (per poter stampare l'email nel CSV)

  • Convalida se il motivo è nullo, e se sì, popola un valore vuoto (questo è affinché il formato CSV sia coerente, poiché in alcuni casi il motivo è fornito nella risposta)

  • Imposta le opzioni e le chiavi per il modulo json2csv.

  • Converti il JSON in CSV e produci l'output (utilizzando json2csv)

  • Scrivi i progressi nel terminale

  • Infine, se il numero di email nel file = convalide completate, ferma il timer e stampa i risultati


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

Un'ultima questione che ho trovato è che mentre ciò funzionava molto bene su Mac, ho riscontrato il seguente errore utilizzando Windows dopo circa 10.000 convalide:

Errore: connect ENOBUFS XX.XX.XXX.XXX:443 – Locale (undefined:undefined) con email XXXXXXX@XXXXXXXXXX.XXX

Dopo aver fatto ulteriori ricerche, sembra essere un problema con il pool di connessione cliente HTTP di NodeJS che non riutilizza le connessioni. Ho trovato questo articolo di Stackoverflow sul problema e, dopo ulteriori ricerche, ho trovato una buona configurazione predefinita per la libreria axios che ha risolto questo problema. Non sono ancora certo del perché questo problema si presenti solo su Windows e non su Mac.

Dopo aver letto e convalidato gli argomenti del terminale, eseguo il seguente codice. Prima leggo il file CSV delle email e conto ogni riga. Ci sono due scopi per questa funzione: 1) mi consente di riportare accuratamente i progressi del file [come vedremo più avanti], e 2) mi consente di fermare un timer quando il numero di email nel file equivale alle convalide completate. Ho aggiunto un timer in modo da poter eseguire dei benchmark e assicurarmi di ottenere buoni risultati.

let count = 0; // Line count
require("fs")
    .createReadStream(myArgs[1])
    .on("data", function (chunk) {
        for (let i = 0; i < chunk.length; ++i)
            if (chunk[i] == 10) count++;
    })
    // Reads the infile and increases the count for each line
    .on("close", function () {
        // At the end of the infile, after all lines have been counted, run the recipient validation function
        validateRecipients.validateRecipients(count, myArgs);
    });


 Poi chiamo la funzione validateRecipients. Nota che questa funzione è asincrona. Dopo aver convalidato che il file di input e il file di output sono CSV, scrivo una riga di intestazione e avvio un timer del programma utilizzando la libreria JSDOM.

async function validateRecipients(email_count, myArgs) {
    if (
        // If both the infile and outfile are in .csv format
        extname(myArgs[1]).toLowerCase() == ".csv" &&
        extname(myArgs[3]).toLowerCase() == ".csv"
    ) {
        let completed = 0; // Counter for each API call
        email_count++; // Line counter returns #lines - 1, this corrects the number of lines
        // Start a timer
        const { window } = new JSDOM();
        const start = window.performance.now();
        const output = fs.createWriteStream(myArgs[3]); // Outfile
        output.write(
            "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n"
        ); // Write the headers in the outfile
    }
}

Lo script seguente è realmente il nucleo del programma, quindi lo dividerò e spiegherò cosa sta succedendo. Per ogni riga del file di input:

fs.createReadStream(myArgs[1])
    .pipe(csv.parse({ headers: false }))
    .on("data", async (email) => {
        let url =
            SPARKPOST_HOST +
            "/api/v1/recipient-validation/single/" +
            email;
        await axios
            .get(url, {
                headers: {
                    Authorization: SPARKPOST_API_KEY,
                },
            });
        // For each row read in from the infile, call the SparkPost Recipient Validation API
    });

Poi, sulla risposta

  • Aggiungi l'email al JSON (per poter stampare l'email nel CSV)

  • Convalida se il motivo è nullo, e se sì, popola un valore vuoto (questo è affinché il formato CSV sia coerente, poiché in alcuni casi il motivo è fornito nella risposta)

  • Imposta le opzioni e le chiavi per il modulo json2csv.

  • Converti il JSON in CSV e produci l'output (utilizzando json2csv)

  • Scrivi i progressi nel terminale

  • Infine, se il numero di email nel file = convalide completate, ferma il timer e stampa i risultati


.then(function (response) {
    response.data.results.email = String(email); 
    // Adds the email as a value/key pair to the response JSON for output
    response.data.results.reason ? null : (response.data.results.reason = ""); 
    // If reason is null, set it to blank so the CSV is uniform
    // Utilizes json-2-csv to convert the JSON to CSV format and output
    let options = {
        prependHeader: false, // Disables JSON values from being added as header rows for every line
        keys: [
            "results.email",
            "results.valid",
            "results.result",
            "results.reason",
            "results.is_role",
            "results.is_disposable",
            "results.is_free",
            "results.delivery_confidence",
        ], // Sets the order of keys
    };
    let json2csvCallback = function (err, csv) {
        if (err) throw err;
        output.write(`${csv}\n`);
    };
    converter.json2csv(response.data, json2csvCallback, options);
    completed++; // Increase the API counter
    process.stdout.write(`Done with ${completed} / ${email_count}\r`); 
    // Output status of Completed / Total to the console without showing new lines
    // If all emails have completed validation
    if (completed == email_count) {
        const stop = window.performance.now(); // Stop the timer
        console.log(
            `All emails successfully validated in ${(stop - start) / 1000} seconds`
        );
    }
});

 

Un'ultima questione che ho trovato è che mentre ciò funzionava molto bene su Mac, ho riscontrato il seguente errore utilizzando Windows dopo circa 10.000 convalide:

Errore: connect ENOBUFS XX.XX.XXX.XXX:443 – Locale (undefined:undefined) con email XXXXXXX@XXXXXXXXXX.XXX

Dopo aver fatto ulteriori ricerche, sembra essere un problema con il pool di connessione cliente HTTP di NodeJS che non riutilizza le connessioni. Ho trovato questo articolo di Stackoverflow sul problema e, dopo ulteriori ricerche, ho trovato una buona configurazione predefinita per la libreria axios che ha risolto questo problema. Non sono ancora certo del perché questo problema si presenti solo su Windows e non su Mac.

Prossimi passi

Per qualcuno che cerca un programma semplice e veloce che prenda un csv, chiami l'API di validazione del destinatario e restituisca un CSV, questo programma fa per te.

Alcune aggiunte a questo programma sarebbero le seguenti:

  • Creare un'interfaccia front-end o un'interfaccia utente più semplice per l'uso

  • Gestione degli errori e dei tentativi migliore, perché se per qualche motivo l'API genera un errore, il programma attualmente non ripete la chiamata

  • Considerare l'implementazione come una Funzione Azure senza server per scalabilità automatica e gestione ridotta dell'infrastruttura


Sarei anche curioso di vedere se risultati più rapidi potrebbero essere ottenuti con un'altra lingua come Golang o Erlang/Elixir. Oltre alla scelta del linguaggio, le limitazioni dell'infrastruttura possono influenzare le prestazioni - lo abbiamo appreso direttamente quando abbiamo incontrato limiti DNS non documentati in AWS che hanno influenzato i nostri sistemi di elaborazione email ad alto volume.

Per i programmatori interessati a combinare l'elaborazione delle API con strumenti di flusso di lavoro visivi, dai un'occhiata a come integrare Flow Builder con Google Cloud Functions per flussi di lavoro di automazione senza codice.

Sentiti libero di fornirmi qualsiasi feedback o suggerimenti per espandere questo progetto.

Per qualcuno che cerca un programma semplice e veloce che prenda un csv, chiami l'API di validazione del destinatario e restituisca un CSV, questo programma fa per te.

Alcune aggiunte a questo programma sarebbero le seguenti:

  • Creare un'interfaccia front-end o un'interfaccia utente più semplice per l'uso

  • Gestione degli errori e dei tentativi migliore, perché se per qualche motivo l'API genera un errore, il programma attualmente non ripete la chiamata

  • Considerare l'implementazione come una Funzione Azure senza server per scalabilità automatica e gestione ridotta dell'infrastruttura


Sarei anche curioso di vedere se risultati più rapidi potrebbero essere ottenuti con un'altra lingua come Golang o Erlang/Elixir. Oltre alla scelta del linguaggio, le limitazioni dell'infrastruttura possono influenzare le prestazioni - lo abbiamo appreso direttamente quando abbiamo incontrato limiti DNS non documentati in AWS che hanno influenzato i nostri sistemi di elaborazione email ad alto volume.

Per i programmatori interessati a combinare l'elaborazione delle API con strumenti di flusso di lavoro visivi, dai un'occhiata a come integrare Flow Builder con Google Cloud Functions per flussi di lavoro di automazione senza codice.

Sentiti libero di fornirmi qualsiasi feedback o suggerimenti per espandere questo progetto.

Per qualcuno che cerca un programma semplice e veloce che prenda un csv, chiami l'API di validazione del destinatario e restituisca un CSV, questo programma fa per te.

Alcune aggiunte a questo programma sarebbero le seguenti:

  • Creare un'interfaccia front-end o un'interfaccia utente più semplice per l'uso

  • Gestione degli errori e dei tentativi migliore, perché se per qualche motivo l'API genera un errore, il programma attualmente non ripete la chiamata

  • Considerare l'implementazione come una Funzione Azure senza server per scalabilità automatica e gestione ridotta dell'infrastruttura


Sarei anche curioso di vedere se risultati più rapidi potrebbero essere ottenuti con un'altra lingua come Golang o Erlang/Elixir. Oltre alla scelta del linguaggio, le limitazioni dell'infrastruttura possono influenzare le prestazioni - lo abbiamo appreso direttamente quando abbiamo incontrato limiti DNS non documentati in AWS che hanno influenzato i nostri sistemi di elaborazione email ad alto volume.

Per i programmatori interessati a combinare l'elaborazione delle API con strumenti di flusso di lavoro visivi, dai un'occhiata a come integrare Flow Builder con Google Cloud Functions per flussi di lavoro di automazione senza codice.

Sentiti libero di fornirmi qualsiasi feedback o suggerimenti per espandere questo progetto.

Altre notizie

Leggi di più da questa categoria

A person is standing at a desk while typing on a laptop.

La piattaforma completa nativa dell'IA che si espande con la tua azienda.

© 2025 Uccello

A person is standing at a desk while typing on a laptop.

La piattaforma completa nativa dell'IA che si espande con la tua azienda.

© 2025 Uccello