
Quick Answer
C# web scraping usually pairs HttpClient with an HTML parser such as AngleSharp. Reuse one client, set firm limits, validate every response, and stop pagination loops before saving records. Use Playwright for JavaScript-rendered pages, and add proxies only when a documented routing or location requirement exists.
Need proxy infrastructure for this workflow? Compare residential and mobile proxy options.
Key Takeaways
- HttpClient handles requests, connection pooling, timeouts, cookies, decompression, redirects, and proxy configuration.
- AngleSharp parses HTML into a browser-like DOM and supports familiar CSS selectors.
- Reliable scrapers validate status, content type, body size, required fields, duplicate records, and pagination boundaries.
- Playwright fits pages whose required content depends on JavaScript execution or browser interaction.
- Large crawls need bounded workers, per-host pacing, retry limits, duplicate control, and data-quality measurements.
- Proxy routing should answer a defined location, route-diversity, or session requirement rather than conceal bad crawler behavior.
Which C# Web Scraping Tool Should You Choose?
The best C# scraping tool depends on whether the page needs HTML parsing, browser execution, or custom transport control. Start with the smallest toolset that returns complete records and exposes failures clearly.
| Tool | Primary role | Best for | Main limitation |
|---|---|---|---|
| HttpClient and AngleSharp | Request and parse | Static HTML, CSS selectors, custom validation, and multi-page crawls | Does not execute page JavaScript |
| HttpClient and Html Agility Pack | Request and parse | Tolerant HTML parsing and XPath-based extraction | CSS selection needs additional support |
| Playwright for .NET | Browser automation | Rendered content, interactions, screenshots, and browser testing | Uses more memory, processing time, and bandwidth |
HttpClient ships with .NET and provides direct control over headers, timeouts, cookies, redirects, and connection reuse. It does not parse HTML, so the scraper still needs a parser with selectors and document traversal.
AngleSharp parses HTML into a standards-oriented DOM and supports `QuerySelector()` and `QuerySelectorAll()`. Its familiar selector model works well for teams that already inspect pages through browser developer tools.
Html Agility Pack is useful when an existing project relies on XPath or its parsing behavior. Do not add two HTML parsers without a specific compatibility requirement, because duplicate abstractions make testing and maintenance harder.
Playwright launches a browser and executes page JavaScript. Choose it only after confirming the required values are absent from the initial response. The headless browser guide explains the additional resource and waiting costs.
The broader web scraping guide separates retrieval, parsing, validation, and storage. Those stages remain separate responsibilities even when one C# program coordinates them.
What Do You Need Before Scraping With C#?
A C# scraper needs a supported .NET SDK, defined crawl boundaries, stable selectors, and explicit output requirements before work begins. This tutorial uses .NET 10, AngleSharp 1.8.5, and a public practice website.
.NET 10 is an active LTS release supported through November 2028, according to the .NET support policy. Install the current patch for your operating system, then confirm the SDK:
The tutorial collects hockey table rows from https://www.scrapethissite.com/pages/forms/. Each record contains the team name, year, wins, losses, and final source URL. The program follows six paginated result pages during the current practice-site test.
Inspect one row and the next-page link before writing selectors. The sample expects `tr.team`, `.name`, `.year`, `.wins`, `.losses`, and `a[aria-label='Next']`. Any other source needs its own selectors, expected markers, and record rules.
Define the allowed host, page limit, response limit, request delay, and required fields before collection begins. Save a representative HTML fixture after removing sensitive values. Parser tests should not depend entirely on live network responses.
Replace the example contact URL inside the user agent before accessing another source. A monitored contact page gives operators a way to report unexpected crawler behavior.
Test direct access before adding routing. The web scraping with proxies guide explains where proxy selection belongs after the extraction baseline works.
How Do You Install C# Web Scraping Dependencies?
Install AngleSharp beside the .NET HTTP client, then create a lockfile that preserves the reviewed dependency graph for the project. HttpClient already ships with .NET, so the project needs only one added package.
Create a console project and enter its directory:
Add the tested AngleSharp version, then create `packages.lock.json`:
AngleSharp 1.8.5 supports .NET 8 and compatible later runtimes, including .NET 10. Pinning the tutorial version prevents a future package release from silently changing the copied example.
Commit the project file and `packages.lock.json` together. Production builds should restore with `--locked-mode`, which fails when declared dependencies disagree with the reviewed lockfile.
Package pinning does not remove maintenance work. Review release notes, run vulnerability checks, and test parser behavior before accepting an update. A package audit also cannot find flawed selectors or unsafe application logic.
Run `dotnet list package --outdated` during scheduled maintenance, not automatically before every production build. Deliberate updates preserve reproducibility and leave time for regression tests. Keep the SDK version documented through `global.json` when the build environment includes multiple SDK releases on shared build machines.
The generated project enables nullable reference analysis and implicit imports by default. Keep both settings enabled. They catch several common mistakes without adding runtime overhead.
How Do You Build a C# Scraper With HttpClient and AngleSharp?
A reliable C# scraper separates request handling, response checks, parsing, pagination, validation, and storage within one program. The sample keeps those boundaries visible within one runnable console program.
- Create one client: Configure its handler, cookies, timeouts, decompression, connection lifetime, and optional proxy before sending requests.
- Fetch each page: Follow only bounded, same-host HTTPS redirects, and stop oversized or non-HTML responses.
- Parse expected records: Use AngleSharp selectors, require every field, and reject malformed numbers or duplicate team-year pairs.
- Bound pagination: Track every requested URL, stop loops, delay between pages, and enforce a maximum page count.
- Write safe output: Escape CSV fields, neutralize spreadsheet formulas, and replace output only after writing succeeds.
Replace the generated `Program.cs` with this code:
The allowed-host check prevents redirects from silently moving the crawler to another domain. Update both `StartUri` and `AllowedHost` when adapting the program to an approved source.
The code disables automatic redirects because each destination must pass that allowlist. It also reads response bodies as streams, so the five-megabyte limit applies during transfer rather than afterward.
The CSV writer creates a temporary file beside the final output. A completed run replaces the previous file, while a failed run removes only its temporary file.
How Do You Run and Validate the C# Scraper?
Run the scraper only after restoring locked dependencies, compiling without warnings, and checking packages for known vulnerabilities. These checks confirm the project state before a live request tests its assumptions.
Execute the following commands inside `CSharpScraper`:
The validated sample saved 582 records across six pages and created `teams.csv` in the project directory. That count describes the test run, not a permanent promise about changing practice-site content.
Open the CSV and confirm its header, row count, required fields, source URLs, and duplicate policy. A successful process exit or HTTP 200 does not prove the extracted data is complete.
Test at least five failure paths before adapting the scraper. Cover a wrong selector, non-HTML response, excessive body, unexpected redirect host, and repeated pagination URL. Also cancel one run and confirm the previous complete output remains usable.
Compare a second run with the first after deleting the output file. Both runs should produce identical keys when source data remains unchanged. For automated checks, assert schema, minimum record count, maximum rejection rate, and unique keys before deployment.
Keep credentials, complete response bodies, and sensitive query values out of logs. The how to test proxies guide helps separate route failures from target and parser failures.
How Do You Use a Proxy With HttpClient?
Configure one proxy on the handler before creating HttpClient, because each handler owns its connection pool and selected route. The sample reads an HTTP proxy endpoint and optional credentials from environment variables.
Set the variables in Bash without placing the password in source code:
Omit both credential variables when the endpoint uses source-IP allowlisting. The program rejects partial authentication settings, invalid hostnames, and ports outside the valid range.
`WebProxy` configures an HTTP proxy for this example. Confirm protocol support before adapting the handler to another endpoint type. A proxy test should verify the observed public IP, not only a successful TCP connection.
Do not embed credentials inside a proxy URL, repository, build log, or exception message. Use a secret manager for deployed workloads, and rotate exposed credentials immediately.
HttpClient reuses connections through `SocketsHttpHandler`. Microsoft recommends long-lived clients with `PooledConnectionLifetime`, or clients created through `IHttpClientFactory`, in its HttpClient guidance. Separate handlers are appropriate when projects require different proxies or isolated cookie containers.
Connection reuse can also affect observed rotation. Test the public exit with the same client lifecycle, endpoint, and session settings used in production. The proxy rotation guide explains why changing a credential or request does not always replace an existing connection.
When Does C# Web Scraping Need a Browser?
C# web scraping needs a browser when required data appears only after JavaScript execution or browser interaction during collection. Confirm that dependency before accepting the browser's additional latency, bandwidth, memory, and failure modes.
Check the initial HTML and approved network responses first. Many rendered pages fetch structured data from an endpoint that may offer a simpler authorized integration. Prefer an official API when it provides the required data and permits the workload.
Playwright for .NET can launch Chromium, Firefox, or WebKit and control page interactions. It can wait for selectors, preserve browser state, capture evidence, and inspect rendered content.
Browser waits should target observable conditions rather than fixed delays. Wait for the required selector, response, or application state, then validate the extracted value. A page-load event alone may occur before asynchronous content appears.
Keep each browser context isolated when cookies or storage must not cross jobs. Reuse browser processes carefully, cap simultaneous pages, and close contexts after each bounded task. Record browser version, viewport, locale, route, and extraction version for reproducible tests.
Blocking images or fonts can lower bandwidth, but it can also change page behavior. Measure the effect against validated output before adopting resource blocking across the workload. Keep resources required for layout, authentication, or extracted values.
What Common C# Web Scraping Errors Should You Check?
C# scraping errors become easier to diagnose when retrieval, parsing, validation, pagination, and storage failures stay separate. Classify the failing stage before changing headers, selectors, routes, or retry behavior.
| Symptom | Likely cause | First check |
|---|---|---|
| `407 Proxy Authentication Required` | Wrong credentials or missing source-IP approval | Test the endpoint and authentication mode without printing secrets |
| `429 Too Many Requests` | Request pressure or destination policy | Honor `Retry-After`, reduce concurrency, and review authorization |
| HTTP 200, but no records | Consent page, wrong locale, soft block, or selector drift | Inspect sanitized HTML and expected markers |
| Repeated page URL | Broken next-link logic or redirect loop | Compare normalized requested and final URLs |
| Timeout | Slow connection, slow destination, or oversized work | Record connect, header, body, and parser timing separately |
| Garbled text | Wrong response encoding or parser assumption | Inspect `Content-Type`, bytes, and declared document encoding |
| Duplicate records | Overlapping pagination or unstable ordering | Use a domain-specific key and preserve source evidence |
| CSV opens incorrectly | Missing escaping, encoding mismatch, or formula-like fields | Test commas, quotes, newlines, Unicode, and leading formula characters |
Retry only failures that are temporary and safe to repeat. Use a small attempt cap, exponential backoff, jitter, and the destination's stated delay. Do not retry authentication failures, unexpected content, or invalid records as transport failures.
Store a classified reason for every rejected page and record. A proxy pool also needs route health checks, but route changes cannot repair parser bugs.
How Do You Scale C# Web Scraping Safely?
C# scraping scales safely through bounded concurrency, per-host pacing, measured retries, queue limits, and output validation. Increasing task count without those controls can amplify failures faster than useful records.
Use `Channel<T>`, `SemaphoreSlim`, or another bounded queue to cap active work. Apply separate limits for each destination, because one global limit can still overload a smaller host. Begin with one worker and increase concurrency only after measuring latency, errors, and accepted output.
Reuse a limited set of HttpClient instances instead of creating one per request. Give different routes or cookie boundaries their own handlers where needed. Set `PooledConnectionLifetime` according to DNS changes, route behavior, and tested connection reuse.
Retry at one layer only. Nested retries in request, page, and job code can multiply traffic unexpectedly. Cache unchanged responses where permitted, deduplicate queued URLs, and stop jobs when validation failure rates exceed a defined threshold.
Track requests, transferred bytes, HTTP statuses, latency, retries, rejected pages, parsed records, duplicate records, and valid records. Measure cost per valid record rather than cost per request. Cheap traffic can become expensive when it produces unusable output.
Partition work by source and preserve a stable record key. Write checkpoints only after validated batches reach durable storage. A restart should resume known work without repeating every accepted page.
What Rules Should a C# Web Scraper Follow?
A C# scraper should collect permitted data, follow source-specific rules, minimize load, and protect stored information. Proxy routing does not create permission or override authentication and access controls.
Review applicable law, binding terms, documented rate limits, and the source's access instructions before collection. Prefer an official API or licensed feed when it satisfies the requirement. Obtain legal review for personal, regulated, copyrighted, or contract-restricted data.
Collect only fields needed for the stated business purpose. Set retention limits, restrict access, encrypt sensitive storage, and document deletion procedures. Public visibility does not automatically permit every form of reuse.
Use an identifiable user agent when appropriate, maintain a monitored contact channel, and pace requests conservatively. Honor `Retry-After` and stop after explicit access denial. Do not rotate routes to evade a withdrawn permission, account restriction, purchase limit, or authentication boundary.
Keep credentials in managed secrets rather than code or environment files committed to version control. Redact secrets from exceptions and monitoring. Review stored fixtures before sharing them with test systems or developers.
Document the target, purpose, fields, frequency, owner, and approval for each job. That record makes future reviews and incident handling much easier. Reassess approvals when a source changes ownership, authentication, access rules, or data sensitivity.
How Can Proxidize Support C# Web Scraping?
Proxidize gives C# scrapers configurable proxy routes without changing the program's extraction and validation logic at runtime. The sample's `WebProxy` configuration accepts a standard endpoint, port, and optional credentials.
One access point can hold the selected routing, session, location, and authentication settings. The C# application stays focused on requests, parsing, validation, and storage. Teams can test a new route configuration without rewriting CSS selectors or CSV logic.
Residential Proxies fit global public-data projects that require country, city, or ISP targeting. Rotating sessions suit independent observations, while sticky sessions support related requests needing temporary route continuity.
Best For: Residential Proxies suit C# data pipelines that need broad geographic coverage and many eligible residential routes.
Mobile Proxies fit authorized tests that specifically require mobile-network routing. Session and location settings remain separate from the scraper's record validation and output rules.
Best For: Mobile Proxies suit C# testing workflows whose results must reflect an available mobile-network route.
Start with direct requests, then compare a proxy test under the same target, selectors, client version, and acceptance rules. Record the observed exit, latency, response classification, transferred bytes, and valid-record count. Select routes using measured workload results rather than the IP label alone.
HttpClient connection pooling can preserve an existing connection beyond a routing change. Design session boundaries deliberately, and dispose the relevant client and handler before changing routes. Verify the observed exit before collecting records.
What Should You Remember About C# Web Scraping?
Successful C# web scraping depends on controlled requests, explicit validation, bounded work, and accurate production measurements. These decisions matter more than choosing the largest library or worker count.
- Pair HttpClient with AngleSharp when the required data exists in returned HTML.
- Use Playwright only when JavaScript execution or browser interaction is necessary.
- Reuse clients, bound redirects and bodies, and validate every response before parsing.
- Treat pagination, duplicates, malformed fields, and partial output as explicit failure cases.
- Scale through measured concurrency, per-host pacing, limited retries, and durable checkpoints.
- Add Proxidize only when the workload needs specific locations, route diversity, or session behavior.
Frequently asked questions
C# works well for maintainable web scrapers within existing .NET systems, and HttpClient provides detailed request control. AngleSharp or Html Agility Pack handles HTML parsing, while Playwright adds browser rendering when required. The language also provides strong async, type, testing, and deployment support.
AngleSharp is a practical choice when static HTML and CSS selectors meet the requirement. Html Agility Pack fits XPath projects, while Playwright handles required JavaScript execution or browser interaction. The best library is the smallest one that returns validated records and exposes useful failures.
Yes, Playwright for .NET can execute JavaScript and extract rendered content through Chromium, Firefox, or WebKit. Check the initial HTML and approved data endpoints first. A direct request is usually cheaper and easier to test when it already contains the required information.
Create a `WebProxy`, assign it to `SocketsHttpHandler.Proxy`, and create HttpClient before sending requests. Store credentials outside source code, and use separate handlers for distinct routes or cookie boundaries. Each handler owns its connection pool and proxy configuration throughout its lifetime.
Each HttpClient handler manages a connection pool, so creating one client per request wastes connections and can exhaust ports. Reuse a long-lived client with an appropriate pooled connection lifetime. Use separate clients only when route, cookie, or policy boundaries require isolation.
No universal worker count is safe for every source. Begin with one worker, apply a per-host limit, and increase gradually while measuring latency, errors, and valid output. Stop when failures rise or source guidance sets a lower boundary, because more tasks do not guarantee more usable records.
The programming language does not determine whether collection is lawful or permitted. Review applicable law, binding terms, source instructions, data rights, and contractual access before running a scraper. Prefer official APIs where suitable, minimize collected data, and obtain legal advice for regulated or uncertain workflows.