Erreichen

Grow

Manage

Automate

Erreichen

Grow

Manage

Automate

Entwicklung eines Bulk-Asynchronen Vogel-Empfänger-Validierungstools

E-Mail

1 min read

Entwicklung eines Bulk-Asynchronen Vogel-Empfänger-Validierungstools

E-Mail

1 min read

Entwicklung eines Bulk-Asynchronen Vogel-Empfänger-Validierungstools

Für jemanden, der nach einem einfachen, schnellen Programm sucht, das eine CSV-Datei einliest, die Empfänger-Validierungs-API aufruft und eine CSV-Datei ausgibt, ist dieses Programm genau das Richtige für Sie.

Eine der Fragen, die wir gelegentlich erhalten, ist, wie kann ich E-Mail-Listen mit Empfängerüberprüfung in großen Mengen validieren? Es gibt hier zwei Optionen, eine besteht darin, eine Datei über die SparkPost UI zur Überprüfung hochzuladen, und die andere besteht darin, einzelne Anfragen pro E-Mail an die API zu richten (da die API eine Einzel-E-Mail-Validierung ist).

Die erste Option funktioniert großartig, hat jedoch eine Begrenzung von 20 MB (ungefähr 500.000 Adressen). Was ist, wenn jemand eine E-Mail-Liste mit Millionen von Adressen hat? Das könnte bedeuten, dass diese in 1.000 CSV-Datei-Uploads aufgeteilt werden muss.

Da das Hochladen von Tausenden von CSV-Dateien etwas weit hergeholt klingt, habe ich diesen Anwendungsfall genommen und begann zu überlegen, wie schnell ich die API zum Laufen bringen könnte. In diesem Blogbeitrag werde ich erklären, was ich versucht habe und wie ich schließlich zu einem Programm kam, das rund 100.000 Überprüfungen in 55 Sekunden durchführen konnte (während ich in der UI etwa 100.000 Überprüfungen in 1 Minute und 10 Sekunden hinbekam). Und obwohl es immer noch etwa 100 Stunden dauern würde, um etwa 654 Millionen Überprüfungen abzuschließen, kann dieses Skript im Hintergrund laufen und erhebliche Zeit sparen.

Die endgültige Version dieses Programms kann hier gefunden werden.

Mein erster Fehler: die Verwendung von Python

Python ist eine meiner Lieblingsprogrammiersprachen. Es glänzt in vielen Bereichen und ist unglaublich unkompliziert. Allerdings gibt es einen Bereich, in dem es nicht glänzt: parallele Prozesse. Während Python die Fähigkeit hat, asynchrone Funktionen auszuführen, verfügt es über das, was als The Python Global Interpreter Lock oder GIL bekannt ist.

„The Python Global Interpreter Lock oder GIL ist, einfach ausgedrückt, ein Mutex (oder eine Sperre), die es nur einem Thread ermöglicht, die Kontrolle über den Python-Interpreter zu halten.

Das bedeutet, dass sich zu jedem Zeitpunkt nur ein Thread im Ausführungszustand befinden kann. Die Auswirkungen des GIL sind für Entwickler, die Single-Threaded-Programme ausführen, nicht sichtbar, können jedoch in CPU-intensiven und Multi-Threaded-Code ein Performance-Engpass sein.

Da das GIL es nur einem Thread erlaubt, gleichzeitig zu laufen, selbst in einer Multi-Threaded-Architektur mit mehr als einem CPU-Kern, hat das GIL einen Ruf als „berüchtigtes“ Merkmal von Python erlangt.“ (https://realpython.com/python-gil/)”

Am Anfang war ich mir des GIL nicht bewusst, also begann ich in Python zu programmieren. Am Ende, obwohl mein Programm asynchron war, wurde es blockiert, und egal wie viele Threads ich hinzufügte, ich erreichte immer noch nur etwa 12-15 Iterationen pro Sekunde.

Der Hauptteil der asynchronen Funktion in Python ist unten zu sehen:

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)

Also habe ich die Verwendung von Python aufgegeben und bin zurück ans Reißbrett gegangen…

Ich entschied mich für die Nutzung von NodeJS aufgrund seiner Fähigkeit, nicht blockierende I/O-Operationen extrem gut auszuführen. Ich bin auch ziemlich vertraut mit der Programmierung in NodeJS.

