La visibilidad de la cadena de suministro requiere datos de cientos de portales de proveedores, plataformas logísticas y sistemas de inventario. Muchos de ellos protegen sus datos detrás de CAPTCHA. CaptchaAI maneja estos desafíos para que las tuberías de monitoreo funcionen sin interrupciones.
Dónde los CAPTCHA bloquean los datos de la cadena de suministro
| Tipo de fuente | Tipo CAPTCHA | Datos | Frecuencia |
|---|---|---|---|
| Portales de proveedores | reCAPTCHA v2 | Inventario, precios, plazos de entrega. | Diariamente |
| Transportistas de envío | Cloudflare Turnstile | Seguimiento, tarifas, ETA de entrega. | Por hora |
| Catálogos de fabricantes | CAPTCHA de imagen | Especificaciones del producto, MOQ | Semanal |
| Portales aduaneros | reCAPTCHA v2 | Tipos de derechos, códigos arancelarios | Diariamente |
| Autoridades portuarias | CAPTCHA de imagen | Horarios de los buques, congestión portuaria | Cada 6 horas |
| Bolsas de productos básicos | reCAPTCHA v3 | Precios al contado, futuros | En tiempo real |
Monitor de múltiples proveedores
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))
Política de fallback por proveedor
| Estado del proveedor | Acción | Resultado esperado |
|---|---|---|
| CAPTCHA resuelto y parser estable | Publicar inventario completo | El proveedor entra como success |
| CAPTCHA resuelto pero DOM cambió | Guardar HTML y marcar partial |
No perder el resto del barrido multi-proveedor |
| CAPTCHA no resuelto tras reintentos | Mantener último dato conocido y alertar | Evita huecos totales en el reporte diario |
Monitoreo de tarifas de envío
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 []
Alertas sobre cambios de acciones
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)
Solución de problemas
| Problema | causa | Solución |
|---|---|---|
| El diseño de la página del proveedor cambió | Rediseño del sitio | Actualizar selectores CSS |
| CAPTCHA en cada cheque | Comprobando con demasiada frecuencia | Aumentar el intervalo entre controles. |
| La sesión caduca a mitad de la verificación | Tiempo de espera del portal | Utilice sesión fija, verifique más rápido |
| Faltan datos de tarifas | Iniciar sesión requerido | Agregar paso de autenticación |
| Se muestran precios incorrectos | Precios basados en geografía | Haga coincidir la ubicación del proxy con el mercado |
Preguntas frecuentes
¿Con qué frecuencia debo revisar el inventario de proveedores?
Diariamente para la mayoría de los proveedores. Cada hora para componentes críticos durante la escasez de suministro. Las comprobaciones demasiado frecuentes activan los CAPTCHA más rápidamente.
¿Puedo monitorear cientos de proveedores?
Sí. Rotar entre proveedores con retrasos entre cada uno. Utilice proxies rotativos para distribuir la carga entre las IP.
¿Qué tipo de CAPTCHA es más común en los sitios de la cadena de suministro?
reCAPTCHA v2 en portales de proveedores, Cloudflare Turnstile en sitios de logística/carrier. Los sitios de fabricantes más antiguos suelen utilizar CAPTCHA de imágenes.
Guías relacionadas
- Representantes residenciales rotativos
- Sesiones fijas vs rotativas
- Persistencia de la sesión del navegador
Mantenga visible su cadena de suministro.obtenga su clave CaptchaAIy automatizar la recopilación de datos en todos los portales de proveedores.