|
|
|
|
@ -1,4 +1,3 @@ |
|
|
|
|
import time |
|
|
|
|
import logging |
|
|
|
|
import requests |
|
|
|
|
import re |
|
|
|
|
@ -6,6 +5,7 @@ from playwright.sync_api import sync_playwright |
|
|
|
|
from datetime import datetime, timedelta |
|
|
|
|
import json |
|
|
|
|
import traceback |
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed |
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
@ -212,49 +212,6 @@ def scrape_fft_club_tournaments_all_pages(club_code, club_name, start_date=None, |
|
|
|
|
'pages_scraped': page + 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
def get_umpire_data(tournament_id): |
|
|
|
|
""" |
|
|
|
|
Scrapes umpire data for a specific tournament |
|
|
|
|
""" |
|
|
|
|
logger.info(f"Getting umpire data for tournament {tournament_id}") |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
url = f"https://tenup.fft.fr/tournoi/{tournament_id}" |
|
|
|
|
|
|
|
|
|
headers = { |
|
|
|
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
response = requests.get(url, headers=headers, timeout=30) |
|
|
|
|
|
|
|
|
|
if response.status_code != 200: |
|
|
|
|
logger.error(f"Failed to fetch tournament page: {response.status_code}") |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
html_content = response.text |
|
|
|
|
|
|
|
|
|
# Extract name |
|
|
|
|
name_pattern = r'tournoi-detail-page-inscription-responsable-title">\s*([^<]+)\s*<' |
|
|
|
|
name_match = re.search(name_pattern, html_content) |
|
|
|
|
name = name_match.group(1).strip() if name_match else None |
|
|
|
|
|
|
|
|
|
# Extract email |
|
|
|
|
email_pattern = r'mailto:([^"]+)"' |
|
|
|
|
email_match = re.search(email_pattern, html_content) |
|
|
|
|
email = email_match.group(1) if email_match else None |
|
|
|
|
|
|
|
|
|
# Extract phone |
|
|
|
|
phone_pattern = r'<div class="details-bloc">\s*(\d{2}\s+\d{2}\s+\d{2}\s+\d{2}\s+\d{2})\s*</div>' |
|
|
|
|
phone_match = re.search(phone_pattern, html_content) |
|
|
|
|
phone = phone_match.group(1).strip() if phone_match else None |
|
|
|
|
|
|
|
|
|
logger.info(f"Extracted umpire data: name={name}, email={email}, phone={phone}") |
|
|
|
|
return name, email, phone |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error getting umpire data: {e}") |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
def _parse_ajax_response(commands): |
|
|
|
|
""" |
|
|
|
|
Parse the AJAX response commands to extract tournament data |
|
|
|
|
@ -555,3 +512,437 @@ def scrape_fft_all_tournaments(sorting_option=None, page=0, start_date=None, end |
|
|
|
|
logger.error(f"Error in Playwright scraping: {e}") |
|
|
|
|
logger.error(f"Traceback: {traceback.format_exc()}") |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_umpire_data(tournament_id): |
|
|
|
|
""" |
|
|
|
|
Fast umpire data extraction using Playwright (optimized for speed) |
|
|
|
|
""" |
|
|
|
|
logger.info(f"Getting umpire data for tournament {tournament_id}") |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
with sync_playwright() as p: |
|
|
|
|
browser = p.chromium.launch( |
|
|
|
|
headless=True, |
|
|
|
|
args=[ |
|
|
|
|
'--no-sandbox', |
|
|
|
|
'--disable-dev-shm-usage', |
|
|
|
|
'--disable-images', # Don't load images |
|
|
|
|
'--disable-javascript', # Disable JS for faster loading |
|
|
|
|
'--disable-plugins', |
|
|
|
|
'--disable-extensions' |
|
|
|
|
] |
|
|
|
|
) |
|
|
|
|
page = browser.new_page() |
|
|
|
|
|
|
|
|
|
# Navigate to tournament page quickly |
|
|
|
|
url = f"https://tenup.fft.fr/tournoi/{tournament_id}" |
|
|
|
|
logger.info(f"Navigating to tournament page: {url}") |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
# Fast navigation - don't wait for everything to load |
|
|
|
|
page.goto(url, timeout=15000, wait_until="domcontentloaded") |
|
|
|
|
|
|
|
|
|
# Quick Queue-It check |
|
|
|
|
if "queue-it.net" in page.url.lower(): |
|
|
|
|
logger.warning("Hit Queue-It on tournament page") |
|
|
|
|
browser.close() |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
# Extract data using the fastest method - regex on HTML content |
|
|
|
|
html_content = page.content() |
|
|
|
|
|
|
|
|
|
# Extract name |
|
|
|
|
name_pattern = r'tournoi-detail-page-inscription-responsable-title">\s*([^<]+)\s*<' |
|
|
|
|
name_match = re.search(name_pattern, html_content) |
|
|
|
|
name = name_match.group(1).strip() if name_match else None |
|
|
|
|
|
|
|
|
|
# Extract email |
|
|
|
|
email_pattern = r'mailto:([^"]+)"' |
|
|
|
|
email_match = re.search(email_pattern, html_content) |
|
|
|
|
email = email_match.group(1) if email_match else None |
|
|
|
|
|
|
|
|
|
# Extract phone |
|
|
|
|
phone_pattern = r'<div class="details-bloc">\s*(\d{2}\s+\d{2}\s+\d{2}\s+\d{2}\s+\d{2})\s*</div>' |
|
|
|
|
phone_match = re.search(phone_pattern, html_content) |
|
|
|
|
phone = phone_match.group(1).strip() if phone_match else None |
|
|
|
|
|
|
|
|
|
browser.close() |
|
|
|
|
|
|
|
|
|
logger.info(f"Extracted umpire data: name={name}, email={email}, phone={phone}") |
|
|
|
|
return name, email, phone |
|
|
|
|
|
|
|
|
|
except Exception as page_error: |
|
|
|
|
logger.error(f"Error loading tournament page: {page_error}") |
|
|
|
|
browser.close() |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error in umpire data extraction: {e}") |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
def _get_umpire_data_requests_fallback(tournament_id): |
|
|
|
|
""" |
|
|
|
|
Fallback method using requests (may hit Queue-It) |
|
|
|
|
""" |
|
|
|
|
logger.info(f"Using requests fallback for tournament {tournament_id}") |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
url = f"https://tenup.fft.fr/tournoi/{tournament_id}" |
|
|
|
|
|
|
|
|
|
headers = { |
|
|
|
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
response = requests.get(url, headers=headers, timeout=30) |
|
|
|
|
|
|
|
|
|
if response.status_code != 200: |
|
|
|
|
logger.error(f"Failed to fetch tournament page: {response.status_code}") |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
html_content = response.text |
|
|
|
|
|
|
|
|
|
# Extract using regex (original method) |
|
|
|
|
name_pattern = r'tournoi-detail-page-inscription-responsable-title">\s*([^<]+)\s*<' |
|
|
|
|
name_match = re.search(name_pattern, html_content) |
|
|
|
|
name = name_match.group(1).strip() if name_match else None |
|
|
|
|
|
|
|
|
|
email_pattern = r'mailto:([^"]+)"' |
|
|
|
|
email_match = re.search(email_pattern, html_content) |
|
|
|
|
email = email_match.group(1) if email_match else None |
|
|
|
|
|
|
|
|
|
phone_pattern = r'<div class="details-bloc">\s*(\d{2}\s+\d{2}\s+\d{2}\s+\d{2}\s+\d{2})\s*</div>' |
|
|
|
|
phone_match = re.search(phone_pattern, html_content) |
|
|
|
|
phone = phone_match.group(1).strip() if phone_match else None |
|
|
|
|
|
|
|
|
|
logger.info(f"Extracted umpire data (requests): name={name}, email={email}, phone={phone}") |
|
|
|
|
return name, email, phone |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error getting umpire data with requests: {e}") |
|
|
|
|
return None, None, None |
|
|
|
|
|
|
|
|
|
def _scrape_single_page(sorting_option, page, start_date, end_date, city, distance, |
|
|
|
|
categories, levels, lat, lng, ages, tournament_types, national_cup): |
|
|
|
|
""" |
|
|
|
|
Helper function to scrape a single page of tournaments |
|
|
|
|
""" |
|
|
|
|
return scrape_fft_all_tournaments( |
|
|
|
|
sorting_option=sorting_option, |
|
|
|
|
page=page, |
|
|
|
|
start_date=start_date, |
|
|
|
|
end_date=end_date, |
|
|
|
|
city=city, |
|
|
|
|
distance=distance, |
|
|
|
|
categories=categories, |
|
|
|
|
levels=levels, |
|
|
|
|
lat=lat, |
|
|
|
|
lng=lng, |
|
|
|
|
ages=ages, |
|
|
|
|
tournament_types=tournament_types, |
|
|
|
|
national_cup=national_cup |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def scrape_fft_all_tournaments_concurrent(sorting_option=None, start_date=None, end_date=None, |
|
|
|
|
city='', distance=15, categories=None, levels=None, |
|
|
|
|
lat=None, lng=None, ages=None, tournament_types=None, |
|
|
|
|
national_cup=False, max_workers=5): |
|
|
|
|
""" |
|
|
|
|
Scrapes all remaining pages of FFT tournaments concurrently (pages 1 to end) |
|
|
|
|
This assumes page 0 was already fetched by the client |
|
|
|
|
""" |
|
|
|
|
logger.info(f"Starting concurrent scraping for remaining tournament pages") |
|
|
|
|
|
|
|
|
|
# First, get the first page to determine total results and pages |
|
|
|
|
first_page_result = scrape_fft_all_tournaments( |
|
|
|
|
sorting_option=sorting_option, |
|
|
|
|
page=0, |
|
|
|
|
start_date=start_date, |
|
|
|
|
end_date=end_date, |
|
|
|
|
city=city, |
|
|
|
|
distance=distance, |
|
|
|
|
categories=categories, |
|
|
|
|
levels=levels, |
|
|
|
|
lat=lat, |
|
|
|
|
lng=lng, |
|
|
|
|
ages=ages, |
|
|
|
|
tournament_types=tournament_types, |
|
|
|
|
national_cup=national_cup |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
if not first_page_result: |
|
|
|
|
logger.error("Failed to get first page results for pagination info") |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
total_results = first_page_result.get('total_results', 0) |
|
|
|
|
first_page_tournaments = first_page_result.get('tournaments', []) |
|
|
|
|
results_per_page = len(first_page_tournaments) |
|
|
|
|
|
|
|
|
|
logger.info(f"Total results: {total_results}, Results per page: {results_per_page}") |
|
|
|
|
|
|
|
|
|
if total_results == 0: |
|
|
|
|
return {'tournaments': [], 'total_results': 0, 'current_count': 0, 'pages_scraped': 0} |
|
|
|
|
|
|
|
|
|
# Calculate number of pages needed |
|
|
|
|
if results_per_page > 0: |
|
|
|
|
total_pages = (total_results + results_per_page - 1) // results_per_page |
|
|
|
|
else: |
|
|
|
|
total_pages = 1 |
|
|
|
|
|
|
|
|
|
logger.info(f"Total pages: {total_pages}") |
|
|
|
|
|
|
|
|
|
# If only one page total, return empty since page 0 was already handled |
|
|
|
|
if total_pages <= 1: |
|
|
|
|
return {'tournaments': [], 'total_results': total_results, 'current_count': 0, 'pages_scraped': 0} |
|
|
|
|
|
|
|
|
|
# Scrape all remaining pages concurrently (pages 1 to total_pages-1) |
|
|
|
|
all_tournaments = [] |
|
|
|
|
|
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor: |
|
|
|
|
futures = [] |
|
|
|
|
|
|
|
|
|
for page in range(1, total_pages): |
|
|
|
|
future = executor.submit( |
|
|
|
|
_scrape_single_page, |
|
|
|
|
sorting_option, page, start_date, end_date, city, distance, |
|
|
|
|
categories, levels, lat, lng, ages, tournament_types, national_cup |
|
|
|
|
) |
|
|
|
|
futures.append((page, future)) |
|
|
|
|
|
|
|
|
|
# Collect results as they complete |
|
|
|
|
for page, future in futures: |
|
|
|
|
try: |
|
|
|
|
result = future.result(timeout=60) # 60 second timeout per page |
|
|
|
|
if result and result.get('tournaments'): |
|
|
|
|
tournaments = result.get('tournaments', []) |
|
|
|
|
all_tournaments.extend(tournaments) |
|
|
|
|
logger.info(f"Page {page} completed: {len(tournaments)} tournaments") |
|
|
|
|
else: |
|
|
|
|
logger.warning(f"Page {page} returned no results") |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error processing page {page}: {e}") |
|
|
|
|
|
|
|
|
|
logger.info(f"Concurrent scraping completed: {len(all_tournaments)} tournaments from {total_pages-1} remaining pages") |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
'tournaments': all_tournaments, |
|
|
|
|
'total_results': total_results, |
|
|
|
|
'current_count': len(all_tournaments), |
|
|
|
|
'pages_scraped': total_pages - 1 # Excluding page 0 which was handled separately |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
def _parse_clubs_ajax_response(json_data): |
|
|
|
|
""" |
|
|
|
|
Parse the clubs AJAX response to match Swift FederalClubResponse structure |
|
|
|
|
""" |
|
|
|
|
try: |
|
|
|
|
# Log the raw response structure to understand what we're getting |
|
|
|
|
logger.info(f"Raw clubs response structure: {json_data}") |
|
|
|
|
|
|
|
|
|
club_markers = [] |
|
|
|
|
total_results = 0 |
|
|
|
|
|
|
|
|
|
# Try to extract clubs data from different possible response structures |
|
|
|
|
if isinstance(json_data, dict): |
|
|
|
|
# Pattern 1: Direct club_markers array |
|
|
|
|
if 'club_markers' in json_data: |
|
|
|
|
clubs_data = json_data['club_markers'] |
|
|
|
|
total_results = json_data.get('nombreResultat', len(clubs_data)) |
|
|
|
|
|
|
|
|
|
# Pattern 2: Results wrapper |
|
|
|
|
elif 'results' in json_data: |
|
|
|
|
results = json_data['results'] |
|
|
|
|
clubs_data = results.get('clubs', results.get('items', results.get('club_markers', []))) |
|
|
|
|
total_results = results.get('nombreResultat', results.get('total', results.get('nb_results', len(clubs_data)))) |
|
|
|
|
|
|
|
|
|
# Pattern 3: Direct array in response |
|
|
|
|
elif 'data' in json_data: |
|
|
|
|
clubs_data = json_data['data'] |
|
|
|
|
total_results = len(clubs_data) |
|
|
|
|
|
|
|
|
|
# Pattern 4: Response is the clubs array directly |
|
|
|
|
else: |
|
|
|
|
clubs_data = json_data if isinstance(json_data, list) else [] |
|
|
|
|
total_results = len(clubs_data) |
|
|
|
|
|
|
|
|
|
elif isinstance(json_data, list): |
|
|
|
|
clubs_data = json_data |
|
|
|
|
total_results = len(clubs_data) |
|
|
|
|
|
|
|
|
|
else: |
|
|
|
|
logger.error(f"Unexpected response format: {type(json_data)}") |
|
|
|
|
clubs_data = [] |
|
|
|
|
total_results = 0 |
|
|
|
|
|
|
|
|
|
# Parse each club to match ClubMarker structure |
|
|
|
|
for item in clubs_data: |
|
|
|
|
if isinstance(item, dict): |
|
|
|
|
# Extract pratiques array |
|
|
|
|
pratiques = [] |
|
|
|
|
if 'pratiques' in item: |
|
|
|
|
pratiques = item['pratiques'] |
|
|
|
|
elif 'practices' in item: |
|
|
|
|
pratiques = item['practices'] |
|
|
|
|
else: |
|
|
|
|
# Default to PADEL if not specified |
|
|
|
|
pratiques = ["PADEL"] |
|
|
|
|
|
|
|
|
|
# Ensure pratiques are uppercase strings |
|
|
|
|
pratiques = [p.upper() if isinstance(p, str) else str(p).upper() for p in pratiques] |
|
|
|
|
|
|
|
|
|
club_marker = { |
|
|
|
|
"nom": item.get('nom', item.get('name', '')), |
|
|
|
|
"clubId": str(item.get('clubId', item.get('id', item.get('code', '')))), |
|
|
|
|
"ville": item.get('ville', item.get('city', '')), |
|
|
|
|
"distance": str(item.get('distance', '0')), |
|
|
|
|
"terrainPratiqueLibelle": item.get('terrainPratiqueLibelle', item.get('courtsInfo', '')), |
|
|
|
|
"pratiques": pratiques, |
|
|
|
|
"lat": float(item.get('lat', item.get('latitude', 0.0))), |
|
|
|
|
"lng": float(item.get('lng', item.get('longitude', 0.0))) |
|
|
|
|
} |
|
|
|
|
club_markers.append(club_marker) |
|
|
|
|
|
|
|
|
|
logger.info(f"Successfully parsed {len(club_markers)} club markers from response") |
|
|
|
|
|
|
|
|
|
# Return the response in the format expected by Swift FederalClubResponse |
|
|
|
|
return { |
|
|
|
|
"typeRecherche": "clubs", |
|
|
|
|
"nombreResultat": total_results, |
|
|
|
|
"club_markers": club_markers |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error parsing clubs AJAX response: {e}") |
|
|
|
|
return { |
|
|
|
|
"typeRecherche": "clubs", |
|
|
|
|
"nombreResultat": 0, |
|
|
|
|
"club_markers": [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def scrape_federal_clubs(country=None, city='', latitude=None, longitude=None, |
|
|
|
|
radius=15, max_workers=5): |
|
|
|
|
""" |
|
|
|
|
Scrapes FFT federal clubs by extracting data from the HTML response |
|
|
|
|
""" |
|
|
|
|
logger.info(f"Starting federal clubs scraping for city: {city}, country: {country}") |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
with sync_playwright() as p: |
|
|
|
|
browser = p.chromium.launch(headless=True) |
|
|
|
|
page_obj = browser.new_page() |
|
|
|
|
|
|
|
|
|
page_obj.set_extra_http_headers({ |
|
|
|
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15" |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
# Clean up city name - remove zip code and extra info |
|
|
|
|
clean_city = city |
|
|
|
|
if city: |
|
|
|
|
import re |
|
|
|
|
clean_city = re.sub(r'[,\s]*\d{5}.*$', '', city).strip() |
|
|
|
|
clean_city = clean_city.rstrip(',').strip() |
|
|
|
|
|
|
|
|
|
logger.info(f"Cleaned city name: '{city}' -> '{clean_city}'") |
|
|
|
|
|
|
|
|
|
# Build the results URL directly |
|
|
|
|
params = f"ville={clean_city}&pratique=PADEL&distance={int(radius)}&country={country or 'fr'}" |
|
|
|
|
results_url = f"https://tenup.fft.fr/recherche/clubs/resultats?{params}" |
|
|
|
|
|
|
|
|
|
logger.info(f"Requesting results URL: {results_url}") |
|
|
|
|
|
|
|
|
|
# Navigate to the results page |
|
|
|
|
page_obj.goto(results_url) |
|
|
|
|
|
|
|
|
|
# Wait for the page to load |
|
|
|
|
page_obj.wait_for_timeout(3000) |
|
|
|
|
|
|
|
|
|
# Check if we're in queue |
|
|
|
|
if "queue-it.net" in page_obj.url.lower(): |
|
|
|
|
logger.warning("Hit Queue-It on results page") |
|
|
|
|
browser.close() |
|
|
|
|
return { |
|
|
|
|
"typeRecherche": "clubs", |
|
|
|
|
"nombreResultat": 0, |
|
|
|
|
"club_markers": [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
# Use JavaScript to extract the data directly from the page |
|
|
|
|
extraction_script = """ |
|
|
|
|
() => { |
|
|
|
|
try { |
|
|
|
|
// Check if Drupal.settings exists and has the data |
|
|
|
|
if (typeof Drupal !== 'undefined' && |
|
|
|
|
Drupal.settings && |
|
|
|
|
Drupal.settings.fft_recherche_club) { |
|
|
|
|
|
|
|
|
|
const data = Drupal.settings.fft_recherche_club; |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
success: true, |
|
|
|
|
typeRecherche: data.typeRecherche || 'club', |
|
|
|
|
total: data.total || 0, |
|
|
|
|
resultat: data.resultat || [] |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
success: false, |
|
|
|
|
error: 'Drupal.settings.fft_recherche_club not found' |
|
|
|
|
}; |
|
|
|
|
} catch (error) { |
|
|
|
|
return { |
|
|
|
|
success: false, |
|
|
|
|
error: error.message |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
result = page_obj.evaluate(extraction_script) |
|
|
|
|
|
|
|
|
|
browser.close() |
|
|
|
|
|
|
|
|
|
if result.get('success'): |
|
|
|
|
type_recherche = result.get('typeRecherche', 'club') |
|
|
|
|
total = result.get('total', 0) |
|
|
|
|
resultat = result.get('resultat', []) |
|
|
|
|
|
|
|
|
|
logger.info(f"Successfully extracted {total} clubs") |
|
|
|
|
|
|
|
|
|
# Convert resultat to club_markers format |
|
|
|
|
club_markers = [] |
|
|
|
|
for club in resultat: |
|
|
|
|
club_markers.append({ |
|
|
|
|
"nom": club.get('nom', ''), |
|
|
|
|
"clubId": club.get('clubId', ''), |
|
|
|
|
"ville": club.get('ville', ''), |
|
|
|
|
"distance": club.get('distance', ''), |
|
|
|
|
"terrainPratiqueLibelle": club.get('terrainPratiqueLibelle', ''), |
|
|
|
|
"pratiques": club.get('pratiques', []), |
|
|
|
|
"lat": club.get('lat', 0.0), |
|
|
|
|
"lng": club.get('lng', 0.0) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
"typeRecherche": type_recherche, |
|
|
|
|
"nombreResultat": total, |
|
|
|
|
"club_markers": club_markers |
|
|
|
|
} |
|
|
|
|
else: |
|
|
|
|
logger.error(f"Failed to extract data: {result.get('error')}") |
|
|
|
|
return { |
|
|
|
|
"typeRecherche": "clubs", |
|
|
|
|
"nombreResultat": 0, |
|
|
|
|
"club_markers": [] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f"Error in federal clubs scraping: {e}") |
|
|
|
|
logger.error(f"Traceback: {traceback.format_exc()}") |
|
|
|
|
return { |
|
|
|
|
"typeRecherche": "clubs", |
|
|
|
|
"nombreResultat": 0, |
|
|
|
|
"club_markers": [] |
|
|
|
|
} |
|
|
|
|
|