Reach

Grow

Manage

Automate

Reach

Grow

Manage

Automate

Costruzione di uno strumento di convalida per destinatari di uccelli in blocco e in modo asincrono

Email

1 min read

Costruzione di uno strumento di convalida per destinatari di uccelli in blocco e in modo asincrono

Email

1 min read

Costruzione di uno strumento di convalida per destinatari di uccelli in blocco e in modo asincrono

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

Una delle domande che riceviamo occasionalmente è, come posso convalidare in blocco elenchi di email con la convalida del destinatario? Ci sono due opzioni qui, una è caricare un file attraverso l'interfaccia utente di SparkPost per la convalida, e l'altra è effettuare chiamate individuali per email all'API (poiché l'API è per la convalida di singole email).

La prima opzione funziona benissimo ma ha una limitazione di 20Mb (circa 500.000 indirizzi). Cosa succede se qualcuno ha un elenco di email contenente milioni di indirizzi? Potrebbe significare dover suddividere in 1000 caricamenti di file CSV.

Dato che caricare migliaia di file CSV sembra un po' inverosimile, ho considerato quel caso d'uso e ho cominciato a chiedermi quanto velocemente potrei far funzionare l'API. In questo post del blog, spiegherò cosa ho provato e come alla fine sono arrivato a un programma che poteva ottenere circa 100.000 convalide in 55 secondi (mentre nell'interfaccia utente ho ottenuto circa 100.000 convalide in 1 minuto e 10 secondi). E mentre questo richiederebbe ancora circa 100 ore per completare circa 654 milioni di convalide, questo script può funzionare 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 molte aree 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 è noto come The Python Global Interpreter Lock o GIL.

“The Python Global Interpreter Lock o GIL, in parole semplici, è un mutex (o un blocco) che consente solo a un 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 singolo thread, ma può essere un collo di bottiglia delle prestazioni nel codice legato alla CPU e multi-threaded.

Poiché il GIL consente solo a un thread di eseguire alla volta anche in un'architettura multi-thread con più di un core della CPU, il GIL ha guadagnato la reputazione di una funzionalità “famigerata” di Python.” (https://realpython.com/python-gil/)”

All'inizio, non ero a conoscenza del GIL, quindi ho iniziato a programmare in python. Alla fine, anche se il mio programma era asincrono, si bloccava, e non importa quanti thread aggiungessi, riuscivo comunque a ottenere solo circa 12-15 iterazioni al secondo.

La parte principale della funzione asincrona in Python può essere vista di seguito:

async def validateRecipients(f, fh, apiKey, snooze, count): h = {'Authorization': apiKey, 'Accept': 'application/json'} with tqdm(total=count) as pbar: async with aiohttp.ClientSession() as session: for address in f: for i in address: thisReq = requests.compat.urljoin(url, i) async with session.get(thisReq,headers=h, ssl=False) as resp: content = await resp.json() row = content['results'] row['email'] = i fh.writerow(row) pbar.update(1)

Quindi ho scartato l'uso di Python e sono tornato al tavolo da disegno...

Mi sono deciso a utilizzare NodeJS per la sua capacità di eseguire operazioni di i/o non bloccanti estremamente bene. Inoltre, sono abbastanza familiare con la programmazione in NodeJS.

