### Manual Setup with venv and pip/poetry Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Set up a virtual environment and install OddsHarvester using pip or poetry. ```bash python3 -m venv .venv source .venv/bin/activate # Unix/macOS # .venv\Scripts\activate # Windows pip install . --use-pep517 # or: poetry install ``` -------------------------------- ### Install OddsHarvester from Source with uv Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Clone the repository and install dependencies using uv. ```bash git clone https://github.com/jordantete/OddsHarvester.git cd OddsHarvester pip install uv uv sync ``` -------------------------------- ### Install Dependencies Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Installs project dependencies using uv. ```bash uv sync ``` -------------------------------- ### Install OddsHarvester Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/00-START-HERE.txt Use pip to install the OddsHarvester library. ```bash pip install oddsharvester ``` -------------------------------- ### Install OddsHarvester from Source Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/integration-guide.md Clone the repository and install OddsHarvester from source using pip. ```bash git clone https://github.com/jordantete/OddsHarvester.git cd OddsHarvester pip install -e . ``` -------------------------------- ### CLI Usage Examples Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-cli.md Demonstrates basic CLI commands for help and version checks, as well as subcommand help. ```bash oddsharvester --help oddsharvester --version oddsharvester upcoming --help oddsharvester historic --help ``` -------------------------------- ### Environment Variable Setup with Proxy Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/configuration.md Configure proxy settings, locale, and timezone using environment variables. This setup is useful when accessing resources through a proxy server or for region-specific data. ```bash export OH_PROXY_URL=http://proxy.example.com:8080 export OH_PROXY_USER=myuser export OH_PROXY_PASS=mypass export OH_LOCALE=fr-BE export OH_TIMEZONE=Europe/Brussels oddsharvester upcoming -s football -d 20250301 -m 1x2 --headless ``` -------------------------------- ### RetryConfig Usage Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-internal-classes.md Demonstrates how to instantiate and use RetryConfig with custom parameters for the retry_with_backoff function. Ensure RetryConfig and retry_with_backoff are imported. ```python from oddsharvester.core.retry import RetryConfig, retry_with_backoff config = RetryConfig( max_attempts=5, base_delay=1.0, max_delay=60.0, exponential_base=2.0, jitter_factor=0.1 ) result = await retry_with_backoff(some_async_func, config=config) ``` -------------------------------- ### Basic Environment Variable Setup Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/configuration.md Set up basic configuration using environment variables for sport, headless mode, and concurrency. Then, run the oddsharvester CLI with additional options. ```bash export OH_SPORT=football export OH_HEADLESS=true export OH_CONCURRENCY=3 # Now just pass other CLI options oddsharvester upcoming -d 20250301 -m 1x2 ``` -------------------------------- ### Display Version Information Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-cli.md Show the installed version of the Oddsharvester CLI and exit by using the --version flag. ```bash oddsharvester --version ``` -------------------------------- ### StorageType get_storage_instance() Method Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/types.md Shows how to obtain a storage handler instance based on the selected StorageType. The handler can then be used to save data. ```python storage_type = StorageType.LOCAL handler = storage_type.get_storage_instance() handler.save_data(data=matches, file_path="output.json", ...) ``` -------------------------------- ### Run Historical Data CLI Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/00-START-HERE.txt Example of using the oddsharvester CLI to scrape historical football data for a specific league and season. ```bash oddsharvester historic -s football -l england-premier-league --season 2024-2025 -m 1x2 ``` -------------------------------- ### Successful Match Data Structure Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-data-structures.md This is a comprehensive example of the dictionary structure returned for a successfully scraped match. It includes details like match ID, sport, teams, date, league, URL, status, odds, venue, score, and period. ```json { "match_id": "12345", "sport": "football", "home_team": "Manchester United", "away_team": "Liverpool", "date": "2025-03-01T15:00:00Z", "league": "england-premier-league", "match_url": "https://www.oddsportal.com/football/england/premier-league/", "status": "upcoming", # "upcoming", "live", "finished" "odds": [ { "market": "1x2", "submarket": "1x2", "bookmaker": "Bet365", "home_odds": 2.10, "draw_odds": 3.50, "away_odds": 3.40, "odds_last_updated": "2025-02-28T14:00:00Z" }, { "market": "btts", "submarket": "btts", "bookmaker": "William Hill", "yes_odds": 1.95, "no_odds": 1.90, "odds_last_updated": "2025-02-28T13:45:00Z" } ], "venue": { "name": "Old Trafford", "city": "Manchester", "country": "England" }, "score": null, # null for upcoming, e.g., {"home": 2, "away": 1} for finished "period": "full_time" } ``` -------------------------------- ### Run Scraper Function Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-core-functions.md Demonstrates how to use the `run_scraper` function to initiate a scraping task for upcoming football matches. It specifies the command, sport, date, markets, and configuration for headless mode and concurrency. ```python import asyncio from oddsharvester.core.scraper_app import run_scraper from oddsharvester.utils.command_enum import CommandEnum result = asyncio.run( run_scraper( command=CommandEnum.UPCOMING_MATCHES, sport="football", date="20250301", markets=["1x2", "btts"], headless=True, concurrency_tasks=3, ) ) if result and result.success: print(f"Scraped {result.stats.successful} matches") print(f"Failed: {result.stats.failed}") ``` -------------------------------- ### Run Upcoming Matches CLI Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/00-START-HERE.txt Example of using the oddsharvester CLI to scrape upcoming football matches for a specific date and market. ```bash oddsharvester upcoming -s football -d 20250301 -m 1x2 --headless ``` -------------------------------- ### CLI Configuration for Upcoming Matches Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/README.md Configure the oddsharvester to fetch upcoming football matches for a specific date, market, and output format using the command-line interface. This example specifies local storage, JSON format, and output to 'matches.json', with headless mode, concurrency, and request delay settings. ```bash oddsharvester upcoming -s football -d 20250301 -m 1x2 \ --storage local \ --format json \ --output matches.json \ --headless \ --concurrency 5 \ --request-delay 1.5 ``` -------------------------------- ### Historical Data Availability Examples Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/leagues-and-sports.md Fetch historical sports data using the 'oddsharvester historic' command. Specify the league and season using the '--season' flag. ```bash oddsharvester historic -s football -l england-premier-league --season 2024-2025 oddsharvester historic -s football -l spain-laliga --season 2023-2024 oddsharvester historic -s football -l england-premier-league --season current ``` -------------------------------- ### Environment Variable Setup for Regional Domain Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/configuration.md Configure the base URL, locale, and timezone for accessing regional domains. This is useful for scraping data from specific country-specific websites. ```bash export OH_BASE_URL=https://www.centroquote.it export OH_LOCALE=it-IT export OH_TIMEZONE=Europe/Rome export OH_HEADLESS=true oddsharvester historic -s football -l italy-serie-a --season 2024-2025 -m 1x2 ``` -------------------------------- ### Programmatic Configuration for Upcoming Matches Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/README.md Initiate the oddsharvester scraper programmatically using Python to fetch upcoming football matches. This example demonstrates setting sport, date, headless mode, concurrency, and request delay via function arguments. ```python result = await run_scraper( command=CommandEnum.UPCOMING_MATCHES, sport="football", date="20250301", headless=True, concurrency_tasks=5, request_delay=1.5, ) ``` -------------------------------- ### Run Scraper Programmatically Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/00-START-HERE.txt Example of using the run_scraper function from the core library to scrape upcoming matches programmatically. ```python import asyncio from oddsharvester.core.scraper_app import run_scraper from oddsharvester.utils.command_enum import CommandEnum result = asyncio.run( run_scraper( command=CommandEnum.UPCOMING_MATCHES, sport="football", date="20250301", markets=["1x2"], ) ) ``` -------------------------------- ### Programmatic Scraping of Upcoming Matches Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/README.md Use the `run_scraper` function for programmatic access to scraping capabilities. This example scrapes upcoming football matches with specified markets and headless mode. ```python import asyncio from oddsharvester.core.scraper_app import run_scraper from oddsharvester.utils.command_enum import CommandEnum async def main(): result = await run_scraper( command=CommandEnum.UPCOMING_MATCHES, sport="football", date="20250301", markets=["1x2", "btts"], headless=True, ) if result and result.success: print(f"Scraped {result.stats.successful} matches") for match in result.success: print(f"{match['home_team']} vs {match['away_team']}") asyncio.run(main()) ``` -------------------------------- ### Set Environment Variables for OddHarvester CLI Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-cli.md Use export commands to set environment variables before running the oddsharvester command. This example sets the sport, headless mode, and concurrency. ```bash export OH_SPORT=football export OH_HEADLESS=true export OH_CONCURRENCY=5 oddsharvester upcoming -d 20250301 -m 1x2 ``` -------------------------------- ### Venue Object Structure Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-data-structures.md This is the structure for the 'venue' object, which is included in the successful match data when venue information is available. It contains the name, city, and country of the match venue. ```json { "name": "Old Trafford", "city": "Manchester", "country": "England" } ``` -------------------------------- ### CSV Output Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-data-structures.md The CSV output format provides flattened match data. Each row represents a match, with columns detailing various aspects of the match and associated odds. Note that multiple bookmakers and markets for a single match will result in a Cartesian product of rows. ```text | Column | |--------|--------|------|---------| | match_id | string | 12345 | Unique identifier | | sport | string | football | Sport name | | home_team | string | Manchester United | Home competitor | | away_team | string | Liverpool | Away competitor | | date | datetime | 2025-03-01T15:00:00Z | ISO 8601 UTC | | league | string | england-premier-league | League slug | | match_url | string | https://... | OddsPortal URL | | status | string | upcoming | upcoming/live/finished | | market | string | 1x2 | Market type | | submarket | string | 1x2 | Market variant | | bookmaker | string | Bet365 | Bookmaker name | | home_odds | float | 2.10 | Home odds (or null) | | draw_odds | float | 3.50 | Draw odds (or null) | | away_odds | float | 3.40 | Away odds (or null) | | odds_last_updated | datetime | 2025-02-28T14:00:00Z | When odds were last updated | | venue_name | string | Old Trafford | Stadium name (or null) | | score_home | int | 2 | Home score (null if not finished) | | score_away | int | 1 | Away score (null if not finished) | ``` -------------------------------- ### League Slug Format Examples Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/leagues-and-sports.md League slugs follow a lowercase, hyphen-separated format. Examples include 'england-premier-league' and 'spain-laliga'. ```text england-premier-league spain-laliga france-ligue-1 italy-serie-a germany-bundesliga united-states-nba brazil-serie-a japan-j1-league argentina-primera-division croatia-hnl ``` -------------------------------- ### Import main function from CLI module Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-cli.md Shows how to import the main entry point for the CLI executable. ```python from oddsharvester.cli.cli import main ``` -------------------------------- ### Handling MarketExtractionError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching MarketExtractionError and conditionally retrying based on the 'is_retryable' attribute. ```python try: await scraper.scrape_upcoming(...) except MarketExtractionError as e: if e.is_retryable: print(f"Retrying market extraction for {e.url}") else: print(f"Market extraction failed (not retryable): {e.url}") ``` -------------------------------- ### Handling PageNotFoundError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching PageNotFoundError, indicating that a requested page could not be found and should not be retried. ```python try: await scraper.scrape_historic(...) except PageNotFoundError as e: print(f"Page not found: {e.url}") # Do not retry; page no longer exists ``` -------------------------------- ### Run Integration Tests (Live Network) Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Executes integration tests against the live network. ```bash uv run pytest tests/integration/ -q -m integration --live ``` -------------------------------- ### Handling ParsingError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching ParsingError, highlighting that the page structure has changed and the operation should not be retried. ```python try: await scraper.scrape_upcoming(...) except ParsingError as e: print(f"Page structure changed at {e.url}") # Do not retry; issue requires investigation ``` -------------------------------- ### Handle PartialDataError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching PartialDataError to access partial data and decide on further action. This error is not retryable. ```python try: await scraper.scrape_upcoming(...) except PartialDataError as e: print(f"Partial data available for {e.url}") print(f"Missing: {e.partial_data.get('missing_markets', [])}") # Decide whether to use partial data ``` -------------------------------- ### Handling RateLimitError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching RateLimitError and using the 'retry_after' attribute to implement a delay before retrying the operation. ```python try: await scraper.scrape_upcoming(...) except RateLimitError as e: print(f"Rate limited. Retry after {e.retry_after}s") # Safe to retry after delay ``` -------------------------------- ### Handling NavigationError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of catching NavigationError, indicating a failure to navigate to a URL and that the operation is safe to retry. ```python try: await scraper.scrape_upcoming(...) except NavigationError as e: print(f"Failed to navigate to {e.url}: {e.message}") # Safe to retry ``` -------------------------------- ### Handling ScraperError Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md Example of how to catch and handle the base ScraperError, accessing its message, URL, and retryability status. ```python try: result = await run_scraper(...) except ScraperError as e: print(f"Scraper error: {e.message}") print(f"URL: {e.url}") print(f"Retryable: {e.is_retryable}") ``` -------------------------------- ### Create Volleyball Integration Test File Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Initializes the integration test file for volleyball. This test is marked `live_only` due to the use of H2H fragment URLs which cannot be deterministically replayed from HAR files. ```python """Integration tests for volleyball scraping. Regression guard: volleyball must STORE ODDS, not just match metadata. The home_away odds field must be non-empty. NOTE: volleyball leagues on OddsPortal use H2H fragment URLs (/volleyball/h2h///#), which cannot be replayed deterministically from HAR (same limitation as NBA/baseball/handball H2H tests). This test is marked live_only and SKIPS in default HAR-replay mode. Run with --live against a real H2H URL, or capture a fixture once a direct match URL is available: uv run python -m tests.integration.helpers.capture --sport volleyball \ --league italy-superlega \ --match-url "" \ --markets "home_away" \ --period "full_time" \ --bookies-filter "all" \ --capture-har """ import json import pytest from tests.integration.helpers.comparison import compare_match_data SUPERLEGA_MATCH = { "sport": "volleyball", "league": "italy-superlega", "match_id": "PLACEHOLDER_CAPTURE_PENDING", "url": "https://www.oddsportal.com/volleyball/italy/superlega/", } @pytest.mark.integration @pytest.mark.live_only class TestVolleyballBasicMarkets: """Regression tests for volleyball odds extraction (home_away must be non-empty).""" def test_vb_001_home_away_full_time( self, run_scraper, load_fixture, temp_output_dir, fixture_exists, har_for_match, ): """VB-001: Volleyball home_away market, full time, all bookies — odds must be present.""" fixture_name = "home_away_full_time_all.json" if not fixture_exists( SUPERLEGA_MATCH["sport"], SUPERLEGA_MATCH["league"], SUPERLEGA_MATCH["match_id"], fixture_name, ): pytest.skip(f"Fixture not yet captured: {fixture_name} — see module docstring") output_path = temp_output_dir / "output" exit_code, _stdout, stderr = run_scraper( sport="volleyball", match_link=SUPERLEGA_MATCH["url"], markets=["home_away"], output_path=output_path, period="full_time", bookies_filter="all", har_path=har_for_match( SUPERLEGA_MATCH["sport"], SUPERLEGA_MATCH["league"], SUPERLEGA_MATCH["match_id"], fixture_name, ), ) assert exit_code == 0, f"Scraper failed: {stderr}" with open(f"{output_path}.json") as f: actual = json.load(f) assert actual[0].get("home_away") or (actual[0].get("odds") or {}).get( "home_away" ), "Volleyball regression: home_away odds missing — scraper stored metadata only" expected = load_fixture( SUPERLEGA_MATCH["sport"], SUPERLEGA_MATCH["league"], SUPERLEGA_MATCH["match_id"], fixture_name, ) result = compare_match_data(actual[0], expected[0]) assert result.passed, str(result) ``` -------------------------------- ### Prepare and tag a new release Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Update the version in pyproject.toml, commit the change, tag the release, and push to origin. This triggers the automated release process. ```bash git checkout master && git pull origin master uv run pytest tests/ -q --ignore=tests/integration/ # bump version in pyproject.toml (X.Y.Z), then: git add pyproject.toml git commit -m "chore: release vX.Y.Z" git tag vX.Y.Z git push origin master --tags ``` -------------------------------- ### Run OddsHarvester Docker Container (CLI Args) Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Run the OddsHarvester Docker container and pass arguments to the CLI. ```bash # Run (CLI args are appended to the ENTRYPOINT `python3 -m oddsharvester`) docker run --rm odds-harvester:local upcoming -s football -d 20250301 -m 1x2 --headless ``` -------------------------------- ### Run Test to Verify Failure Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Execute the newly written test for volleyball markets to confirm it fails as expected, indicating that volleyball is not yet mapped. ```bash uv run pytest tests/utils/test_utils.py::test_get_supported_markets_volleyball -q ``` -------------------------------- ### View Oddsharvester CLI Help Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/README.md Display the help message for the oddsharvester CLI to see all available commands and options. ```bash oddsharvester --help ``` -------------------------------- ### Build OddsHarvester Docker Image Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Build a local Docker image for the OddsHarvester application. ```bash # Build docker build -t odds-harvester:local . ``` -------------------------------- ### RetryResult Usage Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-internal-classes.md Shows how to process the RetryResult object returned by retry_with_backoff. Check the 'success' attribute to determine if the operation completed and access 'result' or error details. ```python result = await retry_with_backoff(my_function, config=config) if result.success: data = result.result else: print(f"Failed after {result.attempts} attempts") print(f"Error: {result.last_error}") print(f"Type: {result.error_type}") ``` -------------------------------- ### Run OddsHarvester Docker Container (Volume Mount) Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Run the Docker container and save the JSON output to a host volume. ```bash # Run and keep the JSON output on the host (mount a volume + use -o) # On macOS+colima, prefer a path under $HOME (e.g. $PWD); /tmp is not shared by default. docker run --rm -v "$PWD/_docker_out:/out" odds-harvester:local \ upcoming -s football -d 20250301 -m 1x2 --headless -o /out/result.json ``` -------------------------------- ### Regional Domain Usage Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/leagues-and-sports.md Harvest upcoming sports data from a specific regional domain using the '--base-url', '--locale', and '--timezone' flags. ```bash oddsharvester upcoming -s football -d 20250301 -m 1x2 \ --base-url https://www.centroquote.it \ --locale it-IT \ --timezone Europe/Rome ``` -------------------------------- ### Create New Fixtures with Capture Script Source: https://github.com/jordantete/oddsharvester/blob/master/tests/integration/fixtures/README.md Command to generate new fixture data by capturing information from OddsPortal. Requires specifying sport, league, match URL, markets, period, and bookies filter. ```bash python -m tests.integration.helpers.capture \ --sport football \ --league premier-league \ --match-url "https://www.oddsportal.com/..." \ --markets "1x2,btts" \ --period "full_time" \ --bookies-filter "all" ``` -------------------------------- ### Directory Structure for Fixtures Source: https://github.com/jordantete/oddsharvester/blob/master/tests/integration/fixtures/README.md Illustrates the hierarchical organization of fixture data for different sports and leagues. ```bash fixtures/ ├── football/ │ ├── premier-league/ │ │ └── {match-slug}/ │ │ ├── metadata.json │ │ └── {markets}_{period}_{bookies}.json │ └── super-cup-2025/ │ └── ... ├── basketball/ │ └── nba/ │ └── ... └── tennis/ └── australian-open/ └── ... ``` -------------------------------- ### Configure Local Storage Output Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/configuration.md Use this snippet to configure Oddsharvester to save upcoming football match data in JSON format to a local file, overwriting existing content. ```bash oddsharvester upcoming -s football -d 20250301 -m 1x2 \ --storage local \ --format json \ --output /data/matches.json \ --no-append ``` -------------------------------- ### Enable Debug Logging with --verbose Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-cli.md Activate detailed debug logging output by using the --verbose or -v flag. This is helpful for troubleshooting. ```bash oddsharvester -v upcoming -s football -d 20250301 -m 1x2 ``` -------------------------------- ### Run Pytest Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Executes the pytest suite to verify the correct registration of all markets, including volleyball-specific tests. This command should be run after implementing the market registration changes. ```bash uv run pytest tests/core/test_sport_market_registry.py -q ``` -------------------------------- ### Add Volleyball Market Imports Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Import necessary constants for volleyball markets into the `sport_market_registry.py` file. Ensure alphabetical order with existing imports. ```python VolleyballAsianHandicapPointsMarket, VolleyballAsianHandicapSetsMarket, VolleyballCorrectScoreMarket, VolleyballMarket, VolleyballOverUnderPointsMarket, VolleyballOverUnderSetsMarket, ``` -------------------------------- ### Format Code Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Formats code in the project using Ruff. ```bash uv run ruff format . ``` -------------------------------- ### JSON Output Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-data-structures.md The JSON output format uses JSON Lines, where each line is a self-contained JSON object representing match data. This format is suitable for streaming and easy parsing. ```json {"match_id":"12345","sport":"football","home_team":"Manchester United",...} {"match_id":"12346","sport":"football","home_team":"Chelsea",...} ``` -------------------------------- ### Linting Command Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-17-regional-base-url.md Run the linting commands to format code and check for style issues using ruff. ```bash uv run ruff format . && uv run ruff check --fix src/ ``` -------------------------------- ### run_scraper() Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-core-functions.md Orchestrates the entire scraping process, including browser setup and retry logic. It allows for detailed configuration of scraping tasks, from specific matches to broad historical data retrieval. ```APIDOC ## Function: run_scraper() ### Description Orchestrates the entire scraping process with browser setup and retry logic. ### Module `oddsharvester.core.scraper_app` ### Import ```python from oddsharvester.core.scraper_app import run_scraper ``` ### Signature ```python async def run_scraper( command: CommandEnum, match_links: list | None = None, sport: str | None = None, date: str | None = None, leagues: list[str] | None = None, season: str | None = None, markets: list | None = None, max_pages: int | None = None, proxy_url: str | None = None, proxy_user: str | None = None, proxy_pass: str | None = None, browser_user_agent: str | None = None, browser_locale_timezone: str | None = None, browser_timezone_id: str | None = None, base_url: str | None = None, target_bookmaker: str | None = None, scrape_odds_history: bool = False, headless: bool = True, preview_submarkets_only: bool = False, bookies_filter: str = "all", period: str | None = None, request_delay: float = 1.0, concurrency_tasks: int = 3, include_started: bool = False, ) -> ScrapeResult | None ``` ### Parameters #### Path Parameters * None #### Query Parameters * None #### Request Body * None ### Parameters Table | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | command | CommandEnum | ✓ | — | Scraping command: `scrape_historic` or `scrape_upcoming` | | match_links | list | — | None | Specific match URLs to scrape (overrides sport/date/leagues) | | sport | str | ✓* | None | Sport to scrape (e.g., `football`, `tennis`). *Required unless match_links provided | | date | str | — | None | Target date in YYYYMMDD format (for upcoming) | | leagues | list[str] | — | None | Comma-separated league slugs (e.g., `england-premier-league`) | | season | str | — | None | Season for historic scraping (YYYY, YYYY-YYYY, or `current`) | | markets | list | — | None | Markets to scrape (e.g., `1x2`, `btts`) | | max_pages | int | — | None | Maximum pages to scrape (historic only) | | proxy_url | str | — | None | Proxy URL (`http://...` or `socks5://...`) | | proxy_user | str | — | None | Proxy username | | proxy_pass | str | — | None | Proxy password | | browser_user_agent | str | — | None | Custom browser user agent | | browser_locale_timezone | str | — | None | Browser locale (e.g., `fr-BE`) | | browser_timezone_id | str | — | None | Browser timezone (e.g., `Europe/Brussels`) | | base_url | str | — | None | Regional OddsPortal domain (e.g., `https://www.centroquote.it`) | | target_bookmaker | str | — | None | Filter odds for specific bookmaker | | scrape_odds_history | bool | — | False | Include historical odds movement | | headless | bool | — | True | Run browser in headless mode | | preview_submarkets_only | bool | — | False | Fast mode: average odds only, no individual bookmakers | | bookies_filter | str | — | "all" | Bookmaker filter: `all`, `classic`, or `crypto` | | period | str | — | None | Match period (sport-specific, e.g., `full_time`) | | request_delay | float | — | 1.0 | Delay in seconds between requests | | concurrency_tasks | int | — | 3 | Number of concurrent scraping tasks | | include_started | bool | — | False | Include matches already started (upcoming only) | ### Return Type `ScrapeResult | None` - **ScrapeResult:** Contains `success` (list of match dicts), `failed` (list of FailedUrl), `partial` (list of PartialResult), and `stats` (ScrapeStats) - **None:** Returned on fatal error during initialization ### Throws / Rejects - **ValueError:** If required parameter combination is missing (e.g., no sport/leagues/date for upcoming) - **Exception:** Re-raises non-retryable errors ### Example ```python import asyncio from oddsharvester.core.scraper_app import run_scraper from oddsharvester.utils.command_enum import CommandEnum result = asyncio.run( run_scraper( command=CommandEnum.UPCOMING_MATCHES, sport="football", date="20250301", markets=["1x2", "btts"], headless=True, concurrency_tasks=3, ) ) if result and result.success: print(f"Scraped {result.stats.successful} matches") print(f"Failed: {result.stats.failed}") ``` ``` -------------------------------- ### Run Historic Scraper Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Scrapes historical football match data for specified leagues and seasons. ```bash uv run oddsharvester scrape-historic --sport football --leagues england-premier-league --season 2022-2023 --markets 1x2 ``` -------------------------------- ### Analyze ScrapeResult for Error Breakdown Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/errors.md After a scraping operation, use this pattern to get a detailed breakdown of errors encountered. It categorizes failures by error type and identifies URLs that are candidates for retrying. ```python from oddsharvester.core.scrape_result import ErrorType result = await run_scraper(...) # Get breakdown by error type breakdown = result.get_error_breakdown() for error_type, urls in breakdown.items(): print(f"{error_type}: {len(urls)} failures") for url in urls: print(f" - {url}") # Identify retryable failures retryable = result.get_retryable_urls() if retryable: print(f"Can retry {len(retryable)} URLs") ``` -------------------------------- ### Python CLI Base URL Validation Function Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-17-regional-base-url.md Implements the `--base-url` CLI validator. It ensures the provided URL starts with http or https, has a valid host, and contains no path, query, or fragment. ```python def validate_base_url(ctx, param, value): """Validate --base-url: host-only http(s) URL (no path/query/fragment).""" if not value: return None normalized = value.rstrip("/") parts = urlsplit(normalized) if parts.scheme not in ("http", "https"): raise click.BadParameter( f"Invalid base URL '{value}'. Must start with http:// or https:// (e.g. https://www.centroquote.it)." ) if not parts.netloc: raise click.BadParameter(f"Invalid base URL '{value}'. Missing host (e.g. https://www.centroquote.it).") if parts.path or parts.query or parts.fragment: raise click.BadParameter( f"Invalid base URL '{value}'. Provide host only, no path/query (e.g. https://www.centroquote.it)." ) return normalized ``` -------------------------------- ### Run OddsHarvester Docker Container (Environment Variables) Source: https://github.com/jordantete/oddsharvester/blob/master/README.md Run the Docker container using environment variables for configuration. ```bash # Or with environment variables docker run --rm \ -e OH_SPORT=football \ -e OH_HEADLESS=true \ odds-harvester:local upcoming -d 20250301 -m 1x2 ``` -------------------------------- ### Initialize ProxyManager Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-internal-classes.md Initializes the `ProxyManager` with optional proxy URL, username, and password. This class manages proxy configurations for scraping tasks. ```python from oddsharvester.utils.proxy_manager import ProxyManager proxy_manager = ProxyManager( proxy_url="http://proxy.example.com:8080", proxy_user="user", proxy_pass="pass" ) proxy_config = proxy_manager.get_current_proxy() # Returns: { # "server": "http://proxy.example.com:8080", # "username": "user", # "password": "pass" # } ``` -------------------------------- ### Odds Object Structure Example Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-data-structures.md This structure defines an individual odds object within the 'odds' array of a successful match. It includes market type, submarket, bookmaker, various odds fields (which vary by market type), and the timestamp of the last update. ```json { "market": "1x2", # Market type "submarket": "1x2", # Specific submarket variant "bookmaker": "Bet365", # Bookmaker name "home_odds": 2.10, # Home/Yes odds (varies by market) "draw_odds": 3.50, # Draw odds (or null) "away_odds": 3.40, # Away/No odds "yes_odds": 1.95, # Yes odds (for binary markets) "no_odds": 1.90, # No odds "handicap_value": -1.5, # For handicap markets (optional) "odds_last_updated": "2025-02-28T14:00:00Z" # When odds were last updated } ``` -------------------------------- ### Wire into register_all_markets Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Integrates the newly registered volleyball markets into the main market registration function. Ensure this call is placed after other sport registrations. ```python cls.register_volleyball_markets() ``` -------------------------------- ### Implement Register Volleyball Markets Method Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Add the `register_volleyball_markets` classmethod to `SportMarketRegistrar`. This method mirrors the structure of `register_tennis_markets` and is responsible for registering all defined volleyball market types. ```python def register_volleyball_markets(cls): """Register markets for volleyball.""" cls.register_market( SportMarketRegistrar.MarketType.HOME_AWAY, VolleyballMarket, Sport.VOLLEYBALL, ) cls.register_market( SportMarketRegistrar.MarketType.OVER_UNDER_SETS_3_5, VolleyballOverUnderSetsMarket, Sport.VOLLEYBALL, ) cls.register_market( SportMarketRegistrar.MarketType.OVER_UNDER_POINTS_184_5, VolleyballOverUnderPointsMarket, Sport.VOLLEYBALL, ) cls.register_market( SportMarketRegistrar.MarketType.ASIAN_HANDICAP_2_5_SETS, VolleyballAsianHandicapSetsMarket, Sport.VOLLEYBALL, ) cls.register_market( SportMarketRegistrar.MarketType.ASIAN_HANDICAP_2_5_POINTS, VolleyballAsianHandicapPointsMarket, Sport.VOLLEYBALL, ) cls.register_market( SportMarketRegistrar.MarketType.CORRECT_SCORE_3_0, VolleyballCorrectScoreMarket, Sport.VOLLEYBALL, ) ``` -------------------------------- ### Fixture File Naming Convention Source: https://github.com/jordantete/oddsharvester/blob/master/tests/integration/fixtures/README.md Defines the pattern for naming fixture files, indicating markets, period, and bookies. ```bash {markets}_{period}_{bookies_filter}.json ``` ```bash 1x2_full_time_all.json ``` ```bash 1x2_btts_double_chance_full_time_all.json ``` ```bash home_away_1st_half_all.json ``` ```bash match_winner_1st_set_classic.json ``` -------------------------------- ### ScrapeStats Constructor Source: https://github.com/jordantete/oddsharvester/blob/master/_autodocs/api-reference-scraper-app.md Initialize a ScrapeStats object to track scraping statistics. Defaults are set for counts. ```python @dataclass class ScrapeStats: total_urls: int = 0 successful: int = 0 failed: int = 0 partial: int = 0 ``` -------------------------------- ### Run Integration Tests (HAR Replay) Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Executes integration tests using HAR replay, which is the default behavior. ```bash uv run pytest tests/integration/ -q -m integration ``` -------------------------------- ### Add base_url parameter to BaseScraper.__init__ Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-17-regional-base-url.md Integrates the `base_url` parameter into the `BaseScraper` constructor, allowing it to be passed during initialization and stored as an instance attribute. Includes documentation for the new parameter. ```python preview_submarkets_only: bool = False, base_url: str | None = None, base_url (str | None): Regional OddsPortal domain override (scheme+host). When None, the canonical https://www.oddsportal.com is used. self.preview_submarkets_only = preview_submarkets_only self.base_url = base_url ``` -------------------------------- ### Run Unit Tests Source: https://github.com/jordantete/oddsharvester/blob/master/CLAUDE.md Executes unit tests, excluding integration tests. ```bash uv run pytest tests/ -q --ignore=tests/integration/ ``` -------------------------------- ### Implement rebase_url helper function Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-17-regional-base-url.md Implementation of the `rebase_url` function which swaps the scheme and host of a given URL with those from a base URL. It preserves the path, query, and fragment, and handles cases where the base URL is None or empty. ```python from urllib.parse import urlsplit, urlunsplit ``` ```python def rebase_url(url: str, base_url: str | None) -> str: """ Swap the scheme and host of ``url`` with those of ``base_url``. Path, query, and fragment are preserved exactly. When ``base_url`` is None or empty, ``url`` is returned unchanged (the default oddsportal.com path). Idempotent. ``base_url`` is expected to be host-only (validated upstream). """ if not base_url: return url base = urlsplit(base_url) parts = urlsplit(url) return urlunsplit((base.scheme, base.netloc, parts.path, parts.query, parts.fragment)) ``` -------------------------------- ### Commit Changes for Volleyball Support Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Stage and commit the documentation changes for volleyball support. ```bash git add README.md CLAUDE.md docs/agentic-gotchas.md git commit -m "docs: document volleyball support and Sets/Points dual-axis gotcha" ``` -------------------------------- ### Run Volleyball Integration Test Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-18-volleyball-support.md Execute the integration test for volleyball. This command is used to verify that the test is collected and skips in default mode when live odds are not enabled. ```bash uv run pytest tests/integration/test_volleyball.py -q -m integration ``` -------------------------------- ### Commit Changes Source: https://github.com/jordantete/oddsharvester/blob/master/docs/plans/2026-05-17-regional-base-url.md Stage and commit the documentation changes for the --base-url feature. ```bash git add README.md docs/agentic-gotchas.md git commit -m "docs: document --base-url regional domain support" ```