DeepSeek Web Scraping: Turn Table-Soup HTML Into Clean JSON
Scraping and Proxy Management Expert
TL;DR:
- DeepSeek is the budget extraction engine, and it earns the title on messy markup. A live run in this guide pulled 10 structured records — text, author, tag list — out of a nested-table page for 3,397 tokens on a model priced under a dime per million input tokens.
- The API is a
base_urlswap away. DeepSeek's endpoint is OpenAI-compatible: the official OpenAI Python SDK pointed athttps://api.deepseek.comwith JSON mode on is the whole integration. - Model names moved recently — most tutorials are stale. Current models are
deepseek-v4-flashanddeepseek-v4-pro; the long-familiardeepseek-chatanddeepseek-reasonernames are deprecated as of 24-Jul-2026. - The model still cannot fetch. Rendering, sessions, and access challenges belong to a fetch layer; this guide's is one POST per page through the Scrapeless Universal Scraping API.
- No DeepSeek key yet? The identical request runs through OpenRouter. This guide executed it live on
deepseek/deepseek-v4-flashand shows the captured output. - Free to start on the fetch side. Create your Scrapeless API key at app.scrapeless.com.
Can DeepSeek scrape websites?
DeepSeek reads pages; it does not retrieve them. The API takes text you already fetched and returns the fields you name — and because its per-token price sits at the bottom of the market, it is the model people reach for when extraction has to run across a lot of pages. Fetching those pages, rendering their JavaScript, and surviving their access controls is a separate layer that no chat-completions endpoint provides.
Where DeepSeek shines is the markup you would least want to parse by hand. Semantic HTML is kind to selectors; real pages often are not. The demo target in this guide is a quotes page rendered entirely as nested tables — no classes on the data cells, quote text, authors, and tags interleaved row by row — the kind of structure where selector logic turns into row-counting arithmetic that dies on the next redesign. A language model does not care: it reads the table the way a person does.
The plan: fetch the table-soup page once with a controlled fetch layer, then have DeepSeek return clean JSON. If your interest is the broader tool category instead, the LLM scraper explainer and the ranked list of LLM scrapers map the field.
Install
One SDK covers both the native path and the aggregator path, plus requests for fetching:
bash
pip install "openai==2.34.0" requests
Configure
bash
export DEEPSEEK_API_KEY="sk-your_deepseek_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
Fetch the page nobody wants to parse
The target is a table-based rendering of a public quotes site built for scraping practice. One POST to the Universal Scraping API returns it with rendering and unlocking handled server-side:
python
# fetch_tableful.py — retrieve the table-soup page through Scrapeless
import os
import requests
resp = requests.post(
"https://api.scrapeless.com/api/v1/unlocker/request",
headers={
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
},
json={
"actor": "unlocker.webunlocker",
"input": {"url": "https://quotes.toscrape.com/tableful/", "method": "GET"},
},
timeout=120,
)
resp.raise_for_status()
html = resp.json().get("data", "")
print(f"fetched {len(html):,} characters")
print("table elements:", html.count("<table"), "| table rows:", html.count("<tr"))
with open("page.html", "w", encoding="utf-8") as f:
f.write(html)
The run prints one <table> and 22 <tr> rows for what is conceptually ten records with tags — quote rows and tag rows alternating, exactly the shape the HTML tables specification permits and selector authors dread.
Basic implementation: DeepSeek as the extractor
The integration is the OpenAI SDK with two DeepSeek-specific values: the base URL and the model name. JSON mode (response_format={"type": "json_object"}) and temperature=0 make the output parseable and stable — the parameters are documented in the DeepSeek API reference. Use the current model names: deepseek-v4-flash for extraction work, deepseek-v4-pro when you genuinely need more model; the older deepseek-chat and deepseek-reasoner names are deprecated as of 24-Jul-2026.
Note: This block needs a
DEEPSEEK_API_KEYwith credit — the one prerequisite this guide does not assume. The next section runs the identical extraction live through OpenRouter, with captured output.
python
# extract_deepseek.py — native DeepSeek extraction (requires DEEPSEEK_API_KEY)
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.deepseek.com",
api_key=os.environ["DEEPSEEK_API_KEY"],
)
page_html = open("page.html", encoding="utf-8").read()
completion = client.chat.completions.create(
model="deepseek-v4-flash",
temperature=0,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": 'Extract every quote from this table-based page. Reply ONLY with JSON: '
'{"quotes":[{"text":str,"author":str,"tags":[str]}]}.',
},
{"role": "user", "content": page_html},
],
)
data = json.loads(completion.choices[0].message.content)
print(f"extracted {len(data['quotes'])} quotes from table markup")
No row arithmetic, no sibling-axis XPath — the schema names the output and the model does the reading.
No DeepSeek key? Run it through OpenRouter
Because both surfaces are OpenAI-shaped, the aggregator variant differs by two strings. This is the version this guide executed for real, fetch and extraction in one self-contained script:
python
# extract_openrouter.py — the same extraction, executed via OpenRouter
import json
import os
import requests
from openai import OpenAI
resp = requests.post(
"https://api.scrapeless.com/api/v1/unlocker/request",
headers={
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
},
json={
"actor": "unlocker.webunlocker",
"input": {"url": "https://quotes.toscrape.com/tableful/", "method": "GET"},
},
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="deepseek/deepseek-v4-flash",
temperature=0,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": 'Extract every quote from this table-based page. Reply ONLY with JSON: '
'{"quotes":[{"text":str,"author":str,"tags":[str]}]}.',
},
{"role": "user", "content": page_html},
],
)
data = json.loads(completion.choices[0].message.content)
print(f"extracted {len(data['quotes'])} quotes from table markup")
print(json.dumps(data["quotes"][0], ensure_ascii=False))
The live run pulled all 10 records out of the table soup, first one:
text
extracted 10 quotes from table markup
{"text": "The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.", "author": "Albert Einstein", "tags": ["change", "deep-thoughts", "thinking", "world"]}
Tags came back as arrays even though the page renders them in a separate row from the quote itself — the model associated each tag row with the quote row above it, which is precisely the join a selector script has to hand-code. The whole call: 3,397 tokens.
Get your API key on the free plan: app.scrapeless.com
Advanced patterns
- Reserve the model for pages that deserve it. DeepSeek is cheap per token, and a selector script is still cheaper: free. Route clean, stable layouts to generated-then-reviewed selector code and spend model calls on the table soup, the redesign-prone, and the long tail.
- State the row semantics when you know them. On this page the prompt did not need it, but on nastier tables one sentence — "each record spans two rows: data, then tags" — buys accuracy more cheaply than a bigger model does.
- Keep one page per call and loop in Python. Page boundaries are record boundaries; prompt-side batching blurs them.
- Watch the deprecation calendar. Pinned model names age.
deepseek-chatserved for a long run and stops on 24-Jul-2026 — put model ids in config, not in ten scripts.
Troubleshooting
model_not_foundor similar on the native endpoint. You are probably sending a deprecated model name; move todeepseek-v4-flashordeepseek-v4-pro.- Prose instead of JSON. JSON mode is off — set
response_format={"type": "json_object"}and keep the schema in the system message. - Records merge or tags land on the wrong quote. Add the row-semantics sentence to the system message, and check the fetched HTML actually contains the rows you expect, the way the fetch script counts them.
- Output varies between runs.
temperature=0. Extraction is a transcription task, not a writing task.
Conclusion
DeepSeek's case for the extraction layer is arithmetic plus tolerance: bottom-of-market token prices and no fear of markup that punishes selector logic. The table-soup demo is the proof — 10 records with correctly-joined tag arrays out of anonymous <tr> rows, for 3,397 tokens. What the model cannot supply is the page itself, rendered and reachable; one server-side POST per page covers that side, and the two layers together make a scraper that costs almost nothing to run and little to maintain when the markup shifts.
Ready to Feed DeepSeek 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 DeepSeek scrape websites by itself?
No. The DeepSeek API is a language-model endpoint: it extracts from text you provide and cannot issue requests, render JavaScript, or hold sessions. Every working DeepSeek scraping setup pairs it with a fetch layer that returns faithful page content.
Q: Which DeepSeek model should I use for web scraping?
deepseek-v4-flash for extraction — the live run in this guide used it and returned all 10 records with correct tag joins. Reach for deepseek-v4-pro only when clean input still produces wrong fields. Avoid the deprecated deepseek-chat and deepseek-reasoner names in new code.
Q: Why use DeepSeek instead of a bigger-name model for extraction?
Price at equal adequacy. Schema-constrained extraction at temperature=0 is a bounded task most current models pass; when they all pass, the cheapest passing model wins. The measured run here cost 3,397 tokens on a model priced under a dime per million input tokens — at that rate, extraction stops being the expensive part of the pipeline.
Q: When is a selector script still better than DeepSeek?
When the layout is clean, stable, and high-volume. A reviewed selector script costs nothing per page forever; a model call costs tokens every time. The working split: selectors for the stable core of your targets, DeepSeek for tangled markup and layouts that keep changing.
Q: Is scraping with DeepSeek legal?
The extraction model does not change collection rules. 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 — plus DeepSeek's usage terms on the model side.
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.



