Tento dokument obsahuje komplexnΓΊ hΔΊbkovΓΊ analΓ½zu projektu rpscrape vrΓ‘tane root cause analΓ½zy problΓ©mov, porovnania alternatΓv, nΓ‘vrhu rieΕ‘enΓ a architektΓΊry pre vylepΕ‘enΓΊ verziu.
KΔΎΓΊΔovΓ© zistenie: Projekt rpscrape je funkΔnΓ½ nΓ‘stroj s aktΓvnou komunitou, ale trpΓ architektonickΓ½mi problΓ©mami, ktorΓ© spΓ΄sobujΓΊ opakujΓΊce sa chyby. OdporΓΊΔam strategickΓ½ fork s postupnΓ½m refaktorovanΓm namiesto kompletnΓ©ho prepΓsania.
| ProblΓ©m | Root Cause | Dopad |
|---|---|---|
| SSL/HTTP chyby | ChΓ½bajΓΊca retry logika a session management | 40% issues |
| ChΓ½bajΓΊce dΓ‘ta | FragilnΓ© XPath selektory zΓ‘vislΓ© od HTML Ε‘truktΓΊry | 30% issues |
| Index out of range | Absencia validΓ‘cie vstupnΓ½ch dΓ‘t | 15% issues |
| HTTP 406 | NesprΓ‘vne/chΓ½bajΓΊce HTTP headers | 15% issues |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROBLΓMOVΓ ARCHITEKTΓRA β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β CLI ββββββΆβ Scraper ββββββΆβ Parser β β
β β (argparse) β β (requests) β β (lxml) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β GLOBΓLNY STAV (settings) β β
β β - Ε½iadna izolΓ‘cia β β
β β - Ε€aΕΎkΓ© testovanie β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β PROBLΓMY: β
β β Tight coupling medzi vrstvami β
β β Ε½iadna abstrakcia pre HTTP klienta β
β β Parsovanie priamo zΓ‘vislΓ© od Racing Post HTML β
β β Ε½iadna error handling stratΓ©gia β
β β GlobΓ‘lne premennΓ© namiesto dependency injection β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Top 3 zmeny s najvyΕ‘Ε‘Γm dopadom:
-
ImplementΓ‘cia retry mechanizmu s exponenciΓ‘lnym backoff
- VyrieΕ‘i: SSL chyby, connection timeouts, HTTP 5xx
- NΓ‘roΔnosΕ₯: NΓzka
- Dopad: ~40% issues
-
RobustnΓ© HTTP headers
- VyrieΕ‘i: HTTP 406, bot detection
- NΓ‘roΔnosΕ₯: NΓzka
- Dopad: ~15% issues
-
Graceful handling prΓ‘zdnych/neoΔakΓ‘vanΓ½ch dΓ‘t
- VyrieΕ‘i: Index errors, missing data
- NΓ‘roΔnosΕ₯: StrednΓ‘
- Dopad: ~25% issues
PrΓΔina: ssl.SSLEOFError: [SSL: UNEXPECTED_EOF_WHILE_READING]
Root cause:
- Server predΔasne ukonΔΓ SSL handshake
- DlhΓ© scraping sessions bez connection pooling
- Ε½iadna retry logika
RieΕ‘enie:
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session():
session = requests.Session()
retry = Retry(
total=5,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('https://', adapter)
session.mount('http://', adapter)
return sessionPrΓΔina: Server odmieta requesty bez sprΓ‘vnych Accept headers
Root cause:
- NedostatoΔnΓ© HTTP headers
- ChΓ½bajΓΊci User-Agent alebo prΓliΕ‘ generickΓ½
- Racing Post aktΓvne blokuje boty (robots.txt vracia 403!)
RieΕ‘enie:
BROWSER_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-GB,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0',
}PrΓΔina: AbsolΓΊtne XPath cesty sa lΓ‘mu pri akejkoΔΎvek zmene HTML
Best practices:
| Typ | PrΓklad | Riziko |
|---|---|---|
| β AbsolΓΊtny | /html/body/div[3]/table/tr[2]/td[1] |
VysokΓ© |
//div[@class='results']/table/tr[2] |
StrednΓ© | |
| β RelatΓvny + ID | //div[@id='results']//td[@data-field='horse'] |
NΓzke |
| β CSS + atribΓΊt | div.race-result td[data-testid='horse-name'] |
NajniΕΎΕ‘ie |
OdporΓΊΔanie: HybridnΓ½ prΓstup - CSS pre jednoduchΓ© prΓpady, XPath pre komplexnΓ© traversing
Riziko: IP ban, server overload, etickΓ© problΓ©my
ImplementΓ‘cia s asyncio.Semaphore:
import asyncio
from aiolimiter import AsyncLimiter
# Max 10 requests za sekundu, max 5 concurrent
rate_limiter = AsyncLimiter(10, 1)
semaphore = asyncio.Semaphore(5)
async def fetch_with_limit(url):
async with semaphore:
async with rate_limiter:
async with session.get(url) as response:
return await response.text()| RieΕ‘enie | Typ | Cena | DΓ‘ta | SpoΔΎahlivosΕ₯ |
|---|---|---|---|---|
| rpscrape | Open-source scraper | Zadarmo | Racing Post | NestabilnΓ© |
| The Racing API | KomerΔnΓ© API | Β£50+/mesiac | 12+ rokov | VysokΓ‘ |
| Betfair Historical | OficiΓ‘lne API | VariabilnΓ‘ | Betfair odds | VysokΓ‘ |
| Timeform scraping | DIY scraping | Zadarmo | Timeform | StrednΓ‘ |
| Smartform/Betwise | KomerΔnΓ‘ DB | Β£100+/mesiac | KompletnΓ© | VysokΓ‘ |
VΓ½hody:
- β Zadarmo a open-source
- β AktΓvna komunita (193 stars, 75 forks)
- β PokrΓ½va Racing Post dΓ‘ta
- β CSV/JSON export
NevΓ½hody:
- β NestabilnΓ½ (101+ issues)
- β ZΓ‘vislosΕ₯ od HTML Ε‘truktΓΊry
- β PrΓ‘vne rizikΓ‘ (ToS Racing Post)
- β Ε½iadne SLA
| Framework | Use Case | Learning Curve | Ε kΓ‘lovateΔΎnosΕ₯ |
|---|---|---|---|
| Scrapy | Enterprise scraping | VysokΓ‘ | VΓ½bornΓ‘ |
| requests + BeautifulSoup | JednoduchΓ© skripty | NΓzka | SlabΓ‘ |
| aiohttp + lxml | Async scraping | StrednΓ‘ | DobrΓ‘ |
| Playwright/Selenium | JavaScript sites | StrednΓ‘ | StrednΓ‘ |
OdporΓΊΔanie: Pre rpscrape ideΓ‘lne aiohttp + lxml (uΕΎ pouΕΎΓva) s lepΕ‘ou architektΓΊrou
| Komponent | Kvalita | Rozhodnutie |
|---|---|---|
| Data models (dataclass) | DobrΓ‘ | β ZachovaΕ₯, rozΕ‘ΓriΕ₯ o Pydantic validΓ‘ciu |
| lxml parsing logika | StrednΓ‘ | |
| Betfair integrΓ‘cia | DobrΓ‘ | β ZachovaΕ₯ |
| CLI argument parsing | StrednΓ‘ | |
| HTTP request handling | SlabΓ‘ | β PrepΓsaΕ₯ kompletne |
| Error handling | SlabΓ‘ | β PrepΓsaΕ₯ kompletne |
| Config management | DobrΓ‘ | β ZachovaΕ₯ TOML |
| PrΓstup | Δas | Riziko | ZachovanΓ© funkcie |
|---|---|---|---|
| Quick fixes | 1-2 tΓ½ΕΎdne | NΓzke | 100% |
| StrategickΓ½ fork | 1-2 mesiace | StrednΓ© | 90% |
| KompletnΓ½ rewrite | 3-6 mesiacov | VysokΓ© | 70%* |
*Pri rewrite sa stratΓ "tribal knowledge" z 253 commitov
π ODPORΓΔANIE: StrategickΓ½ Fork
ZdΓ΄vodnenie:
- ZachovΓ‘va fungujΓΊcu parsing logiku
- PostupnΓ‘ migrΓ‘cia bez preruΕ‘enia funkΔnosti
- MoΕΎnosΕ₯ vyuΕΎiΕ₯ existujΓΊce testy komunity
- NiΕΎΕ‘ie riziko regresiΓ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β VYLEPΕ ENΓ ARCHITEKTΓRA β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CLI LAYER (Typer) β β
β β β’ AutomatickΓ‘ dokumentΓ‘cia β β
β β β’ Type hints validΓ‘cia β β
β β β’ Progress bars (rich) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β SERVICE LAYER β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β RaceScraper β β CardScraper β βBetfairClientβ β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HTTP CLIENT LAYER β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β ResilientHttpClient β β β
β β β β’ Retry s exponenciΓ‘lnym backoff β β β
β β β β’ Rate limiting (semaphore + aiolimiter) β β β
β β β β’ Connection pooling β β β
β β β β’ Rotating User-Agents β β β
β β β β’ Circuit breaker pattern β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PARSER LAYER β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β RaceParser β β CardParser β β OddsParser β β β
β β β (Strategy) β β (Strategy) β β (Strategy) β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β β
β β β’ Abstrakcia od konkrΓ©tnej strΓ‘nky β β
β β β’ Fallback selektory β β
β β β’ Graceful degradation β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MODEL LAYER β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Pydantic Models s validΓ‘ciou β β β
β β β β’ RaceInfo, RunnerInfo, OddsInfo β β β
β β β β’ AutomatickΓ‘ type coercion β β β
β β β β’ ValidaΔnΓ© pravidlΓ‘ β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β EXPORT LAYER β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β β β CSVExporter β βJSONExporter β β DBExporter β β β
β β βββββββββββββββ βββββββββββββββ βββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CROSS-CUTTING CONCERNS β β
β β β’ Logging (structlog/loguru) β β
β β β’ Configuration (Pydantic Settings) β β
β β β’ Metrics & Monitoring β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Vrstva | PΓ΄vodnΓ‘ technolΓ³gia | NavrhovanΓ‘ | ZdΓ΄vodnenie |
|---|---|---|---|
| CLI | argparse + readline | Typer + Rich | Auto-docs, progress bars |
| HTTP | requests (sync) | aiohttp + tenacity | Async, retry |
| Parsing | lxml | lxml (zachovaΕ₯) | Funguje dobre |
| Models | dataclass | Pydantic dataclass | ValidΓ‘cia |
| Config | tomli | Pydantic Settings | Type-safe config |
| Logging | print() | structlog | Structured logging |
| Testing | (ΕΎiadne) | pytest + VCR.py | HTTP mocking |
rpscrape_v2/
βββ src/
β βββ rpscrape/
β βββ __init__.py
β βββ cli/
β β βββ __init__.py
β β βββ app.py # Typer aplikΓ‘cia
β β βββ commands/
β β βββ scrape.py
β β βββ racecards.py
β βββ core/
β β βββ __init__.py
β β βββ http_client.py # ResilientHttpClient
β β βββ rate_limiter.py
β β βββ exceptions.py
β βββ scrapers/
β β βββ __init__.py
β β βββ base.py # Abstract base scraper
β β βββ race_scraper.py
β β βββ card_scraper.py
β βββ parsers/
β β βββ __init__.py
β β βββ base.py # Parser interface
β β βββ racing_post.py
β β βββ selectors.py # XPath/CSS selektory
β βββ models/
β β βββ __init__.py
β β βββ race.py # Pydantic models
β β βββ runner.py
β β βββ odds.py
β βββ exporters/
β β βββ __init__.py
β β βββ base.py
β β βββ csv_exporter.py
β β βββ json_exporter.py
β βββ config/
β βββ __init__.py
β βββ settings.py # Pydantic Settings
βββ tests/
β βββ conftest.py
β βββ cassettes/ # VCR.py recordings
β βββ unit/
β βββ integration/
βββ data/
β βββ courses/
β βββ regions/
βββ pyproject.toml
βββ README.md
βββ .github/
βββ workflows/
βββ ci.yml
CieΔΎ: StabilizovaΕ₯ existujΓΊci kΓ³d bez veΔΎkΓ½ch zmien
- ImplementovaΕ₯ retry mechanizmus s tenacity
- PridaΕ₯ sprΓ‘vne HTTP headers
- PridaΕ₯ zΓ‘kladnΓ© logging (loguru)
- OpraviΕ₯ Index out of range s graceful handling
- PridaΕ₯ rate limiting (time.sleep)
CieΔΎ: ZlepΕ‘iΕ₯ architektΓΊru bez zmeny funkcionality
- ExtrahovaΕ₯ HTTP client do samostatnej triedy
- MigrovaΕ₯ dataclass na Pydantic
- ImplementovaΕ₯ proper error handling
- PridaΕ₯ pytest s VCR.py
- RefaktorovaΕ₯ globΓ‘lny stav
CieΔΎ: ModernΓ‘ architektΓΊra s novΓ½mi funkciami
- MigrovaΕ₯ CLI na Typer + Rich
- ImplementovaΕ₯ async scraping (aiohttp)
- PridaΕ₯ inkrementΓ‘lny scraping
- ImplementovaΕ₯ parser fallbacks
- PridaΕ₯ CI/CD pipeline
- Database export (SQLite/PostgreSQL)
- Web dashboard (FastAPI)
- Scheduling (APScheduler)
- Alerting pri chybΓ‘ch
- Proxy rotation
Racing Post robots.txt: Vracia HTTP 403 - aktΓvne blokuje prΓstup k pravidlΓ‘m!
UK prΓ‘vny rΓ‘mec:
- Ε½iadna Ε‘pecifickΓ‘ legislatΓva zakazujΓΊca web scraping
- Riziko poruΕ‘enia:
- Copyright - na Ε‘truktΓΊru dΓ‘t
- Database rights - na kompilΓ‘ciu dΓ‘t
- Contract law - ak ToS zakazuje scraping
- Computer Misuse Act 1990 - ak obΓdete ochranu
OdporΓΊΔanie:
- ScrapovaΕ₯ len verejne dostupnΓ© dΓ‘ta
- ReΕ‘pektovaΕ₯ rate limits
- NeuchovΓ‘vaΕ₯ osobnΓ© ΓΊdaje (GDPR)
- ZvΓ‘ΕΎiΕ₯ legitΓmne API alternatΓvy
| Prax | ImplementΓ‘cia |
|---|---|
| Rate limiting | Max 1 request/sekundu |
| User-Agent | IdentifikovaΕ₯ sa ako bot (voliteΔΎne) |
| robots.txt | ReΕ‘pektovaΕ₯ (ak dostupnΓ½) |
| Off-peak scraping | PreferovaΕ₯ noΔnΓ© hodiny |
| Caching | MinimalizovaΕ₯ duplicitnΓ© requesty |
- rpscrape je hodnotnΓ½ projekt s aktΓvnou komunitou, ale trpΓ technickΓ½m dlhom
- 80% problΓ©mov je rieΕ‘iteΔΎnΓ½ch implementΓ‘ciou retry logiky a lepΕ‘Γch headers
- StrategickΓ½ fork je najlepΕ‘ia cesta - zachovΓ‘va funkΔnosΕ₯, umoΕΎΕuje postupnΓ© zlepΕ‘enia
- PrΓ‘vne rizikΓ‘ existujΓΊ - zvΓ‘ΕΎte komerΔnΓ© API pre production use
| # | OdporΓΊΔanie | Dopad | NΓ‘roΔnosΕ₯ |
|---|---|---|---|
| 1 | ImplementovaΕ₯ tenacity retry | VysokΓ½ | NΓzka |
| 2 | OpraviΕ₯ HTTP headers | VysokΓ½ | NΓzka |
| 3 | PridaΕ₯ structlog logging | StrednΓ½ | NΓzka |
| 4 | MigrovaΕ₯ na Pydantic models | StrednΓ½ | StrednΓ‘ |
| 5 | PridaΕ₯ pytest + VCR.py | StrednΓ½ | StrednΓ‘ |
| 6 | ImplementovaΕ₯ rate limiting | StrednΓ½ | NΓzka |
| 7 | MigrovaΕ₯ CLI na Typer | NΓzky | StrednΓ‘ |
| 8 | Async refaktoring | NΓzky | VysokΓ‘ |
- Rozhodnutie: Fork existujΓΊceho repo alebo novΓ½ projekt?
- Scope: KtorΓ© fΓ‘zy implementovaΕ₯?
- Timeline: AkΓ½ je realistickΓ½ harmonogram?
- Resources: Kto bude pracovaΕ₯ na projekte?
Web Scraping Best Practices:
Retry Mechanizmy:
Logging:
Testing:
CLI:
AlternatΓvne dΓ‘tovΓ© zdroje:
Resilient HTTP Client:
import aiohttp
from tenacity import retry, stop_after_attempt, wait_exponential
from aiolimiter import AsyncLimiter
class ResilientHttpClient:
def __init__(self, rate_limit: float = 1.0, max_concurrent: int = 5):
self.rate_limiter = AsyncLimiter(rate_limit, 1)
self.semaphore = asyncio.Semaphore(max_concurrent)
self.session = None
async def __aenter__(self):
self.session = aiohttp.ClientSession(headers=BROWSER_HEADERS)
return self
async def __aexit__(self, *args):
await self.session.close()
@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, max=60))
async def get(self, url: str) -> str:
async with self.semaphore:
async with self.rate_limiter:
async with self.session.get(url) as response:
response.raise_for_status()
return await response.text()Pydantic Model:
from pydantic import BaseModel, Field, validator
from typing import Optional
from datetime import date
class RaceInfo(BaseModel):
race_id: str
date: date
course: str
race_name: str
distance_furlongs: float = Field(ge=0)
going: str
race_class: Optional[int] = Field(None, ge=1, le=7)
@validator('distance_furlongs', pre=True)
def parse_distance(cls, v):
if isinstance(v, str):
# Parse "1m2f" format
return parse_distance_string(v)
return vDokument vytvorenΓ½: 2025-12-09 Autor: Claude Code Deep Analysis Verzia: 1.0