From laraclaude
Generates a Larascraper scraper for a target website, chaining browser actions (click, type, wait, scroll), conditional flow, captcha solving, and file/PDF downloads.
How this skill is triggered — by the user, by Claude, or both
Slash command
/laraclaude:generate-scraper [ScraperName] [url][ScraperName] [url]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create a [Larascraper](https://github.com/edulazaro/larascraper) scraper that fetches a target URL with Puppeteer and parses it. The action chain is a little query builder for the page: chain browser **actions** (click, type, wait, waitForSelector, scroll, ...), branch and loop with **conditional flow** (`when()` / `repeatUntil()` + `Condition`), solve simple **captchas** (`solveCaptcha()`), an...
Create a Larascraper scraper that fetches a target URL with Puppeteer and parses it. The action chain is a little query builder for the page: chain browser actions (click, type, wait, waitForSelector, scroll, ...), branch and loop with conditional flow (when() / repeatUntil() + Condition), solve simple captchas (solveCaptcha()), and download files/PDFs (FileScraper + submitAndCapture()).
| Subcommand | Description |
|---|---|
| (no argument) | Prompt for the scraper name and the target URL / what to scrape. |
[ScraperName] | Generate the scraper class, then ask for the target URL. |
[ScraperName] [url] | Generate the scraper for the given URL and infer the selectors/actions. |
This is a generator skill -- it always creates files. There is no analyze-only mode.
Grep to check if edulazaro/larascraper exists in composer.json.Larascraper is not installed. Install it with:
composer require edulazaro/larascraper
php artisan larascraper:install # installs the Node packages Puppeteer needs
node_modules/puppeteer-extra is missing, tell the user to run php artisan larascraper:install. The runner will otherwise fail with a clear message, but it is better to catch it up front.Read the installed package from vendor to match the actual installed version, not assumptions:
Read to load vendor/edulazaro/larascraper/src/Scraper.php and the Concerns/BuildsActions.php trait (the action methods live there). Note:
scrape(), proxy(), timeout(), headers(), retry(), run().click(), clickAndWait(), type(), select(), hover(), press(), waitForSelector(), waitForNavigation(), wait(), scroll() / scrollToBottom().when($condition, $then, $else) and repeatUntil($condition, $body, max:, delay:), plus the Support\Condition helper (Condition::selectorExists/selectorMissing/textContains/urlContains/captured).solveCaptcha($imageSelector, $inputSelector, $options).EduLazaro\Larascraper\FileScraper class, the submitAndCapture($formSelector, $options) action, and $result->file / $result->contentType on the response.handle() parses $this->crawler (a Symfony\Component\DomCrawler\Crawler).Concerns/BuildsActions.php or FileScraper.php are missing, the install predates those features: use only what is present and tell the user to composer update edulazaro/larascraper for the rest.BikeScraper. Append Scraper suffix if not already present. Supports subfolders: News/MegaScraper.Glob to find existing scrapers in app/Scrapers/.Read one or two to match conventions: imports, how handle() is structured, return shape, how they are invoked (proxy, retry).->retry(3, 20)->proxy(...)).Prefer the package's own generator (it uses the up-to-date stub):
php artisan make:scraper {ScraperName}
Be Docker-aware: if a docker-compose.yml / Sail setup exists, run artisan inside the container (e.g., docker exec -u sail -w /var/www/html <container> php artisan make:scraper {ScraperName}).
This creates app/Scrapers/{ScraperName}.php extending Scraper with a handle() skeleton. If make:scraper is unavailable, Write the file manually:
<?php
namespace App\Scrapers;
use EduLazaro\Larascraper\Scraper;
/**
* Scrapes a page and returns the parsed data.
*/
class {ScraperName} extends Scraper
{
/**
* Parse the fetched page and return the scraped data.
*
* @return array
*/
protected function handle(): array
{
return [
//
];
}
}
Determine the right selectors from the real page, never guess:
WebFetch on the target URL to understand the markup, ORphp artisan tinker --execute="dd(\App\Scrapers\{ScraperName}::scrape('{url}')->run()->html);"
(Docker-aware. run() returns a ScraperResponse, so read ->html for the markup and ->data for the parsed result.) Read the returned HTML to pick stable selectors.click(), or when(Condition::selectorExists('#banner'), fn($b) => $b->click('#accept')) if it only sometimes appears.type() + press('Enter', waitForNavigation: true) or click($submit, waitForNavigation: true).click() + waitForSelector(), or repeatUntil(Condition::selectorExists('#end'), fn($b) => $b->clickAndWait('a.next'), max: N).repeatUntil(Condition::selectorExists('#footer'), fn($b) => $b->scrollToBottom()->wait(500), max: N).waitForSelector() on the element you need.solveCaptcha('#captcha-img', '#captcha-input'), usually inside a repeatUntil because OCR is imperfect (see Step 7). Requires php artisan larascraper:install --captcha.FileScraper + submitAndCapture() instead of parsing HTML (see Step 7).handle() (parse the crawler)handle() only parses $this->crawler. Fill it with the selectors found in Step 5:
protected function handle(): array
{
return [
'title' => $this->crawler->filter('h1')->text(''),
'price' => $this->crawler->filter('.price')->text(''),
];
}
For a list of items, map over a node list:
protected function handle(): array
{
return $this->crawler->filter('.product-card')->each(function ($node) {
return [
'name' => $node->filter('.name')->text(''),
'price' => $node->filter('.price')->text(''),
'link' => $node->filter('a')->attr('href'),
];
});
}
Use ->text('') / ->attr('...') with safe defaults so a missing node does not throw.
Actions are chained at the call site (Scraper::scrape($url)-> ... ->run()), not inside handle(). run() returns a ScraperResponse (->success, ->status, ->error, ->html, ->data); the parsed handle() output is in ->data. (In larascraper 1.x run() returned the data directly -- check the installed version from Step 1.) Produce a usage snippet tailored to the site. Examples:
Plain page (no interaction):
$result = {ScraperName}::scrape('{url}')
->retry(3, 20)
->run();
$items = $result->data; // parsed handle() output (check $result->success first)
Behind a cookie wall + lazy load:
$result = {ScraperName}::scrape('{url}')
->click('#accept-cookies')
->scrollToBottom()
->waitForSelector('.product-card')
->run();
Search form:
$result = {ScraperName}::scrape('{url}')
->type('#search', 'zelda')
->press('Enter', waitForNavigation: true)
->waitForSelector('.results')
->run();
Pagination ("next" that reloads):
$result = {ScraperName}::scrape('{url}')
->clickAndWait('a.next')
->waitForSelector('.results')
->run();
Conditional flow (only-if + retry loop):
use EduLazaro\Larascraper\Support\Condition;
$result = {ScraperName}::scrape('{url}')
->when( // only if the banner is there
Condition::selectorExists('#cookie-banner'),
fn ($b) => $b->click('#accept'),
)
->repeatUntil( // load more until the footer shows (bounded)
Condition::selectorExists('#end-of-list'),
fn ($b) => $b->scrollToBottom()->wait(500),
max: 10,
)
->run();
The when()/repeatUntil() closures receive a sub-builder ($b) with the same action methods. repeatUntil() is always bounded by max (and an optional delay between iterations to avoid hammering the server).
Simple captcha (read image, retry until accepted):
use EduLazaro\Larascraper\Support\Condition;
$result = {ScraperName}::scrape('{url}')
->repeatUntil(
Condition::selectorMissing('#captcha-img'), // stop once the captcha is gone
fn ($b) => $b
->solveCaptcha('#captcha-img', 'input[name=captcha]')
->clickAndWait('#submit'),
max: 6,
delay: 1000,
)
->run();
solveCaptcha() needs the optional OCR packages: php artisan larascraper:install --captcha.
Downloading a file / PDF (use FileScraper, read $result->file):
use EduLazaro\Larascraper\FileScraper;
$result = FileScraper::scrape('{url}')
->submitAndCapture('form', ['expect' => 'application/pdf'])
->run();
if ($result->success && $result->file) {
file_put_contents('document.pdf', $result->file); // raw bytes; type in $result->contentType
}
For a file behind a captcha, combine both with Condition::captured() (stop once the file is grabbed):
FileScraper::scrape('{url}')
->repeatUntil(
Condition::captured(),
fn ($b) => $b->solveCaptcha('#captcha-img', 'input[name=captcha]')
->submitAndCapture('form', ['expect' => 'application/pdf']),
max: 8, delay: 500,
)
->run();
FileScraper is used directly (no class to write); the bytes land in $result->file, not $result->data.
Match the project's proxy/retry conventions from Step 3 when present.
Read the generated class to confirm it is correct.php -l on the file (Docker-aware).$result->success === true and that $result->data is populated. Refine selectors/actions if any come back empty.$result->success is false and $result->error holds the message -- adjust the selector or add a waitForSelector() before it.Show the user the tailored call (Step 7) plus how to read the result, and remind them that handle() receives the HTML after all actions have run.
These build an ordered list Puppeteer runs in a single browser session, after navigating and before handle() parses the HTML. The waits happen inside Node (where the page is alive), not in PHP.
| Method | Use it for |
|---|---|
->click($selector) | Click an element (waits for it first). |
->click($selector, waitForNavigation: true) / ->clickAndWait($selector) | A click that loads a new page. |
->type($selector, $text) | Fill an input. |
->select($selector, $value) | Pick a <select> option by value. |
->hover($selector) | Reveal hover menus. |
->press($key) | Press a key; pass waitForNavigation: true when it submits. |
->waitForSelector($selector) | Wait for lazy/JS content to appear. |
->waitForNavigation() | Wait for a navigation to finish. |
->wait($ms) | Fixed pause in milliseconds. |
->scroll('bottom'|'top') / ->scrollToBottom() | Trigger lazy / infinite scroll. |
->when($cond, $then, $else?) | Run a branch only if a condition holds (closures receive a sub-builder). |
->repeatUntil($cond, $body, max:, delay:) | Repeat a branch until a condition holds. Always bounded by max. |
->solveCaptcha($img, $input, $opts?) | OCR a simple image captcha and type it. Needs --captcha install. |
->submitAndCapture($form, ['expect'=>...]) | Submit a form and capture the response file (use with FileScraper). |
$selector is any CSS selector, including attribute selectors like [name=email] or input[name=captcha].
Conditions (EduLazaro\Larascraper\Support\Condition, for when()/repeatUntil()):
| Condition | True when... |
|---|---|
Condition::selectorExists($selector) | an element matching the selector exists |
Condition::selectorMissing($selector) | no element matching the selector exists |
Condition::textContains($text, $selector?) | the text is found (in $selector, or the whole page) |
Condition::urlContains($text) | the current URL contains the substring |
Condition::captured() | a file has been captured by submitAndCapture() |
Navigation tip: for a click or key press that loads a new page, use waitForNavigation: true on that action (or clickAndWait()) instead of a separate ->waitForNavigation() -- it arms the wait before the action, avoiding a race.
handle() is parse-only; it must not perform interactions. All interaction lives in the action chain at the call site.Scraper with a handle(). For pure file/PDF downloads, use FileScraper directly (no class, no handle()); the result is in $result->file, not $result->data.repeatUntil() must always be bounded: pass a sensible max, and a delay when each iteration hits a remote server, so the loop never hammers it.->text('') / ->attr('...') defaults so missing nodes do not throw.solveCaptcha() only handles simple image (text) captchas, not reCAPTCHA/hCaptcha. It needs the optional OCR packages (php artisan larascraper:install --captcha).composer update edulazaro/larascraper.npx claudepluginhub edulazaro/laraclaude --plugin laraclaudeScaffolds complete Playwright/TypeScript web scraper projects using PageObject pattern with Docker deployment. Supports agent-browser site analysis for automated selector discovery.
Builds production-ready web scrapers for any site using Bright Data infrastructure. Guides site analysis, API selection, selector extraction, pagination, and implementation.
Automatically scrapes websites by analyzing page structure, handling pagination/anti-blocking, discovering article series using Playwright and Crawl4AI. Zero config needed.