Unter Nutzung der asynchronen Aspekte von NodeJS funktionierte dies letztendlich gut. Für mehr Details über asynchrone Programmierung in NodeJS, siehe https://blog.risingstack.com/node-hero-async-programming-in-node-js/

Mein zweiter Fehler: der Versuch, die Datei in den Speicher zu lesen

Meine anfängliche Idee war wie folgt:

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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, laden Sie die E-Mails in ein Array und prüfen Sie, ob sie im richtigen Format sind. Drittens, asynchron die Empfängerüberprüfungs-API aufrufen. Viertens, auf die Ergebnisse warten und sie in eine Variable laden. Und schließlich, diese Variable in eine CSV-Datei ausgeben.

Dies funktionierte sehr gut für kleinere Dateien. Das Problem trat auf, als ich versuchte, 100.000 E-Mails durchzuführen. Das Programm blieb bei etwa 12.000 Validierungen hängen. Mit Hilfe eines unserer Frontend-Entwickler sah ich, dass das Problem darin bestand, alle Ergebnisse in eine Variable zu laden (und daher schnell den Speicher zu erschöpfen). Wenn Sie die erste Version dieses Programms sehen möchten, habe ich sie hier verlinkt: Version 1 (NICHT EMPFOHLEN).


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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, zählen Sie die Anzahl der E-Mails in der Datei zu Berichtszwecken. Drittens, während jede Zeile asynchron gelesen wird, die Empfängerüberprüfungs-API aufrufen und die Ergebnisse in eine CSV-Datei ausgeben.

So rufe ich für jede gelesene Zeile die API auf und schreibe die Ergebnisse asynchron aus, um diese Daten nicht im Langzeitspeicher zu behalten. Ich habe auch die Syntaxüberprüfung der E-Mail entfernt, nachdem ich mit dem Empfängerüberprüfungsteam gesprochen habe, da sie mir mitteilten, dass die Empfängerüberprüfung bereits über Prüfungen verfügt, um zu überprüfen, ob eine E-Mail gültig ist oder nicht.

Meine anfängliche Idee war wie folgt:

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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, laden Sie die E-Mails in ein Array und prüfen Sie, ob sie im richtigen Format sind. Drittens, asynchron die Empfängerüberprüfungs-API aufrufen. Viertens, auf die Ergebnisse warten und sie in eine Variable laden. Und schließlich, diese Variable in eine CSV-Datei ausgeben.

Dies funktionierte sehr gut für kleinere Dateien. Das Problem trat auf, als ich versuchte, 100.000 E-Mails durchzuführen. Das Programm blieb bei etwa 12.000 Validierungen hängen. Mit Hilfe eines unserer Frontend-Entwickler sah ich, dass das Problem darin bestand, alle Ergebnisse in eine Variable zu laden (und daher schnell den Speicher zu erschöpfen). Wenn Sie die erste Version dieses Programms sehen möchten, habe ich sie hier verlinkt: Version 1 (NICHT EMPFOHLEN).


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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, zählen Sie die Anzahl der E-Mails in der Datei zu Berichtszwecken. Drittens, während jede Zeile asynchron gelesen wird, die Empfängerüberprüfungs-API aufrufen und die Ergebnisse in eine CSV-Datei ausgeben.

So rufe ich für jede gelesene Zeile die API auf und schreibe die Ergebnisse asynchron aus, um diese Daten nicht im Langzeitspeicher zu behalten. Ich habe auch die Syntaxüberprüfung der E-Mail entfernt, nachdem ich mit dem Empfängerüberprüfungsteam gesprochen habe, da sie mir mitteilten, dass die Empfängerüberprüfung bereits über Prüfungen verfügt, um zu überprüfen, ob eine E-Mail gültig ist oder nicht.

Meine anfängliche Idee war wie folgt:

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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, laden Sie die E-Mails in ein Array und prüfen Sie, ob sie im richtigen Format sind. Drittens, asynchron die Empfängerüberprüfungs-API aufrufen. Viertens, auf die Ergebnisse warten und sie in eine Variable laden. Und schließlich, diese Variable in eine CSV-Datei ausgeben.

