Stealth Web Scraper - HTML, Text & Contact Data
Pricing
from $4.00 / 1,000 pages
Stealth Web Scraper - HTML, Text & Contact Data
Extract text, HTML, CSS-selected fields and public contact details from supplied web pages across countries. Pay per successful page row plus a small start fee.
Pricing
from $4.00 / 1,000 pages
Rating
0.0
(0)
Developer
kane liu
Maintained by CommunityActor stats
2
Bookmarked
324
Total users
17
Monthly active users
11 hours ago
Last modified
Categories
Features
Share
Get text, rendered HTML, selected fields and public contact details from web pages as rows you can export.
- One successful page per row, with its URL, title and collection time.
- Plain text for analysis, HTML for further parsing, or both.
- CSS selectors for page-specific fields such as headings and prices.
- Public phones, emails, addresses and social links when found on the supplied page.
- A separate dataset for failed pages, without a row charge.
- Pay $5.00 per 1,000 delivered rows on the Free plan, plus a small start fee.
What does Stealth Web Scraper do?
Stealth Web Scraper collects the public HTTP or HTTPS pages you supply in urls. It is a general page scraper, not a search engine or a site-specific catalog. Use it for page content, business contact pages or scheduled comparisons of the same URLs.
- Fetch pages: enter up to 40 URLs. Duplicate URLs are removed after validation, keeping the first occurrence.
- Choose content:
outputFormatreturnstext,htmlorboth. - Select fields:
extractSelectorsadds named values to the same page row. - Read contact details: available contact attributes are included automatically, not as separately billed rows. There is no contact add-on switch.
The Actor has one URL-fetching mode and no fixed country catalog. proxyCountry is an optional country hint, not a guarantee that a regional page is accessible. It does not translate content or follow links to find more pages.
What data can you extract from Stealth Web?
Successful page rows
| Field | Description | Example |
|---|---|---|
rowType | Identifies a page row. | page |
id | Validated requested URL used as the row identity. | https://example.com |
url | URL associated with the requested page. | https://example.com |
title | Page title, or heading when no title is found. | Example Domain |
statusCode | HTTP status of the page result. | 200 |
blocked | Whether the page was blocked. Successful rows are not blocked. | false |
text | Readable page text when requested. | Example Domain... |
html | Rendered HTML when requested. | <h1>Example Domain</h1> |
extracted | Values named by your CSS selector map. | {"heading":"Example Domain"} |
waitSelectorSatisfied | Whether the required selector appeared, when supplied. | true |
dataQuality | Quality of the delivered content. | ok or partial |
phones | Phone attributes, including type and normalized number when available. | [{"value":"+1 555 0100","type":"voice"}] |
emails | Email attributes found on the page. | [{"value":"name@example.com","type":"public"}] |
address.street | Street text, including a house number when shown. | 123 Example Street |
address.unit | Explicit suite or apartment marker, not the house number. | Suite 100 |
address.locality | City or locality when identifiable. | Example City |
org.name | Organization name when identifiable. | Example Company |
web.socials | Social profile URLs found on the page. | [] |
geo | Latitude and longitude when present in the source. | {} when absent |
person | Explicit person details when available. | {} when absent |
leadAbsences | Reasons contact attributes could not be extracted. | {"phones":{"reason":"not_extracted","surface":"https://example.com"}} |
htmlTruncated | HTML was shortened. | true |
textTruncated | Text was shortened. | true |
extractedTruncated | Selector values were shortened. | true |
scrapedAt | Collection timestamp. | 2026-10-24T08:00:00+00:00 |
Failed page rows
These go to the separate failures dataset and are not charged as successful rows.
| Field | Description | Example |
|---|---|---|
url | Page that failed or could not be delivered. | https://example.com/missing |
reason | Failure or delivery-limit reason. | charge_limit_reached |
statusCode | HTTP status when available. | 404 |
blocked | Whether access was blocked, when known. | true |
How to scrape Stealth Web
- Open Stealth Web Scraper in Apify Console. A free Apify account is enough to try it.
- Enter one public page in
urls. Leave the advanced options unchanged. - Set
maxResultsto1for the example. Each URL already produces at most one row; use fewer URLs to make the run smaller. - Set your maximum run cost, click Start, and wait for completion.
- Open the default dataset and download JSON, CSV or Excel, or read the rows through the API.
Empty input starts an example run using https://example.com, https://example.org and https://example.net, with at most three rows. Normal start and row prices apply; it is not a free preview. Supplying urls replaces the example targets. There is no mode to select.
Fetch pages and select fields
Set extractSelectors when you need specific elements. A selector matching one element returns a single value; multiple matches return a list. Values prefer the element's content, href, src or value attribute, then its text. Missing values may be omitted from the delivered row.
Use waitForSelector when a particular element must appear. If it does not appear, the page goes to the failures dataset instead of being billed as a successful row.
What to fill in
| Field | What to enter | Default |
|---|---|---|
urls | 1–40 full public HTTP or HTTPS URLs. | Three example URLs when empty |
maxResults | Rows per URL; any value of 1 or more gives at most one page row per URL. | 70; form example 3 |
extractSelectors | Object mapping your field names to CSS selectors. | {} |
outputFormat | html, text or both. | text |
waitForSelector | CSS selector required before extraction. | Empty |
maxConcurrency | Requested number of pages processed together, from 1 to 4; may be reduced. | 3 |
pageTimeout | Page-loading allowance in seconds, from 30 to 120. | 45 |
proxyCountry | Optional two-letter country hint, such as us or gb. | Empty |
proxyGroup | Optional connection preference; normally leave unchanged. | RESIDENTIAL |
cookies | Optional cookie objects with name and value, plus applicable scope fields. | Empty |
headers | Optional object of request header names and string values. | Empty |
gotoWaitUntil | Navigation milestone: commit, domcontentloaded, load or networkidle. | domcontentloaded |
blockResources | Skip images, fonts and media where supported. | true |
{"urls": ["https://example.com"],"maxResults": 1,"outputFormat": "both","extractSelectors": {"heading": "h1"},"waitForSelector": "h1"}
url,startUrlsandpageUrlsare aliases forurls. URL objects such as{"url":"https://example.com"}are accepted too.maxItemsandlimitare aliases formaxResults, not total-run limits.- Unknown fields are not rejected by the input resolver. Close misspellings can be corrected; other unknown fields are ignored and reported.
- The
INPUT_REPORTrecord in the run's key-value store lists input resolution, corrections and ignored fields. - Invalid URLs fail before collection, for example
INVALID_INPUT: urls[0] — use a full public web address such as https://example.com/page. Other validation messages can use different wording. No page rows are charged for rejected input; the start fee can still apply.
Output example
Illustrative successful row with the selector example above. Contact objects are empty here because no contact details were extracted.
{"rowType": "page","id": "https://example.com","url": "https://example.com","statusCode": 200,"blocked": false,"title": "Example Domain","text": "Example Domain...","html": "<html><body><h1>Example Domain</h1></body></html>","extracted": {"heading": "Example Domain"},"waitSelectorSatisfied": true,"dataQuality": "ok","billable": true,"phones": [],"emails": [],"address": {},"org": {},"web": {"socials": []},"leadReveal": "absent","scrapedAt": "2026-10-24T08:00:00+00:00"}
Export the dataset as JSON, CSV, Excel, XML, RSS or an HTML table. JSON preserves nested contact objects. RUN_REPORT and RUN_SUMMARY contain run counts and failure summaries; ERROR_SUMMARY contains failure details when present.
Why use this Stealth Web scraper?
- Pay for delivered rows. Failed pages have no row charge; the start fee is separate.
- Keep delivered work. Rows already written remain available if a later page fails or the run stops. Unfinished pages are not guaranteed to be delivered.
- Combine URLs. Supply several pages in one run without repeating duplicate inputs.
- See missing data. Empty attributes and
leadAbsencesdistinguish missing extraction from a verified absence on the site. - Reuse the output. Store page text for analysis or compare repeated runs by URL and timestamp.
- No proxy setup. You do not need to set up proxies for normal use.
How much does it cost to scrape Stealth Web?
The Actor uses pay-per-event pricing: a $0.005 start event plus a charge for each successful page row written to the default dataset. Platform usage is included in these prices.
| Apify plan | Price per row | Price per 1,000 rows |
|---|---|---|
| Free | $0.005 | $5.00 |
| Starter | $0.005 | $5.00 |
| Scale | $0.0045 | $4.50 |
| Business | $0.004 | $4.00 |
| Platinum | $0.004 | $4.00 |
| Diamond | $0.004 | $4.00 |
The start event is counted per GB of memory, with a minimum of one event. At the default 512 MB, one start costs $0.005. For three successful URLs on the Free plan, the example is 3 × $0.005 + $0.005 = $0.020.
- Each unique URL yields at most one row.
maxResultsdefaults to70, but does not create more rows or cap the number of supplied URLs. - This Actor accepts at most 40 URLs per run, on every plan. It does not split requests into 70-row batches.
- Free-plan users are limited to 5 runs and 40 URLs per run by this Actor. The smaller 40-URL limit applies here, not a generic 200-row allowance.
- Maximum cost per run (
maxTotalChargeUsdin the API) limits delivery across the run. The Actor stops delivering additional billable rows when the available charge allowance is exhausted.
What happens when the run timeout is too short
If collection needs more time, the run can pause with status ABORTED and a message such as “Queued on the collection backend; this run will resume automatically when results are ready (about Ns).” For this automatic pause, wait rather than starting another run.
When results are ready, the same run resumes with “Resumed: delivering N rows.” It writes to the same dataset and can finish as SUCCEEDED. Rows already delivered are skipped, so they are not delivered or charged twice. This pause can happen at most three times per run. Keep the default 900-second timeout to reduce interruptions. A genuine collection failure can still fail the run.
The synchronous API endpoints run-sync and run-sync-get-dataset-items wait at most 300 seconds and may return an empty dataset while the run is paused. SDK calls that wait for completion can also return at the temporary stop. For longer jobs, start asynchronously and read the dataset after SUCCEEDED, or use an ACTOR.RUN.SUCCEEDED webhook.
Use with AI agents and the API
Apify MCP server (Claude, ChatGPT, Cursor, VS Code)
Add this Actor to an MCP client that supports remote servers:
https://mcp.apify.com?tools=lentic_clockss/stealth-web-scraper
Sign in with Apify through OAuth, or send Authorization: Bearer <YOUR_APIFY_TOKEN>. The tool starts a run and returns run and dataset IDs; the agent can read rows with get-dataset-items when available in its tools. For clients without remote support, use npx @apify/actors-mcp-server --tools lentic_clockss/stealth-web-scraper with APIFY_TOKEN set.
Example prompt: “Fetch https://example.com, return text and the h1 heading, and use maxResults 1. Wait for the run to succeed before reading the dataset.”
- Agent execution note: Set
maxResultsbetween5and20on exploratory tool calls to keep responses compact, and readRUN_SUMMARYandINPUT_REPORTfrom the default key-value store to inspect normalized inputs and delivered row counts. When a long run briefly reportsABORTEDwith the automatic-resume status message, wait for the same run ID to finish asSUCCEEDEDinstead of starting a duplicate run.
Apify API
These examples read rows only if the waiting call returns a successful run. If it returns ABORTED with the automatic-resume message, retain its ID and wait for the same run to succeed.
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("lentic_clockss/stealth-web-scraper").call(run_input={"urls": ["https://example.com"], "maxResults": 1})if run["status"] == "SUCCEEDED":for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item)else:print(run["id"], run["status"])
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('lentic_clockss/stealth-web-scraper').call({urls: ['https://example.com'], maxResults: 1});if (run.status === 'SUCCEEDED') {const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);} else {console.log(run.id, run.status);}
For an asynchronous start, send POST https://api.apify.com/v2/acts/lentic_clockss~stealth-web-scraper/runs with JSON input and an Authorization: Bearer <YOUR_APIFY_TOKEN> header. After success, read GET https://api.apify.com/v2/datasets/<datasetId>/items?format=json.
Integrations
Use the Actor's Integrations tab to connect results to Google Sheets, Make, Zapier, n8n, Slack, Airtable or a webhook. Apify Schedules can repeat the same URL input for change tracking.
FAQ
Is it legal to scrape public pages?
Public availability does not remove your responsibilities. Check the site's terms and applicable data protection law. Collecting or using personal data requires a lawful basis; only process information you are entitled to use.
Why did my run end as ABORTED?
Check its status message. The automatic-resume message means collection is pending and the same run should resume. A manual abort or another failure is not a promise of automatic continuation.
How many results can I get?
At most one successful row for each of up to 40 unique URLs per run, subject to the cost allowance. Duplicates and failed pages do not add successful rows. Free-plan users have a five-run allowance.
Why are some fields empty?
The page may not show the value, extraction may be ambiguous, or a selector may not match. leadAbsences explains contact gaps. Missing scalar values can be omitted from dataset rows; not_extracted does not prove the site lacks the information.
Why do leadAbsences reasons look different from earlier runs?
From November 2026 the reasons are English codes. Earlier runs returned Chinese-language labels with the same meaning. If you filter on the old labels, switch to the new codes:
| Earlier label (JSON-escaped) | Meaning | New value |
|---|---|---|
"\u672a\u63d0\u53d6\u5230" | Not found in the page | not_extracted |
"\u6765\u6e90\u5b9e\u4f53\u4e0d\u660e\u786e" | The page's structured data names several organizations or people and none is the main one (address.street, geo, person, org) | not_extracted (source entity is ambiguous) |
"\u63d0\u53d6\u5931\u8d25" | Contact extraction failed on the fetched page (every entry except address.unit) | not_extracted (extraction failed) |
Only reason changed. Entries keep their shape: {"reason": ..., "surface": ...} for phones, emails, address.street and geo, where surface is the page URL (html when none is known), and {"reason": ...} for address.unit, person, org and web. None of these codes means the site lacks the value.
Does it find contact pages automatically?
No. Supply the contact page URL yourself. It does not follow contact links or submit contact forms. Contact attributes belong to the supplied page, not a complete company profile.
Can I extract prices or links?
Yes, when they are present and your CSS selectors match. Put your chosen names in extractSelectors. This does not automatically produce one row per product or review on a listing page.
Am I charged for failed pages?
There is no successful-row charge for a failed page or a missing required selector. Check the failures dataset. The run start fee still applies, and a run with no successful pages can fail.
Why is page content shortened?
Compact row JSON is limited to 250,000 bytes. HTML, text, selector values and contact lists may be shortened. Check the truncation flags; htmlChars and textChars record original lengths when present. Contact lists contain at most 50 entries, with totals when capped.
Limitations
- Public pages only; login-only dashboards and complex interactive workflows are not supported use cases.
- Success and country-specific availability vary by page. Test one representative URL before a larger run.
- No automatic pagination, link crawling or separate product/review rows.
- No guaranteed phone, email, address or person details for every URL.
- Localhost, private-network and other non-public addresses are rejected.
- Large content can be shortened, and resource blocking can affect page completeness.
- A dedicated site scraper may be more suitable when you need site-specific structured fields.
Related scrapers
More developer and data tools from the same publisher:
- Phone Number Validator
- GitHub Scraper
- Polymarket Scraper
- akamai Session solver
- AWS WAF Scraper
- Cloudflare solver
- DataDome Scraper
- Email Validator
Browse all of our scrapers at apify.com/lentic_clockss.
Support
Found a problem or need another field? Open an issue in the Issues tab of this Actor's page and include the run ID and the input you used.