
Untuk seseorang yang sedang mencari program sederhana dan cepat yang menerima file csv, memanggil API validasi penerima, dan mengeluarkan file CSV, program ini untuk Anda.
Business in a box.
Temukan solusi kami.
Bicaralah kepada tim penjualan kami
Salah satu pertanyaan yang kadang-kadang kami terima adalah, bagaimana saya dapat memvalidasi daftar email secara massal dengan validasi penerima? Ada dua pilihan di sini, satu adalah mengunggah file melalui antarmuka pengguna SparkPost untuk validasi, dan yang lainnya adalah melakukan panggilan individu per email ke API (karena API adalah validasi email tunggal).
Pilihan pertama bekerja dengan baik tetapi memiliki batasan 20Mb (sekitar 500.000 alamat). Bagaimana jika seseorang memiliki daftar email yang berisi jutaan alamat? Itu bisa berarti membagi menjadi 1.000’s unggahan file CSV.
Karena mengunggah ribuan file CSV tampak sedikit berlebihan, saya mengambil kasus penggunaan itu dan mulai bertanya-tanya seberapa cepat saya bisa menjalankan API. Dalam posting blog ini, saya akan menjelaskan apa yang saya coba dan bagaimana akhirnya saya menemukan program yang dapat melakukan sekitar 100.000 validasi dalam 55 detik (Sedangkan di antarmuka pengguna saya mendapatkan sekitar 100.000 validasi dalam 1 menit 10 detik). Dan meskipun ini masih akan memakan waktu sekitar 100 jam untuk menyelesaikan sekitar 654 juta validasi, skrip ini dapat berjalan di latar belakang menghemat waktu yang signifikan.
Versi akhir dari program ini dapat ditemukan di sini.
Kesalahan pertama saya: menggunakan Python
Python adalah salah satu bahasa pemrograman favorit saya. Python menonjol di banyak bidang dan sangat mudah dipahami. Namun, salah satu area yang tidak unggul adalah proses bersamaan. Meskipun Python memiliki kemampuan menjalankan fungsi asinkron, ia memiliki apa yang disebut dengan Kunci Interpretor Global Python atau GIL.
"Kunci Interpretor Global Python atau GIL, secara sederhana, adalah mutex (atau kunci) yang memungkinkan hanya satu thread untuk memegang kendali interpreter Python.
Ini berarti bahwa hanya satu thread yang dapat berada dalam keadaan eksekusi pada waktu tertentu. Dampak GIL tidak terlihat oleh pengembang yang menjalankan program single-threaded, tetapi dapat menjadi penghambat kinerja dalam kode yang mengandalkan CPU dan multi-threaded.
Karena GIL hanya memungkinkan satu thread untuk dieksekusi pada satu waktu bahkan dalam arsitektur multi-threaded dengan lebih dari satu inti CPU, GIL telah mendapatkan reputasi sebagai fitur "terkenal" dari Python." (https://realpython.com/python-gil/)
Pada awalnya, saya tidak menyadari keberadaan GIL, jadi saya mulai memprogram menggunakan Python. Pada akhirnya, meskipun program saya asinkron, program tersebut tetap terkunci, dan tidak peduli berapa banyak thread yang saya tambahkan, saya tetap hanya mendapatkan sekitar 12-15 iterasi per detik.
Bagian utama dari fungsi asinkron di Python dapat dilihat di bawah ini:
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)
Jadi saya mengabaikan penggunaan Python dan kembali ke papan gambar…
Saya memilih untuk menggunakan NodeJS karena kemampuannya melakukan operasi i/o non-blokir dengan sangat baik. Saya juga cukup familiar dengan pemrograman di NodeJS.
Menggunakan aspek asinkron dari NodeJS, ini akhirnya bekerja dengan baik. Untuk lebih jelasnya tentang pemrograman asinkron di NodeJS, lihat https://blog.risingstack.com/node-hero-async-programming-in-node-js/
Kesalahan kedua saya: mencoba membaca file ke dalam memori
Memecah kode terakhir
Setelah membaca dan memvalidasi argumen terminal, saya menjalankan kode berikut. Pertama, saya membaca file CSV email dan menghitung setiap baris. Ada dua tujuan dari fungsi ini, 1) ini memungkinkan saya untuk secara akurat melaporkan kemajuan file [seperti yang akan kita lihat nanti], dan 2) ini memungkinkan saya untuk menghentikan timer ketika jumlah email dalam file sama dengan validasi yang telah selesai. Saya menambahkan timer agar saya bisa menjalankan tolok ukur dan memastikan saya mendapatkan hasil yang baik.
let count = 0; //Penghitungan baris require("fs") .createReadStream(myArgs[1]) .on("data", function (chunk) { for (let i = 0; i < chunk.length; ++i) if (chunk[i] == 10) count++; }) //Membaca infile dan menambah jumlah untuk setiap baris .on("close", function () { //Pada akhir infile, setelah semua baris dihitung, jalankan fungsi validasi penerima validateRecipients.validateRecipients(count, myArgs); });
Kemudian saya memanggil fungsi validateRecipients. Catat bahwa fungsi ini asinkron. Setelah memvalidasi bahwa infile dan outfile adalah CSV, saya menulis baris header, dan memulai timer program menggunakan perpustakaan JSDOM.
async function validateRecipients(email_count, myArgs) { if ( //Jika baik infile dan outfile dalam format .csv extname(myArgs[1]).toLowerCase() == ".csv" && extname(myArgs[3]).toLowerCase() == ".csv" ) { let completed = 0; //Penghitung untuk setiap panggilan API email_count++; //Penghitung baris mengembalikan #baris - 1, ini dilakukan untuk memperbaiki jumlah baris //Memulai 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" ); //Menulis header dalam outfile
Skrip berikut ini adalah inti dari program sehingga saya akan memecahnya dan menjelaskan apa yang terjadi. Untuk setiap baris dari infile:
Sambil menelepon dengan asinkron, baris tersebut diproses menggunakan API validasi penerima.
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, }, }) //Untuk setiap baris yang dibaca dari infile, memanggil SparkPost Recipient Validation API
Kemudian, pada responsnya:
Tambahkan email ke JSON (agar dapat mencetak email dalam CSV)
Validasi jika alasan (reason) adalah null, dan jika iya, isi dengan nilai kosong (ini agar format CSV konsisten, karena dalam beberapa kasus alasan diberikan dalam respons)
Tentukan opsi dan kunci untuk modul json2csv.
Konversi JSON ke CSV dan keluaran (menggunakan json2csv)
Tulis kemajuan di terminal
Akhirnya, jika jumlah email dalam file = validasi yang selesai, hentikan timer dan cetak hasilnya
.then(function (response) { response.data.results.email = String(email); //Menambahkan email sebagai nilai/pasangan kunci ke JSON respons untuk digunakan dalam keluaran response.data.results.reason ? null : (response.data.results.reason = ""); //Jika alasan adalah null, atur ke kosong sehingga CSV uniform //Menggunakan json-2-csv untuk mengubah JSON menjadi format CSV dan keluaran let options = { prependHeader: false, //Menonaktifkan nilai JSON ditambah sebagai baris header setiap baris keys: [ "results.email", "results.valid", "results.result", "results.reason", "results.is_role", "results.is_disposable", "results.is_free", "results.delivery_confidence", ], //Mengatur urutan kunci }; let json2csvCallback = function (err, csv) { if (err) throw err; output.write(`${csv}\n`); }; converter.json2csv(response.data, json2csvCallback, options); completed++; //Meningkatkan penghitung API process.stdout.write(`Selesai dengan ${completed} / ${email_count}\r`); //Mengeluarkan status Selesai / Total ke konsol tanpa menampilkan baris baru //Jika semua email telah selesai divalidasi if (completed == email_count) { const stop = window.performance.now(); //Menghentikan timer console.log( `Semua email berhasil divalidasi dalam ${ (stop - start) / 1000 } detik` ); } })
Satu masalah terakhir yang saya temukan adalah saat ini bekerja dengan baik di Mac, saya mengalami kesalahan berikut menggunakan Windows setelah sekitar 10,000 validasi:
Error: connect ENOBUFS XX.XX.XXX.XXX:443 – Lokal (undefined:undefined) dengan email XXXXXXX@XXXXXXXXXX.XXX
Setelah melakukan beberapa penelitian lebih lanjut, tampaknya ini adalah masalah dengan pool koneksi klien HTTP NodeJS yang tidak memanfaatkan koneksi yang sudah ada. Saya menemukan artikel Stackoverflow tentang masalah ini, dan setelah menggali lebih dalam, menemukan konfigurasi default yang bagus untuk pustaka axios yang menyelesaikan masalah ini. Saya masih belum yakin mengapa isu ini hanya terjadi di Windows dan tidak di Mac.
Langkah Selanjutnya
Untuk seseorang yang mencari program cepat dan sederhana yang menerima csv, memanggil recipient validation API, dan mengeluarkan CSV, program ini adalah untuk Anda.
Beberapa tambahan untuk program ini adalah sebagai berikut:
Membangun front end atau UI yang lebih mudah untuk digunakan
Penanganan kesalahan dan pengulangan yang lebih baik karena jika karena suatu alasan API mengeluarkan kesalahan, program saat ini tidak mengulangi panggilan
Saya juga penasaran apakah hasil yang lebih cepat dapat dicapai dengan bahasa lain seperti Golang atau Erlang/Elixir.
Silakan merasa bebas untuk memberikan umpan balik atau saran kepada saya untuk memperluas proyek ini.