GuaranteeReclaim 5+ hours per week or get a 100% refund.See Pricing
Operations/Blog/Hybrid DOM & OCR Architecture
Engineering & Architecture8 min readSeptember 16, 2026

Why HTML Scraping Fails on Early-Stage D2C Brands: Building a Hybrid DOM and OCR Pipeline

How we solved Amazon CDN template stripping, eliminated third-party vision API fees with local ONNX inference, and raised dual-contact discovery to 53.3%.

JJ
Jacob JamesLead Consultant, Neovis

Marketplace scrapers targeting B2B contact information usually follow a predictable pattern: fetch product HTML, locate technical specification tables, match phone numbers and emails with regular expressions, and save the records.

This workflow breaks down when applied to early-stage direct-to-consumer (D2C) brands on Amazon India. Emerging manufacturers frequently omit technical specification tables during catalog upload, and their seller profile pages (/sp?seller=...) often display only a legal trade name with blank phone and email fields. Compounding the issue, repeated requests cause Amazon's CDN to swap full 2.5 MB desktop pages for stripped-down 300 KB client-side templates that lack seller tables altogether.

The physical product packaging, however, contains the missing data. Under the Legal Metrology (Packaged Commodities) Rules, 2011, Indian law prohibits selling packaged food products without a visible manufacturer address, consumer support telephone number, and email address.

To capture these details automatically, we built Amazon Lead Scout. The pipeline pairs HTML parsing with local Optical Character Recognition (OCR) running in memory via ONNX Runtime, raising dual-contact discovery from 13.3% to 53.3% on test catalogs.

1. Preventing Amazon CDN Layout Degradation

Scrapers using libraries like requests, playwright, or curl_cffi accumulate session cookies across successive HTTP calls. As cookie headers grow, Amazon's traffic management servers flag the session and return minimalist dynamic layouts that lack productDetails_techSpec_section_1 and seller metadata containers.

Clearing session cookies before each GET request resets the session state, prompting the CDN to treat each call as an initial desktop visit:

# src/core/fetcher.py
class AmazonFetcher:
    def __init__(self):
        self.session = requests.Session(impersonate="chrome120")
        self.session.headers.update(DEFAULT_HEADERS)

    def get(self, url: str) -> Optional[str]:
        self._sleep_polite()
        for attempt in range(1, MAX_RETRIES + 1):
            try:
                # Reset cookies to force full server-rendered desktop DOM
                self.session.cookies.clear()
                resp = self.session.get(url, timeout=REQUEST_TIMEOUT)
                if resp.status_code == 200:
                    return resp.text
                elif resp.status_code in (429, 503):
                    backoff = self._calculate_backoff(attempt)
                    time.sleep(backoff)
            except Exception:
                time.sleep(self._calculate_backoff(attempt))
        return None

Pairing cookie flushes with Chrome TLS fingerprint impersonation (curl_cffi) maintains access to server-rendered DOM tables without requiring headless browser overhead.

2. Rate Limiting with Jittered Exponential Backoff

To handle 429 and 503 response codes without creating synchronized request spikes across workers, backoff intervals incorporate uniform random jitter:

Delay = InitialDelay × (BackoffFactorattempt - 1) × (1 + random(0, JitterRatio))

Adding jitter desynchronizes retry attempts across asynchronous workers and prevents recurring request collisions against CDN rate limit thresholds.

3. In-Memory Packaging OCR via ONNX Runtime

Commercial cloud vision APIs charge per image, which becomes expensive when scanning four to six packaging images per ASIN. Local Tesseract installations require external system binaries and struggle with curved surfaces, low-contrast typography, and rotated labels.

The pipeline uses RapidOCR (PaddleOCR models executed through ONNX Runtime). Running locally on standard x86 CPU threads, inference completes in roughly 600 milliseconds per image.

Images stream directly into RAM as byte arrays and convert to NumPy arrays through OpenCV. No intermediate image files touch the local disk:

# src/core/image_ocr.py
def _process_single_image(self, img_url: str) -> str:
    resp = self.fetcher.session.get(img_url, timeout=15)
    if resp.status_code != 200:
        return ""

    # Decode bytes directly in RAM
    img_arr = np.frombuffer(resp.content, dtype=np.uint8)
    img = cv2.imdecode(img_arr, cv2.IMREAD_COLOR)
    if img is None:
        return ""

    # Local ONNX inference on CPU
    result, _ = self.ocr_engine(img)
    if not result:
        return ""

    # Keep text lines with confidence >= 0.45
    high_confidence_text = [
        line[1] for line in result if float(line[2]) >= 0.45
    ]
    return "\n".join(high_confidence_text)

4. Conditional OCR Triggering

Running OCR across every image on every listing creates unnecessary computational overhead. The pipeline uses an asymmetric trigger:

  • When HTML parsing extracts both a valid telephone number and an email address from the DOM, OCR processing is bypassed entirely.
  • When either the phone number or the email address is missing from the DOM, the pipeline downloads secondary product photos and executes the OCR routine.

In live batch runs, this conditional check bypassed OCR on approximately 70% of qualified listings.

5. Cleaning Hidden Directional Unicode Characters

Amazon India pages include hidden directional formatting characters (\u200e, \u200f, \xa0, and \u202a through \u202e) inside brand names, phone strings, and seller addresses. These characters do not render visually in browsers, but they break regular expressions and trigger UnicodeEncodeError crashes on Windows systems running cp1252 console encoding.

All raw text passes through an upfront sanitizer before validation:

# src/core/validator.py
CLEAN_CHARS_REGEX = re.compile(r'[\u200e\u200f\xa0\u202a-\u202e\t\r]')

def sanitize_text(raw_text: str) -> str:
    if not raw_text:
        return ""
    return CLEAN_CHARS_REGEX.sub(' ', raw_text).strip()

Benchmark Results Across 15 Live Listings

MetricDOM OnlyOCR OnlyHybrid PipelineNet Improvement
Valid Phone12 (80.0%)5 (33.3%)14 (93.3%)+13.3% (+2)
Valid Email5 (33.3%)6 (40.0%)9 (60.0%)+26.7% (+4)
Dual Contacts2 (13.3%)4 (26.7%)8 (53.3%)4x Increase
Total Usable Coverage15/15 (100%)7/15 (46.7%)15/15 (100%)100% Verified

Conclusion

Marketplace scrapers that rely exclusively on HTML elements miss valuable B2B contact data when sellers omit specifications. In regulated categories like food, cosmetics, and health supplements, packaging photos contain verified legal contact details mandated by law. Combining session cookie resets with local in-memory ONNX OCR provides a practical, low-latency method to extract complete contact records without third-party vision APIs.

Frequently Asked Questions

How does resetting session cookies prevent CDN degradation?

As scrapers accumulate session cookies across successive HTTP calls, Amazon's traffic management servers flag the session and swap full desktop pages for minimalist dynamic templates. Clearing cookies forces full server-rendered HTML on every visit.

Why use local ONNX RapidOCR instead of cloud vision APIs?

Commercial vision APIs charge per image, which becomes cost-prohibitive across thousands of ASINs. RapidOCR runs locally on standard CPU threads in ~600ms with zero per-image API fees.

How does the conditional OCR trigger work?

If HTML parsing yields both a valid phone and email from the DOM, OCR is skipped entirely. Only when contact fields are missing does the pipeline download packaging photos and invoke local OCR.

Need Robust Automation Built for Complex Workflows?

Whether building custom web scrapers, data pipelines, or private AI engines, we engineer systems that run with zero monthly SaaS taxes.