Este documento explica cómo se construyó el JSON original en inglés a partir de los signos de Cornell Consultant y cómo después se generó la versión traducida al español automáticamente mediante Python. La idea es mostrar de forma clara el flujo completo: extracción → estructuración JSON → traducción automática.
El proceso completo se divide en dos fases principales. Primero se genera el JSON base en inglés mediante scraping estructurado de Cornell Consultant. Después, ese JSON se recorre y se traduce automáticamente al español manteniendo la misma estructura.
El script abre sings_link.txt, donde cada línea contiene un código (por ejemplo A00, C0A, DE3) y el nombre del proceso asociado.
Para cada código se genera la URL de Cornell Consultant correspondiente al signo: https://consultant.vet.cornell.edu/?Fun=F_Sign&spc=Canine&dxkw=&sxkw=&signs=1-XXX.
La lista de diagnósticos no aparece con un simple GET. Por eso el scraper usa un POST al mismo endpoint, enviando el payload que simula la pulsación del botón y el checkbox del signo.
Tras recibir la página “post-submit”, se analiza el HTML con BeautifulSoup y se recogen los enlaces <a class="news"> cuyo href contiene Fun=Cause_. Cada texto se guarda como diagnóstico diferencial.
Se crea un JSON estructurado con los campos Proceso, especialidades, Diagnósticos diferenciales y una sección meta con el código y la URL utilizada.
Después, un segundo script carga consultant_signs_canine.json y traduce automáticamente Proceso, especialidades y cada elemento de Diagnósticos diferenciales usando GoogleTranslator.
Si una traducción falla, el script reintenta tras unos segundos. Si vuelve a fallar, conserva el texto original para no romper el proceso. Además, guarda una copia temporal cada 10 items como medida de seguridad.
Este script genera el JSON base en inglés desde sings_link.txt, simulando correctamente el POST que hace aparecer los diagnósticos diferenciales en la web.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import re
import time
from dataclasses import dataclass
from typing import Dict, List, Tuple
import requests
from bs4 import BeautifulSoup
BASE_URL = "https://consultant.vet.cornell.edu/"
URL_TMPL = BASE_URL + "?Fun=F_Sign&spc=Canine&dxkw=&sxkw=&signs=1-{code}"
INPUT_TXT = "sings_link.txt"
OUTPUT_JSON = "consultant_signs_canine.json"
SYSTEM_MAP: Dict[str, str] = {
"A": "Acoustic",
"C": "Cardiovascular",
"D": "Digestive",
"E": "Ophthalmology",
"G": "General",
"M": "Musculoskeletal",
"N": "Nervous",
"P": "Pain/Discomfort",
"R": "Respiratory",
"S": "Skin/Integumentary",
"T": "Reproductive",
"U": "Urinary",
}
SLEEP_SECONDS = 0.6
TIMEOUT = 25
@dataclass
class SignRow:
code: str
proceso: str
def read_signs_txt(path: str) -> List[SignRow]:
rows: List[SignRow] = []
with open(path, "r", encoding="utf-8", errors="replace") as f:
for raw in f:
line = raw.strip()
if not line:
continue
parts = re.split(r"\t+", line, maxsplit=1)
if len(parts) < 2:
parts = re.split(r"\s{2,}", line, maxsplit=1)
if len(parts) < 2:
continue
code = parts[0].strip()
proceso = parts[1].strip()
if code and proceso:
rows.append(SignRow(code=code, proceso=proceso))
return rows
def infer_system(code: str) -> str:
return SYSTEM_MAP.get(code[:1].upper(), "All") if code else "All"
def extract_diagnoses(html: str) -> List[str]:
soup = BeautifulSoup(html, "html.parser")
diags: List[str] = []
seen = set()
for a in soup.select("a.news"):
href = a.get("href", "") or ""
if "Fun=Cause_" not in href:
continue
text = a.get_text(" ", strip=True)
if text and text not in seen:
seen.add(text)
diags.append(text)
return diags
def fetch_page_after_submit(session: requests.Session, code: str) -> str:
"""
IMPORTANT: The diagnoses list appears after submitting the form (POST),
not just by opening the URL (GET).
"""
url = URL_TMPL.format(code=code)
data = {
"SPC": "Canine",
"kw": "",
"SubBut": "Search for Diagnoses",
f"SX_{code}": f"SX_{code}",
}
r = session.post(url, data=data, timeout=TIMEOUT)
r.raise_for_status()
r.encoding = r.apparent_encoding or "utf-8"
return r.text
def main() -> None:
signs = read_signs_txt(INPUT_TXT)
if not signs:
raise SystemExit(f"No valid rows found in {INPUT_TXT}")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) CornellConsultantScraper/1.1",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Connection": "keep-alive",
"Referer": BASE_URL,
})
out: List[dict] = []
errors: List[Tuple[str, str]] = []
for i, row in enumerate(signs, start=1):
try:
html = fetch_page_after_submit(session, row.code)
diags = extract_diagnoses(html)
out.append({
"Proceso": row.proceso,
"especialidades": infer_system(row.code),
"Diagnósticos diferenciales": diags,
"meta": {
"sign_code": row.code,
"url": URL_TMPL.format(code=row.code),
"method": "POST(SubBut=Search for Diagnoses)"
}
})
except Exception as e:
errors.append((row.code, str(e)))
time.sleep(SLEEP_SECONDS)
if i % 25 == 0:
print(f"Progress: {i}/{len(signs)}")
payload = {
"source": "consultant.vet.cornell.edu",
"species": "Canine",
"generated_from": INPUT_TXT,
"count": len(out),
"items": out,
"errors": [{"code": c, "error": msg} for c, msg in errors],
}
with open(OUTPUT_JSON, "w", encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
print(f"Saved: {OUTPUT_JSON} (items={len(out)}, errors={len(errors)})")
if __name__ == "__main__":
main()
Este segundo script carga el JSON base en inglés y traduce automáticamente los campos principales al español, generando la versión consultant_signs_canine_ES.json.
import json
import time
from deep_translator import GoogleTranslator
from deep_translator.exceptions import TranslationNotFound
# Configurar traductor
traductor = GoogleTranslator(source='en', target='es')
# Cargar datos
filename = 'consultant_signs_canine.json'
try:
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: No se encuentra {filename}")
exit()
items = data['items']
total = len(items)
def traducir_texto(texto):
if not texto or texto.strip() == "":
return texto
try:
# Intentar traducir
return traductor.translate(texto)
except Exception:
try:
# Si falla, esperar 5 segundos y reintentar una vez
print(f" ...reintentando traducción para: {texto[:30]}...")
time.sleep(5)
return traductor.translate(texto)
except Exception:
# Si vuelve a fallar, devolver el original para no romper el script
print(f" ⚠️ No se pudo traducir, manteniendo original: {texto[:30]}")
return texto
print(f"Iniciando traducción de {total} procesos médicos...")
for i, item in enumerate(items):
# Traducir Proceso
item['Proceso'] = traducir_texto(item['Proceso'])
# Traducir Especialidad
item['especialidades'] = traducir_texto(item['especialidades'])
# Traducir lista de diagnósticos
nuevos_dx = []
for dx in item['Diagnósticos diferenciales']:
nuevos_dx.append(traducir_texto(dx))
# Pausa mínima entre frases para evitar bloqueos
time.sleep(0.1)
item['Diagnósticos diferenciales'] = nuevos_dx
# Mostrar progreso
print(f"✅ Progreso: {i+1}/{total} ({((i+1)/total)*100:.1f}%) - Último: {item['Proceso']}")
# Guardar copia de seguridad cada 10 items por si acaso
if i % 10 == 0:
with open('consultant_signs_canine_ES_temp.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Guardar resultado final
with open('consultant_signs_canine_ES.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("\n¡FINALIZADO! Archivo guardado como: consultant_signs_canine_ES.json")