Files
Priceflow/app/services/parsers/parser_factory.py
T
Claude d2c492ff5c 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.
2025-11-30 16:09:05 +00:00

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")