Files
wallabicher/ADDING_PLATFORMS.md
Omar Sánchez Pizarro 4111f57564 add abstraction ob platform and article + vinted
"

Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
2025-10-10 14:58:27 +02:00

14 KiB

Guía para Añadir Vinted y Buyee

Esta guía específica te ayudará a implementar las plataformas Vinted y Buyee en el monitor.

Vinted

1. Investigar la API de Vinted

Vinted no tiene una API pública oficial, pero tiene una API interna que puedes usar:

URL Base: https://www.vinted.es/api/v2/catalog/items

Parámetros comunes:

  • search_text: Términos de búsqueda
  • catalog_ids: IDs de categorías
  • price_from: Precio mínimo
  • price_to: Precio máximo
  • currency: Moneda (EUR, USD, etc.)
  • order: Ordenar por (newest_first, price_low_to_high, etc.)
  • per_page: Artículos por página

2. Ejemplo de Implementación

# platforms/vinted_platform.py
import requests
import logging
import time
from datetime import datetime
from platforms.base_platform import BasePlatform
from models.article import Article

REQUEST_RETRY_TIME = 5

class VintedPlatform(BasePlatform):
    """Vinted marketplace platform implementation"""
    
    def __init__(self, item_monitor):
        super().__init__(item_monitor)
        self.logger = logging.getLogger(__name__)
    
    def get_platform_name(self):
        return "vinted"
    
    def create_url(self):
        """Construir URL de búsqueda de Vinted"""
        url = "https://www.vinted.es/api/v2/catalog/items"
        params = []
        
        # Query de búsqueda
        search_query = self._item_monitor.get_search_query()
        params.append(f"search_text={search_query}")
        
        # Ordenar por más reciente
        params.append("order=newest_first")
        
        # Precio
        if self._item_monitor.get_min_price() != 0:
            params.append(f"price_from={self._item_monitor.get_min_price()}")
        
        if self._item_monitor.get_max_price() != 0:
            params.append(f"price_to={self._item_monitor.get_max_price()}")
        
        # Moneda
        params.append("currency=EUR")
        
        # Resultados por página
        params.append("per_page=50")
        
        return url + "?" + "&".join(params)
    
    def get_request_headers(self):
        """Headers para Vinted"""
        headers = super().get_request_headers()
        headers['Accept'] = 'application/json'
        headers['Accept-Language'] = 'es-ES'
        return headers
    
    def fetch_articles(self):
        """Obtener artículos desde Vinted"""
        url = self.create_url()
        
        while True:
            try:
                headers = self.get_request_headers()
                response = requests.get(url, headers=headers)
                response.raise_for_status()
                break
            except requests.exceptions.RequestException as err:
                self.logger.error(f"Vinted Request Exception: {err}")
                time.sleep(REQUEST_RETRY_TIME)
        
        json_response = response.json()
        json_items = json_response.get('items', [])
        articles = self.parse_response(json_items)
        return articles
    
    def parse_response(self, json_items):
        """Parsear respuesta de Vinted"""
        articles = []
        for json_article in json_items:
            article = self._parse_single_article(json_article)
            if article:
                articles.append(article)
        return articles
    
    def _parse_single_article(self, json_data):
        """Parsear un artículo individual de Vinted"""
        try:
            # Extraer imágenes
            images = []
            if 'photo' in json_data and json_data['photo']:
                images.append(json_data['photo'].get('url', ''))
            
            # Más imágenes si están disponibles
            if 'photos' in json_data:
                for photo in json_data['photos'][:3]:
                    if photo.get('url'):
                        images.append(photo['url'])
            
            # Convertir fecha
            updated_at = json_data.get('updated_at', '')
            try:
                dt = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
                modified_at = dt.strftime("%Y-%m-%d %H:%M:%S")
            except:
                modified_at = updated_at
            
            # Precio
            price = float(json_data.get('price', {}).get('amount', 0))
            currency = json_data.get('price', {}).get('currency_code', 'EUR')
            
            # Ubicación
            location = json_data.get('user', {}).get('city', 'Unknown')
            
            # URL
            article_url = f"https://www.vinted.es/items/{json_data.get('id')}"
            
            # Envíos (Vinted suele permitir envíos)
            allows_shipping = True
            
            return Article(
                id=str(json_data['id']),
                title=json_data.get('title', ''),
                description=json_data.get('description', ''),
                price=price,
                currency=currency,
                location=location,
                allows_shipping=allows_shipping,
                url=article_url,
                images=images[:3],  # Máximo 3 imágenes
                modified_at=modified_at,
                platform=self.get_platform_name()
            )
        except (KeyError, ValueError) as e:
            self.logger.error(f"Error parsing Vinted article: {e}")
            return None

