← Ruta Perico
Etapa 01

Montar el entorno de Python

Llano Publicada 15 min
01

Python instala los paquetes en un sitio que sorprende a casi todo el mundo la primera vez, y conviene entenderlo antes de instalar nada.

La diferencia que importa

pip installpip es el instalador de paquetes de Python — no escribe dentro del proyecto. Escribe en el Python global del sistema. Instalas algo para un proyecto y se lo acabas metiendo a todos los demás — y a macOS, que usa su propio Python para sus cosas.

Quien venga de Node reconocerá el contraste: allí npm install escribe en el node_modules/ de la carpeta en la que estás, así que el aislamiento sale gratis y sin pedirlo. En Python hay que pedirlo.

El entorno virtual (venv) existe para eso: una copia aislada del intérprete y sus paquetes, dentro del proyecto.

Si el vocabulario de Node sirve de referencia, el mapa completo es este:

En NodeEn Python
node_modules/.venv/
npmpip
package.jsonrequirements.txt
package-lock.jsonel mismo requirements.txt, con versiones fijadas

Crear el entorno

No uses el Python del sistema (en macOS suele ser un 3.9 antiguo). Instala uno moderno con Homebrew y crea el entorno con él:

terminal
brew install python@3.14

# crea la carpeta .venv/ con un interprete aislado
/opt/homebrew/bin/python3.14 -m venv .venv

.venv/bin/python --version
# Python 3.14.7
Sáltate el «activate»

Verás en todas partes source .venv/bin/activate. Funciona, pero deja estado invisible en tu shell: abres otra pestaña, se te olvida, y acabas instalando en el Python global sin enterarte.

Llama al binario directamente — .venv/bin/python, .venv/bin/pip. Siempre correcto, nunca ambiguo.

Instalar y fijar las dependencias

Para el scraper solo hacen falta dos paquetes:

terminal
.venv/bin/pip install httpx beautifulsoup4

Y se anotan en requirements.txt con la versión exacta:

requirements.txt
httpx==0.28.1           # cliente HTTP (el fetch/axios de Python)
beautifulsoup4==4.15.0  # parseo de HTML (el cheerio de Python)
Por qué == y no >=

Python no genera lockfile por su cuenta. Ese fichero hace de lista de dependencias y de cierre de versiones a la vez — en Node harían falta package.json y package-lock.json para lo mismo. Con >=, dentro de tres meses pip install -r instalará otra versión y no sabrás por qué se rompió.

Estructura y .gitignore

estructura del proyecto
perico-translator/
├── .venv/            # interprete aislado — NO se versiona
├── requirements.txt
├── data/
│   ├── raw/          # volcado del scraper — NO se versiona
│   ├── archive/      # texto crudo extraido — SI se versiona
│   └── clean/        # corpus procesado — SI se versiona
├── src/
└── docs/

Las tres carpetas de data/ no son manía organizativa: son las tres fases por las que pasa todo pipeline de datos, y conviene fijarlas ya. raw/ es el volcado tal cual llega de la fuente, sin tocar ni un carácter. archive/ es el texto ya extraído de ese volcado pero sin filtrar: la copia de seguridad de una fuente que puede desaparecer mañana. clean/ es el corpus procesado, el único que consume el resto del proyecto.

Cada paso lee de una carpeta y escribe en la siguiente, y nunca sobrescribe la anterior. Eso es lo que te deja cambiar de criterio sobre qué es una frase buena y reejecutar en un segundo, en vez de volver a bajarlo todo.

Que data/raw/ no vaya a git y que data/archive/ y data/clean/ vayan es deliberado, y la regla de detrás sirve para cualquier proyecto de datos: se versiona lo que no puedas volver a conseguir.

raw/ es el volcado en bruto. Ocupa mucho y, mientras la fuente siga viva, se recupera con un comando. archive/ y clean/ son texto ya extraído, y ahí está el matiz que lo decide todo: las fuentes se pudren. Los vídeos se borran y las webs se caen, y el día que eso pasa el volcado ya no se puede regenerar. El corpus procesado es el activo real del proyecto.

Dicho corto: capturar es caro e irrepetible, procesar es barato e infinito. Es la misma regla que en la etapa 6 decide que no se filtre nada durante la captura.

Comprobar que funciona

Nunca des por bueno un entorno sin ejecutar algo real en él:

terminal
.venv/bin/python -c "
import httpx, bs4
r = httpx.get('https://example.com', timeout=10)
soup = bs4.BeautifulSoup(r.text, 'html.parser')
print(r.status_code, soup.find('h1').text)
"
# 200 Example Domain