The month's coverage list is done: a spreadsheet of article links that the client will want to see in the report, each with a screenshot beside it. Taken by hand, every screenshot means opening the article, dismissing a consent pop-up, waiting for the ads to settle and cropping. Taken by a script that does none of that, a good share of them show the consent pop-up over the article.
This guide takes the screenshots both ways. First one article in the free Site-Shot browser tool, then the whole list with a short Python script. The script captures each article without cookie pop-ups or ads, checks that the page text names your client, and lists the captures to look at before they go into the report.
We ran it on 10 November 2026 against six articles from TechCrunch, Forbes, The Guardian, BBC News, The Verge and Wired. Three came back clean, and the script flagged the other three for real problems. It covers the screenshots, not the whole report: audience metrics, social posts and print clippings stay with your coverage tool or your spreadsheet.
Screenshot one article in the browser, free
- Open site-shot.com and paste the article's URL. Capturing is free and needs no signup.
- Tick full page, and leave No ads / No cookie ticked. It is on by default, and it runs the capture with an ad blocker and a cookie-banner blocker.
- Press Shot, check that the headline and your client's mention are in the picture, and click the download arrow under it (its tooltip says Download) to save the PNG.
That handles the article a client forwarded this morning. A month of coverage is a job for the API.
Screenshot a whole coverage list with Python
You need Python 3.9 or newer, the Site-Shot SDK and an API key. Install the SDK:
pip install site-shot
For the key, a confirmed account's API key comes with the free plan: 100 API captures a month, no card needed; paid plans start at $5/mo for 2,000. The API quickstart gets you one. Each article on the list is one capture.
Put the list in coverage.csv, one article per row, with the name the article should mention:
url,mention
https://www.bbc.co.uk/news/articles/cmp3g37l566po,OpenAI
https://www.wired.com/story/security-flaws-across-uk-government-sites-exposed-god-tier-information/,GitHub
A CSV downloaded from Google Sheets works as it is. So does one saved from Excel as CSV UTF-8, as long as its columns are separated by commas; Excel in some languages uses semicolons.
Save the script as coverage_report.py in the same folder:
"""Screenshot every article in coverage.csv and build coverage/report.html."""
import base64
import csv
import html
from pathlib import Path
from urllib.parse import urlsplit
from site_shot import SiteShot, SiteShotError
client = SiteShot() # reads SITESHOT_API_KEY from the environment
OUT = Path("coverage")
OUT.mkdir(exist_ok=True)
SETTINGS = dict(
width=1280, height=800,
full_size=True, max_height=2400,
no_cookie_popup=True, no_ads=True,
fresh_session=True, markdown=True,
)
def headline(markdown):
"""The first level-one heading in the Markdown, or "" when there is none."""
for line in markdown.splitlines():
if line.startswith("# "):
return line[2:].strip()
return ""
def plain(text):
"""Lower case, curly apostrophes made straight: either spelling of a name matches."""
return text.replace("\u2019", "'").lower()
def review(reply, png, mention):
"""What to check by hand in one capture; an empty list means no check failed."""
notes = []
status = reply.get("response", {}).get("status_code")
if status != 200:
notes.append("capture status %s" % status)
if int.from_bytes(png[20:24], "big") <= SETTINGS["height"]: # the PNG's height
notes.append("only one screen tall: a wall or pop-up may cover the page")
if reply.get("markdown_status") != "ok":
notes.append("no page text (%s)" % reply.get("markdown_status_detail"))
elif not mention:
notes.append("no mention to check")
elif plain(mention) not in plain(reply["markdown"]):
notes.append("%r not found in the page text" % mention)
return notes
rows = []
with open("coverage.csv", newline="", encoding="utf-8-sig") as f:
for n, row in enumerate(csv.DictReader(f), 1):
url = (row.get("url") or "").strip()
mention = (row.get("mention") or "").strip()
host = urlsplit(url if "//" in url else "//" + url).hostname or "page"
image = "%02d-%s.png" % (n, host)
try:
reply = client.capture_json(url, **SETTINGS)
except SiteShotError as error:
rows.append((url, host, "", "-", "capture failed: %s" % error))
else:
png = base64.b64decode(reply["image"].split(",", 1)[1]) # drop "data:...,"
(OUT / image).write_bytes(png)
check = "; ".join(review(reply, png, mention)) or "OK"
rows.append((url, host, image, headline(reply.get("markdown", "")), check))
print(n, host, rows[-1][-1])
cells = "".join(
"<tr><td><a href='%s'>%s</a></td><td>%s</td><td>%s</td><td>%s</td></tr>" % (
html.escape(url), html.escape(host), html.escape(title or "(copy it from the screenshot)"),
html.escape(check), "<img src='%s' width='360'>" % image if image else "")
for url, host, image, title, check in rows
)
(OUT / "report.html").write_text(
"<meta charset='utf-8'><table border='1' cellpadding='6'><tr><th>Outlet</th>"
"<th>Headline</th><th>Check</th><th>Screenshot</th></tr>" + cells + "</table>",
encoding="utf-8",
)
Run it with the key in the environment. Typing it at a hidden prompt keeps it out of your shell history. This works in bash and zsh:
printf 'Site-Shot API key: '; read -rs SITESHOT_API_KEY; echo
export SITESHOT_API_KEY
python coverage_report.py
The script runs on your own computer and writes everything into a coverage folder next to it. It saves one PNG per article and prints a line per row. At the end it writes coverage/report.html, a table with the outlet, the headline, the check result and the screenshot for each article. Open it in a browser, go down the Check column, and insert the PNGs you keep from the coverage folder into your Google Slides or PowerPoint report. A capture that fails leaves a row saying so, and the run carries on with the next article.
What each setting does
full_size=Truewithmax_height=2400captures the page from the top down to 2,400 pixels at the same width every time. That takes in the headline, byline, lead image and opening paragraphs. A taller cap shows more of the article, but some sites load the next story underneath as you scroll. With a 4,000-pixel cap, our TechCrunch capture ran on into the next article.no_cookie_popup=Trueandno_ads=Trueare the two blockers behind the browser tool's No ads / No cookie box.fresh_session=Truestarts each capture in a clean browser, without the cookies and local storage that earlier captures saved.markdown=Trueadds the page's text to the reply. The HTTP API also supports rendered-page Markdown: requestmarkdown=1withresponse_type=json.capture_jsonsendsresponse_type=jsonfor you, so one reply carries both the screenshot and the text of the same page load. Themarkdownfield is present only whenmarkdown_statusisok, and the text is capped at 40,000 Unicode code points. It is the whole page's readable text, menus and related headlines included. The script searches it for the mention and takes the headline from its first top-level heading.
Which captures to look at
The Check column says OK, or lists what failed:
- capture status … means the status the reply reports for the page was not 200. For a page that loaded, that is the page's own status, such as 404 when an article moved or came down. A 504 usually means the capture ran out of time: on Forbes the full-page scroll timed out, and the reply came back with a screenshot of the article but no text. After a timeout the image can be a timeout notice rather than the page, so look before you use it.
- only one screen tall means the full-page screenshot came back no taller than the browser window, so the page did not scroll. A wall or pop-up that locks the page does that, which is how The Verge's subscription wall showed up.
- no page text means the reply had no readable text for the page, so the mention could not be checked.
- '…' not found in the page text means the text came back without the name. The Wired article names GitHub further down, but the paywall cut the text off first. The flag can also mean the article doesn't mention your client at all, which is worth knowing before it goes in a report.
- no mention to check means the row's
mentioncell was empty. - A Headline cell that reads (copy it from the screenshot) means the page text had no top-level heading, or there was no text at all.
The checks narrow down which screenshots need a look. They do not replace the look: a bar that a site draws over an otherwise complete article passes all of them, and so does a mention that only appears in a sidebar.
What the run returned on 10 November 2026
| Article | Check column | What the screenshot showed |
|---|---|---|
| TechCrunch | OK | The article, with no privacy notice or ads; the Headline cell said (copy it from the screenshot) |
| Forbes | capture status 504; no page text (view_unavailable) | The article, with the site's membership bar across it |
| The Guardian | OK | The article; the blocked ad left an empty box above the masthead |
| BBC News | OK | The article, with no consent dialog |
| The Verge | only one screen tall: a wall or pop-up may cover the page | A subscription wall over the article |
| Wired | 'GitHub' not found in the page text | The opening lines, then a paywall |
An earlier run that morning without the two blockers (with a taller cap and a longer wait before the screenshot) shows what they remove. The Guardian's consent pop-up covered the article and kept the page from scrolling. BBC News opened with a cookie consent dialog over the article. TechCrunch carried a privacy notice and display ads. The Verge served the whole article on our first two visits that morning and the subscription wall on the next three.
Where the honest limits are
- Paywalls stay. The screenshot shows the page as a visitor who is not logged in sees it, so a paywalled article shows its paywall. Dedicated coverage tools hit the same wall: CoverageBook's help center says it "may not be able to collect an automated screenshot" behind one. Capture those articles in your own logged-in browser.
- A bar the site draws itself can stay. The blockers work from lists of common consent tools and ad networks. A membership bar or privacy notice that a site draws itself is not on those lists. Forbes' membership bar stayed, and so did a privacy notice on The Verge in the earlier run.
- Metered walls change between visits. The same article can come back whole once and walled the next time.
- The text is the whole page's readable text, menus and related headlines included, so an OK does not prove that the article itself names your client. It can miss the headline, as on TechCrunch, or come back with none, as on Forbes. A paywalled page's text stops where the paywall starts.
- Screenshots, not a coverage tool. There are no reach, domain authority or share counts, no social posts, print or broadcast clips, and no designed report. CoverageBook and ClipCoverage add those metrics to their own screenshots. The script is for teams that want the screenshots and the checks inside a report they build themselves.
- Every row is a capture. Running the script again captures every row again.
FAQ
How do I screenshot articles for a PR coverage report without cookie pop-ups? For one article, paste its URL into the free Site-Shot browser tool, tick full page and leave No ads / No cookie ticked; it is on by default. For a whole coverage list, call the Site-Shot API for each URL with no_cookie_popup=1 and no_ads=1. A confirmed account's API key comes with the free plan: 100 API captures a month, no card needed; paid plans start at $5/mo for 2,000.
Can Site-Shot screenshot paywalled articles? Site-Shot captures the page as a visitor who is not logged in sees it, so a paywalled article shows its paywall. Capture those articles in your own logged-in browser.
Can a script check that each article mentions my client? Yes, when the page's text is readable. The Site-Shot API can return the rendered page's Markdown in the same reply as the screenshot (markdown=1 with response_type=json), and a script can search it for your client's name. The text includes menus and related headlines, and a paywall cuts it off, so check the screenshot before a row goes into the report.
Does Site-Shot add reach or domain authority to a coverage report? No. Site-Shot captures the screenshot and the page's text. Coverage tools such as CoverageBook and ClipCoverage add audience and share metrics.
Related reading
- How to Take a Website Screenshot with Python — the SDK's other return modes and options.
- How to Put Website Screenshots in Google Sheets with Apps Script — when the coverage list lives in a spreadsheet.
- How to Screenshot Every Page of a Website (From Its Sitemap) — the same loop over a site's own pages.
- How to Verify Backlinks and Screenshot Them for a Link-Building Report — when the report lists the links you placed, not the articles.
Screenshotting one article? Do it free — no signup — at site-shot.com. For a whole coverage list, get an API key from the API quickstart or see the paid plans on the pricing page.