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

465 lines
14 KiB
Markdown

# 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
```python
# 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`:
```python
from platforms.vinted_platform import VintedPlatform
class PlatformFactory:
_platforms = {
'wallapop': WallapopPlatform,
'vinted': VintedPlatform,
}
```
### 4. Usar en workers.json
```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)
```python
# 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
```bash
pip install beautifulsoup4
```
Añadir a `requirements.txt`:
```
beautifulsoup4>=4.12.0
```
### 4. Registrar Buyee
En `platforms/platform_factory.py`:
```python
from platforms.buyee_platform import BuyeePlatform
class PlatformFactory:
_platforms = {
'wallapop': WallapopPlatform,
'vinted': VintedPlatform,
'buyee': BuyeePlatform,
}
```
### 5. Usar en workers.json
```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:
```python
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