3. Registrar Vinted

En platforms/platform_factory.py:

from platforms.vinted_platform import VintedPlatform

class PlatformFactory:
    _platforms = {
        'wallapop': WallapopPlatform,
        'vinted': VintedPlatform,
    }

4. Usar en workers.json

{
  "name": "Gameboy en Vinted",
  "platform": "vinted",
  "search_query": "gameboy",
  "min_price": 10,
  "max_price": 100,
  "thread_id": 10
}

Buyee (Yahoo Auctions Japan)

1. Investigar la API de Buyee

Buyee es un servicio proxy para Yahoo Auctions Japan. Opciones:

Opción A - Yahoo Auctions API (si tienes acceso):

  • URL: https://auctions.yahooapis.jp/AuctionWebService/V2/search
  • Requiere API key

Opción B - Web Scraping (más común):

  • URL: https://buyee.jp/yahoo/auction/search/query/SEARCH_QUERY
  • Parsear HTML con BeautifulSoup

2. Ejemplo de Implementación (Web Scraping)

# platforms/buyee_platform.py
import requests
import logging
import time
from datetime import datetime
from bs4 import BeautifulSoup
from platforms.base_platform import BasePlatform
from models.article import Article

REQUEST_RETRY_TIME = 5

class BuyeePlatform(BasePlatform):
    """Buyee (Yahoo Auctions Japan) marketplace platform implementation"""
    
    def __init__(self, item_monitor):
        super().__init__(item_monitor)
        self.logger = logging.getLogger(__name__)
    
    def get_platform_name(self):
        return "buyee"
    
    def create_url(self):
        """Construir URL de búsqueda de Buyee"""
        search_query = self._item_monitor.get_search_query()
        
        # URL base de Buyee
        url = f"https://buyee.jp/yahoo/auction/search/query/{search_query}"
        
        params = []
        
        # Precio
        if self._item_monitor.get_min_price() != 0:
            params.append(f"min_price={self._item_monitor.get_min_price()}")
        
        if self._item_monitor.get_max_price() != 0:
            params.append(f"max_price={self._item_monitor.get_max_price()}")
        
        # Ordenar por más reciente
        params.append("sort=end_time&order=a")
        
        if params:
            url += "?" + "&".join(params)
        
        return url
    
    def get_request_headers(self):
        """Headers para Buyee"""
        headers = super().get_request_headers()
        headers['Accept'] = 'text/html,application/xhtml+xml'
        headers['Accept-Language'] = 'en-US,en;q=0.9,ja;q=0.8'
        return headers
    
    def fetch_articles(self):
        """Obtener artículos desde Buyee"""
        url = self.create_url()
        
        while True:
            try:
                headers = self.get_request_headers()
                response = requests.get(url, headers=headers)
                response.raise_for_status()
                break
            except requests.exceptions.RequestException as err:
                self.logger.error(f"Buyee Request Exception: {err}")
                time.sleep(REQUEST_RETRY_TIME)
        
        # Parsear HTML
        soup = BeautifulSoup(response.text, 'html.parser')
        articles = self.parse_response(soup)
        return articles
    
    def parse_response(self, soup):
        """Parsear HTML de Buyee"""
        articles = []
        
        # Los selectores pueden cambiar, esto es un ejemplo
        # Necesitarás investigar la estructura HTML actual
        items = soup.select('.product-item') or soup.select('.item')
        
        for item in items:
            article = self._parse_single_article(item)
            if article:
                articles.append(article)
        
        return articles
    
    def _parse_single_article(self, item_element):
        """Parsear un artículo individual de Buyee"""
        try:
            # NOTA: Estos selectores son ejemplos, necesitas verificar la estructura real
            title_elem = item_element.select_one('.product-title') or item_element.select_one('.title')
            title = title_elem.text.strip() if title_elem else "No title"
            
            price_elem = item_element.select_one('.product-price') or item_element.select_one('.price')
            price_text = price_elem.text.strip() if price_elem else "0"
            # Limpiar precio: "¥1,500" -> 1500
            price = float(price_text.replace('¥', '').replace(',', '').strip())
            
            link_elem = item_element.select_one('a[href]')
            url = link_elem['href'] if link_elem else ""
            if url and not url.startswith('http'):
                url = f"https://buyee.jp{url}"
            
            # Extraer ID de la URL
            item_id = url.split('/')[-1] if url else "unknown"
            
            # Imagen
            img_elem = item_element.select_one('img')
            images = [img_elem['src']] if img_elem and 'src' in img_elem.attrs else []
            
            # Descripción (si está disponible)
            desc_elem = item_element.select_one('.description') or item_element.select_one('.desc')
            description = desc_elem.text.strip() if desc_elem else ""
            
            return Article(
                id=item_id,
                title=title,
                description=description,
                price=price,
                currency="JPY",
                location="Japan",
                allows_shipping=True,  # Buyee siempre permite envíos internacionales
                url=url,
                images=images,
                modified_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                platform=self.get_platform_name()
            )
        except Exception as e:
            self.logger.error(f"Error parsing Buyee article: {e}")
            return None

