PINGDOM_CHECK

#ExtractSummit2026 The world's largest web scraping conference returns. Austin Oct 7–8 · Dublin Nov 10–11.

Register now
Data Services
Pricing
Login
Try Zyte APIContact Sales
  • Unblocking and Extraction

    Zyte API

    The ultimate API for web scraping. Avoid website bans and access a headless browser or AI Parsing

    Ban Handling

    Headless Browser

    AI Extraction

    SERP

    Enterprise

    DocumentationSupport

    Hosting and Deployment

    Scrapy Cloud

    Run, monitor, and control your Scrapy spiders however you want to.

    Coding Agent Add-Ons

    Agentic Web Data

    Plugins that give coding agents the context to build production Scrapy projects. Starts with Claude Code.

  • Data Services
  • Pricing
  • Browse

    • BlogArticles, podcasts, videos
    • Case studiesCustomer outcomes
    • White papersIn-depth reports
    • DocumentationGuides & API reference
    • EventsConferences, webinars, recordings

    Subscribe

    • NewsletterSwiftly delivered
    • Join our community2,000+ web scraping engineers
  • Product and E-commerce

    From e-commerce and online marketplaces

    Data for AI

    Collect and structure web data to feed AI

    Job Posting

    From job boards and recruitment websites

    Real Estate

    From Listings portals and specialist websites

    News and Article

    From online publishers and news websites

    Search

    Search engine results page data (SERP)

    Social Media

    From social media platforms online

  • Meet Zyte

    Our story, people and values

    Contact us

    Get in touch

    Support

    Knowledge base and raise support tickets

    Terms and Policies

    Accept our terms and policies

    Open Source

    Our open source projects and contributions

    Web Data Compliance

    Guidelines and resources for compliant web data collection

    Join the team building the future of web data
    We're Hiring
    Trust Center
    Security, compliance & certifications
Login
Try Zyte APIContact Sales
All articles
AI71, 71 articles
Data quality15, 15 articles
Developer interest60, 60 articles
Integration2, 2 articles
Open-source50, 50 articles
Proxies35, 35 articles
Scraping practice35, 35 articles
Scraping strategy46, 46 articles
Search results4, 4 articles
Web data74, 74 articles
Web scraping APIs49, 49 articles
Scrapy47, 47 articles
Scrapy Cloud26, 26 articles
Web Scraping Copilot11, 11 articles
Zyte API65, 65 articles
AI & Machine Learning3, 3 articles
Automotive3, 3 articles
E-commerce & retail33, 33 articles
Entertainment & Streaming2, 2 articles
Financial Services8, 8 articles
Government2, 2 articles
Market Research & Intelligence7, 7 articles
Media & publishing11, 11 articles
Real Estate2, 2 articles
Recruitment & HR3, 3 articles
Transportation & Logistics2, 2 articles
Travel & hospitality3, 3 articles
iPaaS2, 2 articles
Large language model29, 29 articles
MCP3, 3 articles
Python110, 110 articles
Scraping at Scale7, 7 articles
Scraping Fundamentals11, 11 articles
Web Scraping Industry Report20, 20 articles

Appearance

Discord Community
BlogLearnWhy your API responses look like gibberish: the gzip decompression trap
LearnWeb data collection

Why your API responses look like gibberish: the gzip decompression trap

A

Ayan Pahwa

·

6 min read · April 8, 2026

Why your API responses look like gibberish: the gzip decompression trap

The script was working. Requests were going out, responses were coming back with HTTP 200. But the response body was unreadable noise, a wall of binary characters that crashed the JSON parser and reported "no data found." No error code, no timeout, no network failure; just garbage where structured data should be.

The culprit was gzip compression. Specifically, the mismatch between what the HTTP client promised it could handle and what it actually did with the compressed bytes it received.

This is a common trap in web scraping and API clients, and it tends to waste an hour because nothing looks obviously wrong. Here is what is happening, why Python's standard library makes it worse, and how to fix it for good.

What gzip compression is and why APIs use it

gzip is a lossless compression format based on the DEFLATE algorithm. Originally built for compressing files on Unix systems, it became the web's dominant response compression method because the trade-off is excellent: a typical JSON API response compresses to 20% to 30% of its original size, with negligible CPU cost on modern hardware.

For web scraping workloads where you are fetching dozens or hundreds of pages, that bandwidth reduction is meaningful. Compressed responses arrive faster, consume less egress on the server, and allow more concurrent connections to run without hitting network limits. In one real-world parallel-fetching scenario, keeping gzip enabled cut total wall-clock time by roughly 60% compared to uncompressed sequential fetches.

How HTTP compression negotiation works

HTTP compression uses a two-header handshake:

Accept-Encoding is sent by the client in the request. It declares which compression formats the client supports:

Content-Encoding is sent by the server in the response. It declares which compression format was actually applied to the response body:

The contract is: the client advertises its capabilities, the server compresses and labels the response, and the client is responsible for decompressing before reading. The phrase "responsible for decompressing" is where things break.

The urllib problem

Most HTTP clients abstract away this responsibility. curl --compressed handles decompression transparently. Python's requests library decompresses automatically. You never see the compressed bytes.

Python's urllib, however, is lower-level. When you manually set an Accept-Encoding header in a urllib.request call, you are signaling to the library: "I know what I am doing — give me the raw bytes."

And it does exactly that. It sends your header, receives the compressed response, and hands you the compressed binary blob without touching it. The Content-Encoding: gzip header is right there in the response, but urllib does not act on it automatically when you set Accept-Encoding yourself.