Sfruttando gli aspetti asincroni di NodeJS, ciò ha finito per funzionare bene. Per ulteriori dettagli sulla programmazione asincrona in NodeJS, vedere https://blog.risingstack.com/node-hero-async-programming-in-node-js/

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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e verificare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione del destinatario. 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 cercato di elaborare 100.000 email. Il programma si è bloccato intorno alle 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era nel caricare tutti i risultati in una variabile (esaurendo quindi rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Version 1 (NOT RECOMMENDED).


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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, man mano che ogni riga viene letta in modo asincrono, chiamare l'API di validazione del destinatario e esportare i risultati in un file CSV.

Quindi, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere nessuno di questi dati nella memoria a lungo termine. Ho anche rimosso il controllo della sintassi delle email dopo aver parlato con il team di validazione del destinatario, poiché mi hanno informato che la validazione del destinatario ha già controlli incorporati 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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e verificare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione del destinatario. 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 cercato di elaborare 100.000 email. Il programma si è bloccato intorno alle 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era nel caricare tutti i risultati in una variabile (esaurendo quindi rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Version 1 (NOT RECOMMENDED).


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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, man mano che ogni riga viene letta in modo asincrono, chiamare l'API di validazione del destinatario e esportare i risultati in un file CSV.

Quindi, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere nessuno di questi dati nella memoria a lungo termine. Ho anche rimosso il controllo della sintassi delle email dopo aver parlato con il team di validazione del destinatario, poiché mi hanno informato che la validazione del destinatario ha già controlli incorporati 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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, caricare le email in un array e verificare che siano nel formato corretto. In terzo luogo, chiamare in modo asincrono l'API di validazione del destinatario. 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 cercato di elaborare 100.000 email. Il programma si è bloccato intorno alle 12.000 validazioni. Con l'aiuto di uno dei nostri sviluppatori front-end, ho visto che il problema era nel caricare tutti i risultati in una variabile (esaurendo quindi rapidamente la memoria). Se desideri vedere la prima iterazione di questo programma, l'ho collegata qui: Version 1 (NOT RECOMMENDED).


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.


Per prima cosa, ingerire un elenco di email in formato CSV. In secondo luogo, contare il numero di email nel file per scopi di reporting. In terzo luogo, man mano che ogni riga viene letta in modo asincrono, chiamare l'API di validazione del destinatario e esportare i risultati in un file CSV.

Quindi, per ogni riga letta, chiamo l'API e scrivo i risultati in modo asincrono per non mantenere nessuno di questi dati nella memoria a lungo termine. Ho anche rimosso il controllo della sintassi delle email dopo aver parlato con il team di validazione del destinatario, poiché mi hanno informato che la validazione del destinatario ha già controlli incorporati per verificare se un'email è valida o meno.

Scomposizione del codice finale

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

let count = 0; //Conteggio righe require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) //Legge il file di input e aumenta il conteggio per ogni riga .on("close", function () { //Alla fine del file di input, dopo che tutte le righe sono state contate, eseguire la funzione di validazione dei destinatari validateRecipients.validateRecipients(count, myArgs); });


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

async function validateRecipients(email_count, myArgs) { if ( //Se sia il file di input che quello di output sono in formato .csv extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; //Contatore per ogni chiamata API email_count++; //Il contatore di righe restituisce #lines - 1, questo viene fatto per correggere il numero di righe //Avvia un timer const { window } = new JSDOM(); const start = window.performance.now(); const output = fs.createWriteStream(myArgs[3]); //File di output output.write( "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n" ); //Scrivi le intestazioni nel file di output


Lo script seguente è veramente il fulcro del programma, quindi lo spezzetterò 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, }, }) //Per ogni riga letta dal file di input, chiamare la SparkPost Recipient Validation API


Quindi, sulla risposta

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

  • Verifica se il motivo è nullo, e se lo è, inserire un valore vuoto (questo per mantenere il formato CSV 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 output (utilizzando json2csv)

  • Scrivi il progresso nel terminale

  • Infine, se il numero di email nel file è uguale alle validazioni completate, fermare il timer e stampare i risultati


.then(function (response) { response.data.results.email = String(email); //Aggiunge l'email come coppia chiave/valore al JSON di risposta da utilizzare per l'output response.data.results.reason ? null : (response.data.results.reason = ""); //Se il motivo è nullo, impostalo a vuoto in modo che il CSV sia uniforme //Utilizza json-2-csv per convertire il JSON in formato CSV e output let options = { prependHeader: false, //Disabilita l'aggiunta dei valori JSON come righe di intestazione per ogni riga keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], //Imposta l'ordine delle chiavi }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; //Incrementa il contatore API process.stdout.write(`Fatto con ${completed} / ${email_count}\r`); //Output stato di Completo / Totale alla console senza mostrare nuove righe //Se tutte le email hanno completato la validazione if (completed == email_count) { const stop = window.performance.now(); //Ferma il timer console.log( `Tutte le email validate con successo in ${ (stop - start) / 1000 } secondi` ); } })

 

Un ultimo problema che ho riscontrato è stato che mentre questo funzionava bene su Mac, ho incontrato il seguente errore su Windows dopo circa 10.000 validazioni:

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

Dopo ulteriori ricerche, sembra essere un problema con il pool di connessioni del client HTTP di NodeJS che non riutilizza le connessioni. Ho trovato questo articolo su Stackoverflow sul problema e, dopo ulteriori approfondimenti, ho trovato un buon config predefinito per la libreria axios che ha risolto questo problema. Non sono ancora certo del perché questo problema si verifichi solo su Windows e non su Mac.

Passi successivi

Per qualcuno che cerca un programma semplice e veloce che accetti un csv, chiami la recipient validation API e produca un CSV, questo programma è per te.

Alcuni aggiunte a questo programma sarebbero le seguenti:

  • Costruire un frontend o un'interfaccia utente più semplice per l'uso

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


Sarei anche curioso di vedere se si possono ottenere risultati più veloci con un altro linguaggio come Golang o Erlang/Elixir.

Sentiti libero di fornirmi qualsiasi feedback o suggerimento per ampliare questo progetto.

Iscriviti alla nostra Newsletter.

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Iscriviti alla nostra Newsletter.

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Iscriviti alla nostra Newsletter.

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Pinterest logo
Uber logo
Logo Square
Logo Adobe
Meta logo
Logo PayPal

Azienda

Impostazioni sulla privacy

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Uber logo
Logo Square
Logo Adobe
Meta logo

Azienda

Impostazioni sulla privacy

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.

Uber logo
Logo Adobe
Meta logo

Reach

Grow

Manage

Automate

Risorse

Azienda

Newsletter

Rimani aggiornato con Bird attraverso aggiornamenti settimanali nella tua inbox.

Inviando, accetti che Bird possa contattarti riguardo ai nostri prodotti e servizi.

Puoi annullare l'iscrizione in qualsiasi momento. Consulta la Informativa sulla Privacy di Bird per i dettagli sul trattamento dei dati.