Zasięg

Grow

Manage

Automate

Zasięg

Grow

Manage

Automate

Budowanie narzędzia do walidacji odbiorców ptaków w trybie asynchronicznym masowo

Email

1 min read

Budowanie narzędzia do walidacji odbiorców ptaków w trybie asynchronicznym masowo

Email

1 min read

Budowanie narzędzia do walidacji odbiorców ptaków w trybie asynchronicznym masowo

Dla kogoś, kto szuka prostego, szybkiego programu, który przyjmuje plik CSV, wywołuje API weryfikacji odbiorcy i zwraca plik CSV, ten program jest dla Ciebie.

Jedno z pytań, które czasami otrzymujemy, to jak mogę masowo weryfikować listy e-mailowe za pomocą recipient validation? Istnieją tutaj dwie opcje: jedna polega na przesłaniu pliku przez interfejs SparkPost do weryfikacji, a druga na wykonywaniu indywidualnych zapytań dla każdego e-maila do API (ponieważ API umożliwia weryfikację pojedynczych e-maili).

Pierwsza opcja działa świetnie, ale ma ograniczenie do 20Mb (około 500 000 adresów). Co jeśli ktoś ma listę e-mailową zawierającą miliony adresów? Może to oznaczać podział na tysiące plików CSV do przesyłania.

Jako że przesyłanie tysięcy plików CSV wydaje się nieco nieprawdopodobne, wziąłem ten przypadek użycia i zacząłem się zastanawiać, jak szybko mogę uruchomić API. W tym wpisie na blogu wyjaśnię, co próbowałem i jak w końcu doszedłem do programu, który mógł osiągnąć około 100 000 weryfikacji w 55 sekund (podczas gdy w interfejsie uzyskałem około 100 000 weryfikacji w 1 minutę i 10 sekund). I chociaż nadal zajęłoby to około 100 godzin, aby zakończyć około 654 milionów weryfikacji, ten skrypt może działać w tle, oszczędzając znaczną ilość czasu.

Ostateczna wersja tego programu jest dostępna tutaj.

My first mistake: używanie Python

Python jest jednym z moich ulubionych języków programowania. Świetnie radzi sobie w wielu dziedzinach i jest niezwykle prosty. Jednak jednym z obszarów, w którym nie jest najlepszy, są procesy równoległe. Choć Python ma zdolność do uruchamiania funkcji asynchronicznych, posiada coś, co jest znane jako Python Global Interpreter Lock lub GIL.

„The Python Global Interpreter Lock lub GIL, mówiąc prościej, jest to mutex (lub blokada), która pozwala tylko jednemu wątkowi na kontrolowanie interpretera Pythona.

Oznacza to, że tylko jeden wątek może być w stanie wykonania w danym momencie. Wpływ GIL nie jest widoczny dla deweloperów, którzy uruchamiają programy jednordzeniowe, ale może być wąskim gardłem wydajnościowym w kodzie obciążonym CPU i wielowątkowym.

Ponieważ GIL pozwala tylko jednemu wątkowi na wykonanie w danym czasie, nawet w architekturze wielowątkowej z więcej niż jednym rdzeniem CPU, GIL zyskał reputację jako „niesławna” cecha Pythona.” (https://realpython.com/python-gil/)”

Na początku nie byłem świadomy istnienia GIL, więc zacząłem programować w Pythonie. Ostatecznie, chociaż mój program był asynchroniczny, blokował się, i niezależnie od tego, ile wątków dodałem, nadal uzyskiwałem tylko około 12-15 iteracji na sekundę.

Główna część funkcji asynchronicznej w Pythonie wygląda następująco:

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)

Więc porzuciłem używanie Pythona i wróciłem do rysowania od nowa...

Zdecydowałem się na NodeJS ze względu na jego zdolność do wykonywania operacji i/o bez blokowania. Również jestem całkiem dobrze zaznajomiony z programowaniem w NodeJS.

