Guías · Nivel 2

Python para IT: entorno virtual, un CSV, una API con token, un informe programado y el script que falla en el servidor

Un entorno virtual por script, leer un CSV con el módulo csv, llamar a una API REST con el token en una variable de entorno, escribir el informe en CSV y HTML, logging, programarlo con el Programador de tareas o cron, y el script que funciona a mano pero falla por codificación o por el proxy.

Qué vas a conseguir

Un script de Python que lee la lista de equipos de un CSV, pregunta a una API REST el estado de cada uno, escribe un informe en CSV y en HTML, deja un log de lo que ha hecho y se ejecuta solo cada mañana. Es el patrón de casi cualquier automatización de operaciones: entrada, consulta, informe, programación. Y lo que hacer cuando funciona en tu equipo y falla en el servidor.

Antes de empezar

  • Python 3 de python.org (en Windows, con el lanzador py) o el del sistema en Linux.
  • Una carpeta para el script, por ejemplo C:\scripts\informe-equipos o /opt/informe-equipos.
  • La URL de la API y un token con permisos de solo lectura, pedido a quien la administra.
  • Si la empresa usa proxy con inspección TLS, el certificado de la CA corporativa en formato PEM (te lo da el equipo de red o seguridad).

1. Un entorno virtual por script

Cada script con sus propias librerías, sin tocar el Python del sistema:

PowerShell · crear el entorno e instalar requests
cd C:\scripts\informe-equipospy -m venv .venv.\.venv\Scripts\Activate.ps1pip install requestspip freeze > requirements.txt

En Linux, python3 -m venv .venv y source .venv/bin/activate. Con requirements.txt el entorno se rehace en otro servidor (pip install -r requirements.txt); .venv no se copia ni va a Git.

2. El script

informe.py
import csvimport htmlimport loggingimport osfrom datetime import date import requests API = "https://api.contoso.local/v1/devices"TOKEN = os.environ["INVENTARIO_TOKEN"]  # nunca escrito en el códigologging.basicConfig(filename="informe.log", level=logging.INFO, encoding="utf-8",                    format="%(asctime)s %(levelname)s %(message)s") def estado(equipo):    r = requests.get(f"{API}/{equipo}", headers={"Authorization": f"Bearer {TOKEN}"}, timeout=15)    r.raise_for_status()    return r.json() filas = []with open("equipos.csv", newline="", encoding="utf-8-sig") as f:    for linea in csv.DictReader(f):        nombre = linea["Equipo"]        try:            d = estado(nombre)            filas.append({"Equipo": nombre, "SO": d.get("os"), "Ultimo contacto": d.get("lastSeen")})        except requests.RequestException as e:            logging.warning("%s: %s", nombre, e)            filas.append({"Equipo": nombre, "SO": "", "Ultimo contacto": "ERROR"})logging.info("%d equipos procesados", len(filas)) salida = f"informe-{date.today()}.csv"with open(salida, "w", newline="", encoding="utf-8-sig") as f:    w = csv.DictWriter(f, fieldnames=["Equipo", "SO", "Ultimo contacto"])    w.writeheader()    w.writerows(filas) celdas = "".join(f"<tr><td>{html.escape(str(x['Equipo']))}</td><td>{html.escape(str(x['SO']))}</td>"                 f"<td>{html.escape(str(x['Ultimo contacto']))}</td></tr>" for x in filas)with open(salida.replace(".csv", ".html"), "w", encoding="utf-8") as f:    f.write(f"<meta charset='utf-8'><table><tr><th>Equipo</th><th>SO</th><th>Último contacto</th></tr>{celdas}</table>")

Lo que importa de cada parte:

  • encoding="utf-8-sig" al leer: Excel guarda los CSV en UTF-8 con BOM, y sin -sig la primera columna se llama \ufeffEquipo y el KeyError desconcierta. Al escribir, el BOM hace que Excel abra bien las tildes.
  • timeout=15: sin él, una API que no responde deja el script colgado para siempre, y la tarea programada nunca termina.
  • raise_for_status() convierte un 401 o un 500 en una excepción que el try recoge: un equipo que falla no tumba el informe entero, y queda en el log.

