This file is 324 lines long; read all of them.
SKILL_DIR below stands for the absolute path of the directory that contains this file. SKILLS_DIR stands for the directory that holds every skill directory, one of them being SKILL_DIR.
You are orchestrating the full web scraping workflow, from a user's prompt to a working Scrapy spider with web-poet page objects.
Prerequisites
Requires uv. Install if missing.
Input
This is the user prompt: $ARGUMENTS. You need to extract the following information from it:
- url: target website URL. May be a homepage or a specific detail page.
- what: what to extract (e.g. "product", "job listing", "recipe")
- other useful details: user instructions that should affect the plan (e.g. which fields to extract, whether to use browser rendering etc.)
Project context
The workflow may run in a repository that already contains Scrapy projects. Repeated runs should normally add a spider to the existing project rather than create another domain-named one.
A candidate project is a directory containing scrapy.cfg, in the current
directory or an immediate child. items.py, spiders/, pages/ and a
standalone pyproject.toml are not project markers. Ignore .venv, .scrape,
__pycache__ and other generated directories.
The bundled script applies these criteria and prints each candidate as JSON with
a project_dir. Run it before the schema handoff, so project and schema context
can inform /scrape-plan:
uv run SKILL_DIR/scripts/detect_projects.py
With no candidate, create a new project using domain-based naming. With several, ask which to use, or whether to create a new one — do not guess.
With exactly one, the user's wording decides:
- Reuse or add to an existing project → select it.
- Use an existing schema → select the project holding it, then apply the schema-matching rules below to its item classes.
- Add a spider, with no request for a new project → reuse intent. "Add a spider for https://example.com" means extend the one project already there.
- Explicitly asks for a new project → create one, skip reuse.
- Anything broader ("create a scraper", "scrape this site") → blocking decision: ask whether to reuse that project or create a separate one, since schema definition needs the answer.
Where a project is selected, no confirmation is needed; name it in a brief status note.
When reusing a project, record:
project_dir: the project root that containsscrapy.cfgproject_name: the Python package from the default settings module inscrapy.cfg(for example,catalog_crawlers.settingsidentifies thecatalog_crawlerspackage)
After selecting a project, read its scrapy.cfg to resolve the package and inspect
the project structure. Before schema definition, read
{project_name}/items.py when it exists and list existing item classes and fields.
Also look for existing .scrape/*/*/spec.json files as prior schema context. Treat
an existing items.py as the source of truth for generated code; .scrape specs
are useful context and examples. Use this context to avoid asking the user to
redefine unchanged fields and to avoid accidentally damaging existing items, page
objects, fixtures, or spiders.
Choose an existing schema only when it plausibly matches what the user wants to extract. Match the requested singular data type against item class names, ignoring navigation-only classes and list-wrapper classes whose only field contains a list of items. If the user explicitly asks to reuse an existing schema and exactly one class is a plausible match, use that class. If multiple classes plausibly match, ask the user which one to reuse. If none match, reuse the project but define a new data type normally; fields from an unrelated item class must not leak into the new schema.
Track progress
After project reuse/new-project mode is decided and before Stage 1, create
exactly these tasks with TaskCreate, in order:
- "Plan and validate the scrape" —
/scrape-plan - "Build the Scrapy project, page objects and spider" (or "Extend the
existing Scrapy project with the new spider" when reusing) — Stage 2,
using
/scrapy-extrafor conventions
Stop after any stage when that satisfies the user's request, for example after plan approval.
As you launch each skill, TaskUpdate the task to in_progress. Mark it completed
only after the skill returns successfully. Do not batch updates — flip status at
the boundary so the user sees live progress.
Do NOT create tasks inside the sub-skills; they share this session's task list and would duplicate entries.
Each stage's result feeds the next: the validated plan feeds the build stage. For a normal end-to-end request, run both stages, a limited test crawl, and the final Report. Intermediate results — an approved schema, a finished spec, a prepared project — are valid workflow outputs when the user explicitly requested a partial workflow or stopping point, when a required project/schema decision is unresolved, or when an unrecoverable error prevents further progress.
Do not shortcut this workflow because the site appears simple or fields are
explicit in the user request. A request to "add a spider", "create a scraper",
or "create and test a scraper" is an end-to-end request: run Stage 1 with
/scrape-plan, then Stage 2. Do not inspect the target site's HTML directly
in the parent workflow to replace planning or site analysis.
Stage 1: Plan and validate
Use the /scrape-plan skill to plan the scrape and produce a validated spec.
Invoke it with all the information extracted from the user prompt. If reusing
a project, include a short summary of existing item classes/fields and any
relevant existing specs in the prompt so schema discovery starts with project
context instead of behaving like an empty folder.
This stage must be an actual /scrape-plan handoff. Explicitly invoke the
scrape-plan skill and wait for it to create .scrape/{site_name}/. Do not
satisfy planning by manually creating .scrape/{site_name}/spec.json or
data-type spec files in the parent workflow before /scrape-plan has run.
The trajectory should make it clear that the selected project context and any
existing approved schema were passed into /scrape-plan before site-specific
spec files were produced.
When an existing item class was selected as the schema:
- Pass its exact field names and apparent types to
/scrape-planas the requested fields, along with useful descriptions/examples from a matching prior spec. - State that these existing fields are the approved schema. Use an explicit
handoff phrase such as
Existing approved schema: ProductItem(title: string, price: string, rating: string)and tell/scrape-planto reuse those fields exactly and skip user approval for unchanged fields. Do not ask the user to list or approve unchanged fields again. /scrape-planstill analyzes the new website to obtain representative values and extraction evidence, but must not silently add unrelated discovered fields or drop existing fields. If an existing field cannot be found or appears to have an incompatible type, it reports that conflict and asks before changing the schema.
If no existing item class matched, use the normal discovery and approval flow.
Fields the user names in the request are an approved schema too: hand them to
/scrape-plan as approved, and do not ask which discovered fields to keep or
how to format values.
/scrape-plan confirms the schema, then downloads and analyzes more pages,
compares HTML variants, extracts values, and optionally presents a browser
review, and finally presents the finalized plan for approval. It returns a
finalized plan with all the details that you need. Example:
Plan approved. Spec validated and saved to .scrape/books-toscrape/.
Details:
- Data types: product, navigation
- Project dir: ./books_toscrape_com
- Project name: books_toscrape_com
- Create the spider: yes
- Start URLs: https://books.toscrape.com
- Using Zyte API: no
Parse these values; they will be needed for the next stages.
Stage 2: Build the working project
This is the coding stage: read /scrapy-extra for the current Scrapy/web-poet
conventions, the project template, and the testing loop — it's the knowledge
base for this work, not a step you hand the spec off to. Following it, turn the
spec at {spec_path} into a working, tested crawler. The spec-specific inputs
/scrapy-extra doesn't cover:
Do not implement a plain selector-only Scrapy spider as a substitute for this stage. The output of this workflow is a Scrapy spider backed by web-poet page objects and web-poet fixtures generated from the captured spec pages.
Fresh run: create a new Scrapy project at
{project_dir}(package{project_name}), both reported by/scrape-plan. Reused project: work inside the existing{project_dir}and{project_name}package — no new project directory.The spec has one folder per data type reported by
/scrape-plan(e.g.product,navigation), each withspec.json(schema,html_variant,url), saved pages underpages/, and expected values undervalues/. For each data type, add an item class and a web-poet page object whose@fieldmethods reproduce the expected values across all saved pages.The primary data type is the first one that is not
navigation; navigation is always present. When the primary one ends with-list(e.g.product-list), all requested fields were found on list pages: its page object extracts a list of items from a list page, and the spider yields them directly fromparse()with no detail-page callback. Otherwise the site uses detail-page extraction (the default).In a reused project, existing item classes, page objects, fixtures, and spiders stay intact unless the user approves changes. An existing item class that matches the data type is the class to use — extend it with missing schema fields as optionals rather than creating a duplicate (a pre-existing
ProductItemstaysProductItem). Derive only the new spider and module names from the new target domain; imports stay under the existing{project_name}package. A name or file collision with an existing spider calls for a distinct domain/data-type-derived name, not a replacement. Make the smallest addition that fits the existing project's item, page object, fixture, and spider conventions; reuse is not permission to restructure unrelated project code.Build the web-poet fixtures from the spec's already-captured pages — do not re-fetch them with
savefixture(that would redownload every page and record fresh HTML that can drift from what the page object was written against). Instead, once each page object exists, run the bundled helper per data type, passing the page object's import path (resolveSCRIPTfrom this skill's directory):SCRIPT=SKILLS_DIR/scrape-plan/scripts/make_fixtures.py uv run --python 3.14 "$SCRIPT" {spec_path}/{data_type} {project_dir} {PageObjectImportPath}It reuses the saved HTML and sets each
output.jsonfrom the spec'svalues/, so the fixtures pass without a network round-trip.Write a spider that uses the navigation page object to follow item links, pagination, and subcategories, and the item page object to extract each item. Use the start URLs reported by
/scrape-plan. A data type whosehtml_variantisrenderedneeds browser rendering.
If the plan says to not create the spider, skip the spider and finish once the page objects and their fixtures are in place.
Before reporting success, confirm the fixtures were generated with the helper
above, that they pass (uv run --with pytest python -m pytest fixtures/).
Unless the spider was skipped, also confirm that a bounded test crawl ran and
its items were inspected — in a reused project, the new spider's crawl only;
existing spiders aren't retested or modified. Bound the crawl with
CLOSESPIDER_ITEMCOUNT or CLOSESPIDER_PAGECOUNT; do not run an unbounded full
site crawl merely to validate the spider. Write temporary crawl feeds inside the
workspace, for example under .scrape/.work/{site_name}/, so sandboxed
harnesses can create them without permission prompts; avoid /tmp,
/private/tmp, and other external output paths. Include at least one scraped
item verbatim in the final report so the user sees real extracted field values,
not just counts.
Feedback marker
Each final Stage 3 outcome below ends by recording a one-shot feedback marker immediately, before reporting back, while you are still executing commands; it is the workflow's final-outcome signal for the optional session-end feedback prompt and does not prompt the user directly. Do not record it for user-aborted workflows or pre-crawl setup blockers, such as missing credentials — a session-stop hook handles cooldown and presentation.
If the bounded crawl or required fixture tests still fail after your repair attempts, stop, report what you found, and record the marker:
SCRAPE_FEEDBACK_STATE_FILE=".scrape/.work/feedback/state.json" \
SCRAPE_FEEDBACK_MARKER_FILE=".scrape/.work/feedback/marker.json" \
SCRAPE_FEEDBACK_EVENT_LOG_FILE=".scrape/.work/feedback/events.jsonl" \
uv run --no-project "SKILL_DIR/scripts/feedback_state.py" mark --context scrape_failed
If the crawl succeeds but items still look wrong or incomplete after your repair attempts, stop, report what you found, and record the marker:
SCRAPE_FEEDBACK_STATE_FILE=".scrape/.work/feedback/state.json" \
SCRAPE_FEEDBACK_MARKER_FILE=".scrape/.work/feedback/marker.json" \
SCRAPE_FEEDBACK_EVENT_LOG_FILE=".scrape/.work/feedback/events.jsonl" \
uv run --no-project "SKILL_DIR/scripts/feedback_state.py" mark --context scrape_incomplete
Only declare the workflow complete once fixtures pass, the bounded crawl runs, and inspected items look correct. Once they do, record the marker:
SCRAPE_FEEDBACK_STATE_FILE=".scrape/.work/feedback/state.json" \
SCRAPE_FEEDBACK_MARKER_FILE=".scrape/.work/feedback/marker.json" \
SCRAPE_FEEDBACK_EVENT_LOG_FILE=".scrape/.work/feedback/events.jsonl" \
uv run --no-project "SKILL_DIR/scripts/feedback_state.py" mark --context scrape_success
This host runs Stop hooks but may not visibly surface hook output. After
recording the marker, run this cooldown-gated visible fallback with the same
context you just marked (scrape_failed, scrape_incomplete, or
scrape_success) and capture stdout. If it prints a feedback line, append that
output verbatim at the very end of your final report; if it prints nothing, do
not mention feedback:
SCRAPE_FEEDBACK_STATE_FILE=".scrape/.work/feedback/state.json" \
SCRAPE_FEEDBACK_MARKER_FILE=".scrape/.work/feedback/marker.json" \
SCRAPE_FEEDBACK_EVENT_LOG_FILE=".scrape/.work/feedback/events.jsonl" \
uv run --no-project "SKILL_DIR/scripts/feedback_state.py" maybe-show --context CONTEXT --source copilot_cli
Report
Created scraping solution for {domain}:
Project: {project_dir}/
Spider: uv run scrapy crawl {spider_name}
Tests: uv run --with pytest python -m pytest fixtures/
If the spider creation was skipped, omit the Spider line.
Offer to help the user deploy to Scrapy Cloud if they wish. It's useful for scheduled or long-running crawls, to keep a job history with results and logs, for job monitoring (with an API that an LLM can use), and more. There is also a free tier.
If they deploy and run it there (via /zyte), the run will by
default wait for the job to finish and validate its results (errors, field
coverage, and whether items match the request) before reporting back — so
expect it to take as long as the crawl does unless they opt out.