Dies funktionierte sehr gut für kleinere Dateien. Das Problem trat auf, als ich versuchte, 100.000 E-Mails durchzuführen. Das Programm blieb bei etwa 12.000 Validierungen hängen. Mit Hilfe eines unserer Frontend-Entwickler sah ich, dass das Problem darin bestand, alle Ergebnisse in eine Variable zu laden (und daher schnell den Speicher zu erschöpfen). Wenn Sie die erste Version dieses Programms sehen möchten, habe ich sie hier verlinkt: Version 1 (NICHT EMPFOHLEN).


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.


Zuerst eine CSV-Liste von E-Mails einlesen. Zweitens, zählen Sie die Anzahl der E-Mails in der Datei zu Berichtszwecken. Drittens, während jede Zeile asynchron gelesen wird, die Empfängerüberprüfungs-API aufrufen und die Ergebnisse in eine CSV-Datei ausgeben.

So rufe ich für jede gelesene Zeile die API auf und schreibe die Ergebnisse asynchron aus, um diese Daten nicht im Langzeitspeicher zu behalten. Ich habe auch die Syntaxüberprüfung der E-Mail entfernt, nachdem ich mit dem Empfängerüberprüfungsteam gesprochen habe, da sie mir mitteilten, dass die Empfängerüberprüfung bereits über Prüfungen verfügt, um zu überprüfen, ob eine E-Mail gültig ist oder nicht.

Aufschlüsselung des finalen Codes

Nachdem die Optionen im Terminal gelesen und validiert wurden, führe ich den folgenden Code aus. Zuerst lese ich die CSV-Datei der E-Mails ein und zähle jede Zeile. Diese Funktion hat zwei Zwecke, 1) ermöglicht sie es mir, genau über den Fortschritt der Datei zu berichten [wie wir später sehen werden], und 2) erlaubt es mir, einen Timer zu stoppen, wenn die Anzahl der E-Mails in der Datei den abgeschlossenen Validierungen entspricht. Ich habe einen Timer hinzugefügt, damit ich Benchmarks durchführen und sicherstellen kann, dass ich gute Ergebnisse erziele.

let count = 0; // Zeilenzähler require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) // Liest die Eingabedatei und erhöht den Zähler für jede Zeile .on("close", function () { // Am Ende der Eingabedatei, nachdem alle Zeilen gezählt wurden, die Empfänger-Validierungsfunktion ausführen validateRecipients.validateRecipients(count, myArgs); });


 Dann rufe ich die validateRecipients-Funktion auf. Beachten Sie, dass diese Funktion asynchron ist. Nachdem geprüft wurde, dass die Eingabe- und Ausgabedateien CSV-Dateien sind, schreibe ich eine Kopfzeile und starte einen Programm-Timer unter Verwendung der JSDOM-Bibliothek.

async function validateRecipients(email_count, myArgs) { if ( // Wenn sowohl die Eingabedatei als auch die Ausgabedatei im .csv-Format sind extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; // Zähler für jeden API-Aufruf email_count++; // Zeilenzähler gibt #Zeilen - 1 zurück, dies wird gemacht, um die Anzahl der Zeilen zu korrigieren // Starten Sie einen Timer const { window } = new JSDOM(); const start = window.performance.now(); const output = fs.createWriteStream(myArgs[3]); // Ausgabedatei output.write( "Email,Valid,Result,Reason,Is_Role,Is_Disposable,Is_Free,Delivery_Confidence\n" ); // Schreibe die Überschriften in die Ausgabedatei


Das folgende Skript ist wirklich der Hauptteil des Programms, also werde ich es aufteilen und erklären, was passiert. Für jede Zeile in der Eingabedatei:

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, }, }) // Für jede Zeile, die aus der Eingabedatei gelesen wird, rufe die SparkPost-Empfänger-Validierungs-API auf


Dann, bei der Antwort

  • Die E-Mail zum JSON hinzufügen (um die E-Mail in der CSV ausdrucken zu können)

  • Überprüfen, ob Reason null ist, und wenn ja, einen leeren Wert einfügen (dies ist, damit das CSV-Format konsistent ist, da in einigen Fällen Reason in der Antwort angegeben wird)

  • Die Optionen und Schlüssel für das json2csv-Modul festlegen.

  • Das JSON in CSV umwandeln und ausgeben (unter Verwendung von json2csv)

  • Fortschritt im Terminal ausgeben

  • Schließlich, wenn die Anzahl der E-Mails in der Datei den abgeschlossenen Validierungen entspricht, stoppen Sie den Timer und geben die Ergebnisse aus


