Liga Magic Scraper
A Ruby gem for scraping product data from Liga Magic's card listings using Capybara and Selenium.
- ๐ Can be used as a gem or standalone CLI tool
- ๐ฅ๏ธ Supports headed (visible) and headless browser modes
- ๐ Saves results to JSON files in the
scrapped/directory
Roadmap
- Global Ligamagic product scrapping
- Store specific scrapping
- Alert System
Features
Global Ligamagic product scrapping
- Extracts product name, ID, and pricing information
- Automatically clicks "Load More" to paginate through all products
- Stops when unavailable products are encountered
- Uses a global-search flag (
-gor--global)
Store specific scrapping
- Extracts product name and pricing information from any Liga Magic store
- Accepts store domain name only (e.g.,
test-store) - Store search with search term support (
-u STORE -s TERM) - Store listing with max pages limit (
-u STORE -p N) - Automatic pagination with safety limits
- Automatically orders by price (most expensive first)
- Filters only in-stock items automatically
- Automatically builds clean URLs with only necessary parameters
- Uses
--storeor-uflag - Extracts store name and ID automatically
- Corrected selectors for card-desc structure
- [!] Limitation: Store searches with search terms cannot extract price/qty due to CSS obfuscation (see docs)
Alert System
- Detects changes between scrapes (new products, removed products, price changes, availability changes)
- Compares current scrape with most recent previous scrape
- Multiple alert types supported: file, telegram
- CLI flag to enable alerts:
-aor--alerts - Configurable alert types via
--alert-types - File alert fully implemented with organized directory structure
- Telegram alert action - currently stub only
Requirements
- Ruby 2.7 or higher
- Chrome browser installed
- ChromeDriver (automatically managed by selenium-webdriver)
Installation
As a Global Command
Install the gem globally to use the ligamagic-scraper command anywhere:
gem install ligamagic-scraperAs a Library in Your Project
Add this line to your application's Gemfile:
gem 'ligamagic-scraper'And then execute:
bundle installUsage
Command Line Interface (Global Installation)
After installing the gem globally, you can use the ligamagic-scraper command from anywhere:
ligamagic-scraper -s "booster box"Search Options:
-
-s,--search SEARCH- Search term (global search or store search with -u) -
-u,--store DOMAIN- Store domain (e.g.,test-store) -
-p,--pages N- Pages to scrape (max: 5, required with -u if no -s)
Options:
-
-g,--global- Use global Liga Magic search (default, optional) -
-b,--browser-mode MODE- Browser mode:headed(default, visible browser) orheadless(no UI) -
-a,--alerts- Enable alert system (detects changes from previous scrapes) -
--alert-types TYPES- Alert types:file,email,telegram,webhook(comma-separated, default: file) -
-h,--help- Show help message -
--version- Show version
Usage Examples:
| Use Case | Command | Pages | Browser |
|---|---|---|---|
| Global Search | |||
| Basic global search | ligamagic-scraper -s "booster box" |
Unlimited | Visible |
| Global search headless | ligamagic-scraper -s "dual land" -b headless |
Unlimited | Headless |
| Global search with alerts | ligamagic-scraper -s "commander" -a |
Unlimited | Visible |
| Store Listing | |||
| List 2 pages | ligamagic-scraper -u test-store -p 2 |
2 | Visible |
| List 5 pages (max) | ligamagic-scraper -u test-store -p 5 |
5 | Visible |
| List with 10 (capped at 5) | ligamagic-scraper -u test-store -p 10 |
5 (max) | Visible |
| List store headless | ligamagic-scraper -u test-store -p 3 -b headless |
3 | Headless |
| List store with alerts | ligamagic-scraper -u test-store -p 4 -a |
4 | Visible |
| Store Search | |||
| Search within store | ligamagic-scraper -u test-store -s "volcanic" |
Unlimited | Visible |
| Store search headless | ligamagic-scraper -u test-store -s "force of will" -b headless |
Unlimited | Headless |
| Store search with alerts | ligamagic-scraper -u test-store -s "mox" -a |
Unlimited | Visible |
| Advanced | |||
| Multiple alert types | ligamagic-scraper -s "lotus" -a --alert-types file,telegram |
Unlimited | Visible |
| Store with custom alerts | ligamagic-scraper -u test-store -s "power nine" -a --alert-types file,email |
Unlimited | Visible |
Search Modes:
The gem supports three search modes:
| Mode | Flags | Pages | When to Use |
|---|---|---|---|
| Global Search | -s TERM |
Unlimited | Search across all Liga Magic stores |
| Store Listing | -u STORE -p N |
1-5 (max) | List products from a specific store |
| Store Search | -u STORE -s TERM |
Unlimited | Search for specific products within a store |
Notes:
-
Maximum 5 pages allowed for store listings (values above 5 are capped)
-
-p 2uses 2 pages -
-p 5uses 5 pages -
-p 10is capped to 5 pages
-
- Store searches with search term have unlimited pagination
-
Automatic filtering: Store results are automatically:
- Ordered by price (most expensive first)
- Filtered to show only in-stock items
-
Limitation: Store searches with search terms (
-u STORE -s TERM) cannot extract price/qty- Liga Magic uses CSS sprite obfuscation for anti-scraping protection
- Only card name and ID are extracted when searching
- Store listings (
-u STORE -p N) work normally with full price/qty data
- Use
-b headlessfor background scraping without browser UI
Using as a Library
You can also import and use the scraper in your Ruby projects:
Global Search
require 'ligamagic_scraper'
# Create a global scraper instance
scraper = LigaMagicScraper::GlobalScraper.new(
search_term: "booster box",
browser_mode: 'headless'
)
# Run the scrape
results = scraper.scrape
# Save results to JSON
scraper.save_to_json(results)
# Access the results
results.each do |product|
puts "#{product[:name]}: R$ #{product[:min_price]}"
end
# Access execution logs
scraper.formatted_logs.each { |log| puts log }Store-Specific Search
require 'ligamagic_scraper'
# Create a store scraper instance for listing (with max pages)
scraper = LigaMagicScraper::StoreScraper.new(
store_domain: "test-store",
max_pages: 5,
browser_mode: 'headless'
)
# Run the scrape
results = scraper.scrape
# Save results to JSON
scraper.save_to_json(results)
# Access the results
results.each do |product|
puts "[#{product[:card_id]}] #{product[:name]}: R$ #{product[:price]} (#{product[:qtd]} available)"
end
# Access execution logs
scraper.formatted_logs.each { |log| puts log }Development Usage (Without Installing)
If you're developing or testing, you can run the executable directly from the project:
# With visible browser (default)
./bin/ligamagic-scraper -s "booster box"
# With headless mode
./bin/ligamagic-scraper -s "commander deck" -b headless
# Or with bundle exec
bundle exec ./bin/ligamagic-scraper -s "commander deck" -b headlessProgrammatic Logging
All scrapers collect execution logs that can be accessed programmatically:
scraper = LigaMagicScraper::GlobalScraper.new(search_term: "test")
results = scraper.scrape
# Access full log entries (with timestamp, level, source)
scraper.logs
# => [
# { timestamp: 2025-10-27 12:00:00, level: :info, message: "๐ Starting...", source: "LigaMagicScraper::GlobalScraper" },
# ...
# ]
# Access just the messages (for display)
scraper.formatted_logs
# => ["๐ Starting Liga Magic global search scraper...", "๐ Search term: test", ...]
# Filter by level
error_logs = scraper.logs.select { |l| l[:level] == :error }
# Display logs
scraper.formatted_logs.each { |msg| puts msg }Log Levels:
-
:info- Major milestones (starting, completing, saving) -
:debug- Detailed progress (pagination clicks, individual product extraction) -
:warning- Non-fatal issues (skipped items, limits reached) -
:error- Errors and exceptions
Note: The CLI automatically displays all logs during execution. When using as a library, logs are collected silently and can be accessed via scraper.logs or scraper.formatted_logs
How it Works
The scraper will:
- Visit the Liga Magic search page with your search term
- Automatically click "Load More" until all available products are loaded
- Extract product names, IDs, slugs, and prices
- Display results in a formatted output
- Save results to a JSON file in
scrapped/YYYYMMDD_search_slug.json - Return a Ruby array of hashes with the data
Output Format
Global Search Output
The global scraper returns an array of hashes with product details:
[
{
id: "48851",
slug: "caixa_de_booster_amonkhet",
name: "Caixa de Booster - Amonkhet",
min_price: 1499.0,
avg_price: 1499.0,
max_price: 1499.0
},
{
id: "80494",
slug: "caixa_de_booster_dominaria",
name: "Caixa de Booster - Dominaria",
min_price: 1399.99,
avg_price: 1482.66,
max_price: 1499.95
},
# ...
]Global Search Fields:
-
id: Unique product code from Liga Magic (pcode), e.g., "48851" (can be null) -
slug: URL-friendly version of product name (normalized, no accents, always present) -
name: Full product name in Portuguese -
min_price: Minimum price across all stores (float) -
avg_price: Average price across all stores (float) -
max_price: Maximum price across all stores (float)
Store-Specific Output
The store scraper returns an array with different fields:
[
{
card_id: "16149",
name: "Volcanic Island",
slug: "volcanic_island",
price: 2500.0,
qtd: 1,
available: true
},
# ...
]Store-Specific Fields:
-
card_id: Unique card identifier from Liga Magic (extracted from link URL) -
name: Full product name -
slug: URL-friendly version of product name -
price: Product price at this store (float) -
qtd: Quantity available (integer) -
available: Whether the product is available (boolean based on qtd > 0)
JSON File Format
Results are automatically saved to organized directories:
Global Searches:
- Directory:
scrapped/global/ - Format:
YYYYMMDD_HHMMSS__search_slug.json - Examples:
scrapped/global/20251027_143022__booster_box.jsonscrapped/global/20251027_151530__commander_deck.json
Store Searches:
- Directory:
scrapped/stores/{store_domain}/ - Format (without search):
YYYYMMDD_HHMMSS.json - Format (with search):
YYYYMMDD_HHMMSS__search_slug.json - Examples:
-
scrapped/stores/test-store/20251027_143022.json(store listing) -
scrapped/stores/test-store/20251027_151530__volcanic_island.json(store search)
-
The datetime format allows multiple scrapes per day, which is useful for price tracking and change detection.
JSON structure:
{
"search_term": "booster box",
"scraped_at": "2025-10-26T14:30:00-03:00",
"total_products": 42,
"products": [
{
"id": "48851",
"slug": "caixa_de_booster_amonkhet",
"name": "Caixa de Booster - Amonkhet",
"min_price": 1499.0,
"avg_price": 1499.0,
"max_price": 1499.0
}
]
}Alert System
The alert system automatically detects changes between scrapes and can notify you through multiple channels.
How It Works
-
Enable alerts with the
-aor--alertsflag - The system automatically finds the most recent previous scrape file
- Compares the current scrape with the previous one
-
Detects changes:
- ๐ New products added
- โ Products removed
- ๐ฐ Price changes (with percentage change)
- ๐ Quantity changes (for store scraper)
- ๐ฆ Availability changes
- Sends notifications via configured alert types
Alert Types
-
file: Save alert to JSON file in
alerts_json/directory (implemented) - telegram: Send Telegram message (stub - not implemented yet)
CLI Usage
# Basic alert (uses file alert)
ligamagic-scraper -s "commander deck" -a
# Multiple alert types
ligamagic-scraper -u test-store -s "volcanic" -a --alert-types file,email,telegram
# Alerts with headless mode
ligamagic-scraper -s "booster box" -a -b headlessLibrary Usage
require 'ligamagic_scraper'
# Configure alerts
alert_config = {
enabled: true,
alert_types: [:file, :email],
compare_previous: true
}
# Create scraper with alerts
scraper = LigaMagicScraper::GlobalScraper.new(
search_term: "commander deck",
alert_config: alert_config
)
results = scraper.scrape
scraper.save_to_json(results) # Automatically processes alertsDetected Changes Format
{
has_changes: true,
timestamp: "2025-10-27T...",
search_type: "global",
search_term: "commander deck",
new_products: [...], # Products that weren't in previous scrape
removed_products: [...], # Products that are no longer available
price_changes: [ # Products with price changes
{
id: "12345",
name: "Product Name",
previous_min: 100.0,
current_min: 85.0,
change: -15.0,
change_percent: -15.0
}
],
quantity_changes: [ # Quantity changes (store scraper only)
{
id: "16149",
name: "Volcanic Island",
previous_qtd: 3,
current_qtd: 1,
change: -2
}
],
availability_changes: [...] # Products that became available/unavailable
}Alert Output
File Alert saves changes to organized directories:
Global Searches:
- Directory:
alerts_json/global/ - Example:
alerts_json/global/20251027_143022.json
Store Searches:
- Directory:
alerts_json/stores/{store_domain}/ - Example:
alerts_json/stores/kamm-store/20251027_143022.json
Each alert file contains full change details as JSON.
Example Alert File:
{
"has_changes": true,
"timestamp": "2025-10-27T14:30:22-03:00",
"search_type": "global",
"search_term": "commander deck",
"new_products": [...],
"removed_products": [...],
"price_changes": [...],
"quantity_changes": [...],
"availability_changes": [...]
}Configuration
The scraper uses:
- Driver: Selenium Chrome (headed mode by default, headless mode available)
-
Browser Modes:
-
headed(default): Visible browser window for monitoring -
headless: No UI, ideal for servers and automation
-
- Wait Time: 10 seconds for dynamic content
-
Base URL:
https://www.ligamagic.com.br/?view=cards%2Fsearch&tipo=1 -
Max Pagination: 50 clicks maximum (configurable via
MAX_CLICKSconstant) -
Output Directory:
scrapped/(created automatically)
Development
After checking out the repo, run bundle install to install dependencies.
Manual Build and Install
To build and install the gem manually from source:
gem build ligamagic-scraper.gemspec
gem install ./ligamagic-scraper-0.6.0.gemTo uninstall:
gem uninstall ligamagic-scraperVersion Management & Releases
The gem includes automated Rake tasks for version management following Semantic Versioning.
Quick Release (Recommended)
The easiest way to release a new version is using the automated release tasks:
# For bug fixes (0.1.0 -> 0.1.1)
rake release_patch
# For new features (0.1.0 -> 0.2.0)
rake release_minor
# For breaking changes (0.1.0 -> 1.0.0)
rake release_majorThese commands will automatically:
- Bump the version number
- Clean old gem files
- Build the new gem
- Install it locally
Check Current Version
rake version
# Output: Current version: 0.1.0Manual Version Bumping
If you just want to bump the version without building:
# Bump patch version (0.1.0 -> 0.1.1)
rake bump_patch
# Bump minor version (0.1.0 -> 0.2.0)
rake bump_minor
# Bump major version (0.1.0 -> 1.0.0)
rake bump_majorBuild & Install Tasks
# Build and install locally (recommended)
rake install_local
# Just build (without installing)
rake build
# Clean old gem files
rake clean
# Uninstall the gem
rake uninstallSemantic Versioning Guide
- Patch (X.X.1): Bug fixes, small tweaks, no new features
- Minor (X.1.0): New features, backward compatible changes
- Major (1.0.0): Breaking changes, major redesigns
Example Workflow
# 1. Make your code changes
# 2. Update CHANGELOG.md with your changes
# 3. Release the new version
rake release_patch
# Output:
# ๐ Version bumped: 0.1.0 -> 0.1.1
# ๐งน Cleaning old gem files...
# ๐จ Building gem...
# ๐ฆ Installing ligamagic-scraper-0.1.1.gem...
# โ
Gem installed successfully!All Available Rake Tasks
To see all available tasks:
rake -TLicense
The gem is available as open source under the terms of the MIT License.
Contributing
Bug reports and pull requests are welcome!