jez500/pricebuddy/url-scraping
Claude Code and agent skill by jez500/pricebuddy
- community
Checking that the SKILL.md file is still in the repository.
This skill should be used when the user asks to "scrape a URL", "fix scraping", "debug scraping", "add a store", "configure selectors", "extract price", "extract data from a page", discusses scraping strategies, store configuration, price extraction, availability detection, or troubleshoots why a URL is not returning data.
Install
npx vskill@latest install jez500/pricebuddy/url-scrapingSKILL.md
URL Scraping
Overview
Price Buddy extracts product data (title, price, image, availability) from retailer URLs. The pipeline is: Store lookup -> HTTP/API fetch -> Strategy-based extraction -> Price storage. Each store defines its own scrape strategy specifying how to extract each field from the page HTML.
Core Architecture
Scraping Pipeline
- A URL is submitted (via UI, command, or scheduled job)
ScrapeUrlresolves the Store by matching the URL's domain againstStore.domains- The appropriate scraper service (HTTP or API) fetches the page HTML
- Each field (title, price, image, availability) is extracted using the store's scrape strategy
- Extracted price is normalized to a float and stored as a
Pricerecord - The parent
Product.price_cacheis updated with denormalized data from all URLs
Key Classes
| Class | Location | Purpose | |
|---|---|---|---|
ScrapeUrl | app/Services/ScrapeUrl.php | Main scraping orchestrator | |
AutoCreateStore | app/Services/AutoCreateStore.php | Auto-detect store settings from a URL | |
SchemaOrgService | app/Services/SchemaOrgService.php | Parse JSON-LD Schema.org product data | |
PriceFetcherService | app/Services/PriceFetcherService.php | Dispatches batch price update jobs | |
Store model | app/Models/Store.php | Holds domains, scrape_strategy, settings | |
Url model | app/Models/Url.php | Product URL with updatePrice() and scrape() | |
Price model | app/Models/Price.php | Historical price record | |
Product model | app/Models/Product.php | Aggregates URLs, holds price_cache | |
CurrencyHelper | app/Services/Helpers/CurrencyHelper.php | Price string to float conversion | |
AppSettings | app/Settings/AppSettings.php | Runtime settings (cache TTL, schedule, retries) |
Enums
| Enum | Values | Purpose | |
|---|---|---|---|
ScraperStrategyType | schema_org, selector, xpath, regex, json | How to extract a field | |
ScraperService | http, api | Which scraper backend to use | |
StockStatus | in_stock, pre_order, back_order, special_order, out_of_stock, discontinued | Availability states |
Store Configuration
Domain Matching
Stores have a domains JSON column containing an array of {"domain": "example.com"} objects. The domainFilter scope matches a URL's host against these. Auto-created stores register both example.com and www.example.com.
Scrape Strategy
The scrape_strategy JSON column defines extraction rules per field:
Each field entry has:
- type - One of
ScraperStrategyTypevalues - value - The selector/xpath/regex/json-path (null for
schema_org) - prepend / append (optional) - Strings to prepend/append to extracted value
Strategy Types
schema_org - Extracts from JSON-LD @type: Product. No value needed. Looks for offers.lowPrice, offers.price, or offers.priceSpecification.price for price. Best option when available.
selector - CSS selector with special syntax:
.price- Get text content of element.selector|attribute- Get attribute value (pipe delimiter). Example:meta[property="og:image"]|content!.selector- Get raw HTML (exclamation prefix). Example:!.product-description
xpath - XPath expression. Use @attribute for attributes, text() for text.
regex - Regular expression. Capture group () extracts the value. Example: ~"price":\s?"(.*?)"~
json - JSON dot-notation path for JSON responses.
Store Settings
The settings JSON column holds:
scraper_service-http(curl, fast) orapi(browser-based, JS rendering)scraper_service_settings- Newline-separatedkey=valuepairs passed as scraper optionstest_url- A URL for testing the store's scrape configurationlocale_settings.locale/locale_settings.currency- For price formatting
Cookies
The cookies text column stores cookies to send with requests (useful for stores that require session/consent cookies).
Availability Match Config
The scrape_strategy.availability.match config maps scraped availability values to StockStatus cases:
Match types: match (exact string comparison) or regex (pattern match). If no match config exists, any non-empty availability value maps to OutOfStock.
Scraper Services
HTTP (ScraperService::Http) - Fast curl-based requests via jez500/web-scraper-for-laravel. Default for most stores.
API (ScraperService::Api) - Browser-based requests through a scraper API service (configured via SCRAPER_BASE_URL env, default http://scraper:3000). Use for JavaScript-rendered pages. Slower and more resource-intensive.
Auto Store Creation
AutoCreateStore automatically detects scrape settings when adding a URL for a new domain. It tries extraction methods in priority order:
- Schema.org JSON-LD
- CSS selectors from
config/price_buddy.phpauto_create_store_strategies - Regex patterns from the same config
The config file at config/price_buddy.php contains the default selectors and regex patterns tried during auto-detection. Common selectors include meta[property="og:title"]|content, .price, [itemProp="price"]|content, etc.
Retry & Caching
ScrapeUrl::scrape()retries up tomax_attempts_to_scrapetimes (default 3, configurable in AppSettings)- On retry,
use_cacheis set tofalseto bypass stale cached responses - If scrape returns
false(no store found), retries stop immediately - Cache TTL is controlled by
AppSettings::scrape_cache_ttl(default 720 minutes) - Sleep between scrapes is configurable via
AppSettings::sleep_seconds_between_scrape(default 10 seconds)
Price Storage
- Prices are normalized to floats via
CurrencyHelper::toFloat()with locale/currency awareness price_factoronUrlsupports bulk items (e.g., price_factor=6 for a 6-pack gives unit_price)- Each scrape creates a new
Pricerecord (historical tracking) Product.price_cacheis a denormalized JSON column updated after each price change viaProduct::updatePriceCache()
Jobs & Scheduling
FetchAllcommand (lando artisan app:fetch-all) triggersPriceFetcherService::updateAllPrices()- Products are chunked (default 10) and dispatched as
UpdateAllPricesJobbatches - Each product dispatches
UpdateProductPricesJobwhich callsProduct::updatePrices() - Schedule is controlled by
AppSettings::scrape_schedulecron expression (default0 6 * * *) - Job timeout is 20 minutes (
PriceFetcherService::JOB_TIMEOUT)
Debugging Scraping Issues
Common Problems
"No store found for URL" - The URL's domain doesn't match any store's domains array. Check domain spelling, www vs non-www variants. Use Store::query()->domainFilter($host)->first() to test.
"Missing title/price when scraping" - The scrape strategy selectors don't match the page HTML. Common causes:
- Page uses JavaScript rendering but store uses HTTP scraper (switch to API)
- CSS class names changed on the retailer's site
- Page returns different HTML to bots (anti-scraping)
- Cached response is stale (retry bypasses cache automatically)
Price extraction returns null - The selector matches but CurrencyHelper::toFloat() can't parse the value. Check for currency symbols, thousands separators, or non-numeric text in the matched content.
Availability incorrectly showing out of stock - Check the match config in the store's scrape strategy. Without a match config, any non-empty availability value maps to OutOfStock.
Debugging Techniques
- Check logs - Scraping errors log to the
dbchannel with URL context. Check the activity log in the admin UI. - Test scrape in tinker -
ScrapeUrl::new('https://example.com/product')->scrape()returns the full result array includingbody,errors, and extracted fields. - Test store resolution -
ScrapeUrl::new('https://example.com/product')->getStore()returns the matched store or null. - Test auto-detection -
AutoCreateStore::new('https://example.com/product')->strategyParse()shows what would be auto-detected. - Inspect raw HTML - The scrape result includes
bodywith the fetched HTML. Check if selectors match the actual page content. - Test selector parsing -
ScrapeUrl::parseSelector('.selector|attr')returns the parsed array to verify selector syntax. - Use the store test URL - Each store has a
test_urlsetting specifically for testing its scrape configuration in the Filament admin UI.
Additional Resources
- `references/scrape-strategy-examples.md` - Detailed examples of scrape strategies for common retailer patterns
Security
- Security scanPassed, 93 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.ai/skills/url-scraping/SKILL.md
Popularity
- Repository stars
- 1,013
- Repository forks
- 57
- 7-day trend score
- 35
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/jez500/pricebuddy/url-scraping)