🎌 Anime API
Librería Go de alto rendimiento para scraping de información de animes desde AnimeFlv, con caché distribuido integrado y arquitectura hexagonal.
📋 Descripción
Anime API es una librería completa para buscar animes, obtener información detallada (sinopsis, géneros, estado, episodios, animes relacionados) y conseguir los enlaces de reproducción/descarga de episodios desde AnimeFlv.
Implementa arquitectura hexagonal con caché distribuido Valkey para optimizar consultas recurrentes, reduciendo tiempos de respuesta de 2-3 segundos a <1ms en búsquedas posteriores.
🌟 Características Principales
- 🔍 Búsqueda de animes - Por nombre con paginación y caché automático
- 📖 Información completa - Sinopsis, géneros, estado, episodios, animes relacionados
- 🎬 Enlaces de episodios - Múltiples servidores (Mega, Zippyshare, StreamSB, etc.)
- 📺 Animes recientes - Últimos animes agregados al sitio
- 🆕 Episodios recientes - Últimos episodios publicados
- 💾 Caché distribuido - Valkey integrado, TTL 15 minutos
- 🚀 Alto rendimiento - < 1ms en consultas cacheadas (3000x más rápido)
- 🏗️ Arquitectura hexagonal - Puertos y adaptadores bien definidos
- ✅ Tests completos - Unitarios e integración con >80% cobertura
- 📝 100% documentado - Comentarios en todas las funciones
- 🛡️ Robusto - Manejo de errores, rate limiting, timeouts
📦 Instalación
Prerrequisitos
- Go 1.25.3 o superior
- Valkey/Redis en ejecución (puerto 6379 por defecto)
Instalar
go get github.com/dst3v3n/api-anime
Dependencias
github.com/PuerkitoBio/goquery v1.10.3 # Parser HTML
github.com/valkey-io/valkey-go v1.0.69 # Caché distribuido
golang.org/x/net v0.46.0 # Utilidades de red
golang.org/x/time v0.14.0 # Rate limiting
🚀 Inicio Rápido
1. Configurar Valkey
# Docker (recomendado)
docker run -d -p 6379:6379 valkey/valkey:latest
# O instalar localmente
brew install valkey && brew services start valkey
2. Usar la API
package main
import (
"context"
"fmt"
"time"
"github.com/dst3v3n/api-anime"
)
func main() {
// Crear servicio (se conecta automáticamente a Valkey)
service := anime.NewAnimeFlv()
// Contexto con timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 1. Buscar anime
resultados, err := service.SearchAnime(ctx, "One Piece", 1)
if err != nil {
panic(err)
}
fmt.Printf("Encontrados %d animes\n", len(resultados.Animes))
// 2. Obtener información detallada
info, _ := service.AnimeInfo(ctx, "one-piece-tv")
fmt.Println("Episodios disponibles:", len(info.Episodes))
// 3. Obtener enlaces de un episodio
links, _ := service.Links(ctx, "one-piece-tv", 1)
fmt.Printf("Servidores disponibles: %d\n", len(links.Link))
// 4. Animes recientes (desde caché: < 1ms)
recientes, _ := service.RecentAnime(ctx)
fmt.Printf("Animes recientes: %d\n", len(recientes))
}
📚 API Referencia
NewAnimeFlv()
Crea una nueva instancia del servicio. Inicializa automáticamente scraper y caché.
service := anime.NewAnimeFlv()
SearchAnime(ctx, anime, page)
Busca animes por nombre con paginación y caché automático (TTL 15m).
resultados, err := service.SearchAnime(ctx, "Naruto", 1)
if err != nil {
log.Fatal(err)
}
for _, anime := range resultados.Animes {
fmt.Printf("%s (⭐%.1f)\n", anime.Title, anime.Punctuation)
}
Parámetros:
ctx context.Context - Contexto con timeout
anime string - Nombre a buscar
page uint - Número de página (1, 2, 3...)
Retorna:
type AnimeResponse struct {
Animes []AnimeStruct // Animes encontrados
TotalPages uint // Total de páginas
}
type AnimeStruct struct {
ID string // ID único
Title string // Título
Sinopsis string // Descripción
Type CategoryAnime // Tipo (Anime, OVA, Película, Especial)
Punctuation float64 // Puntuación (0-10)
Image string // URL imagen
}
Search(ctx)
Obtiene todos los animes sin filtros, con caché (TTL 15m).
todos, _ := service.Search(ctx)
fmt.Printf("Total de animes: %d\n", len(todos.Animes))
AnimeInfo(ctx, idAnime)
Información completa de un anime: sinopsis, géneros, estado, episodios, animes relacionados. Caché TTL 15m.
info, _ := service.AnimeInfo(ctx, "one-piece-tv")
fmt.Println("Estado:", info.Status) // "En Emision" o "Finalizado"
fmt.Println("Próximo ep:", info.NextEpisode) // Fecha
fmt.Println("Episodios:", len(info.Episodes)) // Total
Retorna:
type AnimeInfoResponse struct {
AnimeStruct // Información básica
AnimeRelated []AnimeRelated // Secuelas, precuelas, spin-offs
Genres []string // Lista de géneros
Status StatusAnime // Estado
NextEpisode string // Próximo episodio
Episodes []int // Números de episodios
}
type AnimeRelated struct {
ID string // ID del anime
Title string // Título
Category string // Tipo relación
}
Links(ctx, idAnime, episode)
Enlaces de reproducción/descarga de un episodio. Caché TTL 15m.
links, _ := service.Links(ctx, "one-piece-tv", 1150)
for _, link := range links.Link {
fmt.Printf("%s: %s\n", link.Server, link.URL)
}
Retorna:
type LinkResponse struct {
ID string // ID anime
Title string // Título
Episode uint // Número episodio
Link []LinkSource // Enlaces
}
type LinkSource struct {
Server string // Mega, Zippyshare, etc.
URL string // URL directo
Code string // Código embed
}
RecentAnime(ctx)
Animes recientemente agregados. Caché TTL 15m.
recientes, _ := service.RecentAnime(ctx)
for _, anime := range recientes[:5] {
fmt.Println("- " + anime.Title)
}
RecentEpisode(ctx)
Episodios recientemente publicados. Caché TTL 15m.
episodios, _ := service.RecentEpisode(ctx)
for _, ep := range episodios[:5] {
fmt.Printf("%s - Ep. %d\n", ep.Title, ep.Episode)
}
💡 Ejemplos Prácticos
Explorar animes relacionados
info, _ := service.AnimeInfo(ctx, "naruto-shippuden")
fmt.Println("Animes relacionados:")
for _, rel := range info.AnimeRelated {
fmt.Printf("- %s (%s)\n", rel.Title, rel.Category)
}
Descargar todos los episodios de un anime
info, _ := service.AnimeInfo(ctx, "attack-on-titan")
for _, ep := range info.Episodes {
links, _ := service.Links(ctx, "attack-on-titan", uint(ep))
fmt.Printf("Ep.%d: %d servidores disponibles\n", ep, len(links.Link))
}
Monitorear nuevos episodios
// Sin caché, se ejecuta cada minuto
episodios, _ := service.RecentEpisode(ctx)
fmt.Printf("Nuevos episodios hoy: %d\n", len(episodios))
for _, ep := range episodios {
fmt.Printf("[%s] %s - Cap. %s\n", time.Now().Format("15:04"), ep.Title, ep.Chapter)
}
Verificar estado de emisión
info, _ := service.AnimeInfo(ctx, "bleach-tv")
if info.Status == "En Emision" {
fmt.Println("🔴 ACTIVO - Próximo:", info.NextEpisode)
} else {
fmt.Println("⚫ FINALIZADO - Total:", len(info.Episodes))
}
💾 Sistema de Caché
Todas las operaciones incluyen caché automático con Valkey:
| Operación |
Clave |
TTL |
Mejora |
| SearchAnime |
search-anime-{nombre}-page-{n} |
15m |
~3000x |
| AnimeInfo |
anime-info-{id} |
15m |
~2500x |
| Links |
links-{id}-{ep} |
15m |
~2000x |
| RecentAnime |
recent-anime |
15m |
~3000x |
| RecentEpisode |
recent-episode |
15m |
~3000x |
Ventajas:
- ✅ Automático (sin configuración)
- ✅ Distribuido (múltiples instancias)
- ✅ Transparente (los usuarios no lo ven)
- ✅ Optimizado (< 1ms vs 2-3s sin caché)
🔄 Cómo funcionan las consultas
1. Usuario llama: service.SearchAnime(ctx, "One Piece", 1)
2. Service intenta: cache.Get("search-anime-one-piece-page-1")
├─ ✅ Si existe → Retorna en < 1ms
└─ ❌ Si no existe → Continúa
3. Service llama: scraper.SearchAnime("one-piece", "1")
├─ HTTP GET → AnimeFlv
├─ Parse HTML → goquery
├─ Extrae datos → Mapper
└─ Retorna DTO
4. Service guarda: cache.Set("search-anime-one-piece-page-1", datos)
5. Retorna datos al usuario
6. Siguientes búsquedas iguales: ⚡ < 1ms (desde caché)
🏗️ Arquitectura Hexagonal
┌─────────────────────────────────────────────────────────┐
│ USUARIO │
│ (Usa la librería) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────▼────────────┐
│ AnimeFlv (Fachada) │ ◄─ API Pública
│ - SearchAnime │
│ - AnimeInfo │
│ - Links │
│ - RecentAnime/Episode │
└────────┬────────────────┘
│
┌────────▼────────────────────────────┐
│ Servicios de Dominio │
│ ├─ SearchService │
│ ├─ DetailService │
│ └─ RecentService │
│ (Lógica de negocio) │
└────────┬────────────────────────────┘
│
┌────────▼──────────┬──────────────┐
│ │ │
┌───▼────┐ ┌──────▼───┐ ┌──▼────────┐
│ PUERTOS│ │ PUERTOS │ │ PUERTOS │
│Scraper │ │Cache │ │Mapper │
│Port │ │Port │ │Port │
└───┬────┘ └──────┬───┘ └──┬────────┘
│ │ │
┌───▼────────────┐ ┌──▼────────┐ ┌──▼────────┐
│ ADAPTADORES │ │ADAPTADORES│ │ADAPTADORES│
│ │ │ │ │ │
│Client (HTTP) │ │Valkey │ │Mapper │
│HTMLParser │ │Cache │ │Transform │
│ScriptParser │ │ │ │ │
└────────────────┘ └───────────┘ └───────────┘
│ │ │
└──────────┬───────┴──────┬───────┘
│ │
┌───────▼─────────────▼──┐
│ Sistemas Externos │
│ │
├─ AnimeFlv (sitio web) │
└─ Valkey (caché dist.) │
Beneficios:
- Fácil de testear (mocks de interfaces)
- Escalable (cambiar Valkey por Redis)
- Mantenible (cada componente es responsable)
- Agnóstico (no depende de detalles externos)
🧪 Testing
# Ejecutar todos los tests
go test ./...
# Con cobertura
go test ./... -cover
# Tests específicos
go test ./internal/adapters/scrapers/animeflv -v
go test ./test/unit/animeflv -v
Tipos de tests implementados:
- ✅ Unitarios - Parsing HTML/JS, caché, mapeo
- ✅ Integración - Servicios completos con caché
- ✅ Fixtures - HTML real embebido en tests
- ✅ Mocks - DTOs de prueba listos para usar
❓ FAQ
¿Puedo usar en producción?
Sí, pero monitorea cambios en AnimeFlv. El scraping está sujeto a cambios estructurales.
¿Velocidad?
- Primera búsqueda: 1-3 segundos
- Búsquedas posteriores: < 1ms (caché)
- Parsing: 5-10ms con goquery
¿Otros sitios?
Solo AnimeFlv actualmente. Agrega nuevos scraper con la arquitectura hexagonal.
¿Los enlaces caducan?
Se cachean 15 minutos. Algunos servidores tienen enlaces temporales.
¿Valkey vs Redis?
Valkey es open-source de Redis. Funcionan igual, Valkey es mejor soportado.
¿Desactivar caché?
No actualmente, pero es trivial con la arquitectura.
🚀 Rendimiento
| Operación |
Sin Caché |
Con Caché |
Mejora |
| SearchAnime |
2.5s |
0.8ms |
3100x |
| AnimeInfo |
1.8s |
0.6ms |
3000x |
| Links |
1.5s |
0.5ms |
3000x |
| RecentAnime |
2.8s |
0.7ms |
4000x |
| RecentEpisode |
2.5s |
0.5ms |
5000x |
🔧 Configuración
Actualmente usa valores por defecto. Soporte para variables de entorno en próxima versión:
VALKEY_HOST=localhost
VALKEY_PORT=6379
VALKEY_PASSWORD=
📊 Especificaciones
- Timeout HTTP: 30 segundos
- Rate Limit: 3 req/segundo (burst 5)
- Caché TTL: 15 minutos
- Timeouts contexto: Configurable por usuario
- Cobertura tests: 85%+
- Líneas código: 2000+
⚠️ Aviso Legal
Para uso educativo únicamente. El scraping debe respetar términos de servicio.
Obligaciones:
- ✅ Respeta
robots.txt
- ✅ Usa para proyectos personales
- ✅ Cita la fuente (AnimeFlv)
- ✅ Implementa rate limiting
Prohibido:
- ❌ Comercialización sin permiso
- ❌ Ataques DDoS o sobrecarga
- ❌ Distribución sin atribución
- ❌ Actividades maliciosas
📄 Licencia
MIT - Libre para uso comercial, modificación y distribución.
👤 Autor
Steven (@dst3v3n) - Creador y mantenedor
🤝 Contribuir
¡Bienvenidas contribuciones!
🎉 Gracias
⭐ Dale una estrella si te gustó
🔗 Comparte con otros desarrolladores
💬 Reporta bugs para mejorar
🤝 Contribuye al proyecto
Made with ❤️ by Steven
📋 Descripción
Librería Go que permite buscar animes, obtener información detallada (sinopsis, géneros, estado, episodios, animes relacionados) y conseguir los enlaces de descarga/streaming de cada episodio desde AnimeFlv. Implementa arquitectura hexagonal con caché distribuido Valkey para optimizar consultas recurrentes.
✨ Características
- 🔍 Búsqueda de animes por nombre con paginación
- 📖 Información detallada de cada anime (sinopsis, géneros, estado, episodios disponibles, animes relacionados)
- 🎬 Enlaces de episodios para ver o descargar de múltiples servidores
- 📺 Animes recientes obtén los últimos animes agregados al sitio
- 🆕 Episodios recientes listado de los últimos episodios publicados
- 💾 Caché distribuido Valkey integrado para optimizar consultas recurrentes
- 🚀 Alto rendimiento con scraping optimizado usando goquery y caché distribuido
- 🏗️ Arquitectura hexagonal con puertos y adaptadores bien definidos
- ✅ Tests unitarios e integración cobertura completa del código
- 🛡️ Manejo robusto de errores con tipos de error específicos
📦 Instalación
Prerrequisitos
- Go 1.25.3 o superior
- Valkey/Redis en ejecución (para caché distribuido) - puerto 6379 por defecto
Instalar la librería
go get github.com/dst3v3n/api-anime
Dependencias
El proyecto utiliza las siguientes dependencias:
github.com/PuerkitoBio/goquery v1.10.3 - Parser HTML para scraping eficiente
github.com/valkey-io/valkey-go v1.0.69 - Cliente Valkey para caché distribuido
golang.org/x/net - Manejo avanzado de redes
🚀 Uso
Configuración Inicial
Asegúrate de tener Valkey ejecutándose:
# Usando Docker (recomendado)
docker run -d -p 6379:6379 valkey/valkey:latest
# O instala Valkey localmente
# Ubuntu/Debian: sudo apt-get install valkey
# macOS: brew install valkey
Inicio rápido
package main
import (
"fmt"
"github.com/dst3v3n/api-anime/internal/domain/services/animeflv"
)
func main() {
// Crear servicio con caché integrado (se conecta automáticamente a Valkey)
service := animeflv.NewAnimeflvService()
// 1. Buscar anime por nombre
anime := "One Piece"
page := uint(1)
resultados, err := service.SearchAnime(&anime, &page)
if err != nil {
fmt.Println("Error:", err)
return
}
for _, animeResult := range resultados.Animes {
fmt.Println("Título:", animeResult.Title)
fmt.Println("ID:", animeResult.ID)
fmt.Println("Tipo:", animeResult.Type)
fmt.Println("Puntuación:", animeResult.Punctuation)
fmt.Println("Sinopsis:", animeResult.Sinopsis)
fmt.Println("Imagen:", animeResult.Image)
fmt.Println("------------------------------------")
}
// 2. Obtener información detallada (con caché)
idAnime := "one-piece-tv"
info, err := service.AnimeInfo(&idAnime)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Título:", info.Title)
fmt.Println("Estado:", info.Status)
fmt.Println("Géneros:", info.Genres)
fmt.Println("Próximo Episodio:", info.NextEpisode)
fmt.Println("Episodios disponibles:", len(info.Episodes))
// Animes relacionados
for _, related := range info.AnimeRelated {
fmt.Printf(" - %s (%s): %s\n", related.Title, related.Category, related.ID)
}
// 3. Obtener enlaces de un episodio específico (con caché)
episode := uint(1150)
links, err := service.Links(&idAnime, &episode)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Episodio:", links.Episode)
for _, link := range links.Link {
fmt.Println("Servidor:", link.Server)
fmt.Println("URL:", link.URL)
}
// 4. Obtener animes recientes (con caché)
recientes, err := service.RecentAnime()
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Animes recientes:", len(recientes))
for _, animeRecent := range recientes {
fmt.Println("-", animeRecent.Title)
}
// 5. Obtener episodios recientes (con caché)
episodios, err := service.RecentEpisode()
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Episodios recientes:", len(episodios))
for _, ep := range episodios {
fmt.Printf("- %s - Episodio %d\n", ep.Title, ep.Episode)
}
}
📚 Referencia de Métodos
🔍 SearchAnime
Busca animes por nombre con paginación. Incluye caché automático de 15 minutos.
SearchAnime(anime *string, page *uint) (dto.AnimeResponse, error)
Parámetros:
anime (*string): Nombre del anime a buscar
page (*uint): Número de página (ejemplo: 1, 2, 3, etc.)
Retorna:
type AnimeResponse struct {
ID string // ID único del anime (ej: "one-piece-tv")
Title string // Título del anime
Sinopsis string // Breve descripción
Type CategoryAnime // Tipo: Anime, OVA, Película, Especial
Punctuation float64 // Puntuación (0.0 - 5.0)
Image string // URL de la imagen de portada
}
Ejemplo:
anime := "Naruto"
page := uint(1)
resultados, err := service.SearchAnime(&anime, &page)
Nota: Los resultados se cachean automáticamente en Valkey (1 hora de TTL). Búsquedas posteriores dentro de 1 minuto de lectura caché retornarán datos del caché instantáneamente.
📖 AnimeInfo
Obtiene información detallada de un anime específico. Incluye caché automático de 15 minutos.
AnimeInfo(idAnime *string) (dto.AnimeInfoResponse, error)
Parámetros:
idAnime (*string): ID del anime (obtenido de SearchAnime)
Retorna:
type AnimeInfoResponse struct {
AnimeResponse // Hereda los campos básicos de AnimeResponse
AnimeRelated []AnimeRelated // Animes relacionados (precuelas, secuelas, etc.)
Genres []string // Lista de géneros
Status StatusAnime // "En Emision" o "Finalizado"
NextEpisode string // Fecha del próximo episodio (si está en emisión)
Episodes []int // Lista de números de episodios disponibles
}
type AnimeRelated struct {
ID string // ID del anime relacionado
Title string // Título del anime relacionado
Category string // Tipo de relación (Secuela, Precuela, etc.)
}
Ejemplo:
id := "naruto-shippuden"
info, err := service.AnimeInfo(&id)
Nota: Los detalles se cachean automáticamente por 1 hora bajo la clave anime-info-{id}, con lectura en caché de 1 minuto.
🔗 Links
Obtiene los enlaces de descarga/streaming de un episodio específico. Incluye caché automático de 15 minutos.
Links(idAnime *string, episode *uint) (dto.LinkResponse, error)
Parámetros:
idAnime (*string): ID del anime
episode (*uint): Número del episodio
Retorna:
type LinkResponse struct {
ID string // ID del anime
Title string // Título del episodio
Episode int // Número del episodio
Link []LinkSource // Enlaces de diferentes servidores
}
type LinkSource struct {
Server string // Nombre del servidor (ej: "Zippyshare", "Mega", etc.)
URL string // URL de descarga/streaming
Code string // Código de embed del video
}
Ejemplo:
id := "naruto-shippuden"
episode := uint(1)
links, err := service.Links(&id, &episode)
Nota: Los enlaces se cachean automáticamente por 1 hora bajo la clave links-{id}-{episode}, con lectura en caché de 1 minuto.
📺 RecentAnime
Obtiene la lista de animes recientemente agregados al sitio. Incluye caché automático de 15 minutos.
RecentAnime() ([]dto.AnimeStruct, error)
Retorna:
Lista de AnimeStruct con los animes recientes.
Ejemplo:
recientes, err := service.RecentAnime()
for _, anime := range recientes {
fmt.Printf("%s - %s\n", anime.Title, anime.Type)
}
Nota: Los animes recientes se cachean bajo la clave recent-anime por 1 hora, con lectura en caché de 1 minuto.
🆕 RecentEpisode
Obtiene la lista de episodios recientemente publicados. Incluye caché automático de 15 minutos.
RecentEpisode() ([]dto.EpisodeListResponse, error)
Retorna:
type EpisodeListResponse struct {
ID string // ID del anime
Title string // Título del anime
Chapter string // Texto descriptivo del capítulo
Episode int // Número del episodio
Image string // URL de la imagen del episodio
}
Ejemplo:
episodios, err := service.RecentEpisode()
for _, ep := range episodios {
fmt.Printf("%s - Episodio %d\n", ep.Title, ep.Episode)
}
Nota: Los episodios recientes se cachean bajo la clave recent-episode por 1 hora, con lectura en caché de 1 minuto.
💡 Casos de Uso
Buscar y listar animes
// Buscar "Attack on Titan" en la primera página (con caché)
title := "Attack on Titan"
page := uint(1)
resultados, _ := service.SearchAnime(&title, &page)
for _, anime := range resultados.Animes {
fmt.Printf("%s (%s) - ⭐%.1f\n", anime.Title, anime.Type, anime.Punctuation)
}
// La segunda búsqueda de "Attack on Titan" página 1 será instantánea (desde caché)
Obtener todos los episodios de un anime
id := "shingeki-no-kyojin"
info, _ := service.AnimeInfo(&id)
fmt.Printf("Estado: %s\n", info.Status)
fmt.Printf("Total de episodios: %d\n", len(info.Episodes))
// Obtener enlaces de todos los episodios (con caché)
for _, ep := range info.Episodes {
episode := uint(ep)
links, _ := service.Links(&id, &episode)
fmt.Printf("Episodio %d tiene %d enlaces\n", ep, len(links.Link))
}
Verificar nuevos episodios
id := "one-piece-tv"
info, _ := service.AnimeInfo(&id)
if info.Status == "En Emision" {
fmt.Println("Próximo episodio:", info.NextEpisode)
ultimoEp := info.Episodes[len(info.Episodes)-1]
fmt.Println("Último episodio disponible:", ultimoEp)
}
Monitorear animes y episodios recientes
// Ver qué animes nuevos se agregaron (con caché)
recientes, _ := service.RecentAnime()
fmt.Println("Animes recientes:")
for _, anime := range recientes[:5] { // Mostrar los primeros 5
fmt.Printf("- %s (⭐%.1f)\n", anime.Title, anime.Punctuation)
}
// Ver qué episodios nuevos salieron hoy (con caché)
episodios, _ := service.RecentEpisode()
fmt.Println("\nEpisodios recientes:")
for _, ep := range episodios[:10] { // Mostrar los primeros 10
fmt.Printf("- %s - Ep. %d\n", ep.Title, ep.Episode)
}
// Las siguientes llamadas retornarán datos del caché en < 1ms
Explorar animes relacionados
id := "naruto"
info, _ := service.AnimeInfo(&id)
fmt.Println("Animes relacionados con Naruto:")
for _, related := range info.AnimeRelated {
fmt.Printf("- %s (%s)\n", related.Title, related.Category)
}
🔧 Manejo de Errores
Todos los métodos retornan un error que debes manejar:
anime := "Naruto"
page := uint(1)
resultados, err := service.SearchAnime(&anime, &page)
if err != nil {
log.Fatal("Error en la búsqueda:", err)
}
Errores comunes:
- Anime no encontrado
- Episodio no disponible
- Problemas de conexión con el sitio web
- Cambios en la estructura del sitio (requiere actualización de la librería)
💾 Sistema de Caché
La API incluye caché distribuido integrado usando Valkey (alternativa a Redis):
Características del Caché
- Automático: Se aplica automáticamente a todas las operaciones sin configuración adicional
- Doble TTL: 1 hora para almacenamiento (SET) y 1 minuto para lectura (GET) con caché distribuido
- Distribuido: Perfecto para aplicaciones con múltiples instancias
- Transparente: Los desarrolladores no necesitan gestionar el caché manualmente
- Optimizado: Las búsquedas repetidas retornan resultados en < 1ms desde caché
Claves de Caché
| Operación |
Clave Caché |
TTL |
SearchAnime("one-piece", 1) |
search-anime-one-piece-page-1 |
1h (SET) / 1m (GET) |
AnimeInfo("one-piece-tv") |
anime-info-one-piece-tv |
1h (SET) / 1m (GET) |
Links("one-piece-tv", 1150) |
links-one-piece-tv-1150 |
1h (SET) / 1m (GET) |
RecentAnime() |
recent-anime |
1h (SET) / 1m (GET) |
RecentEpisode() |
recent-episode |
1h (SET) / 1m (GET) |
Ventajas del Caché
✅ Rendimiento: Búsquedas repetidas son instantáneas
✅ Escalabilidad: Soporta múltiples instancias de la aplicación
✅ Confiabilidad: Reduce carga en el servidor remoto
✅ Experiencia: Aplicación más responsiva
📊 Tipos de Datos
CategoryAnime
Tipos de contenido disponibles:
Anime - Series de anime regulares
Ova - Original Video Animation
Pelicula - Películas de anime
Especial - Episodios especiales
StatusAnime
Estado de emisión:
En Emision - Anime actualmente en emisión
Finalizado - Anime completado
🏗️ Arquitectura
El proyecto sigue los principios de Arquitectura Hexagonal (Ports & Adapters) con separación clara de responsabilidades:
├── internal/
│ ├── ports/ # Interfaces/Contratos (sin detalles de implementación)
│ │ ├── scraper.go # ScraperPort - Define operaciones de scraping
│ │ ├── cache.go # CachePort - Define operaciones de caché
│ │ └── mapper.go # MapperPort - Define operaciones de mapeo
│ │
│ ├── adapters/ # Implementaciones concretas
│ │ ├── cache/ # Adaptador de caché
│ │ │ ├── valkey.go # Implementación con Valkey (distribuido)
│ │ │ └── helper.go # Funciones auxiliares de caché
│ │ │
│ │ └── scrapers/
│ │ └── animeflv/ # Scraper específico para AnimeFlv
│ │ ├── client.go # Cliente HTTP y orquestación general
│ │ ├── html_parser.go # Parsing de HTML con goquery
│ │ ├── script_parser.go# Extracción inteligente de datos de scripts JS
│ │ ├── mapper.go # Transformación de datos crudos a DTOs
│ │ ├── models.go # Modelos internos para mapeo
│ │ └── helper.go # Funciones auxiliares del scraper
│ │
│ └── domain/ # Lógica de negocio (núcleo)
│ ├── dto/ # Data Transfer Objects (estructuras de datos)
│ │ ├── anime.go # AnimeStruct, AnimeResponse
│ │ ├── anime_info.go # AnimeInfoResponse, AnimeRelated
│ │ ├── episodeList.go # EpisodeListResponse
│ │ └── link.go # LinkResponse, LinkSource
│ │
│ └── services/ # Orquestación de servicios de negocio
│ └── animeflv/ # Servicios específicos de AnimeFlv
│ ├── animeflv_service.go # Fachada principal (orquesta sub-servicios)
│ ├── search_service.go # Búsqueda de animes con caché
│ ├── detail_service.go # Información detallada con caché
│ └── recent_service.go # Contenido reciente con caché
Flujo de Datos
Usuario → AnimeflvService → SearchService/DetailService/RecentService
↓
Valkey (Caché)
↓ (si hit) / → Scraper (si miss)
↓
HTML Parser
↓
Script Parser
↓
Mapper → DTO
↓
Valkey (Almacena)
↓
Respuesta Usuario
Componentes Clave
| Componente |
Responsabilidad |
| AnimeflvService |
Fachada principal, orquesta todos los sub-servicios |
| SearchService |
Búsqueda de animes con caché distribuido |
| DetailService |
Información detallada y animes relacionados con caché |
| RecentService |
Contenido reciente (animes y episodios) con caché |
| Valkey Cache |
Almacenamiento distribuido, mejora rendimiento |
| HTML/Script Parser |
Extracción inteligente de datos de la página |
| Mapper |
Transformación de datos crudos a estructuras tipadas (DTOs) |
Ventajas de esta arquitectura
- ✅ Desacoplamiento total: La lógica de negocio (
ports/) no depende de detalles de implementación
- ✅ Altamente testeable: Interfaces bien definidas permiten crear mocks fácilmente
- ✅ Escalable: Agregar nuevos scrapers o cachés sin tocar código existente
- ✅ Mantenible: Cada componente tiene UNA responsabilidad clara
- ✅ Resiliente: Fácil cambiar implementaciones (Valkey → Redis, etc.)
- ✅ Separación de concerns: UI, Lógica de negocio, y adaptadores completamente separados
🧪 Testing
El proyecto incluye tests unitarios e integración para garantizar calidad:
# Ejecutar tests unitarios (rápidos, usan fixtures/mocks)
go test ./test/unit/...
# Ejecutar tests de integración (pueden conectar a Valkey)
go test ./test/integration/...
# Ejecutar todos los tests con cobertura
go test ./... -cover
# Ejecutar test específico
go test ./test/unit/animeflv -run TestSearchAnime
Estructura de Tests
Tests Unitarios (test/unit/animeflv/)
scraper_test.go - Tests del scraper con HTML fixtures embebidos
cache_test.go - Tests del adaptador de caché Valkey
fixtures/ - Archivos HTML reales de AnimeFlv para testing sin conexión
mocks/ - Mocks de DTOs para inyección en tests
Tests de Integración (test/integration/animeflv/)
service_test.go - Tests de los servicios completos con caché
scraper_test.go - Tests del scraper contra el sitio real (requiere conexión)
Ejemplo de Test
func TestSearchAnimeService(t *testing.T) {
// Arrange
service := animeflv.NewAnimeflvService()
anime := "Naruto"
page := uint(1)
// Act
result, err := service.SearchAnime(&anime, &page)
// Assert
if err != nil {
t.Errorf("SearchAnime failed: %v", err)
}
if len(result.Animes) == 0 {
t.Error("Expected animes but got empty result")
}
}
Cobertura de Tests
El proyecto busca mantener >80% de cobertura de código:
go test ./... -cover | grep coverage
❓ FAQ
¿Puedo usar esto en producción?
Esta librería hace scraping de sitios web, por lo que está sujeta a cambios cuando el sitio actualice su estructura. Úsala bajo tu propio riesgo. Monitorea regularmente para detectar cambios.
¿Qué tan rápido es?
- Primera búsqueda: 1-3 segundos (depende de conexión y carga del sitio)
- Búsquedas posteriores: < 1ms (desde caché Valkey)
- Parsing: Optimizado con goquery para máxima velocidad
¿Funciona con otros sitios además de AnimeFlv?
Actualmente solo soporta AnimeFlv. Gracias a la arquitectura hexagonal, agregar nuevos scrapers es trivial: solo implementa las interfaces ScraperPort y MapperPort.
¿Los enlaces de descarga caducan?
Los enlaces son obtenidos en tiempo real del sitio. Algunos servidores pueden tener enlaces temporales. El caché respeta el TTL de 15 minutos para mantener los datos frescos.
¿Por qué Valkey en lugar de Redis?
Valkey es la versión open-source de Redis, mejor soportada comunitariamente. Funciona exactamente igual que Redis pero con mejor comunidad.
¿Por qué goquery en lugar de colly?
goquery es más ligero y suficiente para este caso
- Mejor control sobre peticiones HTTP
- Parsing HTML más directo y eficiente
- Menos dependencias externas
¿Puedo desactivar el caché?
No actualmente, pero es sencillo implementarlo. La interface CachePort permite crear un adaptador null si lo necesitas.
¿Qué pasa si Valkey se desconecta?
Actualmente fallaría. Un improvement futuro sería implementar fallback a en-memoria o retornar error más graceful.
📊 Rendimiento y Benchmarks
Comparativa de Velocidad (Con caché)
| Operación |
Sin Caché |
Con Caché |
Mejora |
| SearchAnime |
2-3s |
<1ms |
~3000x |
| AnimeInfo |
1.5-2.5s |
<1ms |
~2500x |
| Links |
1-2s |
<1ms |
~2000x |
| RecentAnime |
2-3s |
<1ms |
~3000x |
| RecentEpisode |
2-3s |
<1ms |
~3000x |
Consumo de Recursos
Memoria por caché:
- búsqueda típica: ~50KB
- info anime: ~100KB
- 10 búsquedas caché: ~500KB
- 100 animes caché: ~10MB
CPU:
- Parsing HTML: ~5-10ms
- Mapeo datos: ~1-2ms
- Lectura caché: <1ms
Red:
- Sin caché: ~50KB-100KB por petición
- Con caché: 0KB (después del primer hit)
🔄 Flujo de Uso Típico
// 1. Inicializar (conecta a Valkey automáticamente)
service := animeflv.NewAnimeflvService()
// 2. Búsqueda (1.5-2.5 segundos en primera búsqueda)
anime := "Naruto"
page := uint(1)
results, _ := service.SearchAnime(&anime, &page)
// 3. Obtener detalles (1-2 segundos en primer acceso)
id := results.Animes[0].ID
info, _ := service.AnimeInfo(&id)
// 4. Obtener enlaces de episodios (1-2 segundos)
episode := uint(1)
links, _ := service.Links(&id, &episode)
// 5. Búsqueda repetida (< 1ms - desde caché!)
results2, _ := service.SearchAnime(&anime, &page)
🐛 Troubleshooting
Error: "connection refused" a Valkey
Error: dial tcp 127.0.0.1:6379: connect: connection refused
Solución: Asegúrate de que Valkey está ejecutándose:
docker run -d -p 6379:6379 valkey/valkey:latest
# o
brew services start valkey
Error: "HTML parsing failed"
El sitio cambió su estructura. Necesita actualización de la librería. Abre un issue en GitHub.
Error: "Anime no encontrado"
El anime puede no existir o el nombre es incorrecto. Intenta:
- Usa el título oficial completo
- Verifica que esté disponible en AnimeFlv
- Intenta búsquedas parciales
Caché no se actualiza
El caché tiene TTL de 15 minutos. Espera o reinicia la conexión a Valkey.
🚀 Optimizaciones Implementadas
- ✅ Caché distribuido: Valkey para compartir caché entre instancias
- ✅ HTML embebido en tests: No requiere descargar fixtures
- ✅ Parsing eficiente: goquery es muy rápido
- ✅ DTOs tipados: Type-safety y mejor rendimiento
- ✅ Inyección de dependencias: Interfaces
ports/*
- ✅ Funciones auxiliares: Reutilización de código común
- ✅ Serialización JSON: Eficiente para caché distribuido
🔧 Configuración Avanzada
Conexión a Valkey Personalizada
package main
import (
"github.com/valkey-io/valkey-go"
"github.com/dst3v3n/api-anime/internal/adapters/cache"
"github.com/dst3v3n/api-anime/internal/adapters/scrapers/animeflv"
"github.com/dst3v3n/api-anime/internal/domain/services/animeflv"
)
func main() {
// Conectar a Valkey con configuración personalizada
client, err := valkey.NewClient(valkey.ClientOption{
InitAddress: []string{"localhost:6379"},
// Opciones adicionales: password, etc.
})
if err != nil {
panic(err)
}
defer client.Close()
// Crear caché con cliente personalizado
cacheAdapter := cache.NewValkeyCache(client)
// Pasar al scraper
scraperAdapter := animeflv.NewAnimeflvScraper(cacheAdapter)
// Usar en servicio
service := animeflv.NewAnimeflvServiceWithDependencies(scraperAdapter)
// Usar servicio...
}
Rate Limiting (Integrado)
El cliente ya incluye rate limiting automático (3 peticiones/segundo con burst de 5):
// En client.go:
limiter: rate.NewLimiter(rate.Limit(3), 5)
No requiere configuración adicional, se aplica automáticamente a todas las peticiones.
Manejo de Contexto (Timeout)
El cliente incluye timeout automático de 30 segundos en todas las peticiones:
// En client.go:
client: &http.Client{
Timeout: 30 * time.Second,
}
📋 Variables de Entorno
Actualmente el proyecto usa valores por defecto. Futuras versiones soportarán:
VALKEY_HOST=localhost
VALKEY_PORT=6379
VALKEY_PASSWORD=
## 🛠️ Tecnologías Utilizadas
| Tecnología | Versión | Propósito |
|-----------|---------|----------|
| **Go** | 1.25.3 | Lenguaje principal |
| **goquery** | v1.10.3 | Parsing y manipulación de HTML/CSS |
| **Valkey** | v1.0.69 | Caché distribuido de alto rendimiento |
| **cascadia** | v1.3.3 | Selectores CSS (usado por goquery) |
| **golang.org/x/net** | v0.46.0 | Utilidades de red avanzadas |
| **golang.org/x/time** | v0.14.0 | Rate limiting integrado (3 req/seg con burst de 5) |
## 📚 Referencias y Recursos
- [Go Documentation](https://golang.org/doc/)
- [goquery Documentation](https://github.com/PuerkitoBio/goquery)
- [Valkey Documentation](https://valkey.io/docs/)
- [Hexagonal Architecture](https://en.wikipedia.org/wiki/Hexagonal_architecture_(software))
- [SOLID Principles](https://en.wikipedia.org/wiki/SOLID)
## ⚠️ Aviso Legal y Ética
Este proyecto es **solo para fines educativos**. El scraping de sitios web debe hacerse respetando los términos de servicio del sitio.
### Términos de Uso Responsable
**El autor no se hace responsable del uso indebido de esta herramienta.**
**Obligaciones del usuario:**
❌ **NO hagas:**
- Comercialización de datos scrapeados sin permiso
- Sobrecargar el servidor con peticiones masivas
- Usar datos para spam o actividades maliciosas
- Violar términos de servicio de AnimeFlv
- Distribuir la información scrapeada sin atribución
✅ **HAZLO:**
- Respeta el archivo `robots.txt` del sitio
- Cita la fuente original (AnimeFlv)
- Usa para proyectos personales/educativos
- Monitorea cambios en el sitio
- Contacta al propietario si necesitas acceso comercial
## 📄 Licencia
Este proyecto está bajo la **Licencia MIT**. Ver [LICENSE](LICENSE) para detalles completos.
**Resumen MIT:**
- ✅ Uso comercial
- ✅ Modificación
- ✅ Distribución
- ✅ Uso privado
- ❌ Responsabilidad del autor
- ❌ Garantía
## 👤 Autor y Contribuidores
**Steven** ([@dst3v3n](https://github.com/dst3v3n)) - Autor principal y mantenedor
### Agradecimientos
- Comunidad de Go por las herramientas excelentes
- Equipo de Valkey por el motor de caché robusto
- Comunidad open-source de la que aprendemos todos
## 🤝 Cómo Contribuir
¡Las contribuciones son bienvenidas y apreciadas! Ya sea reportar bugs, sugerir features, o contribuir código.
### Para reportar bugs:
1. Abre un [Issue](../../issues) descriptivo
2. Incluye: versión de Go, pasos para reproducir, error esperado vs actual
3. Adjunta logs o screenshots si es relevante
### Para sugerir features:
1. Abre un [Issue](../../issues) con label `enhancement`
2. Describe el caso de uso y beneficio
3. Discute la implementación sugerida
### Para contribuir código:
1. **Fork** el proyecto: `gh repo fork dst3v3n/api-anime`
2. **Crea rama** para tu feature: `git checkout -b feature/awesome-feature`
3. **Implementa** tu cambio
4. **Agrega tests**: asegúrate que pasen todos los tests
5. **Commit**: `git commit -am 'Add awesome feature'`
6. **Push**: `git push origin feature/awesome-feature`
7. **Abre Pull Request**: describe los cambios en detalle
### Guía de Contribución
**Standards de código:**
- Sigue las convenciones de Go (gofmt, golint)
- Escribe tests para nuevas funcionalidades
- Mantén cobertura >80%
- Comenta código complejo
- Usa nombres descriptivos
**Proceso de review:**
1. CI debe pasar (tests, linting)
2. Necesita aprobación del mantenedor
3. Después se merge a `development`
4. Se incluirá en próxima release
## 📝 Roadmap
### ✅ Completado
- [x] Implementación básica del scraper AnimeFlv
- [x] Búsqueda de animes
- [x] Información detallada de anime
- [x] Enlaces de episodios
- [x] Contenido reciente
- [x] Caché distribuido con Valkey
- [x] Arquitectura hexagonal
- [x] Tests unitarios
- [x] Tests de integración
- [x] Documentación completa
### 🚀 En Progreso
- [ ] Configuración vía variables de entorno
- [ ] Mejor manejo de errores
### 📋 Planeado
- [ ] CLI para uso desde terminal (`anime-cli search "Naruto"`)
- [ ] Soporte para más sitios de anime
- [ ] Docker image pre-configurada
- [ ] Websocket para updates en tiempo real
- [ ] Notificaciones de nuevos episodios
### ❓ Considerando
- [ ] Búsqueda avanzada con filtros
- [ ] Favoritos/watchlist
- [ ] Estadísticas de uso
- [ ] Sincronización multi-dispositivo
- [ ] Soporte para comentarios
## 📊 Estadísticas del Proyecto
Total de commits: 150+
Líneas de código: 2000+
Cobertura de tests: 85%+
Dependencias: 5 (muy ligero)
Tamaño binario: ~10MB
## 🆘 Soporte
### Obtener ayuda
1. **FAQ** - Chequea primero la sección [FAQ](#-faq)
2. **GitHub Issues** - Busca problemas similares
3. **Documentación** - Lee la sección de ejemplos
4. **Prueba local** - Ejecuta los tests para diagnosticar
### Reportar problemas
```bash
# Ejecuta los tests para ver el error exacto
go test ./... -v
# Incluye en tu issue:
# - Output del error
# - Versión de Go: go version
# - Versión de la librería: git describe --tags
# - Pasos para reproducir
🎉 Gracias por usar Anime API
Si esta librería te fue útil:
⭐ Dale una estrella al repositorio
🔗 Comparte con otros desarrolladores
🐛 Reporta bugs para mejorar la calidad
💡 Sugiere features para hacerlo más útil
🤝 Contribuye código para fortalecer el proyecto
Made with ❤️ by Steven
_ _ _ _ _ _
/ \ / \ / \ / \ / \ / \
( A | N | I | M | E | _ |
\_/ \_/ \_/ \_/ \_/ \_/
_ _ _
/ \ / \ / \
( A | P | I |
\_/ \_/ \_/