Implementing Parsing with Cheerio/BeautifulSoup (HTML Parsing)

Websites change their markup, and parsers suddenly stop collecting data — sound familiar? We develop robust HTML parsing as a turnkey solution: we configure page parsing, monitor changes, and reduce manual data collection significantly.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113
  • Website development for SBH Partners
    Website development for SBH Partners
    1201
  • Website development for Red Pear
    Website development for Red Pear
    619

Why HTML parsing breaks on real websites

A parser that worked perfectly on a test sample of ten pages breaks in production on the very first day. The reason is almost always the same: static HTML from documentation and real-world output are two different worlds.

The markup arrives already assembled by scripts, selectors rely on classes like css-1x2y3z that the bundler generates and changes with every release, and the server only serves content with a valid session and plausible request headers. Let's break down how this is handled in practice.

Dynamic markup is the most common cause of empty result sets. If data is loaded asynchronously, the parser gets the page skeleton without the needed nodes: the selector is formally valid but returns null.

The fix isn't to "wait longer" but to work with the final state of the page — rendering through a headless browser or parsing the same requests the frontend uses to fetch data. The second path is faster and more stable, but it requires analyzing network activity.

Selectors are the second point of failure. Tying to auto-generated classes and to chains like div > div:nth-child(3) > span means that any change in the layout zeroes out the result set.

Resilient parsers rely on semantic attributes, microdata, and text anchors rather than position in the tree, and explicitly verify that they found the expected number of elements — otherwise you get silent degradation instead of an error.

Anti-automation protection and encodings finish off the rest. Rate limiting, bot-signature checks, and content substitution for suspicious clients all produce a response with a success code and an empty body.

Encoding adds its own corruption: without an explicit specification, the server returns windows-1251 or latin-1, and Cyrillic turns into garbage that breaks string comparison. That's why the parser configuration sets the encoding explicitly rather than detecting it automatically.

The practical takeaway: a parser is not a one-off script but a service with monitoring. We build in result-set completeness checks, alerts when the share of successful parses drops, and selector versioning, so that a markup change at the source doesn't turn into silent data loss.

What sustainable HTML page parsing delivers

Sustainable parsing is when page parsing doesn't break from layout changes, and the share of successful parses stays at 98-99% instead of 70-80% for "disposable" scripts.

The difference is visible not at launch, but months later: selectors become outdated, the DOM structure changes, and the parser either keeps working or silently returns empty fields.

We build parsing so that a failure is visible immediately and localized in one place. Selectors are moved into a separate configuration layer rather than scattered across the code — adapting to a new layout takes minutes, not hours of regression testing. Below is what exactly this approach delivers.

  • 98-99% successful parse rate — instead of "works until it breaks." Every run records how many pages were fully parsed, how many partially, and how many failed.
  • Selector maintenance in minutes — rules are moved into a config; when the layout changes, one file is edited, not the entire parser code.
  • Whole classes of failures disappear — empty fields due to changed classes, duplicate records on re-runs, hangs on dynamic blocks.
  • Re-run over failed pages — whatever wasn't parsed is queued and re-parsed instead of being silently lost.
  • Data integrity checks — required fields are validated before writing; broken records don't make it into the database.
  • Degradation monitoring — if the successful parse rate drops below the threshold, a signal arrives before the client notices.

The result — the parser stops being a fragile script and becomes part of the infrastructure: predictable, observable, and maintainable.

Approaches to parsing markup and their limitations

Parsing HTML is always a trade-off between resilience to layout changes and extraction accuracy.

Selectors by classes and identifiers work fast and predictably as long as the markup doesn't change: if the site's developer renames a wrapper or adds an intermediate container, the rule silently returns an empty result instead of an error.

That's why in practice we combine several approaches and build in checks that catch parsing degradation.

Option When it fits Limitations
Static selectors by classes and identifiers Stable layout, known block structure Breaks when classes change, no error signal
Parsing by document structure Lists, tables, repeating cards Sensitive to nesting and node order
Templates for typical blocks Catalogs, product cards, feeds Requires maintenance when the layout changes
Manual text parsing Heterogeneous pages without clear markup Slow, many exceptions, fragile

Static selectors give maximum speed, but they're tied to class names, which change most often. Parsing by document structure relies on the repeatability of nodes — it survives cosmetic edits, but breaks when nesting changes.

