mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-14 10:46:32 +02:00
Complete refactoring of the search module for robustness and reliability: **New Architecture:** - Modular parser system with specialized parsers for each major site - Factory pattern for automatic parser selection - Generic fallback parser for sites without specific implementation **Specialized Parsers:** - ✅ Amazon.fr - Robust parser with multiple fallback selectors - Handles sponsored products, Prime eligibility, ratings, reviews - Based on proven amazon_scraper_v2.py implementation - ✅ Cdiscount.com - Optimized for Cdiscount's HTML structure - ✅ Fnac.com - Handles Fnac's picture elements and article structure - ✅ Darty.com - Specialized for electronics retailer - ✅ Boulanger.com - Optimized for Boulanger's product cards **Key Features:** - Multiple fallback selectors for each element (title, price, image) - Proper price parsing for French format (1 234,56 €) - Rating and review count extraction - Stock status detection - Image URL extraction with multiple attribute fallbacks - Query filtering to ensure relevant results **Benefits:** - Much more robust than previous generic parsing - Easier to maintain (one parser per site) - Easily extensible (add new parser for new site) - Better error handling and logging - Higher success rate for product extraction **Updated Services:** - improved_search_service.py now uses ParserFactory - Legacy _parse_results marked as deprecated - Conversion layer between ProductResult and SearchResult This implements the Amazon France model across all major sites for consistent, reliable product search results.
97 lines
2.8 KiB
Python
97 lines
2.8 KiB
Python
"""
|
|
Parser Factory - Selects the appropriate parser for each site
|
|
"""
|
|
|
|
import logging
|
|
|
|
from .base_parser import BaseParser
|
|
from .amazon_parser import AmazonParser
|
|
from .cdiscount_parser import CdiscountParser
|
|
from .fnac_parser import FnacParser
|
|
from .darty_parser import DartyParser
|
|
from .boulanger_parser import BoulangerParser
|
|
from .generic_parser import GenericParser
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ParserFactory:
|
|
"""
|
|
Factory for creating site-specific parsers
|
|
|
|
Automatically selects the specialized parser based on the site key,
|
|
or falls back to the generic parser for sites without specific implementation.
|
|
"""
|
|
|
|
# Mapping of site keys to their specialized parsers
|
|
_PARSERS = {
|
|
"amazon.fr": AmazonParser,
|
|
"cdiscount.com": CdiscountParser,
|
|
"fnac.com": FnacParser,
|
|
"darty.com": DartyParser,
|
|
"boulanger.com": BoulangerParser,
|
|
}
|
|
|
|
# Cache for parser instances (singleton pattern)
|
|
_instances = {}
|
|
|
|
@classmethod
|
|
def get_parser(cls, site_key: str) -> BaseParser:
|
|
"""
|
|
Get the appropriate parser for a site
|
|
|
|
Args:
|
|
site_key: Site identifier (e.g., "amazon.fr", "cdiscount.com")
|
|
|
|
Returns:
|
|
Parser instance (specialized or generic)
|
|
"""
|
|
# Check cache first
|
|
if site_key in cls._instances:
|
|
return cls._instances[site_key]
|
|
|
|
# Check if we have a specialized parser
|
|
parser_class = cls._PARSERS.get(site_key)
|
|
|
|
if parser_class:
|
|
logger.info(f"✓ Using specialized parser for {site_key}: {parser_class.__name__}")
|
|
parser = parser_class()
|
|
else:
|
|
logger.info(f"⚠ No specialized parser for {site_key}, using GenericParser")
|
|
parser = GenericParser(site_key)
|
|
|
|
# Cache the instance
|
|
cls._instances[site_key] = parser
|
|
|
|
return parser
|
|
|
|
@classmethod
|
|
def register_parser(cls, site_key: str, parser_class: type[BaseParser]):
|
|
"""
|
|
Register a new parser for a site
|
|
|
|
Args:
|
|
site_key: Site identifier
|
|
parser_class: Parser class (must inherit from BaseParser)
|
|
"""
|
|
if not issubclass(parser_class, BaseParser):
|
|
raise ValueError(f"Parser class must inherit from BaseParser")
|
|
|
|
cls._PARSERS[site_key] = parser_class
|
|
# Clear cache for this site
|
|
if site_key in cls._instances:
|
|
del cls._instances[site_key]
|
|
|
|
logger.info(f"Registered parser {parser_class.__name__} for {site_key}")
|
|
|
|
@classmethod
|
|
def list_specialized_parsers(cls) -> list[str]:
|
|
"""Get list of sites with specialized parsers"""
|
|
return list(cls._PARSERS.keys())
|
|
|
|
@classmethod
|
|
def clear_cache(cls):
|
|
"""Clear parser instance cache"""
|
|
cls._instances.clear()
|
|
logger.info("Parser cache cleared")
|