Web Scrape API Empty Content: 3 Steps for JavaScript Pages
Treat an empty response from a JavaScript-rendered product page as a data-path problem first: inspect the browser's network activity, call the JSON endpoint that supplies the listing, and assert that the expected fields exist. Add browser rendering only when no usable data endpoint exists. For a competitor price watch, that order matters because rendering every page multiplies the expensive part of the index pipeline without making the resulting records more trustworthy. Short answer: prefer the page's data endpoint, fail closed on empty content, and keep rendering as an explicit fallback tier. A successful HTTP status is not evidence that a listing was captured. What Should You Do When a Web Scrape API Returns Empty Content? The scrape API may be returning the server's HTML correctly. Many storefronts deliver a small document and let JavaScript request the title, offer, availability, and seller data after load. A non-rendering fetch sees the shell, so selectors aimed at browser-created nodes find nothing. Retrying the same fetch changes no part of that sequence. Confirm the diagnosis in a browser before changing infrastructure. Open the network panel, reload one representative product page, filter for fetch or xhr, and inspect JSON responses. The useful request is the one whose response contains the listing fields you need. Record its method, URL, required parameters, and authentication behavior; do not copy browser cookies into a long-lived collector unless you have permission and a rotation plan. Don't promote the change yet. One trap is especially costly: treating a blank title or price as a valid deletion. In a price-watch index, that converts an extraction failure into false market movement. Empty is an error state until a separate signal proves that the listing was removed. Choose the least expensive trustworthy path The options are not interchangeable. Evaluate them against a fixed sample of real listing types, including pagination and sold-out products, then measure the number of valid records produced per run. Do not compare on HTTP success alone. A source that needs rendering may be perfectly suitable at low volume yet become the dominant cost as the catalog grows; conversely, a brittle private JSON call can cost more operator time than a browser tier if its parameters change every week. The right unit is a validated listing, not a request. Option Best fit Index-cost consequence Boundary Direct JSON request with Go The page exposes a stable data call Avoids browser startup and transfers only the payload used for indexing The endpoint may require parameters or credentials, and its contract can change Playwright Rendering and page interaction belong in your own worker You control browser reuse, concurrency, and resource limits You also own browser packaging, patches, timeouts, and capacity Browserless A hosted browser endpoint is preferable to operating browsers Rendering becomes a separately metered stage that can be invoked only for hard targets It does not remove the need for extraction assertions or retry discipline Apify The target fits an Actor-based crawling workflow Scheduling, runs, and datasets can sit around the browser work The workflow model is broader than a single fetch and should be evaluated as such Infrai You want the scrape provider to be replaceable behind one REST contract The calling code can keep its contract while the provider behind the capability changes Its limitation is browser interaction; choose Playwright or a hosted browser when you must control it That last boundary is important. Infrai offers one REST API and one key across its capability surface, so the collector's contract can stay put while the provider behind it changes. The trade-off is control: it does not replace Playwright when the runbook requires precise browser interaction, and no unified contract makes server HTML contain DOM nodes that only a browser creates. Probe the upstream contract before indexing The following program calls the verified scrape route, rejects non-2xx responses, limits the response size, verifies that the body is JSON, and asserts a target-specific marker. The exact scrape request schema is obtained from the public discovery surface, so the program accepts that validated JSON as SCRAPE_REQUEST_JSON instead of guessing fields that may not exist. This is intentional: generate the request from the discovery path and schema, then place only the secret key in INFRAI_API_KEY. A retry after 429 honors Retry-After when it is a valid number of seconds and otherwise uses bounded exponential backoff. package main import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "os" "strconv" "strings" "time" ) const maxBody = 4 maxBody { return nil, errors.New("response exceeded 4 MiB") } if resp.StatusCode >= 200 && resp.StatusCode < 300 { return body, nil } if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 { return nil, fmt.Errorf("scrape returned %s: %s", resp.Status, body) } delay := time.Second = 0 { delay = time.Duration(seconds) * time.Second } select { case
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to