Templates for typical blocks are convenient when there are many pages of the same type: the rule describes a card once and is applied to hundreds of pages.

We keep manual text parsing as a last resort — when the markup is heterogeneous and formalizing it costs more than handling exceptions.

To keep parsing from degrading unnoticed, we check that each template returned a non-empty result and compare the number of blocks found with the expected number. If a rule stops finding elements, it surfaces immediately rather than weeks later on empty data.

How we build a page parsing pipeline

Parsing starts not with code, but with an inventory of sources: which pages we send to parsing, how often their structure changes, and whether ready-made export channels exist.

At this stage we record where the markup is stable and where it relies on fragile selectors, and we decide what we take from the page and what we pull from accompanying data.

  1. Source audit. We examine the markup of each page, the frequency of template edits, the presence of pagination and hidden blocks that load separately from the main document. The result is a map of sources with notes on where parsing is stable and where a fallback path will be needed.
  2. Parsing schema. We design the data structure and extraction rules: which fields are mandatory, how we handle gaps, and what we do when the layout changes. The schema describes not only "what to extract" but also how to distinguish a valid page from a stub or an error.
  3. Setting up extraction. We build parsing with Cheerio for server-side delivery and BeautifulSoup where resilience to messy markup is needed. We tie selectors to semantic features rather than the order of elements — that way a template edit doesn't break the entire pipeline.
  4. Testing on a stream. We run the parsing on a real volume of pages, compare the extracted fields against a reference sample, and catch discrepancies by page type. We separately test behavior under slow responses and a partially loaded document.
  5. Handling failures. We set up retries, rate limiting, and an error log that preserves the original page — it shows exactly what changed in the markup.
  6. Handover and guidelines. We hand over the parsing schema, field descriptions, and rules for updating selectors so that edits are made predictably, not from memory.
  7. Maintenance. We regularly check the share of successfully parsed pages: a drop in the metric is a signal that the source has changed its template.

This order produces a predictable result: the pipeline survives layout edits, and failures are visible on a chart rather than surfacing in the finished data.

What the client receives after implementation

After implementation, parsing stops being a set of scripts "on a single laptop" and becomes a managed service. Below is exactly what we hand over to the client's team so that the solution can be operated and developed without us.

  • Parsing configurations. Separate profiles for each page type: which selectors extract fields, which attributes are used, and how values are normalized. Selectors are moved out of the code into config — when the source markup changes, the fix takes minutes, without an application release.
  • Source schemas. A map: address, crawl frequency, depth, request limits, rules for traversing pagination and catalogs. It shows where each data block comes from and what to do when a new section appears.
  • Error handling rules. Behavior on structure changes, timeouts, and empty responses: retry with a delay, quarantine the record, notify the responsible person. A failure of one source does not stop the others.
  • Data validation. Checks for required fields, types, and value ranges before writing to storage — bad records do not reach the database and do not break reports.
  • Logs and monitoring. A run log with the number of collected records, error rate, and execution time; alerts on anomalies so the problem is seen before a user complains.
  • Documentation. Architecture description, instructions for adding a source and editing selectors, analysis of typical failures — a new engineer is onboarded into the loop without verbal explanations.
  • Operational regulations. Run schedule, procedure for updating parsing profiles, quality control points, and the people responsible for each step.

The package is self-sufficient: the client's team can change sources and rules on their own, and we step in for complex changes. Describe your task — we will come back with an estimate and a plan for your environment.

Case study: how a parser stopped breaking when the markup changed

The task was typical: collect product cards and prices from an external catalog that regularly changed its layout. The first version of the parsing on Cheerio relied on rigid selector chains — div.list > div.item:nth-child(3) > span.price.

As long as the source was left untouched, everything worked, but every markup change on their side broke the parsing: the selector stopped finding the node, the parser returned empty fields, and some records went into the database with zero prices.

The share of successful parses dropped to 60-70%, and this was noticed only from incorrect data in reports rather than from a process crash — silent degradation is worse than an explicit error.

What was changed

We rewrote the extraction to rely on stable signals instead of position in the tree: data-* attributes, microdata, semantic tags, and text anchors near the price.

We added a schema validation layer — after parsing, the document is validated against the expected fields, and if the share of populated fields falls below a threshold, the run is flagged as suspicious rather than being written to the database silently.

The selectors were moved into configuration separately from the code: a source change is now fixed by editing the rules rather than by a release.

