Merge pull request #149 from R0m1k3/antigravity

feat: Add Cataloguemate.fr scraper service to replace Tiendeo for pro…
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-30 02:05:59 +01:00
commit ff5a3834a3
1 file changed
+36 -14
+36 -14
View File
@@ -23,8 +23,9 @@ BASE_URL = "https://www.cataloguemate.fr"
async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> list[dict[str, Any]]: async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> list[dict[str, Any]]:
""" """
Scrape the list of catalogs for an enseigne from its main page. Scrape the list of catalogs for an enseigne.
URL format: https://www.cataloguemate.fr/{enseigne_slug}/ We use a default city (Paris) because the main page often doesn't list catalogs directly.
URL format: https://www.cataloguemate.fr/offres/paris/{enseigne_slug}/
""" """
slug = enseigne.slug_bonial.lower() slug = enseigne.slug_bonial.lower()
# Handle specific slug mapping # Handle specific slug mapping
@@ -33,7 +34,8 @@ async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> l
elif enseigne.nom.lower() == "la foir'fouille": elif enseigne.nom.lower() == "la foir'fouille":
slug = "la-foirfouille" slug = "la-foirfouille"
url = f"{BASE_URL}/{slug}/" # Use Paris as default city to find national catalogs
url = f"{BASE_URL}/offres/paris/{slug}/"
logger.info(f"Scraping catalog list from: {url}") logger.info(f"Scraping catalog list from: {url}")
config = CrawlerRunConfig( config = CrawlerRunConfig(
@@ -49,8 +51,10 @@ async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> l
soup = BeautifulSoup(result.html, 'html.parser') soup = BeautifulSoup(result.html, 'html.parser')
catalogs = [] catalogs = []
# Find all links that might be catalogs for THIS enseigne # Find all links that might be catalogs
# We look for links starting with /{slug}/ and containing date patterns or 'catalogue' # On city pages, catalogs are often under "Ces produits sont actuellement dans les catalogues"
# or "Catalogues populaires" or just listed.
# We look for links that look like /enseigne/titre-id/
links = soup.find_all('a', href=True) links = soup.find_all('a', href=True)
seen_urls = set() seen_urls = set()
@@ -63,10 +67,14 @@ async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> l
if href.startswith(BASE_URL): if href.startswith(BASE_URL):
href = href.replace(BASE_URL, "") href = href.replace(BASE_URL, "")
# Filter: must start with /{slug}/ to ensure it's for this enseigne # Filter:
# Must NOT be the main page itself or a city search page # 1. Must contain the slug (or be a catalog link for this enseigne)
if href.startswith(f"/{slug}/") and href != f"/{slug}/": # 2. Must NOT be a city search page (/offres/)
if any(x in href for x in ["offres", "magasins", "rechercher"]): # 3. Must NOT be a product search (/rechercher/)
# 4. Should usually have a numeric ID at the end
if f"/{slug}/" in href:
if any(x in href for x in ["/offres/", "/magasins/", "/rechercher/", "page="]):
continue continue
full_url = f"{BASE_URL}{href}" full_url = f"{BASE_URL}{href}"
@@ -74,9 +82,9 @@ async def scrape_catalog_list(crawler: AsyncWebCrawler, enseigne: Enseigne) -> l
if full_url in seen_urls: if full_url in seen_urls:
continue continue
# Check if it looks like a catalog (usually has an ID or date) # Check for numeric ID pattern which is typical for catalogs
# e.g. /action/black-friday-du-mercredi-26-11-2025-61136/ # e.g. -61130/
if re.search(r'\d+', href): if re.search(r'-\d+/?$', href) or "catalogue" in href.lower():
catalogs.append({ catalogs.append({
'url': full_url, 'url': full_url,
'title': text or "Catalogue", 'title': text or "Catalogue",
@@ -144,13 +152,27 @@ async def scrape_catalog_pages(crawler: AsyncWebCrawler, catalog_url: str) -> li
# Check src for keywords # Check src for keywords
is_likely_catalog = any(k in src.lower() for k in ['page', 'flyer', 'catalog', 'upload', 'images']) is_likely_catalog = any(k in src.lower() for k in ['page', 'flyer', 'catalog', 'upload', 'images'])
# Calculate area
area = 0
if width and height:
try:
area = int(width) * int(height)
except:
pass
# Logic:
# 1. If keyword match AND decent size -> Strong candidate
# 2. If no keyword match but HUGE size -> Candidate
if is_likely_catalog: if is_likely_catalog:
# If we have dimensions, prioritize large ones
if area > max_area: if area > max_area:
max_area = area max_area = area
main_image_url = src main_image_url = src
elif area == 0 and not main_image_url: elif area == 0 and not main_image_url:
# If no dimensions but looks like a catalog image, take it if we haven't found one main_image_url = src
elif area > 100000: # Arbitrary large size (e.g. 300x333)
if area > max_area:
max_area = area
main_image_url = src main_image_url = src
if main_image_url: if main_image_url: