Advertising platforms depend on information about the web pages where campaigns may appear.
That information is difficult to produce reliably. Web content changes, documents may be incomplete, websites respond inconsistently, and the same page can contain several competing signals. Even after content has been collected, raw text has little value until it is transformed into classifications that campaign tools and their users can act on.
This case study describes work I contributed to around web content collection, ML-backed analysis, and client-facing campaign controls. The system connected data pipelines with product workflows so that changing information from the web could support practical targeting and exclusion decisions.
Names, clients, proprietary models, internal architecture, and implementation details are intentionally omitted.
The problem
Campaign platforms may need to understand the context of web pages for several reasons:
- deciding whether a page is suitable for a campaign;
- excluding content associated with unwanted topics;
- supporting contextual targeting;
- grouping pages into useful categories;
- automating parts of campaign configuration;
- helping users understand why particular inventory is included or excluded.
The input to these decisions is not a clean internal dataset. It comes from the open web.
Pages may:
- return different content at different times;
- fail temporarily or permanently;
- redirect through several locations;
- require additional rendering;
- contain little meaningful text;
- mix several languages;
- include navigation, advertising, and boilerplate around the main content;
- change after they have already been classified;
- present similar content under several URLs;
- contain signals that do not fit one clear category.
A system that treats the first fetched response as reliable truth will produce unstable classifications and difficult product behavior.
The challenge was therefore broader than crawling pages or calling a model. The system needed to turn imperfect external data into a result that could be used safely inside campaign workflows.
Context
The work connected several layers of a larger platform:
- services responsible for collecting web content;
- processing pipelines extracting useful page information;
- analytical or ML-backed services producing classifications;
- data storage supporting later queries and reprocessing;
- campaign tools presenting the result to users;
- backend workflows applying classifications to targeting or exclusion rules.
Each layer had different expectations.
The crawler needed to tolerate unreliable websites. Processing services needed repeatable inputs. Classification components needed enough structured content to produce useful results. Product workflows needed outputs that were stable, explainable, and available quickly enough to support user actions.
The engineering goal was to connect those concerns without pretending that any single stage was perfectly reliable.
Engineering goals
The system needed to satisfy several practical properties.
Collect content without blocking product workflows
Fetching and analysing external pages could be slow or unreliable. Campaign operations could not depend directly on a live crawl completing during a user request.
Preserve the source and processing history
The system needed to know what content had been collected, when it had been collected, and which processing or classification version produced the current result.
Make reprocessing possible
Extraction rules, classification logic, and models change over time. Previously collected content needed to be processed again without requiring every source page to be fetched immediately.
Separate collection from interpretation
Crawling a page and deciding what the page means are different responsibilities with different failure modes.
Represent uncertainty
A missing or low-confidence classification should not be presented as a strong conclusion.
Turn results into product behavior
The final output needed to support campaign decisions rather than remain an isolated data science result.
Solution overview
The system separated page discovery, content collection, extraction, classification, and product consumption into distinct stages.
A simplified flow looked like this:
- identify a page that requires analysis;
- create or update a durable processing record;
- fetch the page asynchronously;
- normalize the response and extract useful content;
- store the source data and relevant metadata;
- submit the prepared content for classification;
- validate and persist the classification result;
- expose the result through campaign tools and backend rules;
- retry or reschedule temporary failures;
- reprocess stored content when classification logic changes.
This separation prevented external website behavior from becoming a direct dependency of client-facing requests.
Architecture
A simplified processing model looked like this:
Page discovery
|
v
Analysis request
|
v
Durable work queue
|
v
Web crawler
|
+--> fetch page
|
+--> follow allowed redirects
|
+--> record response metadata
|
v
Content extraction
|
+--> remove irrelevant markup
|
+--> identify useful text
|
+--> normalize content
|
v
Structured content store
|
v
Classification service
|
+--> assign categories
|
+--> record confidence or status
|
+--> preserve processing version
|
v
Campaign tools and rules
The most important architectural property was that each stage produced a durable result that could be inspected, retried, or processed again.
Treating the web as an unreliable dependency
External websites behave like any other dependency outside the system’s control.
A request may fail because of:
- DNS or connection errors;
- server timeouts;
- rate limiting;
- invalid responses;
- excessive response size;
- redirect loops;
- unsupported content types;
- temporary blocking;
- malformed markup;
- pages containing no useful content.
The crawler therefore needed an explicit execution policy rather than a generic “retry on exception” rule.
Temporary network failures could be retried with bounded backoff. Permanent failures, such as unsupported content types or invalid destinations, needed a terminal result. Repeated failures required visibility rather than an endless retry loop.
The system also needed limits around response size, execution time, redirect depth, and resource consumption. Reliability included protecting the internal platform from hostile or unexpectedly expensive inputs.
Collection and classification as separate stages
Fetching a page and classifying it were deliberately separated.
The crawler’s responsibility was to obtain and normalize enough source material for later analysis. It did not need to understand campaign rules or assign the final business meaning.
The classification stage consumed structured content rather than interacting directly with the live website.
This separation provided several benefits:
- classification could be retried without fetching the page again;
- stored content could be analysed by a new model or rule set;
- crawling and classification could scale independently;
- failures could be attributed to the correct stage;
- test fixtures could use stable stored inputs;
- product behavior did not depend on the response time of an external website.
It also created a cleaner boundary between data acquisition and interpretation.
Content normalization
Raw HTML is a poor input for downstream analysis.
Pages commonly contain:
- navigation;
- cookie banners;
- related links;
- repeated headers and footers;
- embedded advertisements;
- scripts and styling;
- comments;
- markup with little semantic value.
The extraction stage reduced that noise and produced a more stable representation of the page.
Depending on the source, the normalized record could include:
- canonical or resolved URL;
- page title;
- extracted main text;
- language information;
- response metadata;
- collection timestamp;
- content fingerprint;
- extraction status;
- parser or processing version.
The goal was not to preserve every byte of the page. It was to provide enough evidence for repeatable classification and later investigation.
Duplicate and changing content
Different URLs may expose the same or nearly identical content. The same URL may also change over time.
The system therefore needed to distinguish the identity of a page from the identity of the content collected from it.
Content fingerprints could help identify unchanged material and avoid unnecessary reprocessing. At the same time, collection timestamps and source metadata preserved the fact that a classification represented a particular observation, not an eternal property of the URL.
This mattered because campaign decisions based on stale content could become incorrect even when the processing pipeline itself was functioning normally.
ML-backed classification
The classification component transformed normalized content into categories or signals usable by campaign workflows.
From an engineering perspective, the model was one dependency within a larger system.
Its output needed to be treated carefully because:
- some pages provided too little useful content;
- categories could overlap;
- models could change between versions;
- confidence could vary;
- a technically valid response could still be unsuitable for product use;
- temporary model failures could interrupt processing;
- new category definitions could require previous pages to be analysed again.
The system therefore stored more than the category itself.
A classification record could include:
- classification status;
- assigned categories or signals;
- confidence or quality information where available;
- model or ruleset version;
- processing timestamp;
- source content version;
- reason for an inconclusive result;
- validation outcome.
This made the result traceable and supported controlled reprocessing.
Turning classifications into product controls
A classification pipeline has little value until its result becomes useful to the product.
The backend connected page signals to campaign-facing capabilities such as:
- selecting eligible inventory;
- excluding unwanted contexts;
- applying contextual categories;
- supporting campaign configuration;
- automating repeated operational decisions;
- presenting understandable information to users.
This required a translation between analytical output and business behavior.
A model might produce a detailed set of labels, while the campaign product needed a smaller and more stable set of controls. That translation belonged in an explicit application layer rather than being spread across UI logic or data queries.
The product also needed defined behavior for pages that were:
- not yet analysed;
- temporarily unavailable;
- classified with insufficient confidence;
- assigned conflicting signals;
- waiting for reprocessing;
- known to contain outdated results.
Absence of a result could not silently mean approval or rejection unless that was a deliberate business rule.
Asynchronous processing
Crawling and classification were performed outside the immediate user request.
This allowed the product to remain responsive while analysis continued in the background.
A processing record could move through states such as:
Requested
|
v
Queued
|
v
Fetching
|
+------> Fetch failed
|
v
Extracted
|
v
Classifying
|
+------> Classification failed
|
v
Completed
Additional states could represent retry scheduling, insufficient content, or manual review.
The exact state names were less important than making progress and failure visible.
Retry strategy
Retries were applied according to the stage and failure type.
Fetch retries
Temporary connection failures, timeouts, or short-lived server errors could be retried with bounded backoff.
Invalid URLs, unsupported protocols, excessive responses, or permanent client errors usually required a terminal outcome.
Processing retries
A transient internal service failure could be retried using the stored source content.
A deterministic parsing failure on the same input required investigation or a new processing version rather than repeated execution.
Classification retries
Temporary model or service unavailability could be retried.
A valid but inconclusive classification was a business result, not necessarily a technical failure.
This distinction prevented the queue from repeatedly processing inputs that could not improve without a change in data or logic.
Reprocessing and versioning
Classification systems evolve.
A new model, extraction rule, taxonomy, or validation policy may produce a different result from the same source content.
The processing model therefore preserved enough version information to answer:
- which source content was used;
- which extractor processed it;
- which model or ruleset produced the result;
- when the result was generated;
- whether a newer processing version was available.
This made controlled reprocessing possible.
Instead of replacing results without context, the system could create a new classification and update the active product view according to explicit rules.
Failure scenarios
The design was considered against several recurring situations.
A page is temporarily unavailable
The fetch stage records the failure and schedules a bounded retry. Existing classifications remain distinguishable from the failed refresh attempt.
The same page is requested for analysis several times
The system reuses or updates the durable processing record instead of creating unbounded duplicate work.
The page has not changed since the last successful collection
A content fingerprint can prevent unnecessary extraction and classification.
The page changes after an earlier classification
A later collection creates a new content observation and can trigger reclassification.
The crawler succeeds but classification is unavailable
The normalized content remains stored, allowing classification to continue later without contacting the website again.
The model returns an inconclusive result
The system records the outcome explicitly. Product rules decide how unknown content should be treated.
A new classification version is introduced
Previously collected content can be processed again in a controlled batch, with the new result linked to its processing version.
A user-facing workflow needs a result that is not ready
The product presents a pending or unavailable state instead of blocking on a live crawl.
Observability
Monitoring needed to cover the entire pipeline rather than only crawler availability.
Useful signals included:
- number of pages in each processing state;
- queue age and processing delay;
- fetch success and failure categories;
- response time and content size;
- retry attempts;
- extraction failures;
- pages with insufficient usable content;
- classification latency;
- inconclusive result rate;
- failures by processing version;
- age of active classifications;
- reprocessing progress;
- difference between collected and classified content volumes.
These signals helped distinguish an external website problem from an internal pipeline failure or a classification-quality issue.
Important engineering decisions
Decouple live crawling from user requests
External websites should not determine whether campaign tools remain responsive.
Preserve source material separately from classifications
A classification is an interpretation of observed content, not the content itself.
Make processing stages independently retryable
Fetching, extraction, and classification have different failure modes and should not be retried as one opaque operation.
Version the interpretation
A category without its processing context is difficult to explain or reproduce.
Treat missing information explicitly
Unknown, failed, stale, and low-confidence results are different states.
Connect data outputs to stable business rules
Model results need a deliberate translation into client-facing campaign behavior.
Trade-offs
Data freshness versus processing cost
Frequent recrawling improves freshness but increases network, storage, and processing cost.
The refresh policy needs to reflect how quickly a source may change and how important current classification is to the product.
Pipeline complexity
Separating collection, extraction, classification, and product consumption introduces more components and states.
That complexity makes failures more local, visible, and recoverable than a single synchronous operation.
Storage requirements
Preserving source content, metadata, processing versions, and classifications requires more storage.
The benefit is reproducibility, reprocessing, and the ability to explain why a particular decision was made.
Classification uncertainty
Some pages will never fit neatly into one category.
The product must decide how to handle uncertainty rather than forcing the technical system to manufacture confidence.
Model evolution
New models or taxonomies can improve quality but may also change existing campaign behavior.
Reprocessing and rollout therefore need operational controls rather than an untracked replacement of old results.
What I would avoid
Crawling during a campaign configuration request
This couples product responsiveness to an uncontrolled external website.
Sending raw HTML directly through the entire pipeline
Unnormalized input creates noise, unnecessary processing cost, and unstable results.
Storing only the latest category
Without source and version context, the classification is difficult to explain or reproduce.
Treating every missing result as a technical failure
Some pages genuinely do not provide enough information for a confident classification.
Retrying deterministic failures indefinitely
A parser or model producing the same failure on the same input needs a code, data, or policy change.
Exposing internal model labels directly as product controls
Analytical output and user-facing business concepts evolve for different reasons and should remain separated.
Lessons
The first lesson was that raw data becomes valuable only when it supports a decision.
Crawling pages and generating categories were not the final outcome. The useful result was a set of dependable controls that campaign users and backend workflows could apply.
The second lesson was that an ML-backed feature is still a distributed production system.
The model sits behind collection, extraction, queues, storage, validation, versioning, product rules, and operational monitoring. Reliability depends on the behavior of that entire path.
The third lesson was that external data should be treated as an observation, not as permanent truth.
Web pages change, classification logic evolves, and some inputs remain ambiguous. Preserving source context and processing history made those changes manageable.
By separating collection from interpretation and interpretation from product behavior, the system could transform unreliable web content into information that users could act on without hiding where that information came from or how certain it was.