3. El token, en una variable de entorno

El token es una contraseña. No va en el script, ni en el CSV, ni en Git. Se pone en una variable de entorno del usuario que ejecuta el script:

PowerShell · variable de entorno para el usuario actual
[Environment]::SetEnvironmentVariable("INVENTARIO_TOKEN", "pega-aqui-el-token", "User")# abre una consola nueva para que la veapy -c "import os; print('INVENTARIO_TOKEN' in os.environ)"True

En Linux, en un fichero chmod 600 de la cuenta de servicio. Con un almacén de secretos (Key Vault, CyberArk), el token se lee de ahí.

4. Programarlo

En Windows, con el Programador de tareas, usando el Python del entorno virtual y la carpeta del script como directorio de inicio:

PowerShell · tarea diaria a las 07:00 con una cuenta de servicio
$a = New-ScheduledTaskAction -Execute "C:\scripts\informe-equipos\.venv\Scripts\python.exe" -Argument "informe.py" -WorkingDirectory "C:\scripts\informe-equipos"$t = New-ScheduledTaskTrigger -Daily -At 07:00Register-ScheduledTask -TaskName "IT\Informe equipos" -Action $a -Trigger $t -User "CONTOSO\svc-informes" -Password (Read-Host "Contraseña")

En Linux, con crontab -e del usuario de servicio:

crontab · de lunes a viernes a las 07:00
0 7 * * 1-5 cd /opt/informe-equipos && . ./token.env && .venv/bin/python informe.py

Sin -WorkingDirectory (o el cd en cron), el script busca equipos.csv en C:\Windows\System32 o en el home, y falla con FileNotFoundError aunque a mano funcionara.

5. El ticket: "a mano funciona, programado falla"

El log de la tarea dice una de estas tres cosas:

  1. KeyError: 'INVENTARIO_TOKEN': la tarea corre con otra cuenta (svc-informes) y la variable se definió para tu usuario. Se define para la cuenta de servicio, o a nivel de máquina si la política lo permite.
  2. UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1: el CSV lo ha guardado alguien como CSV (delimitado por comas) en lugar de CSV UTF-8, y viene en cp1252. La solución buena es pedir el fichero en UTF-8; mientras tanto, encoding="cp1252" para ese origen concreto. Si el error es UnicodeEncodeError al imprimir, es la consola de Windows: la variable PYTHONUTF8=1 en la tarea lo evita.
  3. ProxyError o SSLError: CERTIFICATE_VERIFY_FAILED: en tu sesión el proxy se toma de la configuración del usuario y en la tarea no. requests usa las variables HTTPS_PROXY y NO_PROXY; y si el proxy inspecciona TLS, necesita la CA corporativa en REQUESTS_CA_BUNDLE.
PowerShell · variables de máquina para la cuenta de servicio (como administrador)
[Environment]::SetEnvironmentVariable("HTTPS_PROXY", "http://proxy.contoso.local:8080", "Machine")[Environment]::SetEnvironmentVariable("NO_PROXY", "localhost,.contoso.local", "Machine")[Environment]::SetEnvironmentVariable("REQUESTS_CA_BUNDLE", "C:\scripts\ca\contoso-root.pem", "Machine")

verify=False hace que el error desaparezca y deja la conexión abierta a cualquiera que se ponga en medio, con el token viajando dentro. No se usa, tampoco "para probar".

Siguientes pasos

  • La guía de PowerShell cubre lo mismo en el lenguaje nativo de Windows; muchas tareas de IT se hacen mejor en uno u otro, y conviene conocer los dos.
  • Microsoft Graph: la misma estructura de script sirve para leer equipos de Intune o usuarios de Entra ID, con un token de aplicación en lugar de uno fijo.
  • Cuando tienes varios de estos scripts, el siguiente paso es una consola que los reúna. Es la idea del repositorio 05-it-ops-console de mi GitHub.

¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.

Tech Career Lab · newsletter

¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.

Apúntate y llévate las 33 ideas →