NanmiCoder/MediaCrawler · error · HTTPException
Access denied
Error message
Access denied
What it means
Raised in crawler_util.find_login_qrcode when the login page's QR <img> src points at an http(s) URL but fetching it returned a non-200 status. The scraper needs the image bytes to base64-encode for later decoding, so any non-200 aborts with the response body attached. The enclosing except then prints the error and returns an empty string, so upstream this manifests as an empty QR string rather than an exception.
Source
Thrown at api/routers/data.py:113
return {"files": files}
@router.get("/files/{file_path:path}")
async def get_file_content(file_path: str, preview: bool = True, limit: int = 100):
"""Get file content or preview"""
full_path = DATA_DIR / file_path
if not full_path.exists():
raise HTTPException(status_code=404, detail="File not found")
if not full_path.is_file():
raise HTTPException(status_code=400, detail="Not a file")
# Security check: ensure within DATA_DIR
try:
full_path.resolve().relative_to(DATA_DIR.resolve())
except ValueError:
raise HTTPException(status_code=403, detail="Access denied")
if preview:
# Return preview data
try:
if full_path.suffix == ".json":
with open(full_path, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, list):
return {"data": data[:limit], "total": len(data)}
return {"data": data, "total": 1}
elif full_path.suffix == ".csv":
import csv
with open(full_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
rows = []
for i, row in enumerate(reader):
if i >= limit:
breakView on GitHub (pinned to d6f7c5bb90)
Solutions
- Replay the request with the browser's own headers and cookies: pass the page's cookies and Referer (page.url) to client.get
- Fetch the image inside the page instead of via httpx: `await elements.screenshot()` or read the loaded image via CDP — bypasses URL-level anti-bot entirely
- Retry with backoff; a single 403 from a CDN is often transient rate limiting
- Log resp.status_code alongside resp.text — a 403 vs 404 vs 302 points to header vs URL-expiry causes respectively
Example fix
// before
resp = await client.get(login_qrcode_img, headers={"User-Agent": get_user_agent()})
if resp.status_code == 200:
...
raise Exception(f"fetch login image url failed, response message:{resp.text}")
// after
cookies = {c["name"]: c["value"] for c in await page.context.cookies()}
resp = await client.get(
login_qrcode_img,
headers={
"User-Agent": get_user_agent(),
"Referer": page.url,
},
cookies=cookies,
)
if resp.status_code != 200:
# fall back to screenshotting the element the browser already loaded
return base64.b64encode(await elements.screenshot()).decode("utf-8") Defensive patterns
Strategy: fallback
Validate before calling
async def fetch_qrcode_bytes(page, img_el) -> bytes | None:
src = str(await img_el.get_property("src"))
if not src.startswith(("http://", "https://")):
return None # data URI — use as-is, no HTTP fetch needed
cookies = {c["name"]: c["value"] for c in await page.context.cookies()}
async with make_async_client(follow_redirects=True) as client:
resp = await client.get(src, headers={"User-Agent": get_user_agent(), "Referer": page.url}, cookies=cookies)
return resp.content if resp.status_code == 200 else None Type guard
def is_http_img_src(src: str) -> bool:
return isinstance(src, str) and (src.startswith("http://") or src.startswith("https://")) Try / catch
try:
b64 = await find_login_qrcode(page, selector)
except Exception:
b64 = ""
if not b64:
# HTTP fetch failed — the browser already rendered the QR; screenshot it instead
el = await page.wait_for_selector(selector)
b64 = base64.b64encode(await el.screenshot()).decode("utf-8") Prevention
- Prefer screenshotting the <img> element over re-fetching its URL — the browser already defeated the anti-bot checks
- Always send the page's cookies and Referer when re-fetching media discovered on a logged-in page
- Check the returned QR string is non-empty before proceeding; find_login_qrcode silently returns '' on failure
When it happens
Trigger: GET of the QR image URL returns 403/404/302-to-login: the platform blocks requests whose headers/cookies differ from the browser session; the URL is signed and expired by the time it is fetched; the page <img> src attribute literally contains a non-http placeholder that still matches 'http' via data URI edge cases; missing Referer/Cookie headers.
Common situations: Anti-bot CDNs (e.g. WAF rules on qrcode endpoints) rejecting bare httpx requests; using a proxied httpx client whose exit IP is geo-blocked; the login session cookie captured earlier expired; platform changed QR endpoint to require auth headers.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unsupported file type for preview
- [BilibiliLogin.begin] Invalid Login Type Currently only supp
- [DouYinLogin.begin] Invalid Login Type Currently only suppor
- [KuaishouLogin.begin] Invalid Login Type Currently only supp
- [BaiduTieBaLogin.begin]Invalid Login Type Currently only sup
AI-assisted analysis of NanmiCoder/MediaCrawler@d6f7c5bb90 (2026-08-15).
Data as JSON: /api/errors/5505c171b9518595.
Report an issue: GitHub.