🎯 A customizable, anti-detection cloud browser powered by self-developed Chromium designed for web crawlers and AI Agents.👉Try Now
Back to Blog

Llama Web Scraping: Why Rendering Decides the Data

Olivia Patel
Olivia Patel

Senior Cybersecurity Analyst

29-Jul-2026

TL;DR:

  • The gap between "can't extract" and "extracts cleanly" is one render call, and this guide measures it exactly. A plain GET on a JavaScript-only product catalogue returns one unrendered template placeholder; the same URL through the Scrapeless Universal Scraping API with js_render returns 13 real product spans, and Llama correctly extracted all 12 genuine products from them.
  • Llama 4 is the current cheap tier, not Llama 3. meta-llama/llama-4-scout is the cheapest current-generation Llama on the live OpenRouter catalog — a different, newer family from the Llama 3.1 name most existing tutorials still use.
  • The model quietly filtered out a broken template row on its own. The rendered page actually contains 13 product-name spans; one is an un-hydrated ${product.name} placeholder, and Llama's extraction returned exactly the 12 real products, skipping the fake one without being told to.
  • This is the cloud path, not the local one. A separate guide on this site, Web Scraping with LLaMA 3, covers running Llama locally through Ollama; this one runs Llama 4 through a hosted API instead.
  • No native Llama API key yet? The identical request runs through OpenRouter. This guide executed it live on meta-llama/llama-4-scout and shows the captured output.
  • Free to start on the fetch side. Create your Scrapeless API key at app.scrapeless.com.

Can Llama scrape websites?

A hosted Llama endpoint reads text and returns fields; it does not fetch pages, execute their JavaScript, or hold a session. That is true whether the model runs locally through Ollama or through a cloud API — the model weights change nothing about how HTTP works. What differs between the local and cloud paths is only where the inference happens.

Web Scraping with LLaMA 3, already on this site, covers the local route: llama3.1:8b through Ollama, paired with Selenium against product pages. This guide covers the other half — a hosted Llama 4 model reached over an API, with no local model download and no GPU requirement, on a different demo target chosen specifically to prove why the fetch layer matters. For the broader category question, the LLM scraper explainer covers LLM-as-parser tooling in general.

Install

openai covers the OpenRouter path (Llama is served behind an OpenAI-compatible surface there), and requests covers the fetch layer:

bash Copy
pip install "llama-api-client==0.6.0" "openai==2.48.0" requests

Configure

bash Copy
export LLAMA_API_KEY="your_llama_api_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"

Get a page Llama can actually read

The demo target is a public rendering-practice page purpose-built to demonstrate exactly this gap: its product grid is empty until client-side JavaScript populates it, and the raw HTML instead contains an unexecuted template literal string. One script shows the difference and saves the version worth extracting from:

python Copy
# fetch_rendered.py — plain GET vs server-side rendering, same URL
import os

import requests

URL = "https://www.scrapingcourse.com/javascript-rendering"
MARKER = 'class="product-name"'

plain = requests.get(URL, timeout=60).text
print(f"plain GET: {len(plain):,} chars | product-name spans: {plain.count(MARKER)}")

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={
        "actor": "unlocker.webunlocker",
        "input": {"url": URL, "method": "GET", "js_render": True},
    },
    timeout=120,
)
resp.raise_for_status()
rendered = resp.json().get("data", "")
print(f"rendered:  {len(rendered):,} chars | product-name spans: {rendered.count(MARKER)}")

with open("page.html", "w", encoding="utf-8") as f:
    f.write(rendered)

The run prints product-name spans: 1 for the plain fetch — that single hit is an un-executed <span class="product-name">${product.name}</span> template, not real data — and product-name spans: 13 for the rendered one. The gap between the two is exactly the document lifecycle the HTML scripting specification defines: content that only exists after scripts run against the DOM. Rendering and proxy routing happen server-side in that one POST — the Universal Scraping API is the fetch layer, and page.html is now something a model can extract from.

Basic implementation: Llama as the extractor

Meta's official Llama API takes structured output through response_format={"type": "json_schema", "json_schema": {...}}, per the SDK shipped at Meta's Llama API Python client repository — there is no bare json_object mode on this endpoint, only schema-guided output. The current OpenRouter catalog lists meta-llama/llama-4-scout as the cheapest Llama 4-generation model; on Meta's own native API the equivalent model ID carries the fuller Llama-4-Scout-17B-16E-Instruct-FP8 form.

Note: This block needs a LLAMA_API_KEY with credit — the one prerequisite this guide does not assume. The next section runs the identical extraction live through OpenRouter, with captured output.

python Copy
# extract_llama.py — native Llama API extraction (requires LLAMA_API_KEY)
import json
import os

from llama_api_client import LlamaAPIClient

client = LlamaAPIClient(api_key=os.environ["LLAMA_API_KEY"])

page_html = open("page.html", encoding="utf-8").read()

response = client.chat.completions.create(
    model="Llama-4-Scout-17B-16E-Instruct-FP8",
    max_completion_tokens=2000,
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "Products",
            "schema": {
                "type": "object",
                "properties": {
                    "products": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {"name": {"type": "string"}, "price_usd": {"type": "number"}},
                            "required": ["name", "price_usd"],
                        },
                    }
                },
                "required": ["products"],
            },
        },
    },
    messages=[
        {"role": "system", "content": "Extract every real product from this page. Ignore any unrendered template placeholders."},
        {"role": "user", "content": page_html},
    ],
)

data = json.loads(response.completion_message.content.text)
print(f"extracted {len(data['products'])} products")

Note the response shape: Meta's native client returns the message under response.completion_message.content.text, not the choices[0].message.content path OpenAI-compatible clients use.

No native key? Run it through OpenRouter