The result: your JSON parser receives data starting with the gzip magic bytes \x1f\x8b instead of the { it expects. It fails. You see gibberish, or a json.JSONDecodeError, or a silent "no data found" if your error handling swallows the exception.

This is not a urllib bug — it is intentional behavior. The library assumes that if you set the header yourself, you own the decompression step. The problem is that many scrapers copy request headers from curl or browser dev tools, which include Accept-Encoding: gzip, deflate by default, without realizing they have opted into manual decompression handling.

Why this happens with web scraping APIs

Zyte API is standards-compliant. When your client sends Accept-Encoding: gzip, deflate, Zyte API returns compressed responses as it should. The data is there, fully extracted and structured, just wrapped in gzip. The API is doing nothing wrong. The issue is entirely in the client-side handling.

This is not specific to Zyte API. Any well-implemented HTTP API or web server that supports compression will exhibit this behavior. The same trap appears when scraping any site that enables gzip, calling any REST API that respects Accept-Encoding, or consuming any streaming response from a CDN.

Detecting gzip compression reliably

gzip data always begins with the two-byte sequence 0x1f 0x8b. This magic number gives you a format-level check that is more reliable than parsing the Content-Encoding header, because some servers compress the body but omit or misconfigure the header.

The detection pattern is simple:

python

Both gzip and zlib are part of Python's standard library, no additional dependencies needed.

The complete fix with Zyte API

Here is a minimal, working example of a Zyte API call with proper compression handling:

Two lines added after resp.read() — that is the entire fix.

Handling deflate, too

If you want a reusable utility that covers both common encodings:

Call this on any raw response body and it returns decompressed bytes, or the original bytes unchanged if no compression is detected.

When to use requests instead

If you are using the requests library, this problem does not arise. Decompression is handled transparently:

The case for urllib is zero external dependencies — useful when you are packaging a lightweight script or running in an environment where you cannot install packages. The case for requests is that it handles this (and many other edge cases) for you. Choose based on your constraints, but if you go the urllib route, keep the two-line decompression check in mind.

The diagnostic checklist

If your scraper or API client is returning what looks like binary garbage:

  1. Check whether your response starts with the bytes \x1f\x8b ; that is compressed gzip data
  2. Check whether you are manually setting Accept-Encoding in a low-level HTTP client
  3. Check the response's Content-Encoding header : gzip confirms what happened
  4. Add the two-line magic-byte check and gzip.decompress() call
  5. Do not remove Accept-Encoding from your headers — keep compression enabled for the bandwidth savings

The issue surfaces in any language or framework where you are working close to the HTTP layer: Go's net/http without Transport.DisableCompression, Rust's reqwest in manual mode, Node.js's http module without a decompression middleware. The diagnostic is always the same, check the first two bytes.

Summary

gzip compression cuts HTTP response sizes by 70% to 80%, which makes it worth keeping enabled in any high-volume scraping workload.

The catch is that low-level HTTP clients like Python's urllib hand you the raw compressed bytes when you set Accept-Encoding yourself, and do not decompress automatically.

The fix is to check for the gzip magic number after reading the response body and decompress with gzip.decompress() when it is present.

Two lines of code, no extra dependencies, and your responses go from unreadable noise back to clean, parseable JSON.


Learn more: Zyte API documentation | Zyte API automatic extraction

In this article

  • Why your API responses look like gibberish: the gzip decompression trap
  • What gzip compression is and why APIs use it
  • How HTTP compression negotiation works
  • The urllib problem
  • Why this happens with web scraping APIs
  • Detecting gzip compression reliably
  • The complete fix with Zyte API
  • Handling deflate, too
  • When to use requests instead
  • The diagnostic checklist
  • Summary

Other lessons

Learn Scrapy

  • Scrapy Tutorial Part 1: First Spider
  • Scrapy Tutorial Part 2: Page Objects
  • Scrapy Tutorial Part 3: Web Scraping CoPilot

What is web scraping?

  • What Is Web Scraping?
  • What are the elements of a web scraping project?
  • Python Web Scaping Tools & Libraries
  • How to architect a web scraping solution: The step-by-step guide
  • Web crawling vs web scraping
  • Is Web & Data Scraping Legally Allowed?
  • Compliant Web Scraping Checklist
  • Best practices for web scraping
  • A Guide to Web Scraping With Java
  • Transition from Zenrows to Zyte API
  • Guide to Web Scraping APIs
  • Screen Scraping Explained
  • Large Scale Web Scraping with Python
  • Large Scale Web Scraping with Python
  • Building a Web Crawler in Python
  • A Practical Guide to XML Parsing with Python
  • Learn How to Scrape a Website
  • Advanced Use Cases for Session Management
  • Golang Web Scraping in 2025
  • Web Scraping Dynamic Websites With Zyte API
  • What is Data Parsing in Web Scraping?
  • Scrape Web Pages and Files Using Python, wget, and Zyte

Web Scraping How-to Videos

  • Web scraping videos

SERP Data Collection at Scale

  • SERP data collection at scale and why efficiency matters
  • Why Page One SERP data is no longer enough
  • Why pagination logic becomes operational debt
  • Why SERP data costs exploded

What is web scraping used for?

  • What is web scraping used for?
  • Pricing Intelligence Web Scraping
  • Web Scraping For Market Research
  • Use web scraping to build a data-driven product
  • Use web scraping for alternative data for finance
  • Use web scraping for brand monitoring
  • Use web scraping to automate MAP compliance
  • Web Scraping For Lead Generation
  • Web Scraping For Recruitment
  • Use web scraping for business automation
  • Using Data Extraction Tools for Efficient Website Scraping
  • Why Might a Business Use Web Scraping to Collect Data?
  • How to Scrape Images from Any Website: A Complete Guide
  • How to Scrape Search Engine Results

The New Guide to Web Scraping at Scale

  • Introduction
  • 1. A plan is a pathway to success
  • 2. Get serious about legal compliance
  • 3. The quality of your web data is of utmost importance
  • 4. Scaling and maintaining crawling and extracting solutions
  • 5. Adding AI to the web scraping stack
  • 6. The In-house vs outsourced question
  • 7. Questions to ask when scaling web scraping

Essential Web Scraping Techniques

  • TLS Fingerprint and how it blocks requests
  • How to scrape with a browser effectively
  • API First data extraction

More learn articles

Keep learning

All learn articles →
What are residential proxies bannerUse case

What is a residential proxy?

Learn what residential proxies are, how they compare to datacenter proxies, and why modern web scraping needs more than IP diversity.

10 min read

Zyte Case Studies — every customer story, in one placeUse case

How much do rotating proxies cost?

Learn how much rotating proxies cost, what affects pricing, and why total web scraping costs often go beyond proxy subscriptions.

10 min read

Zyte Case Studies — every customer story, in one placeUse case

How do rotating proxies work?

Learn how rotating proxies work, when to use them for web scraping, and why IP rotation alone is not enough for reliable data access.

10 min read

Services

Zyte Data

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Explore Zyte Data

Web Scraping API

Zyte API

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Sign Up

Developers

Zyte Developers

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Join Us
    • Zyte API
    • Ban Handling
    • AI Extraction
    • SERP
    • Enterprise
    • Scrapy Cloud
    • Agentic Web Data
    • Pricing
1Accept-Encoding: gzip, deflate
Copy
1Content-Encoding: gzip
Copy
1raw_bytes = response.read()
2if raw_bytes[:2] == b"\x1f\x8b":
3    raw_bytes = gzip.decompress(raw_bytes)
4body = raw_bytes.decode("utf-8", errors="replace")
Copy
1python
2import urllib.request
3import base64
4import gzip
5import json
6def fetch_from_zyte(url: str, api_key: str) -> dict:
7    auth_string = base64.b64encode(f"{api_key}:".encode()).decode()
8
9    headers = {
10        "Content-Type": "application/json",
11        "Authorization": f"Basic {auth_string}",
12        "Accept-Encoding": "gzip, deflate",
13    }
14
15    payload = json.dumps({"url": url, "product": True}).encode()
16
17    req = urllib.request.Request(
18        "https://api.zyte.com/v1/extract",
19        data=payload,
20        headers=headers,
21        method="POST",
22    )
23
24    with urllib.request.urlopen(req) as resp:
25        raw_bytes = resp.read()
26
27    # Detect and decompress gzip
28    if raw_bytes[:2] == b"\x1f\x8b":
29        raw_bytes = gzip.decompress(raw_bytes)
30
31    return json.loads(raw_bytes.decode("utf-8", errors="replace"))
Copy
1python
2import gzip
3import zlib
4
5def decompress_response(raw_bytes: bytes) -> bytes:
6    # gzip: magic number 0x1f 0x8b
7    if raw_bytes[:2] == b"\x1f\x8b":
8        return gzip.decompress(raw_bytes)
9
10    # zlib/deflate: common header byte 0x78
11    if raw_bytes[:1] == b"\x78":
12        return zlib.decompress(raw_bytes)
13
14    return raw_bytes
Copy
1import requests
2
3response = requests.post(
4    "https://api.zyte.com/v1/extract",
5    auth=(api_key, ""),
6    json={"url": url, "product": True},
7)
8
9data = response.json()  # already decompressed
Copy
    • Product & E-commerce
    • Data for AI
    • Job Posting
    • Real Estate
    • News & Articles
    • Search
    • Social Media
    • Blog
    • Learn
    • Case Studies
    • Webinars
    • White Papers
    • Join our community
    • Documentation
    • Meet Zyte
    • Contact us
    • Jobs
    • Support
    • Terms and Policies
    • Trust Center
    • Do not sell
    • Cookie settings
    • Web Data Compliance
    • Open Source
    • What is Web Scraping
    • Web Scraping in Python: Ultimate Guide
    • Stop getting blocked, start scraping
  • EWDCI logoMost loved workplace certificateZyte rewardISO 27001 iconG2 rewardG2 rewardG2 reward

    © Zyte Group Limited 2026
    XFacebookInstagramYouTubeLinkedInDiscord