3. Instalar BeautifulSoup

pip install beautifulsoup4

Añadir a requirements.txt:

beautifulsoup4>=4.12.0

4. Registrar Buyee

En platforms/platform_factory.py:

from platforms.buyee_platform import BuyeePlatform

class PlatformFactory:
    _platforms = {
        'wallapop': WallapopPlatform,
        'vinted': VintedPlatform,
        'buyee': BuyeePlatform,
    }

5. Usar en workers.json

{
  "name": "Retro Games en Buyee",
  "platform": "buyee",
  "search_query": "ファミコン",
  "min_price": 1000,
  "max_price": 50000,
  "thread_id": 11
}

Consideraciones Importantes

Rate Limiting

  • Vinted y Buyee pueden tener límites de peticiones
  • Considera añadir delays entre peticiones
  • Usa proxies si es necesario

Web Scraping vs API

  • Vinted: Tiene API interna, relativamente estable
  • Buyee: Web scraping puede romperse con cambios en el sitio
  • Considera usar Yahoo Auctions API oficial si tienes acceso

Monedas

  • Vinted: EUR (España), USD (USA), GBP (UK), etc.
  • Buyee: JPY (Yenes japoneses)
  • El sistema ya soporta diferentes monedas en el modelo Article

Ubicaciones Geográficas

  • Vinted: Soporta filtrado por ubicación (como Wallapop)
  • Buyee: Todos los artículos son de Japón

Condición de Artículos

  • Vinted: Tiene estados similares a Wallapop
  • Buyee: Depende del vendedor en Yahoo Auctions

Testing

Para probar tus implementaciones:

from platforms.platform_factory import PlatformFactory
from datalayer.item_monitor import ItemMonitor

# Test Vinted
vinted_config = {
    "name": "Test Vinted",
    "platform": "vinted",
    "search_query": "gameboy"
}
item = ItemMonitor.load_from_json(vinted_config)
vinted = PlatformFactory.create_platform("vinted", item)
articles = vinted.fetch_articles()
print(f"Found {len(articles)} articles on Vinted")

# Test Buyee
buyee_config = {
    "name": "Test Buyee",
    "platform": "buyee",
    "search_query": "ゲームボーイ"
}
item = ItemMonitor.load_from_json(buyee_config)
buyee = PlatformFactory.create_platform("buyee", item)
articles = buyee.fetch_articles()
print(f"Found {len(articles)} articles on Buyee")

Troubleshooting

Vinted no devuelve resultados

  • Verifica que la URL de la API no haya cambiado
  • Comprueba los headers (User-Agent, Accept-Language)
  • Prueba la URL directamente en el navegador

Buyee parseo falla

  • La estructura HTML puede cambiar
  • Inspecciona la página web y actualiza los selectores CSS
  • Considera usar Yahoo Auctions API oficial

Errores 403/429

  • Añade más delays entre peticiones
  • Usa User-Agent realista
  • Considera rotar IPs o usar proxies

Próximos Pasos

  1. Implementa Vinted primero (más fácil, tiene API)
  2. Prueba con búsquedas reales
  3. Implementa Buyee (más complejo, scraping)
  4. Ajusta los selectores según la estructura actual
  5. Añade manejo de errores específico
  6. Documenta cualquier peculiaridad de la plataforma