Herramienta de web scraping para MercadoLibre con dashboard interactivo (Dash) e interfaz de línea de comandos (CLI). Soporta 18 países de Latinoamérica con scrapers especializados por categoría (productos generales, autos e inmuebles).
- Clean Architecture — Dominio, aplicación, infraestructura y presentación desacoplados
- Multi-retailer ready — Container configurable para agregar nuevos retailers sin modificar el core
- Soporte multi-país — 18 países de LATAM (AR, MX, BR, CL, CO, etc.)
- Interfaz dual:
- 🎨 Dashboard web interactivo (Dash + Flask + SocketIO)
- 💻 Interfaz de línea de comandos (CLI)
- Scrapers especializados:
- Productos generales (
MercadoLibreScraper) - Autos (
CarScraper→CarProductDetail) - Inmuebles (
PropertyScraper→PropertyProductDetail)
- Productos generales (
- Jerarquía de entidades —
ProductDetailbase con extensionesCarProductDetailyPropertyProductDetail - Value Objects —
Money,Kilometers,SquareMeterscon parsing y conversión - Exportación a CSV con separador configurable
- Conversión de moneda — USD ↔ ARS via DolarAPI
- Tests — 127 unit + 23 integration
- CI/CD — GitHub Actions con ejecución automática de tests
- Python 3.x
- Dependencias (se instalan via
requirements.txt):requests— Requests HTTP a MercadoLibrebeautifulsoup4— Parsing de HTMLpandas— Creación y manipulación de DataFramestqdm— Barras de progresodash+Flask— Interfaz webflask-socketio— Notificaciones de progreso en tiempo realdash-bootstrap-components— Componentes UI
- Clonar el repositorio:
git clone https://github.com/PabloAlaniz/MercadoLibre-Scraper.git
cd MercadoLibre-Scraper- Instalar dependencias:
pip install -r requirements.txt- Ejecutar la aplicación:
# Dashboard (interfaz web)
python main.py
# CLI (línea de comandos)
python cli_scraper.py- Iniciar el dashboard:
python main.py- Abrir el navegador en:
http://localhost:5003
- En la interfaz web:
- Seleccionar el país desde el dropdown
- Ingresar el nombre del producto
- Configurar la cantidad máxima de productos a escrapear
- Hacer clic en "Buscar"
- Ver resultados en gráficos y tablas
python cli_scraper.pySeguir las indicaciones interactivas:
País (ar/mx/br): ar
Producto: notebook gamer
Límite (default 100): 50
Se encontraron 50 productos.
Los resultados se exportan a data/[nombre_producto].csv.
El proyecto sigue los principios de Clean Architecture:
MercadoLibre-Scraper/
├── domain/ # Capa de dominio
│ ├── entities.py # ProductListing, ProductDetail, CarProductDetail, PropertyProductDetail
│ ├── value_objects.py # Money, Kilometers, SquareMeters
│ ├── enums.py # Currency (enums genéricos)
│ └── ports.py # ScraperPort, ExchangeRatePort, ProgressNotifierPort, ProductExporterPort
│
├── application/ # Capa de aplicación
│ ├── use_cases/
│ │ ├── search_products.py # SearchProductsUseCase
│ │ └── get_product_details.py # GetProductDetailsUseCase
│ └── services/
│ └── price_conversion.py # PriceConversionService
│
├── infrastructure/ # Capa de infraestructura (adapters)
│ └── adapters/
│ ├── csv_exporter.py # CsvProductExporter
│ ├── dolarapi_client.py # DolarApiExchangeRate
│ ├── socketio_notifier.py # SocketIOProgressNotifier
│ └── null_notifier.py # NullProgressNotifier (para CLI/tests)
│
├── presentation/ # Capa de presentación
│ └── dash_presenter.py # DashPresenter (formatea datos para el dashboard)
│
├── scrapers/ # Scrapers por retailer
│ └── mercadolibre/
│ ├── mercadolibre_scraper.py # Scraper principal (productos generales)
│ ├── car_scraper.py # Scraper especializado para autos
│ ├── property_scraper.py # Scraper especializado para inmuebles
│ ├── enums.py # Enums específicos de MercadoLibre
│ └── price_parser.py # Parsing de precios de MercadoLibre
│
├── container.py # Composition root — ensambla dependencias
├── main.py # Entry point del dashboard
├── cli_scraper.py # Entry point del CLI
├── dashboard.py # Configuración de Dash + SocketIO
├── ui.py # Componentes UI del dashboard
├── callbacks.py # Callbacks de Dash (interactividad)
├── config.py # Constantes de configuración
├── log_config.py # Configuración de logging
├── utils.py # Funciones auxiliares
│
├── tests/ # Tests unitarios e integración
│ ├── test_domain.py
│ ├── test_use_cases.py
│ ├── test_adapters.py
│ ├── test_presentation.py
│ ├── test_mercadolibre_scraper.py
│ ├── test_integration.py # Tests de integración (requests reales)
│ └── test_utils.py
│
├── data/ # Directorio de salida (CSVs)
└── docs/ # Screenshots y documentación
Editar config.py para personalizar:
SERVER_CONFIG = {
"debug": True,
"port": 5003
}
SCRAPER_CONFIG = {
'base_url': 'https://listado.mercadolibre.com.{domain}/',
'page_increment': 50,
'max_pages': 100
}
DATA_DIRECTORY = "data"
CSV_SEPARATOR = ";"El scraper soporta 18 países de Latinoamérica:
| Código | País | Dominio |
|---|---|---|
| AR | Argentina | .ar |
| BO | Bolivia | .bo |
| BR | Brasil | .br |
| CL | Chile | .cl |
| CO | Colombia | .co |
| CR | Costa Rica | .cr |
| DO | Rep. Dominicana | .do |
| EC | Ecuador | .ec |
| GT | Guatemala | .gt |
| HN | Honduras | .hn |
| MX | México | .mx |
| NI | Nicaragua | .ni |
| PA | Panamá | .pa |
| PY | Paraguay | .py |
| PE | Perú | .pe |
| SV | El Salvador | .sv |
| UY | Uruguay | .uy |
| VE | Venezuela | .ve |
Ejecutar tests unitarios (excluye integration por defecto):
pytest tests/ -vEjecutar tests de integración (realizan requests reales a MercadoLibre):
pytest tests/ -v -m integrationEjecutar todos los tests:
pytest tests/ -v -m ""- Implementar
ScraperPort— Crear un nuevo scraper enscrapers/nuevo_retailer/:
# scrapers/nuevo_retailer/mi_scraper.py
class MiScraper:
"""Implementa ScraperPort via duck typing (Protocol)."""
def get_page_content(self, url):
...
def scrape_product_list(self, domain, product_name, user_scraping_limit):
...
def scrape_product_details(self, soup):
...- Registrar en el Container — Agregar el retailer en
container.py:
@staticmethod
def _create_scraper(retailer: str, notifier, config=None):
if retailer == 'mercadolibre':
from scrapers.mercadolibre.mercadolibre_scraper import MercadoLibreScraper
return MercadoLibreScraper(progress_notifier=notifier, config=config)
if retailer == 'nuevo_retailer':
from scrapers.nuevo_retailer.mi_scraper import MiScraper
return MiScraper(progress_notifier=notifier, config=config)
raise ValueError(f"Unknown retailer: {retailer}")- Instanciar con el nuevo retailer:
services = Container.create_for_cli(retailer='nuevo_retailer')- Sort para el orden de los resultados
- Scraper secundario que trae info extra de cada producto
- Tests unitarios e integración (127 + 23)
- CI/CD pipeline (GitHub Actions)
- Clean Architecture (refactor completo)
- Input para elegir la cantidad de páginas a escrapear
- Gráfico de barras con la distribución del precio
- Export a Excel (xlsx)
- Docker support
- API REST para scraping on-demand
- Scheduled scraping (cron jobs)
- Base de datos (PostgreSQL/SQLite)
- Rate limiting: Ser respetuoso con los servidores de MercadoLibre. Usar límites razonables.
- Robots.txt: Este scraper es para fines educativos. Revisar los términos de servicio de MercadoLibre antes de hacer scraping a gran escala.
Problema: Error Address already in use al ejecutar python main.py
Solución:
# Encontrar y matar el proceso usando el puerto 5003
lsof -ti:5003 | xargs kill -9
# O cambiar el puerto en config.py
SERVER_CONFIG = {"port": 5004}Problema: El scraper retorna lista vacía o 0 productos
Posibles causas:
- MercadoLibre cambió su estructura HTML → Actualizar selectores CSS en
MercadoLibreScraper - Rate limiting/bloqueo → Agregar delays entre requests, rotar user agents
- Dominio de país inválido → Verificar que el código de país exista en la tabla de países soportados
- Problemas de red → Verificar conexión a internet
Problema: ModuleNotFoundError: No module named 'dash' (u otras dependencias)
Solución:
pip install -r requirements.txt --upgradeProblema: PermissionError o FileNotFoundError al exportar
Solución:
mkdir -p data
chmod 755 data/Este proyecto está licenciado bajo la Licencia MIT.
Autor: Pablo Alaniz
