From platinum-seo-engine
Use when: kullanıcı "scrapling fetch", "URL listesi çek", "competitor snapshot", "rakip site içeriği indir", "stealth fetch", "anti-bot bypass", "Cloudflare aşımı", "tier escalation" der ya da /pseo-scrape çağırır. Phase 6 generic helper — staging-only, Excel'e yazmaz; raw HTML inbox + SQLite-shaped staging JSON üretir. Also use when: bir başka skill ('S1_competitor_snapshot' gibi scenario consumer'ı) bu helper'ı tier_escalation orkestrasyonu için kullanmak istediğinde; mcp__ScraplingServer__ tool'ları aktif ve fetch sırasında 403/429/Cloudflare challenge bekleniyor; kullanıcı paid Scrapling MCP yerine ücretsiz katmanı kullanmak istiyor. Do not use when: GSC verisi geliyor (gsc-pull), DataForSEO çağrısı yapılacak (dfs-pull), Screaming Frog CSV import (sf-import) — ayrı ingestion skill'leri. Per-scenario sub-schema validation gerekiyorsa ADR-025 gereği Phase 7+ scenario skill'ini bekle (templates/scrapling/ altındaki şemalar bu skill içinde HENÜZ enforce edilmiyor). Excel'e doğrudan yazılması istenirse YASAK — Phase 6 staging-only.
How this skill is triggered — by the user, by Claude, or both
Slash command
/platinum-seo-engine:scrapling-opsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
10-step protocol. Steps map 1:1 to `workflow_runner` invocations + the
10-step protocol. Steps map 1:1 to workflow_runner invocations + the
spec §16.5 8-step MCP discipline + the §14.5 canonical tier ladder.
Raw HTML drift recovery is mandatory: every successful tier response is
dropped into inbox/scrapling/ before the staging projection runs,
so a staging bug never costs us the upstream payload.
This skill is the generic Scrapling helper. It does NOT enforce
per-scenario sub-schemas (templates/scrapling/[scenario].schema.json)
— those belong to Phase 7+ scenario consumer skills (S1 competitor
snapshot, S2 external mentions, etc.) per ADR-025. The
templates/scrapling/.gitkeep placeholder must remain untouched until
those skills land.
| Name | Type | Default | Notes |
|---|---|---|---|
project_slug | string | — | Required. Resolves projects/{slug}/_state/staging/. |
urls | array | — | Required. URL list. ≤10 → single MCP, >10 → bulk MCP. |
scenario | string | generic | Stored on every staging row; downstream filter key. |
max_urls | integer | 50 | DURUR threshold; do not silently truncate (DURUR #6). |
projects/{slug}/inbox/scrapling/{date}-{tier}-{slug}.json — raw
per-tier MCP response envelopes (drift recovery; one file per tier
that produced ≥1 successful response).projects/{slug}/_state/staging/scrapling_{date}_{slug}.json —
SQLite-shaped staging table (rows array, columns array). NO Excel
write in Phase 6 — staging-only per ADR-025.projects/{slug}/outputs/reports/{date}-scrapling-ops.md —
human-readable summary (per-tier success counts, DURUR list).projects/{slug}/_state/events.jsonl — event_kind=provenance
entries (source.kind=scrapling_mcp, target_excel_sheet=null).The state machine is fixed by schemas/scrapling-output-mapping.schema .json (tierEscalation definition: ["get", "fetch", "stealthy_fetch"], minItems=3, maxItems=3, const-equal). Each URL
flows through tiers in order; first success short-circuits. All-fail
records the URL in the staging table with tier_used=null and
status='durur' — the orchestrator decides whether to halt the
whole run (DURUR #1) based on aggregate failure rate.
Tier 0 get — basic HTTP GET, no JS, no anti-bot.
Failure modes: 403, 429, captcha_detected, timeout.
Tier 1 fetch — browser-like headers, optional JS render.
Failure modes: persistent 403, captcha,
Cloudflare challenge.
Tier 2 stealthy_fetch — anti-bot bypass (Cloudflare-aware,
fingerprint randomization).
Failure modes: still blocked → DURUR
(do not invent tier 3).
Bulk URL lists (>10) use mcp__ScraplingServer__bulk_fetch and
mcp__ScraplingServer__bulk_stealthy_fetch. Tier 0 (get) always
runs per-URL — it is the cheapest tier and per-URL form keeps logic
identical to the small-list path. The escalation is per-URL: a
partial-success batch can have URLs at different tiers in the same
staging table.
Each step name must match the
steps[*].namepassed toworkflow_runner.create_run. Names are stable identifiers across runs.
create_runOpen a workflow run shell. The state file lives at
projects/{slug}/_state/workflows/{run_id}.json (ADR-021, ADR-019).
from scripts.state import workflow_runner
handle = workflow_runner.create_run(
skill="scrapling-ops",
project_slug=project_slug,
steps=[
{"name": "validate_inputs"},
{"name": "open_session"},
{"name": "tier_escalate"},
{"name": "persist_inbox"},
{"name": "request_approval"},
{"name": "write_staging"},
{"name": "render_report"},
{"name": "close_session"},
],
)
validate_inputsValidate len(urls) <= max_urls and project workspace exists. URL
count over budget → DURUR #6 immediately, no MCP call wasted.
from scripts.ingestion import scrapling_ops
if len(urls) > max_urls:
raise scrapling_ops.UrlBudgetExceededError(
f"url count {len(urls)} exceeds max_urls={max_urls}"
)
open_session (optional)If the MCP server exposes session lifecycle (open_session,
close_session), open one and bind it to the tier callables. Session
imbalance — open without matching close, or close without prior open
— is DURUR #5.
tier_escalate (canonical §14.5 ladder)Drive the state machine via the helper:
from scripts.ingestion import scrapling_ops
mcp_clients = {
"get": mcp__ScraplingServer__get, # or fetch fallback
"fetch": mcp__ScraplingServer__fetch,
"stealthy_fetch": mcp__ScraplingServer__stealthy_fetch,
}
bulk_clients = {
"fetch": mcp__ScraplingServer__bulk_fetch,
"stealthy_fetch": mcp__ScraplingServer__bulk_stealthy_fetch,
}
results = scrapling_ops.bulk_tier_escalate(
urls, mcp_clients, bulk_clients=bulk_clients,
)
If aggregate failure rate exceeds 50% the orchestrator halts → DURUR #2.
persist_inbox (raw drift recovery, §16.5 step 3)For every tier that produced ≥1 success, persist its raw response envelope to:
projects/{slug}/inbox/scrapling/{date}-{tier}-{slug}.json
Inbox path creation failure → DURUR #3.
request_approval (skill EXIT awaiting_approval)workflow_runner.request_approval(
handle.run_id, project_slug=project_slug,
approver="user",
subject=f"{len(urls)} URL fetch tamamlandı, staging'e yazalım mı?",
step_index=4,
)
Skill exits here. The user replies in a fresh session; resume below.
write_staging (NO Excel — Phase 6 staging-only)Build SQLite-shaped staging rows and write to disk. Schema drift —
column count or order mismatch with STAGING_COLUMNS — is DURUR #4.
output_path = (
workspace_root / "projects" / project_slug
/ "_state" / "staging"
/ f"scrapling_{date}_{project_slug}.json"
)
rows = scrapling_ops.build_staging_rows(results, scenario=scenario)
scrapling_ops.write_staging_json(rows, output_path)
Note: transaction.append is NOT called — there is no Excel
target in Phase 6. The staging file is the durable artifact.
render_reportrender_template.py templates/reports/scrapling-ops.template.md data.json → outputs/reports/{date}-scrapling-ops.md. Variables (must
match the template's $tokens exactly): $project_slug, $date,
$scenario, $url_count, $max_urls, $tier_distribution,
$staging_path, $run_id.
source.kind=scrapling_mcp)from scripts.state import events_writer
events_writer.append_provenance(
project_id=project_slug,
source={
"kind": "scrapling_mcp",
"mcp_server": "ScraplingServer",
"mcp_tool": "ScraplingServer__bulk_fetch",
"response_bytes": total_bytes,
},
operation="ingest",
target_excel_sheet=None, # staging-only — no Excel projection
target_table=f"scrapling_staging_{scenario}",
rows_written=len(rows),
)
target_excel_sheet=None is intentional and supported by
schemas/events.schema.json (oneOf: logicalSheet | null) — Phase 6
staging operations carry no Excel sheet.
complete (+ close_session)workflow_runner.complete(handle.run_id, project_slug=project_slug, outputs={
"row_count": len(rows),
"staging_path": str(output_path),
"report_path": str(report_path),
"tier_counts": tier_counts,
})
If a session was opened in Step 3, close it here; imbalance → DURUR #5.
Columns (scripts.ingestion.scrapling_ops.STAGING_COLUMNS):
| Column | Type | Notes |
|---|---|---|
id | integer | 1-based contiguous; row order = input URL order. |
url | string | Original URL as supplied. |
tier_used | string | null | get / fetch / stealthy_fetch; null when all 3 failed. |
status | string | ok (tier_used set) or durur (all tiers failed). |
content_hash | string | null | sha256:<hex64> of UTF-8 content; null on failure. |
content_bytes | integer | Byte length of content; 0 on failure. |
fetched_at | string | ISO 8601 UTC, microsecond, 'Z'-suffixed. |
error_message | string | null | ; -joined per-tier errors on durur; null on ok. |
scenario | string | Carried from input; downstream filter key. |
The container envelope is {"schema_version": "1.0", "table": "scrapling_staging", "columns": [...], "rows": [...], "row_count": N}.
Idempotency note: re-running the same URL list yields the same row
shape (id sequence, columns) but content_hash may drift if the
target page has changed between runs. This is intentional —
content drift is a signal, not a bug.
Stop and flag the manager — do not patch, do not fall back.
durur_count / total > 0.5 in a batch).inbox/scrapling/ not writable
(workspace path missing, permission denied).STAGING_COLUMNS mismatch (column
added/removed without ADR), staging row missing required key, or
id sequence not contiguous-1-based.open_session succeeded but
close_session skipped/failed, or close_session called without
prior open (resource leak).max_urls — refuse rather than silently truncate
(raises UrlBudgetExceededError upstream of any MCP call).schemas/scrapling-output-mapping.schema.json (§14.5
tierEscalation const-locked sequence; scenarios.* deferred to
Phase 7+ per ADR-025), schemas/events.schema.json
(source.kind=scrapling_mcp, target_excel_sheet=null),
schemas/skill-frontmatter.schema.json.scripts/state/workflow_runner.py,
scripts/state/events_writer.py, scripts/reporting/render_template.py.
Note: scripts/excel/transaction.py is NOT consumed —
Phase 6 has no Excel target.scripts/ingestion/scrapling_ops.py (tier state machine,
bulk variants, staging writer).tests/skills/test_scrapling_ops.py (≥6 cases incl. all-fail
DURUR, bulk threshold, max_urls budget).templates/scrapling/.gitkeep placeholder kept untouched
per ADR-025 — per-scenario sub-schemas land with Phase 7+ skills.max_urls cap is
DURUR #6 — no accepted ADR governs the URL-count budget (ADR-026 is the
unrelated DECISIONS byte-cap; Q-015 → ADR-025 per OPEN_QUESTIONS.md).npx claudepluginhub popiliadam/platinum-seo-engine --plugin platinum-seo-engineUse when: kullanıcı "rakip analizi", "competitive analysis", "competitor monitoring", "rakip site snapshot", "rakip içerik takibi", "competitor diff", "rakip keyword'leri", "weekly competitor" der ya da /pseo-competitive-analysis çağırır. Also use when: hedef alan adı için DFS competitors_domain ile rakip listesi çekilecek; rakip URL'leri Scrapling tier-escalation §14.5 ile fetch edilecek; ADR-025 Phase 7+ S1_competitor_snapshot sub-schema enforcement gerekiyor; weekly cadence (S1 senaryosu) cron tetikliyor; sf-import / gsc-pull veri çekiminden bağımsız rakip izleme talep edildi. Do not use when: kendi sitemizin on-page kontrolü (on-page-audit), kendi GSC verisi (gsc-pull), keyword volume/ideas (dfs-pull / cluster-map), cannibalization (cannibalization), tech audit (tech-audit) — ayrı skill'ler. Master.xlsx yokken çağırma; init-project önce çalışmalı. Budget pre-flight FAIL ise fallback YASAK (DURUR #2). >50% URL all-tiers fail ise batch unhealthy → DURUR #4. Tier_escalation override §14.5 canonical sequence dışındaysa STOP (DURUR #7).
Builds AI-generated Bright Data scrapers from natural language via `bdata scraper create` and runs collectors against URLs with `bdata scraper run`. Activates when users describe data they want extracted from a URL.
Automatically scrapes websites by analyzing page structure, handling pagination/anti-blocking, discovering article series using Playwright and Crawl4AI. Zero config needed.