[Python] Playwright Python: PDF renders in new tab (200 application/pdf) but I can’t save it —...

Discussão em 'Python' iniciado por Stack, Setembro 1, 2025.

  1. Stack

    Stack Membro Participativo

    I’m automating the Pima County inspections site with Playwright (Python). The flow is:


    That page then loads the actual PDF from:
    https://aca-prod.accela.com/PIMA/Re...T_SUMMARY_REPORT&reportID=762&agencyCode=PIMA

    In the browser I can see the PDF rendered in the new tab. My network log also shows the PDF response clearly:

    RESP 200 https://aca-prod.accela.com/PIMA/Report/ShowReport.aspx?Module=Building&reportType=PRINT_PERMIT_SUMMARY_REPORT&reportID=762&agencyCode=PIMA | ct=application/pdf cl=160319


    …but my code never saves the bytes, it is very frustrating. acceptDownloads=True doesn’t help (server is Content-Type: application/pdf inline, not an attachment). I tried several approaches.

    Environment:

    • Windows 11
    • Python 3.13.7
    • Playwright Python 1.47.0
    • Browsers: channel="chrome" (headed) and chromium (headed). Same result.

    Minimal code that reproduces

    from pathlib import Path
    from playwright.sync_api import sync_playwright

    BASE = "https://publicworks.pima.gov/inspections"
    DOWNLOADS = Path("downloads"); DOWNLOADS.mkdir(exist_ok=True)

    def is_pdf_response(resp):
    return ("Report/ShowReport.aspx" in resp.url
    and "application/pdf" in (resp.headers.get("content-type","").lower()))

    with sync_playwright() as p:
    browser = p.chromium.launch(channel="chrome", headless=False)
    ctx = browser.new_context(accept_downloads=True) # inline PDF, so this never fires
    page = ctx.new_page()

    # (1) Go to listings and click Prev until rows exist (omitted for brevity)
    page.goto(BASE)

    # (2) Click a record link -> record details page (omitted: selector resolution)
    # page.click("a[href*='CapDetail.aspx']")

    # (3) Click "View inspection summary" which opens a new tab
    with ctx.expect_page() as new_page_info:
    page.click("a#ctl00_PlaceHolderMain_lnkPrintSummary") # link text/ID varies, but this opens the tab
    pdf_tab = new_page_info.value

    # (4) The parameter page loads, then the PDF URL is fetched by the site.
    # Try to catch the actual PDF response:
    caught = {"saved": False}

    def on_response(resp):
    if is_pdf_response(resp):
    # Attempt 1: save body() from the response we just saw
    content = resp.body() # <- returns bytes?
    if content and content[:4] == b"%PDF":
    out = DOWNLOADS / "report_from_response.pdf"
    out.write_bytes(content)
    print("Saved via context.on('response'):", out)
    caught["saved"] = True

    ctx.on("response", on_response)

    # Also try a tab-scoped expect_response around the time it should load:
    try:
    with pdf_tab.expect_response(lambda r: is_pdf_response(r), timeout=15000) as resp_info:
    # Sometimes there is a submit/run-report button on ReportParameter.aspx;
    # sometimes it loads automatically. So just wait here.
    pass
    r = resp_info.value
    body = r.body()
    if body and body[:4] == b"%PDF":
    out = DOWNLOADS / "report_from_expect.pdf"
    out.write_bytes(body)
    print("Saved via expect_response:", out)
    caught["saved"] = True
    except Exception as e:
    print("expect_response failed:", e)

    # Fallback Attempt 3: if we saw the parameter page URL, re-GET the PDF directly
    # with cookies + Referer (same origin).
    if not caught["saved"]:
    param_url = pdf_tab.url
    pdf_url = "https://aca-prod.accela.com/PIMA/Report/ShowReport.aspx?Module=Building&reportType=PRINT_PERMIT_SUMMARY_REPORT&reportID=762&agencyCode=PIMA"
    r = ctx.request.get(pdf_url, headers={"Referer": param_url})
    print("Re-GET status:", r.status, r.headers.get("content-type"))
    body = r.body()
    if body and body[:4] == b"%PDF":
    out = DOWNLOADS / "report_from_reget.pdf"
    out.write_bytes(body)
    print("Saved via re-GET:", out)
    caught["saved"] = True

    print("Done; saved?", caught["saved"])


    Questions:

    What’s the reliable Playwright pattern to capture inline PDFs that render in a new tab (navigation to ShowReport.aspx) and save the bytes?

    Is there a timing/targeting gotcha with expect_response on the newly opened tab (navigation responses vs subresource responses)?

    If the PDF stream is delivered as a navigation response, is there a recommended way to read that response body (CDP hook? another Playwright API?) from Python?

    This Accela flow is public (no login). Any pointers or a minimal snippet that consistently grabs the ShowReport.aspx bytes would be hugely appreciated.

    What I’ve tried:

    BrowserContext.on("response") and page.expect_response(...) with a predicate for ShowReport.aspx + content-type: application/pdf. I can see the response in my own network log, but the save code never triggers, or body() isn’t giving me PDF bytes.

    Fallback ctx.request.get(pdf_url, headers={"Referer": param_url}) using the same cookies/session. Status is 200 and content-type: application/pdf in the network log, still no saved file in my script.

    Clicking the Chrome PDF viewer toolbar “download” button isn’t accessible via Playwright (browser UI).

    accept_downloads=True + page.on("download") doesn’t fire because the server returns inline PDF (no Content-Disposition: attachment).

    Continue reading...

Compartilhe esta Página