Wykorzystując aspekty asynchroniczne NodeJS, to ostatecznie zadziałało dobrze. Więcej szczegółów na temat programowania asynchronicznego w NodeJS można znaleźć na stronie https://blog.risingstack.com/node-hero-async-programming-in-node-js/

Mój drugi błąd: próba odczytania pliku do pamięci

Mój początkowy pomysł był następujący:

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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, załaduj e-maile do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API weryfikacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. I na koniec, wyprowadź tę zmienną do pliku CSV.

Działało to bardzo dobrze dla mniejszych plików. Problem pojawił się, gdy próbowałem przetworzyć 100 000 e-maili. Program zatrzymał się na około 12 000 weryfikacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problemem było ładowanie wszystkich wyników do zmiennej (a zatem szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą iterację tego programu, zamieściłem ją tutaj: 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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, policz liczbę e-maili w pliku do celów raportowania. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API weryfikacji odbiorcy i wyprowadź wyniki do pliku CSV.

Dlatego, dla każdej czytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać żadnej z tych danych w pamięci długoterminowej. Usunąłem również sprawdzanie składni e-maili po rozmowie z zespołem ds. weryfikacji odbiorców, ponieważ poinformowali mnie, że weryfikacja odbiorców już zawiera wbudowane sprawdzenia, czy e-mail jest prawidłowy, czy nie.

Mój początkowy pomysł był następujący:

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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, załaduj e-maile do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API weryfikacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. I na koniec, wyprowadź tę zmienną do pliku CSV.

Działało to bardzo dobrze dla mniejszych plików. Problem pojawił się, gdy próbowałem przetworzyć 100 000 e-maili. Program zatrzymał się na około 12 000 weryfikacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problemem było ładowanie wszystkich wyników do zmiennej (a zatem szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą iterację tego programu, zamieściłem ją tutaj: 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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, policz liczbę e-maili w pliku do celów raportowania. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API weryfikacji odbiorcy i wyprowadź wyniki do pliku CSV.

Dlatego, dla każdej czytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać żadnej z tych danych w pamięci długoterminowej. Usunąłem również sprawdzanie składni e-maili po rozmowie z zespołem ds. weryfikacji odbiorców, ponieważ poinformowali mnie, że weryfikacja odbiorców już zawiera wbudowane sprawdzenia, czy e-mail jest prawidłowy, czy nie.

Mój początkowy pomysł był następujący:

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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, załaduj e-maile do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API weryfikacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. I na koniec, wyprowadź tę zmienną do pliku CSV.

Działało to bardzo dobrze dla mniejszych plików. Problem pojawił się, gdy próbowałem przetworzyć 100 000 e-maili. Program zatrzymał się na około 12 000 weryfikacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problemem było ładowanie wszystkich wyników do zmiennej (a zatem szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą iterację tego programu, zamieściłem ją tutaj: 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.


Po pierwsze, wczytaj listę e-maili z pliku CSV. Po drugie, policz liczbę e-maili w pliku do celów raportowania. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API weryfikacji odbiorcy i wyprowadź wyniki do pliku CSV.

Dlatego, dla każdej czytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać żadnej z tych danych w pamięci długoterminowej. Usunąłem również sprawdzanie składni e-maili po rozmowie z zespołem ds. weryfikacji odbiorców, ponieważ poinformowali mnie, że weryfikacja odbiorców już zawiera wbudowane sprawdzenia, czy e-mail jest prawidłowy, czy nie.

Rozbijanie końcowego kodu

Po przeczytaniu i zweryfikowaniu argumentów terminalu, uruchamiam następujący kod. Najpierw wczytuję plik CSV z e-mailami i zliczam każdą linię. Istnieją dwa cele tej funkcji: 1) pozwala mi dokładnie raportować postęp pracy z plikiem [co zobaczymy później], oraz 2) pozwala mi zatrzymać stoper, gdy liczba e-maili w pliku równa się ukończonym weryfikacjom. Dodałem stoper, aby móc przeprowadzać testy porównawcze i upewnić się, że uzyskuję dobre wyniki.

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


 Następnie wywołuję funkcję validateRecipients. Należy zauważyć, że ta funkcja jest asynchroniczna. Po zweryfikowaniu, że infile i outfile są w formacie CSV, zapisuję wiersz nagłówkowy i uruchamiam stoper programu za pomocą biblioteki 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 is done to correct 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


Następujący skrypt jest naprawdę główną częścią programu, więc podzielę go i wyjaśnię, co się dzieje. Dla każdej linii w pliku wejściowym:

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


Następnie, na odpowiedzi

  • Dodaj e-mail do JSON (aby móc wydrukować e-mail w CSV)

  • Sprawdź, czy powód jest null, a jeśli tak, uzupełnij pustą wartość (jest to konieczne, aby format CSV był spójny, ponieważ w niektórych przypadkach powód jest podany w odpowiedzi)

  • Ustaw opcje i klucze dla modułu json2csv.

  • Konwertuj JSON na CSV i wyprowadź (wykorzystując json2csv)

  • Zapisz postęp w terminalu

  • Wreszcie, jeśli liczba e-maili w pliku = ukończone weryfikacje, zatrzymaj stoper i wydrukuj wyniki


.then(function (response) { response.data.results.email = String(email); //Adds the email as a value/key pair to the response JSON to be used 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` ); } })

 

Ostatnim problemem, który znalazłem, było to, że działało to świetnie na Mac, ale napotkałem następujący błąd podczas korzystania z Windows po około 10 000 weryfikacji:

Błąd: connect ENOBUFS XX.XX.XXX.XXX:443 – Lokalny (undefined:undefined) z e-mailem XXXXXXX@XXXXXXXXXX.XXX

Po dokładniejszych badaniach wydaje się, że jest to problem z puli połączeń klienta HTTP NodeJS nie ponownie używającego połączeń. Znalazłem ten artykuł na Stackoverflow na temat tego problemu, a po dalszych poszukiwaniach znalazłem dobrą konfigurację domyślną dla biblioteki axios, która rozwiązała ten problem. Nadal nie jestem pewien, dlaczego ten problem występuje tylko na Windows, a nie na Mac.

Następne Kroki

Dla kogoś, kto szuka prostego i szybkiego programu, który przyjmuje plik CSV, wywołuje API weryfikacji odbiorców i generuje plik CSV, ten program jest dla Ciebie.

Kilka dodatków do tego programu to:

  • Zbuduj interfejs front-end lub łatwiejszy interfejs użytkownika do użycia

  • Lepsze zarządzanie błędami i ponowne próby, ponieważ jeśli z jakiegoś powodu API zgłasza błąd, program obecnie nie ponawia wywołania


Byłbym również ciekaw, czy można osiągnąć szybsze wyniki za pomocą innego języka, takiego jak Golang czy Erlang/Elixir.

Proszę, nie krępuj się, aby przekazać mi swoje opinie lub sugestie dotyczące rozszerzenia tego projektu.

Dołącz do naszego Newslettera.

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Dołącz do naszego Newslettera.

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Dołącz do naszego Newslettera.

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

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

Company

Ustawienia prywatności

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Logo Uber
Square logo
Logo Adobe
Logo Meta

Company

Ustawienia prywatności

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.

Logo Uber
Logo Adobe
Logo Meta

Zasięg

Grow

Manage

Automate

Zasoby

Company

Biuletyn

Bądź na bieżąco z Bird dzięki cotygodniowym aktualizacjom do Twojej skrzynki odbiorczej.

Przesyłając, zgadzasz się, że Bird może kontaktować się z Tobą w sprawie naszych produktów i usług.

Możesz zrezygnować z subskrypcji w dowolnym momencie. Zobacz Privacy Statement firmy Bird, aby uzyskać szczegóły dotyczące przetwarzania danych.