mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-22 14:48:13 +02:00
feat: Rebuild search system with modular, site-specific parsers
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.
This commit is contained in:
10 files changed
+1257
-5
No files matched your search
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
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")
|
||||
Reference in new issue
Block a user