Tim procurement bisa kehilangan margin bukan karena salah negosiasi, melainkan karena laporan stoknya basi tiga hari. Penyebabnya hampir selalu sama: portal supplier dan situs carrier memasang tantangan CAPTCHA, skrip pemantauan berhenti di halaman verifikasi, dan tidak ada yang menyadarinya sampai purchase order salah terbit. Artikel ini menunjukkan cara menyusun monitor rantai pasokan berbasis Python yang menyelesaikan CAPTCHA lewat API CaptchaAI, sehingga jadwal pengambilan data tetap jalan.
Isinya: peta sumber data, satu kelas monitor multi-supplier, pelacak tarif pengiriman, peringatan stok, dan aturan operasional.
Kenapa monitor rantai pasokan berhenti di halaman verifikasi
Portal supplier jarang menyediakan API publik. Data yang Anda butuhkan — level stok, harga tier, lead time, nomor resi — hanya ada di HTML di balik form. Ketika satu alamat mengambil halaman yang sama setiap jam, sistem anti-bot di sisi supplier menaikkan tekanan verifikasi, dan halaman yang kemarin lolos hari ini mengembalikan tantangan.
Pola yang sering terlihat di Indonesia: importir komponen elektronik memantau enam supplier Asia Tenggara plus dua portal bea cukai lewat cron di AWS ap-southeast-1 (Singapura) atau ap-southeast-3 (Jakarta). Selama satu alamat egress dipakai terus, dua sampai tiga sumber akan meminta verifikasi dalam hitungan hari. Solusinya bukan trik, melainkan menjadikan penyelesaian CAPTCHA langkah normal dalam alur pengambilan data.
Catatan kepatuhan: ambil hanya data yang berhak Anda proses berdasarkan kontrak atau akun supplier Anda sendiri, dan hindari data pribadi — UU 27/2022 tentang Pelindungan Data Pribadi berlaku pada pipeline internal juga.
Peta sumber data dan jenis CAPTCHA-nya
Sebelum menulis kode, petakan setiap sumber: tantangan apa yang muncul, data apa yang diambil, dan seberapa sering data itu berubah. Frekuensi kelewat rapat adalah penyebab utama CAPTCHA muncul di setiap pemeriksaan.
| Jenis sumber | Jenis CAPTCHA | Data yang diambil | Frekuensi wajar |
|---|---|---|---|
| Portal supplier | reCAPTCHA v2 | Persediaan, harga, lead time | Harian |
| Operator pengiriman | Cloudflare Turnstile | Pelacakan, tarif, estimasi tiba | Tiap jam |
| Katalog pabrikan | CAPTCHA gambar | Spesifikasi produk, MOQ | Mingguan |
| Portal bea cukai | reCAPTCHA v2 | Tarif bea, kode HS | Harian |
| Otoritas pelabuhan | CAPTCHA gambar | Jadwal kapal, kepadatan pelabuhan | Tiap 6 jam |
| Bursa komoditas | reCAPTCHA v3 | Harga spot, kontrak berjangka | Mendekati real-time |
Ketiga keluarga tantangan di tabel ini — reCAPTCHA v2/v3, Cloudflare Turnstile, dan CAPTCHA gambar (OCR) — didukung penuh oleh CaptchaAI. Supplier yang memakai hCaptcha atau FunCaptcha jangan dimasukkan ke pipeline ini: keduanya belum didukung, jadi tandai sumber tersebut untuk pemeriksaan manual. GeeTest v4 juga masih segera hadir.
Monitor multi-supplier dalam satu kelas Python
Struktur di bawah ini menyatukan tiga solver — reCAPTCHA, Turnstile, dan gambar — dalam satu loop pemeriksaan, mengikuti pola empat langkah CaptchaAI: kirim task ke in.php, simpan task ID, polling res.php, pakai token. Setiap supplier gagal secara terisolasi.
import requests
import time
import re
import json
import base64
from datetime import datetime
CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"
def solve_recaptcha(sitekey, pageurl):
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY, "method": "userrecaptcha",
"googlekey": sitekey, "pageurl": pageurl, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Timeout")
def solve_turnstile(sitekey, pageurl):
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY, "method": "turnstile",
"sitekey": sitekey, "pageurl": pageurl, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(5)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Timeout")
def solve_image(image_bytes):
img_b64 = base64.b64encode(image_bytes).decode()
resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data={
"key": CAPTCHAAI_KEY, "method": "base64",
"body": img_b64, "json": 1,
})
task_id = resp.json()["request"]
for _ in range(20):
time.sleep(3)
result = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
"key": CAPTCHAAI_KEY, "action": "get",
"id": task_id, "json": 1,
})
data = result.json()
if data["request"] != "CAPCHA_NOT_READY":
return data["request"]
raise TimeoutError("Timeout")
class SupplyChainMonitor:
def __init__(self, suppliers, proxy=None):
self.suppliers = suppliers
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
})
def check_all(self):
"""Check inventory and pricing across all suppliers."""
report = {
"timestamp": datetime.now().isoformat(),
"suppliers": {},
}
for supplier in self.suppliers:
try:
data = self._check_supplier(supplier)
report["suppliers"][supplier["name"]] = {
"status": "success",
"data": data,
}
except Exception as e:
report["suppliers"][supplier["name"]] = {
"status": "error",
"error": str(e),
}
time.sleep(3)
return report
def _check_supplier(self, supplier):
url = supplier["url"]
resp = self.session.get(url, timeout=30)
# Handle CAPTCHA based on type
captcha_type = supplier.get("captcha_type")
if captcha_type and self._has_captcha(resp.text):
resp = self._solve_captcha(resp, url, supplier)
from bs4 import BeautifulSoup
soup = BeautifulSoup(resp.text, "html.parser")
return {
"products": self._extract_inventory(soup),
"last_updated": self._extract_date(soup),
}
def _has_captcha(self, html):
return any(tag in html.lower() for tag in [
'data-sitekey', 'g-recaptcha', 'cf-turnstile', 'captcha',
])
def _solve_captcha(self, resp, url, supplier):
captcha_type = supplier.get("captcha_type", "recaptcha")
sitekey = supplier.get("sitekey", "")
if not sitekey:
match = re.search(r'data-sitekey="([^"]+)"', resp.text)
sitekey = match.group(1) if match else ""
if captcha_type == "turnstile":
token = solve_turnstile(sitekey, url)
return self.session.post(url, data={"cf-turnstile-response": token})
elif captcha_type == "image":
match = re.search(r'src="(/captcha[^"]+)"', resp.text)
if match:
img_resp = self.session.get(url.rstrip("/") + match.group(1))
answer = solve_image(img_resp.content)
return self.session.post(url, data={"captcha": answer})
else:
token = solve_recaptcha(sitekey, url)
return self.session.post(url, data={"g-recaptcha-response": token})
return resp
def _extract_inventory(self, soup):
items = []
for row in soup.select("table.inventory tr, .product-row"):
cols = row.select("td, .col")
if len(cols) >= 3:
items.append({
"sku": cols[0].get_text(strip=True),
"stock": cols[1].get_text(strip=True),
"price": cols[2].get_text(strip=True),
})
return items
def _extract_date(self, soup):
date_el = soup.select_one(".last-updated, .update-time")
return date_el.get_text(strip=True) if date_el else ""
# Configure suppliers
suppliers = [
{
"name": "Supplier A",
"url": "https://supplier-a.example.com/inventory",
"captcha_type": "recaptcha",
"sitekey": "6Lc_xxxxxxx",
},
{
"name": "Carrier B",
"url": "https://carrier-b.example.com/rates",
"captcha_type": "turnstile",
"sitekey": "0x4AAAAAAA_xxx",
},
{
"name": "Manufacturer C",
"url": "https://manufacturer-c.example.com/catalog",
"captcha_type": "image",
},
]
monitor = SupplyChainMonitor(
suppliers=suppliers,
proxy="http://user:pass@residential.proxy.com:5000",
)
report = monitor.check_all()
print(json.dumps(report, indent=2))
Dua detail penting. _has_captcha() memeriksa penanda HTML sebelum memanggil solver, jadi thread tidak terbakar untuk halaman yang lolos tanpa tantangan. Dan jeda time.sleep(3) antar supplier adalah pengaturan ritme termurah yang ada.
Melacak tarif pengiriman dari situs carrier
Situs carrier umumnya memasang Cloudflare Turnstile pada form kalkulasi tarif. Polanya lebih sederhana daripada portal supplier: ambil halaman, baca data-sitekey, selesaikan tantangan, kirim ulang form beserta cf-turnstile-response.
class ShippingRateTracker:
def __init__(self, proxy=None):
self.session = requests.Session()
if proxy:
self.session.proxies = {"http": proxy, "https": proxy}
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/126.0.0.0 Safari/537.36",
})
def get_rates(self, carrier_url, origin, destination, weight):
"""Fetch shipping rates, handling Turnstile CAPTCHA."""
resp = self.session.get(carrier_url, timeout=30)
sitekey_match = re.search(r'data-sitekey="([^"]+)"', resp.text)
if sitekey_match:
token = solve_turnstile(sitekey_match.group(1), carrier_url)
resp = self.session.post(carrier_url, data={
"origin": origin,
"destination": destination,
"weight": weight,
"cf-turnstile-response": token,
})
if resp.status_code == 200:
return resp.json().get("rates", [])
return []
Untuk rute impor yang harganya berubah harian, pemeriksaan tiap jam sudah cukup. Simpan hasil bersama timestamp agar selisih tarif antar hari bisa ditelusuri saat menyusun anggaran logistik.
Memberi peringatan saat stok menyentuh ambang
Data yang tidak dibaca sama saja dengan data yang tidak pernah diambil. Lapisan terakhir membandingkan pemeriksaan terbaru dengan sebelumnya, dan memicu peringatan hanya ketika SKU melewati ambang batas.
def monitor_with_alerts(monitor, alert_thresholds, check_interval=3600):
"""Continuously monitor and alert on inventory changes."""
previous_data = {}
while True:
report = monitor.check_all()
for supplier, info in report["suppliers"].items():
if info["status"] != "success":
continue
for product in info["data"].get("products", []):
sku = product["sku"]
stock = product.get("stock", "")
# Parse stock level
try:
stock_qty = int(re.sub(r'\D', '', stock))
except ValueError:
continue
key = f"{supplier}:{sku}"
prev_qty = previous_data.get(key, stock_qty)
threshold = alert_thresholds.get(sku, 10)
if stock_qty < threshold and prev_qty >= threshold:
print(f"ALERT: {supplier} - {sku} dropped to {stock_qty}")
previous_data[key] = stock_qty
time.sleep(check_interval)
Ganti print dengan pengirim notifikasi yang tim Anda benar-benar baca. Untuk tim kecil, webhook ke grup Telegram atau Slack biasanya direspons lebih cepat daripada email.
Menghitung kebutuhan thread dan biaya bulanan
CaptchaAI menagih per thread bersamaan, bukan per penyelesaian, dan setiap paket memberi penyelesaian tanpa batas pada thread tersebut selama bulan berjalan. Untuk pemantauan terjadwal, ini penting: yang menentukan paket adalah berapa banyak CAPTCHA yang berjalan pada saat bersamaan, bukan totalnya per bulan.
Monitor 8–10 supplier yang berjalan berurutan jarang melewati beberapa penyelesaian bersamaan, jadi BASIC ($15/bulan, 5 thread) atau STANDARD ($30/bulan, 15 thread) sudah memadai. Untuk beberapa worker paralel di puluhan sumber, ADVANCE ($90/bulan, 50 thread) jauh lebih lega. Harga selalu USD; tier lengkap sampai VIP-3 ($7,500/bulan, 5.000 thread) ada di halaman pricing CaptchaAI.
Bagi agensi price-monitoring yang menagih per laporan bulanan, biaya tetap seperti ini lebih mudah dimasukkan ke penawaran dibanding tarif per penyelesaian.
Aturan operasional agar pipeline tetap hidup
- Pisahkan supplier kritis dari sumber opsional, sehingga satu portal bermasalah tidak membatalkan seluruh laporan.
- Tetapkan sejak awal berapa lama data boleh usang, dan kapan supplier yang hilang menjadi insiden, bukan sekadar baris log.
- Perlakukan tekanan CAPTCHA yang terus meningkat sebagai sinyal pengadaan, bukan sekadar kesalahan teknis.
- Catat waktu penyelesaian per sumber. Kalau rata-ratanya melar, biasanya interval pemeriksaan terlalu rapat.
Masalah yang paling sering muncul
| Gejala | Penyebab biasanya | Penanganan |
|---|---|---|
| Struktur halaman supplier berubah | Situs didesain ulang | Perbarui selector CSS, tambahkan tes ekstraksi |
| CAPTCHA muncul di setiap pemeriksaan | Interval pemeriksaan terlalu rapat | Perbesar jeda antar pemeriksaan |
| Sesi putus di tengah pemeriksaan | Batas waktu portal habis | Pertahankan sesi yang sama, persingkat alur cek |
| Data tarif kosong | Halaman butuh login | Tambahkan langkah autentikasi sebelum ekstraksi |
| Harga yang tampil tidak sesuai | Harga berbasis lokasi | Samakan lokasi egress jaringan dengan pasar target |
Pertanyaan umum
Apakah CaptchaAI mendukung semua CAPTCHA di portal supplier?
Tidak semua. reCAPTCHA v2 dan v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, serta CAPTCHA gambar dan grid didukung penuh. CaptchaFox (beta), Friendly Captcha (beta), dan Lemin (beta) tersedia terbatas. hCaptcha dan FunCaptcha belum didukung; GeeTest v4 masih segera hadir — periksa jenis tantangan tiap sumber sebelum memasukkannya ke pipeline.
Berapa thread yang saya butuhkan untuk memantau 50 supplier?
Yang menentukan bukan jumlah supplier, melainkan berapa penyelesaian yang berjalan bersamaan. Lima puluh supplier yang diperiksa berurutan sekali sehari umumnya cukup dengan BASIC ($15/bulan, 5 thread). Dipecah jadi sepuluh worker paralel, naikkan ke ADVANCE ($90/bulan, 50 thread).
Apakah data harga hasil pemantauan boleh saya simpan dan olah?
Untuk data harga dan stok B2B dari akun supplier Anda sendiri, umumnya ya, sepanjang sesuai perjanjian dengan supplier tersebut. Hindari data pribadi, dan perlakukan UU 27/2022 sebagai batas kerja standar. Ini bukan nasihat hukum.
Bagaimana menangani supplier yang tiba-tiba menaikkan verifikasi?
Turunkan frekuensi pemeriksaan, lalu geser jadwalnya keluar dari jam sibuk. Kalau tekanan tetap tinggi, catat sebagai risiko sumber data dan siapkan jalur cadangan seperti feed harga resmi dari supplier.
Apakah monitor ini bisa dijalankan secara serverless?
Bisa. Alurnya hanya request HTTP biasa, cocok sebagai fungsi terjadwal. Perhatikan batas waktu eksekusi: polling reCAPTCHA bisa memakan puluhan detik, jadi beri margin lapang atau pecah pemeriksaan menjadi beberapa invocation.
Panduan terkait
- Mengatur rotasi egress jaringan yang diotorisasi
- Sesi tetap atau sesi berganti: mana yang cocok untuk pemantauan
- Menjaga sesi browser tetap hidup di alur ber-CAPTCHA
Ambil API key CaptchaAI dan jadikan laporan stok serta tarif pengiriman Anda selalu berdasarkan data hari ini, bukan minggu lalu.