OpenRouter serves Llama behind the same OpenAI-compatible chat-completions surface as the other models in this series. This is the version this guide executed for real, fetch and extraction in one self-contained script:

python Copy
# extract_openrouter.py — the same extraction, executed via OpenRouter
import json
import os

import requests
from openai import OpenAI

URL = "https://www.scrapingcourse.com/javascript-rendering"

resp = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={
        "Content-Type": "application/json",
        "x-api-token": os.environ["SCRAPELESS_API_KEY"],
    },
    json={"actor": "unlocker.webunlocker", "input": {"url": URL, "method": "GET", "js_render": True}},
    timeout=120,
)
resp.raise_for_status()
page_html = resp.json().get("data", "")

client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])

completion = client.chat.completions.create(
    model="meta-llama/llama-4-scout",
    temperature=0,
    max_tokens=2000,
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": 'Extract every product. Reply ONLY with JSON: {"products":[{"name":str,"price_usd":number}]}.',
        },
        {"role": "user", "content": page_html},
    ],
)

data = json.loads(completion.choices[0].message.content)
print(f"extracted {len(data['products'])} products from the rendered page")
for row in data["products"]:
    print(json.dumps(row, ensure_ascii=False))

The live run extracted exactly the 12 real products, none of the placeholder:

text Copy
extracted 12 products from the rendered page
{"name": "Chaz Kangeroo Hoodie", "price_usd": 52}
{"name": "Teton Pullover Hoodie", "price_usd": 70}

The rendered page contains 13 product-name spans, but one of them is the unrendered ${product.name} template row left over in the markup — Llama read that literal template string for what it was and left it out, without being told the page had a broken row. The whole call: 5,924 tokens.

Get your API key on the free plan: app.scrapeless.com

Advanced patterns

  • Count a known element before trusting an extraction count. The 0-vs-13 span comparison here is what exposed both the render requirement and the one bad row; a raw count is cheap and catches problems an extraction call alone would hide.
  • Don't assume every occurrence of a marker is real data. This page's ${product.name} leftover is an edge case worth generalizing: any client-rendered page can carry template scaffolding that survives into the "rendered" HTML without ever being hydrated with real values.
  • Loop in Python, one page per call. Page boundaries are record boundaries; batching pages in a single prompt blurs where one page's products end and the next begins.
  • Cloud versus local is a volume and infrastructure decision, not a capability one. The Ollama path in the companion local-Llama guide avoids per-token cost entirely but needs a GPU-capable host; this cloud path needs no local hardware but bills per token — pick based on job volume and where the compute already lives.

Troubleshooting

  • A "rendered" page still returns junk for one field. Check for literal template syntax (${...}, {{...}}, <%...%>) in the raw text before assuming the model made an error — some client frameworks leave unhydrated scaffolding in the DOM even after real content loads elsewhere.
  • Plain requests.get returns a mostly-empty product grid. That is the expected signature of a client-rendered page; add js_render: true on the fetch call rather than trying to parse what plain HTML returns.
  • JSON parsing fails. Confirm response_format is set on the request; without it, an OpenAI-compatible endpoint may return prose instead of a bare JSON object.
  • Output changes between identical runs. Set temperature=0; extraction should be deterministic transcription, not generation.

Conclusion

The 0-vs-13 proof in this guide is the whole argument for a fetch layer: a plain HTTP client sees a template placeholder where a real page has products, and only a render-capable fetch turns that into extractable HTML. Once that HTML exists, Llama 4's cheapest current tier read it correctly on the first try — 12 real products, zero from the leftover template row, for under 6,000 tokens total. Cloud or local, the model was never the missing piece; the page was.

Ready to Feed Llama Real Pages?

The fetch layer here is the Universal Scraping API — plans and request volumes are on the pricing page, with every unlocker.webunlocker parameter in the developer docs. Create a key on the free plan at app.scrapeless.com and both scripts run as written.

FAQ

Q: Can Llama scrape websites by itself?

No, on any hosting path. A Llama endpoint — hosted or local — extracts from text you provide; it cannot issue HTTP requests, render JavaScript, or hold a session. Every working setup pairs it with a fetch layer that returns faithful, rendered page content.

Q: Which Llama model should I use for web-scraping extraction?

meta-llama/llama-4-scout on OpenRouter — the current cheapest Llama 4-generation model on the live catalog, and the one this guide ran live to return 12 correctly extracted products. Older tutorials centered on Llama 3.1 are pointing at a prior, no-longer-current generation.

Q: Should I run Llama locally or through a cloud API for scraping?

Both work with the same fetch-then-extract architecture. Local, through Ollama, avoids per-token cost but needs a GPU-capable host and a model download; this guide's cloud path needs no local hardware but bills per token. The companion post Web Scraping with LLaMA 3 covers the local route in depth.

Q: Why did the model skip one of the products on the rendered page?

Because it wasn't a real product. The rendered HTML in this guide's live run contained 13 product-name spans, but one was an unhydrated ${product.name} template placeholder left over from the page's client-side framework — the model correctly excluded it from the extracted 12.

Q: Is scraping with Llama legal?

The extraction layer does not change collection rules on either the cloud or local path. Fetch public pages only, respect site terms and the robots directives standardized by the Robots Exclusion Protocol, keep volumes bounded, and handle any personal data under applicable law.

At Scrapeless, we only access publicly available data while strictly complying with applicable laws, regulations, and website privacy policies. The content in this blog is for demonstration purposes only and does not involve any illegal or infringing activities. We make no guarantees and disclaim all liability for the use of information from this blog or third-party links. Before engaging in any scraping activities, consult your legal advisor and review the target website's terms of service or obtain the necessary permissions.

Most Popular Articles

Catalogue