Skip to main content

url-scraping

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.

Ir para a instalação

Informações da origem

Repositório
jez500/pricebuddy
Última atividade na origem
29 de março de 2026 às 05:20
Idioma detectado do SKILL.md
inglês
Estrelas
1.100
Forks
73

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
2 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
url-scraping
description
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.
version
0.1.0
# 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 1. A URL is submitted (via UI, command, or scheduled job) 2. `ScrapeUrl` resolves the **Store** by matching the URL's domain against `Store.domains` 3. The appropriate **scraper service** (HTTP or API) fetches the page HTML 4. Each field (title, price, image, availability) is extracted using the store's **scrape strategy** 5. Extracted price is normalized to a float and stored as a `Price` record 6. The parent `Product.price_cache` is 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: ```php [ 'title' => ['type' => 'schema_org', 'value' => null], 'price' => ['type' => 'selector', 'value' => '.price-current'], 'image' => ['type' => 'selector', 'value' => 'meta[property="og:image"]|content'], 'availability' => ['type' => 'selector', 'value' => '.stock-status'], ] ``` Each field entry has: - **type** - One of `ScraperStrategyType` values - **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) or `api` (browser-based, JS rendering) - `scraper_service_settings` - Newline-separated `key=value` pairs passed as scraper options - `test_url` - A URL for testing the store's scrape configuration - `locale_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: ```php 'availability' => [ 'type' => 'selector', 'value' => '.stock-label', 'match' => [ 'default' => 'in_stock', 'out_of_stock' => ['type' => 'match', 'value' => 'Out of Stock'], 'pre_order' => ['type' => 'regex', 'value' => 'pre.?order'], ], ], ``` 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: 1. **Schema.org** JSON-LD 2. **CSS selectors** from `config/price_buddy.php` `auto_create_store_strategies` 3. **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 to `max_attempts_to_scrape` times (default 3, configurable in AppSettings) - On retry, `use_cache` is set to `false` to 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_factor` on `Url` supports bulk items (e.g., price_factor=6 for a 6-pack gives unit_price) - Each scrape creates a new `Price` record (historical tracking) - `Product.price_cache` is a denormalized JSON column updated after each price change via `Product::updatePriceCache()` ## Jobs & Scheduling - `FetchAll` command (`lando artisan app:fetch-all`) triggers `PriceFetcherService::updateAllPrices()` - Products are chunked (default 10) and dispatched as `UpdateAllPricesJob` batches - Each product dispatches `UpdateProductPricesJob` which calls `Product::updatePrices()` - Schedule is controlled by `AppSettings::scrape_schedule` cron expression (default `0 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 1. **Check logs** - Scraping errors log to the `db` channel with URL context. Check the activity log in the admin UI. 2. **Test scrape in tinker** - `ScrapeUrl::new('https://example.com/product')->scrape()` returns the full result array including `body`, `errors`, and extracted fields. 3. **Test store resolution** - `ScrapeUrl::new('https://example.com/product')->getStore()` returns the matched store or null. 4. **Test auto-detection** - `AutoCreateStore::new('https://example.com/product')->strategyParse()` shows what would be auto-detected. 5. **Inspect raw HTML** - The scrape result includes `body` with the fetched HTML. Check if selectors match the actual page content. 6. **Test selector parsing** - `ScrapeUrl::parseSelector('.selector|attr')` returns the parsed array to verify selector syntax. 7. **Use the store test URL** - Each store has a `test_url` setting 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
Ver no GitHub