"""Cloudflare Registrar TLD price scraper.""" import logging from datetime import datetime from typing import Optional import httpx from app.services.tld_scraper.base import BaseTLDScraper, TLDPriceData, ScraperError logger = logging.getLogger(__name__) class CloudflareScraper(BaseTLDScraper): """ Scraper for Cloudflare Registrar domain prices. Cloudflare sells domains at-cost (wholesale price), so their prices are often the lowest available and serve as a baseline. Note: Cloudflare doesn't have a public API, but we can use their known at-cost pricing which they publish. """ name = "cloudflare" base_url = "https://www.cloudflare.com/products/registrar/" # Cloudflare prices are at-cost (wholesale). # These prices are well-documented and rarely change. # Source: https://www.cloudflare.com/products/registrar/ CLOUDFLARE_PRICES = { # Major TLDs (at wholesale cost) "com": {"reg": 10.44, "renew": 10.44, "transfer": 10.44}, "net": {"reg": 11.94, "renew": 11.94, "transfer": 11.94}, "org": {"reg": 10.11, "renew": 10.11, "transfer": 10.11}, "info": {"reg": 11.44, "renew": 11.44, "transfer": 11.44}, "biz": {"reg": 13.44, "renew": 13.44, "transfer": 13.44}, "co": {"reg": 11.02, "renew": 11.02, "transfer": 11.02}, "io": {"reg": 33.98, "renew": 33.98, "transfer": 33.98}, "me": {"reg": 14.94, "renew": 14.94, "transfer": 14.94}, "dev": {"reg": 11.94, "renew": 11.94, "transfer": 11.94}, "app": {"reg": 14.94, "renew": 14.94, "transfer": 14.94}, "xyz": {"reg": 10.44, "renew": 10.44, "transfer": 10.44}, # ccTLDs supported by Cloudflare "uk": {"reg": 8.50, "renew": 8.50, "transfer": 8.50}, "de": {"reg": 7.05, "renew": 7.05, "transfer": 7.05}, "eu": {"reg": 9.00, "renew": 9.00, "transfer": 9.00}, "nl": {"reg": 9.20, "renew": 9.20, "transfer": 9.20}, "ca": {"reg": 12.42, "renew": 12.42, "transfer": 12.42}, "fr": {"reg": 10.22, "renew": 10.22, "transfer": 10.22}, "es": {"reg": 10.05, "renew": 10.05, "transfer": 10.05}, "it": {"reg": 10.99, "renew": 10.99, "transfer": 10.99}, # New gTLDs "club": {"reg": 11.94, "renew": 11.94, "transfer": 11.94}, "shop": {"reg": 28.94, "renew": 28.94, "transfer": 28.94}, "blog": {"reg": 25.94, "renew": 25.94, "transfer": 25.94}, "site": {"reg": 25.94, "renew": 25.94, "transfer": 25.94}, "live": {"reg": 21.94, "renew": 21.94, "transfer": 21.94}, "cloud": {"reg": 19.94, "renew": 19.94, "transfer": 19.94}, } async def scrape(self) -> list[TLDPriceData]: """ Return Cloudflare's known at-cost pricing. Cloudflare doesn't have a public API for pricing, but their prices are well-documented and stable (at wholesale cost). Returns: List of TLDPriceData objects with Cloudflare pricing """ results = [] now = datetime.utcnow() for tld, prices in self.CLOUDFLARE_PRICES.items(): results.append(TLDPriceData( tld=tld, registrar="cloudflare", registration_price=prices["reg"], renewal_price=prices["renew"], transfer_price=prices.get("transfer"), currency="USD", source="static", # These are known prices, not scraped confidence=1.0, # At-cost pricing is reliable scraped_at=now, notes="At-cost (wholesale) pricing", )) logger.info(f"Loaded {len(results)} Cloudflare at-cost prices") return results async def health_check(self) -> bool: """Check if Cloudflare is accessible.""" try: async with httpx.AsyncClient(timeout=10.0) as client: response = await client.get( self.base_url, headers=self.get_headers(), follow_redirects=True, ) return response.status_code == 200 except Exception as e: logger.debug(f"Cloudflare health check failed: {e}") return False