Tworzenie narzędzia do walidacji odbiorców asynchronicznych ptaków z dużą ilością danych
Zachary Samuels
26 maj 2022
1 min read

Najważniejsze informacje
Autor zbudował narzędzie do walidacji masowych odbiorców, aby efektywnie weryfikować miliony adresów e-mail, korzystając z API walidacji odbiorców Bird.
Node.js okazał się szybszy i bardziej skalowalny niż Python dzięki swojemu nieblokującemu I/O i braku ograniczeń GIL.
Narzędzie odczytuje pliki CSV asynchronicznie, wywołuje API walidacji dla każdego e-maila i zapisuje wyniki do nowego pliku CSV w czasie rzeczywistym.
To podejście unika wąskich gardeł w pamięci i poprawia przepustowość do około 100 000 walidacji w mniej niż minutę.
Przyszłe ulepszenia mogą obejmować lepsze zarządzanie ponownymi próbami, przyjazny interfejs użytkownika lub migrację do środowisk bezserwerowych w celu zwiększenia skalowalności.
Podsumowanie pytań i odpowiedzi
Jaki jest cel narzędzia do walidacji odbiorców w trybie asynchronicznym zbiorczym?
Waliduje duże ilości adresów e-mail, integrując się bezpośrednio z API walidacji odbiorców Bird, szybko zwracając zweryfikowane wyniki bez ręcznych przesyłek.
Dlaczego Python był początkowo używany, a później zastąpiony przez Node.js?
Globalny blokada interpreterów Pythona (GIL) ograniczał współbieżność, podczas gdy Node.js pozwalał na prawdziwe asynchroniczne wykonanie, co skutkowało znacznie szybszymi równoległymi wywołaniami API.
Jak narzędzie radzi sobie z dużymi plikami, nie wyczerpując pamięci?
Zamiast ładowania wszystkich danych jednocześnie, skrypt przetwarza każdą linię CSV indywidualnie — wysyłając prośbę o walidację i natychmiast zapisując wyniki do nowego pliku CSV.
Jakie problemy narzędzie rozwiązuje dla programistów?
Umożliwia walidację listy e-mailowej na dużą skalę, pokonując limit 20 MB narzędzia walidacyjnego opartego na interfejsie użytkownika SparkPost i eliminując potrzebę ręcznego przesyłania wielu plików.
Jak szybka jest ostateczna wersja programu?
Około 100 000 walidacji zakończonych w 55 sekund, w porównaniu do ponad minuty przy użyciu wersji interfejsu użytkownika.
Jakie problemy napotkano na systemach Windows?
Pooling połączeń klienta HTTP Node.js spowodował błędy „ENOBUFS” po wielu jednoczesnych żądaniach, które zostały naprawione przez skonfigurowanie ponownego użycia połączeń axios.
Jakie przyszłe usprawnienia są sugerowane?
Dodawanie obsługi błędów i ponownych prób, tworzenie interfejsu front-end lub wdrażanie narzędzia jako funkcji Azure bezserwerowej w celu lepszej skalowalności i odporności.
Dla kogoś, kto szuka prostego i szybkiego programu, który przyjmuje plik csv, wywołuje API do walidacji odbiorcy i generuje plik CSV, ten program jest dla Ciebie.
Podczas tworzenia aplikacji do e-maila, programiści często muszą integrować wiele usług i interfejsów API. Zrozumienie fundamentów API e-mailowego w infrastrukturze chmurowej stanowi podstawę do budowania solidnych narzędzi, takich jak system walidacji zbiorowej, który stworzymy w tym przewodniku.
Jednym z pytań, które czasami otrzymujemy, jest to, jak mogę masowo walidować listy e-mailowe z walidacją odbiorców? Istnieją dwie opcje: jedna to przesłanie pliku przez interfejs SparkPost do walidacji, a druga to dokonywanie pojedynczych wywołań dla każdego e-maila do API (ponieważ API obsługuje walidację pojedynczego e-maila).
Pierwsza opcja działa świetnie, ale ma limit 20 MB (około 500 000 adresów). Co jeśli ktoś ma listę e-mailową zawierającą miliony adresów? Może to oznaczać podzielenie tego na tysiące przesyłek plików CSV.
Skoro 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 poście na blogu wyjaśnię, co próbowałem i jak ostatecznie doszedłem do programu, który mógł uzyskać około 100 000 walidacji w 55 sekund (Podczas gdy w interfejsie uzyskałem około 100 000 walidacji w 1 minutę 10 sekund).
Podejście | Testowane walidacje | Czas realizacji | Przybliżona przepustowość |
|---|---|---|---|
Zbiorowe narzędzie async Node.js | 100 000 | 55 sekund | ~1 818 walidacji/sec |
Przesyłanie przez interfejs SparkPost | 100 000 | 1 min 10 sek | ~1 428 walidacji/sec |
I chociaż zajęłoby to około 100 godzin, aby to zrobić przy około 654 milionach walidacji, ten skrypt może działać w tle, oszczędzając znaczną ilość czasu.
Ostateczna wersja tego programu może być znaleziona tutaj.
Podczas tworzenia aplikacji do e-maila, programiści często muszą integrować wiele usług i interfejsów API. Zrozumienie fundamentów API e-mailowego w infrastrukturze chmurowej stanowi podstawę do budowania solidnych narzędzi, takich jak system walidacji zbiorowej, który stworzymy w tym przewodniku.
Jednym z pytań, które czasami otrzymujemy, jest to, jak mogę masowo walidować listy e-mailowe z walidacją odbiorców? Istnieją dwie opcje: jedna to przesłanie pliku przez interfejs SparkPost do walidacji, a druga to dokonywanie pojedynczych wywołań dla każdego e-maila do API (ponieważ API obsługuje walidację pojedynczego e-maila).
Pierwsza opcja działa świetnie, ale ma limit 20 MB (około 500 000 adresów). Co jeśli ktoś ma listę e-mailową zawierającą miliony adresów? Może to oznaczać podzielenie tego na tysiące przesyłek plików CSV.
Skoro 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 poście na blogu wyjaśnię, co próbowałem i jak ostatecznie doszedłem do programu, który mógł uzyskać około 100 000 walidacji w 55 sekund (Podczas gdy w interfejsie uzyskałem około 100 000 walidacji w 1 minutę 10 sekund).
Podejście | Testowane walidacje | Czas realizacji | Przybliżona przepustowość |
|---|---|---|---|
Zbiorowe narzędzie async Node.js | 100 000 | 55 sekund | ~1 818 walidacji/sec |
Przesyłanie przez interfejs SparkPost | 100 000 | 1 min 10 sek | ~1 428 walidacji/sec |
I chociaż zajęłoby to około 100 godzin, aby to zrobić przy około 654 milionach walidacji, ten skrypt może działać w tle, oszczędzając znaczną ilość czasu.
Ostateczna wersja tego programu może być znaleziona tutaj.
Podczas tworzenia aplikacji do e-maila, programiści często muszą integrować wiele usług i interfejsów API. Zrozumienie fundamentów API e-mailowego w infrastrukturze chmurowej stanowi podstawę do budowania solidnych narzędzi, takich jak system walidacji zbiorowej, który stworzymy w tym przewodniku.
Jednym z pytań, które czasami otrzymujemy, jest to, jak mogę masowo walidować listy e-mailowe z walidacją odbiorców? Istnieją dwie opcje: jedna to przesłanie pliku przez interfejs SparkPost do walidacji, a druga to dokonywanie pojedynczych wywołań dla każdego e-maila do API (ponieważ API obsługuje walidację pojedynczego e-maila).
Pierwsza opcja działa świetnie, ale ma limit 20 MB (około 500 000 adresów). Co jeśli ktoś ma listę e-mailową zawierającą miliony adresów? Może to oznaczać podzielenie tego na tysiące przesyłek plików CSV.
Skoro 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 poście na blogu wyjaśnię, co próbowałem i jak ostatecznie doszedłem do programu, który mógł uzyskać około 100 000 walidacji w 55 sekund (Podczas gdy w interfejsie uzyskałem około 100 000 walidacji w 1 minutę 10 sekund).
Podejście | Testowane walidacje | Czas realizacji | Przybliżona przepustowość |
|---|---|---|---|
Zbiorowe narzędzie async Node.js | 100 000 | 55 sekund | ~1 818 walidacji/sec |
Przesyłanie przez interfejs SparkPost | 100 000 | 1 min 10 sek | ~1 428 walidacji/sec |
I chociaż zajęłoby to około 100 godzin, aby to zrobić przy około 654 milionach walidacji, ten skrypt może działać w tle, oszczędzając znaczną ilość czasu.
Ostateczna wersja tego programu może być znaleziona tutaj.
Mój pierwszy błąd: używanie Pythona
Python jest jednym z moich ulubionych języków programowania. Doskonale sprawdza się w wielu dziedzinach i jest niezwykle prosty. Jednak jedna dziedzina, w której nie sprawdza się najlepiej, to procesy współbieżne. Chociaż Python ma możliwość uruchamiania funkcji asynchronicznych, ma to, co nazywa się Globalnym Zablokowaniem Interpretatora Pythona, czyli GIL.
„Globalne Zablokowanie Interpretatora Pythona, czyli GIL, w prostych słowach, to mutex (lub blokada), który pozwala tylko jednemu wątkowi kontrolować interpreter Pythona.
To oznacza, że tylko jeden wątek może być w stanie wykonania w danym momencie. Wpływ GIL nie jest widoczny dla programistów, którzy wykonują programy jednowątkowe, ale może być wąskim gardłem wydajności w kodzie ograniczonym przez CPU i wielowątkowym.
Ponieważ Globalne Zablokowanie Interpretatora (GIL) pozwala na wykonanie tylko jednego wątku w danym czasie, nawet na systemach wielordzeniowych, zyskało reputację „niesławnej” cechy Pythona (patrz artykuł Real Python o GIL).
Początkowo nie byłem świadomy GIL, więc zacząłem programować w Pythonie. Na końcu, mimo że mój program był asynchroniczny, blokował się, a niezależnie od tego, ile wątków dodałem, osiągałem tylko około 12-15 iteracji na sekundę.
Główną część funkcji asynchronicznej w Pythonie można zobaczyć poniżej:
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 korzystanie z Pythona i wróciłem do rysowania…
Postanowiłem skorzystać z NodeJS ze względu na jego zdolność do wykonywania nieblokujących operacji I/O niezwykle dobrze. Inną doskonałą opcją do obsługi asynchronicznego przetwarzania API jest stworzenie konsumentów webhooków bezserwerowych z Azure Functions, które mogą efektywnie obsługiwać zmienne obciążenia. Również dobrze znam programowanie w NodeJS.
Wykorzystując asynchroniczne aspekty Node.js, to podejście działało dobrze. Aby uzyskać więcej szczegółów na temat programowania asynchronicznego w Node.js, zobacz przewodnik RisingStack na temat programowania asynchronicznego w Node.js.
Python jest jednym z moich ulubionych języków programowania. Doskonale sprawdza się w wielu dziedzinach i jest niezwykle prosty. Jednak jedna dziedzina, w której nie sprawdza się najlepiej, to procesy współbieżne. Chociaż Python ma możliwość uruchamiania funkcji asynchronicznych, ma to, co nazywa się Globalnym Zablokowaniem Interpretatora Pythona, czyli GIL.
„Globalne Zablokowanie Interpretatora Pythona, czyli GIL, w prostych słowach, to mutex (lub blokada), który pozwala tylko jednemu wątkowi kontrolować interpreter Pythona.
To oznacza, że tylko jeden wątek może być w stanie wykonania w danym momencie. Wpływ GIL nie jest widoczny dla programistów, którzy wykonują programy jednowątkowe, ale może być wąskim gardłem wydajności w kodzie ograniczonym przez CPU i wielowątkowym.
Ponieważ Globalne Zablokowanie Interpretatora (GIL) pozwala na wykonanie tylko jednego wątku w danym czasie, nawet na systemach wielordzeniowych, zyskało reputację „niesławnej” cechy Pythona (patrz artykuł Real Python o GIL).
Początkowo nie byłem świadomy GIL, więc zacząłem programować w Pythonie. Na końcu, mimo że mój program był asynchroniczny, blokował się, a niezależnie od tego, ile wątków dodałem, osiągałem tylko około 12-15 iteracji na sekundę.
Główną część funkcji asynchronicznej w Pythonie można zobaczyć poniżej:
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 korzystanie z Pythona i wróciłem do rysowania…
Postanowiłem skorzystać z NodeJS ze względu na jego zdolność do wykonywania nieblokujących operacji I/O niezwykle dobrze. Inną doskonałą opcją do obsługi asynchronicznego przetwarzania API jest stworzenie konsumentów webhooków bezserwerowych z Azure Functions, które mogą efektywnie obsługiwać zmienne obciążenia. Również dobrze znam programowanie w NodeJS.
Wykorzystując asynchroniczne aspekty Node.js, to podejście działało dobrze. Aby uzyskać więcej szczegółów na temat programowania asynchronicznego w Node.js, zobacz przewodnik RisingStack na temat programowania asynchronicznego w Node.js.
Python jest jednym z moich ulubionych języków programowania. Doskonale sprawdza się w wielu dziedzinach i jest niezwykle prosty. Jednak jedna dziedzina, w której nie sprawdza się najlepiej, to procesy współbieżne. Chociaż Python ma możliwość uruchamiania funkcji asynchronicznych, ma to, co nazywa się Globalnym Zablokowaniem Interpretatora Pythona, czyli GIL.
„Globalne Zablokowanie Interpretatora Pythona, czyli GIL, w prostych słowach, to mutex (lub blokada), który pozwala tylko jednemu wątkowi kontrolować interpreter Pythona.
To oznacza, że tylko jeden wątek może być w stanie wykonania w danym momencie. Wpływ GIL nie jest widoczny dla programistów, którzy wykonują programy jednowątkowe, ale może być wąskim gardłem wydajności w kodzie ograniczonym przez CPU i wielowątkowym.
Ponieważ Globalne Zablokowanie Interpretatora (GIL) pozwala na wykonanie tylko jednego wątku w danym czasie, nawet na systemach wielordzeniowych, zyskało reputację „niesławnej” cechy Pythona (patrz artykuł Real Python o GIL).
Początkowo nie byłem świadomy GIL, więc zacząłem programować w Pythonie. Na końcu, mimo że mój program był asynchroniczny, blokował się, a niezależnie od tego, ile wątków dodałem, osiągałem tylko około 12-15 iteracji na sekundę.
Główną część funkcji asynchronicznej w Pythonie można zobaczyć poniżej:
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 korzystanie z Pythona i wróciłem do rysowania…
Postanowiłem skorzystać z NodeJS ze względu na jego zdolność do wykonywania nieblokujących operacji I/O niezwykle dobrze. Inną doskonałą opcją do obsługi asynchronicznego przetwarzania API jest stworzenie konsumentów webhooków bezserwerowych z Azure Functions, które mogą efektywnie obsługiwać zmienne obciążenia. Również dobrze znam programowanie w NodeJS.
Wykorzystując asynchroniczne aspekty Node.js, to podejście działało dobrze. Aby uzyskać więcej szczegółów na temat programowania asynchronicznego w Node.js, zobacz przewodnik RisingStack na temat programowania asynchronicznego w Node.js.
Mój drugi błąd: próba odczytania pliku do pamięci
Moim początkowym pomysłem było follow:

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, załaduj adresy e-mail do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API walidacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. A na koniec, wypisz tę zmienną do pliku CSV.
To działało bardzo dobrze w przypadku mniejszych plików. Problem pojawił się, gdy próbowałem przeprowadzić walidację 100 000 adresów e-mail. Program zatrzymał się na około 12 000 walidacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problem leżał w ładowaniu wszystkich wyników do zmiennej (a tym samym szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą wersję tego programu, oto link: Wersja 1 (NIEZALECANE).

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, zlicz liczbę adresów e-mail w pliku do celów sprawozdawczych. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API walidacji odbiorcy i zapisz wyniki do pliku CSV.
W ten sposób, dla każdej przeczytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać tych danych w pamięci długoterminowej. Również usunąłem sprawdzanie składni adresu e-mail po rozmowie z zespołem walidacji odbiorcy, ponieważ poinformowali mnie, że walidacja odbiorcy ma już wbudowane kontrole, aby sprawdzić, czy adres e-mail jest ważny czy nie.
Moim początkowym pomysłem było follow:

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, załaduj adresy e-mail do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API walidacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. A na koniec, wypisz tę zmienną do pliku CSV.
To działało bardzo dobrze w przypadku mniejszych plików. Problem pojawił się, gdy próbowałem przeprowadzić walidację 100 000 adresów e-mail. Program zatrzymał się na około 12 000 walidacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problem leżał w ładowaniu wszystkich wyników do zmiennej (a tym samym szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą wersję tego programu, oto link: Wersja 1 (NIEZALECANE).

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, zlicz liczbę adresów e-mail w pliku do celów sprawozdawczych. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API walidacji odbiorcy i zapisz wyniki do pliku CSV.
W ten sposób, dla każdej przeczytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać tych danych w pamięci długoterminowej. Również usunąłem sprawdzanie składni adresu e-mail po rozmowie z zespołem walidacji odbiorcy, ponieważ poinformowali mnie, że walidacja odbiorcy ma już wbudowane kontrole, aby sprawdzić, czy adres e-mail jest ważny czy nie.
Moim początkowym pomysłem było follow:

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, załaduj adresy e-mail do tablicy i sprawdź, czy są w poprawnym formacie. Po trzecie, asynchronicznie wywołaj API walidacji odbiorcy. Po czwarte, poczekaj na wyniki i załaduj je do zmiennej. A na koniec, wypisz tę zmienną do pliku CSV.
To działało bardzo dobrze w przypadku mniejszych plików. Problem pojawił się, gdy próbowałem przeprowadzić walidację 100 000 adresów e-mail. Program zatrzymał się na około 12 000 walidacji. Z pomocą jednego z naszych programistów front-end, zauważyłem, że problem leżał w ładowaniu wszystkich wyników do zmiennej (a tym samym szybko kończyła się pamięć). Jeśli chciałbyś zobaczyć pierwszą wersję tego programu, oto link: Wersja 1 (NIEZALECANE).

Po pierwsze, wczytaj listę adresów e-mail w formacie CSV. Po drugie, zlicz liczbę adresów e-mail w pliku do celów sprawozdawczych. Po trzecie, gdy każda linia jest czytana asynchronicznie, wywołaj API walidacji odbiorcy i zapisz wyniki do pliku CSV.
W ten sposób, dla każdej przeczytanej linii, wywołuję API i zapisuję wyniki asynchronicznie, aby nie przechowywać tych danych w pamięci długoterminowej. Również usunąłem sprawdzanie składni adresu e-mail po rozmowie z zespołem walidacji odbiorcy, ponieważ poinformowali mnie, że walidacja odbiorcy ma już wbudowane kontrole, aby sprawdzić, czy adres e-mail jest ważny czy nie.
Rozbicie finalnego kodu
Po wczytaniu i zwalidowaniu argumentów terminala uruchamiam następujący kod. Najpierw wczytuję plik CSV z adresami e-mail i zliczam każdą linię. Funkcja ta ma dwa cele: 1) pozwala mi dokładnie raportować postęp pliku [jak zobaczymy później], i 2) pozwala mi zatrzymać licznik, gdy liczba e-maili w pliku równa się zakończonym walidacjom. Dodałem licznik, aby móc przeprowadzać testy 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. Zauważ, że ta funkcja jest asynchroniczna. Po zwalidowaniu, że plik wejściowy i wyjściowy są w formacie CSV, zapisuję wiersz nagłówka i uruchamiam licznik czasu przy użyciu 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 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 } }
Następujący skrypt to naprawdę trzon programu, więc podzielę go i wyjaśnię, co się dzieje. Dla każdej linii pliku wejściowego:
Asynchronicznie bierz tę linię i wywołaj API walidacji odbiorców.
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 odpowiedź
Dodaj e-mail do JSON (aby móc wydrukować e-mail w pliku CSV)
Sprawdź, czy powód jest null, a jeśli tak, wypełnij pustą wartość (tak, aby format CSV był spójny, jako że w niektórych przypadkach powód jest podawany w odpowiedzi)
Ustaw opcje i klucze dla modułu json2csv.
Przekonwertuj JSON na CSV i wyświetl (używając json2csv)
Zapisz postęp w terminalu
Na koniec, jeśli liczba e-maili w pliku = zakończone walidacje, zatrzymaj licznik czasu i wydrukuj wyniki
.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` ); } });
Jednym ostatecznym problemem, który znalazłem, było to, że podczas gdy to działało świetnie na Macu, natrafiłem na następujący błąd używając Windows po około 10 000 walidacjach:
Błąd: łączność ENOBUFS XX.XX.XXX.XXX:443 – Lokalny (undefined:undefined) z e-mailem XXXXXXX@XXXXXXXXXX.XXX
Po dalszych badaniach, wygląda na to, że jest to problem z pulą połączeń klienta HTTP NodeJS, która nie ponownie wykorzystuje połączeń. Znalazłem ten artykuł ze Stackoverflow na ten temat, a po dalszym poszukiwaniu znalazłem dobrą domyślną konfigurację 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.
Po wczytaniu i zwalidowaniu argumentów terminala uruchamiam następujący kod. Najpierw wczytuję plik CSV z adresami e-mail i zliczam każdą linię. Funkcja ta ma dwa cele: 1) pozwala mi dokładnie raportować postęp pliku [jak zobaczymy później], i 2) pozwala mi zatrzymać licznik, gdy liczba e-maili w pliku równa się zakończonym walidacjom. Dodałem licznik, aby móc przeprowadzać testy 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. Zauważ, że ta funkcja jest asynchroniczna. Po zwalidowaniu, że plik wejściowy i wyjściowy są w formacie CSV, zapisuję wiersz nagłówka i uruchamiam licznik czasu przy użyciu 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 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 } }
Następujący skrypt to naprawdę trzon programu, więc podzielę go i wyjaśnię, co się dzieje. Dla każdej linii pliku wejściowego:
Asynchronicznie bierz tę linię i wywołaj API walidacji odbiorców.
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 odpowiedź
Dodaj e-mail do JSON (aby móc wydrukować e-mail w pliku CSV)
Sprawdź, czy powód jest null, a jeśli tak, wypełnij pustą wartość (tak, aby format CSV był spójny, jako że w niektórych przypadkach powód jest podawany w odpowiedzi)
Ustaw opcje i klucze dla modułu json2csv.
Przekonwertuj JSON na CSV i wyświetl (używając json2csv)
Zapisz postęp w terminalu
Na koniec, jeśli liczba e-maili w pliku = zakończone walidacje, zatrzymaj licznik czasu i wydrukuj wyniki
.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` ); } });
Jednym ostatecznym problemem, który znalazłem, było to, że podczas gdy to działało świetnie na Macu, natrafiłem na następujący błąd używając Windows po około 10 000 walidacjach:
Błąd: łączność ENOBUFS XX.XX.XXX.XXX:443 – Lokalny (undefined:undefined) z e-mailem XXXXXXX@XXXXXXXXXX.XXX
Po dalszych badaniach, wygląda na to, że jest to problem z pulą połączeń klienta HTTP NodeJS, która nie ponownie wykorzystuje połączeń. Znalazłem ten artykuł ze Stackoverflow na ten temat, a po dalszym poszukiwaniu znalazłem dobrą domyślną konfigurację 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.
Po wczytaniu i zwalidowaniu argumentów terminala uruchamiam następujący kod. Najpierw wczytuję plik CSV z adresami e-mail i zliczam każdą linię. Funkcja ta ma dwa cele: 1) pozwala mi dokładnie raportować postęp pliku [jak zobaczymy później], i 2) pozwala mi zatrzymać licznik, gdy liczba e-maili w pliku równa się zakończonym walidacjom. Dodałem licznik, aby móc przeprowadzać testy 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. Zauważ, że ta funkcja jest asynchroniczna. Po zwalidowaniu, że plik wejściowy i wyjściowy są w formacie CSV, zapisuję wiersz nagłówka i uruchamiam licznik czasu przy użyciu 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 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 } }
Następujący skrypt to naprawdę trzon programu, więc podzielę go i wyjaśnię, co się dzieje. Dla każdej linii pliku wejściowego:
Asynchronicznie bierz tę linię i wywołaj API walidacji odbiorców.
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 odpowiedź
Dodaj e-mail do JSON (aby móc wydrukować e-mail w pliku CSV)
Sprawdź, czy powód jest null, a jeśli tak, wypełnij pustą wartość (tak, aby format CSV był spójny, jako że w niektórych przypadkach powód jest podawany w odpowiedzi)
Ustaw opcje i klucze dla modułu json2csv.
Przekonwertuj JSON na CSV i wyświetl (używając json2csv)
Zapisz postęp w terminalu
Na koniec, jeśli liczba e-maili w pliku = zakończone walidacje, zatrzymaj licznik czasu i wydrukuj wyniki
.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` ); } });
Jednym ostatecznym problemem, który znalazłem, było to, że podczas gdy to działało świetnie na Macu, natrafiłem na następujący błąd używając Windows po około 10 000 walidacjach:
Błąd: łączność ENOBUFS XX.XX.XXX.XXX:443 – Lokalny (undefined:undefined) z e-mailem XXXXXXX@XXXXXXXXXX.XXX
Po dalszych badaniach, wygląda na to, że jest to problem z pulą połączeń klienta HTTP NodeJS, która nie ponownie wykorzystuje połączeń. Znalazłem ten artykuł ze Stackoverflow na ten temat, a po dalszym poszukiwaniu znalazłem dobrą domyślną konfigurację 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 walidacji odbiorcy i generuje plik CSV, ten program jest dla Ciebie.
Niektóre dodatki do tego programu mogłyby być następujące:
Stworzenie front-endu lub prostszego interfejsu użytkownika do użytku
Lepsze zarządzanie błędami i ponownym wywołaniem, ponieważ jeśli z jakiegoś powodu API zwraca błąd, program obecnie nie próbuje ponownie wykonać wywołania
Rozważ wdrożenie jako funkcję Azure bezserwerową dla automatycznego skalowania i zmniejszonego zarządzania infrastrukturą
Byłbym również ciekaw, czy szybsze wyniki można osiągnąć w innym języku, takim jak Golang lub Erlang/Elixir. Poza wyborem języka, ograniczenia infrastrukturalne mogą również wpływać na wydajność - nauczyliśmy się tego na własnej skórze, kiedy napotkaliśmy nieudokumentowane limity DNS w AWS, które wpłynęły na nasze systemy przetwarzania e-maili o dużym wolumenie.
Dla deweloperów zainteresowanych łączeniem przetwarzania API z wizualnymi narzędziami do tworzenia przepływu, sprawdź, jak zintegrować Flow Builder z Google Cloud Functions w celu automatyzacji robót bez kodu.
Proszę, czuj się swobodnie, aby przekazać mi jakiekolwiek opinie lub sugestie dotyczące rozszerzenia tego projektu.
Dla kogoś, kto szuka prostego i szybkiego programu, który przyjmuje plik CSV, wywołuje API walidacji odbiorcy i generuje plik CSV, ten program jest dla Ciebie.
Niektóre dodatki do tego programu mogłyby być następujące:
Stworzenie front-endu lub prostszego interfejsu użytkownika do użytku
Lepsze zarządzanie błędami i ponownym wywołaniem, ponieważ jeśli z jakiegoś powodu API zwraca błąd, program obecnie nie próbuje ponownie wykonać wywołania
Rozważ wdrożenie jako funkcję Azure bezserwerową dla automatycznego skalowania i zmniejszonego zarządzania infrastrukturą
Byłbym również ciekaw, czy szybsze wyniki można osiągnąć w innym języku, takim jak Golang lub Erlang/Elixir. Poza wyborem języka, ograniczenia infrastrukturalne mogą również wpływać na wydajność - nauczyliśmy się tego na własnej skórze, kiedy napotkaliśmy nieudokumentowane limity DNS w AWS, które wpłynęły na nasze systemy przetwarzania e-maili o dużym wolumenie.
Dla deweloperów zainteresowanych łączeniem przetwarzania API z wizualnymi narzędziami do tworzenia przepływu, sprawdź, jak zintegrować Flow Builder z Google Cloud Functions w celu automatyzacji robót bez kodu.
Proszę, czuj się swobodnie, aby przekazać mi jakiekolwiek opinie lub sugestie dotyczące rozszerzenia tego projektu.
Dla kogoś, kto szuka prostego i szybkiego programu, który przyjmuje plik CSV, wywołuje API walidacji odbiorcy i generuje plik CSV, ten program jest dla Ciebie.
Niektóre dodatki do tego programu mogłyby być następujące:
Stworzenie front-endu lub prostszego interfejsu użytkownika do użytku
Lepsze zarządzanie błędami i ponownym wywołaniem, ponieważ jeśli z jakiegoś powodu API zwraca błąd, program obecnie nie próbuje ponownie wykonać wywołania
Rozważ wdrożenie jako funkcję Azure bezserwerową dla automatycznego skalowania i zmniejszonego zarządzania infrastrukturą
Byłbym również ciekaw, czy szybsze wyniki można osiągnąć w innym języku, takim jak Golang lub Erlang/Elixir. Poza wyborem języka, ograniczenia infrastrukturalne mogą również wpływać na wydajność - nauczyliśmy się tego na własnej skórze, kiedy napotkaliśmy nieudokumentowane limity DNS w AWS, które wpłynęły na nasze systemy przetwarzania e-maili o dużym wolumenie.
Dla deweloperów zainteresowanych łączeniem przetwarzania API z wizualnymi narzędziami do tworzenia przepływu, sprawdź, jak zintegrować Flow Builder z Google Cloud Functions w celu automatyzacji robót bez kodu.
Proszę, czuj się swobodnie, aby przekazać mi jakiekolwiek opinie lub sugestie dotyczące rozszerzenia tego projektu.