Metrics before and after

Metric Before After
Share of successful parses 60-70% 98-99%
Recovery time after a markup change 1-2 days 1-3 hours
Records with empty fields in the database regularly isolated, caught by validation

The key thing is not the number itself but the class of failure. Previously, a markup change meant silent data loss and manual incident triage; now it is a signal in monitoring and a configuration fix the same day.

Recovery time dropped because the point of failure is localized in the rules rather than smeared across the parsing code.

How does parsing static and dynamic markup differ?

Static markup arrives from the server in its entirety: the HTML already contains all the necessary nodes, and the parser reads them directly through selectors.

Libraries like Cheerio or BeautifulSoup work here — they build a document tree and allow you to extract data by classes, attributes, and nesting structure.

This kind of parsing is predictable: one request — one complete response, and the result depends only on how stable the selectors are on the source side.

Dynamic markup is assembled in the browser: the server returns an empty skeleton, and data is loaded by scripts and appears in the DOM some time after loading.

Direct HTML parsing here would return nothing, so you need an engine that executes scripts and waits for the required blocks to render.

This changes the logic itself: instead of "fetch and parse," there is waiting for a specific page state, otherwise the data simply won't have time to appear.

The difference directly affects resilience. A static parser breaks mainly when the markup changes — this is noticeable immediately and is fixed by adjusting the selectors.

A dynamic one also depends on load order, lazy loading on scroll, and network delays, so retries, timeouts, and verification that an element is actually rendered rather than merely present in the markup are built in here.

In practice, sources are almost always mixed: some data is in the HTML, some comes in separate requests.

We determine this at the parsing stage and choose an approach for each block separately — this way the parser doesn't collapse because of a single lazy-loaded section and remains predictable when the source is updated.

Technical concerns about HTML parsing

Parsing HTML is the most fragile part of data collection: a page changes without warning, and the parser silently returns empty fields instead of an error. Below are the engineering questions most often asked before kickoff, and how we address each one.

How do you make sure the collected data is correct?

We build checks at three levels. The data schema validates types and required fields — a missing price or an empty title is flagged immediately rather than making it into the export.

Checksums and record counts are compared against the expected range: a sharp drop in the number of items signals a broken selector. In addition, we run reference pages where the result is known in advance — we catch any discrepancy before the data reaches you.

What happens when the source site changes its markup?

We tie selectors not to classes like col-md-7, but to stable signals — block structure, attributes, element order.

If the markup does shift anyway, the validation from the previous point kicks in: the task is marked as failed, a notification is sent, and collection stops instead of writing garbage. We fix the selector in a targeted way, without rewriting the entire collector.

How do you avoid blocks from the source?

We work within a reasonable load: we spread requests over time, observe pauses, use an address pool and header rotation. Where the source has an official data interface, we use it instead of parsing the markup. This reduces both the risk of blocking and the fragility of the solution.

Can the parser be reused for another site?

Partly. The crawling, storage, and validation logic carries over, while the extraction layer is written separately for each source — everyone's markup is different.

That's why a new source is added faster than the first one: the infrastructure is already in place, and only the description of what to take and from where changes.

Describe the sources and the fields you need in the output — we'll come back with an estimate and a collection plan.

Let's discuss your page parsing task

HTML parsing is a task where 80% of the risk lies in the sources, not the code. Markup changes without warning, some data is loaded by scripts, some sites have protection against frequent requests, and some require authorization and a session.

The sooner we see the real pages, the more accurately we can estimate the scope of work and avoid padding the plan.

Describe your task — we'll come back with an estimate and a plan. What we need to understand at the start:

  • which sources we're parsing: a list of addresses, page types, whether access requires authorization;
  • what exactly we're extracting: specific fields, nested blocks, tables, attachments and files;
  • how often and at what volume: a one-time export, daily updates, or a real-time stream;
  • what form the result should take: a database, a table, a file export, or delivery into your system;
  • how the source serves data: static markup or loading via scripts and requests;
  • whether there are restrictions: request limits, pause requirements, data usage rules;
  • what to do on failure: notification, retry, rollback to the last correct export.

Based on these answers, we'll put together a parsing scheme, define control points, and build in protection against markup changes. You'll receive a timeline estimate, a phased work plan, and a clear picture of what will be delivered and when.

Send us a list of sources and sample pages — that's enough for us to come back with specifics.