.then(function (response) { response.data.results.email = String(email); // Fügt die E-Mail als Wert-Schlüssel-Paar zur Antwort-JSON hinzu, um für die Ausgabe verwendet zu werden response.data.results.reason ? null : (response.data.results.reason = ""); // Wenn Reason null ist, auf leer setzen, damit die CSV einheitlich ist // Nutzt json-2-csv, um das JSON in CSV-Format umzuwandeln und auszugeben let options = { prependHeader: false, // Deaktiviert JSON-Werte, die als Kopfzeilen für jede Zeile hinzugefügt werden keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], // Legt die Reihenfolge der Schlüssel fest }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; // Erhöhen des API-Zählers process.stdout.write(`Done with ${completed} / ${email_count}\r`); // Ausgabenstatus von Abgeschlossen / Gesamt auf die Konsole ohne neue Zeilen anzuzeigen // Wenn alle E-Mails validiert wurden if (completed == email_count) { const stop = window.performance.now(); // Timer stoppen console.log( `Alle E-Mails erfolgreich validiert in ${ (stop - start) / 1000 } Sekunden` ); } })

 

Ein letztes Problem, das ich festgestellt habe, war, dass dies auf Mac großartig funktionierte, aber ich stieß auf den folgenden Fehler bei der Verwendung von Windows nach etwa 10.000 Validierungen:

Error: connect ENOBUFS XX.XX.XXX.XXX:443 – Local (undefined:undefined) mit E-Mail XXXXXXX@XXXXXXXXXX.XXX

Nach weiterer Recherche scheint es sich um ein Problem mit dem NodeJS HTTP-Client-Verbindungspool zu handeln, der Verbindungen nicht wiederverwendet. Ich fand diesen Stackoverflow-Artikel zu diesem Thema und nach weiterem Suchen fand ich eine gute Standardkonfiguration für die Axios-Bibliothek, die dieses Problem löste. Ich bin mir immer noch nicht sicher, warum dieses Problem nur auf Windows und nicht auf Mac auftritt.

Nächste Schritte

Für jemanden, der ein einfaches schnelles Programm sucht, das eine CSV einliest, die Empfänger-Validierungs-API aufruft und eine CSV ausgibt, ist dieses Programm genau das Richtige.

Einige Ergänzungen für dieses Programm wären die folgenden:

  • Ein Frontend oder eine benutzerfreundlichere UI für die Nutzung erstellen

  • Bessere Fehler- und Wiederholungsbehandlung, denn wenn die API aus irgendeinem Grund einen Fehler auslöst, wiederholt das Programm derzeit den Aufruf nicht


Ich wäre auch daran interessiert zu sehen, ob schnellere Ergebnisse mit einer anderen Sprache wie Golang oder Erlang/Elixir erzielt werden könnten.

Bitte zögern Sie nicht, mir Feedback oder Vorschläge zur Erweiterung dieses Projekts zu geben.

Abonnieren Sie unseren Newsletter.

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Abonnieren Sie unseren Newsletter.

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Abonnieren Sie unseren Newsletter.

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Pinterest-Logo
Uber-Logo
Square-Logo
Adobe-Logo
Meta-Logo
PayPal-Logo

Unternehmen

Datenschutzeinstellungen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Uber-Logo
Square-Logo
Adobe-Logo
Meta-Logo

Unternehmen

Datenschutzeinstellungen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.

Uber-Logo
Adobe-Logo
Meta-Logo

Erreichen

Grow

Manage

Automate

Ressourcen

Unternehmen

Newsletter

Bleiben Sie mit Bird auf dem Laufenden durch wöchentliche Updates in Ihrem Posteingang.

Durch die Übermittlung stimmen Sie zu, dass Bird Sie bezüglich unserer Produkte und Dienstleistungen kontaktieren darf.

Sie können sich jederzeit abmelden. Weitere Informationen zur Datenverarbeitung finden Sie in Birds Datenschutzerklärung.