Flow: website screenshots and PDFs with core nodes only (no Chromium on the box)

I needed screenshots of a few web pages from Node-RED (a weekly snapshot of some status pages and a dashboard image for a Telegram bot), so here is the small flow I ended up with. It only uses core nodes: inject, function, http request, switch and file.

It calls a hosted screenshot API, CurlShot (disclosure: I build it; there is a free plan with 100 screenshots a month, no card), so there is no Chromium or Puppeteer to install on the Pi or in the container. The same shape works for any screenshot API that returns the image as the response body.

How it works

  • The inject node sets msg.url to the page you want.
  • The function node builds the query (format, full_page, block_cookie_banners, block_ads) and puts the key in an X-Access-Key header, read from the env var CURLSHOT_KEY.
  • The http request node returns a binary buffer.
  • The switch checks statusCode; a 200 goes to the file node, anything else (the API answers errors as JSON with error_message) goes to debug.

Change format to pdf for a PDF of the page, or add viewport_device=iphone_15_pro for the mobile layout. Instead of the file node you can send msg.payload straight to a Telegram or email node.

[
 {
  "id": "cs_inject",
  "type": "inject",
  "name": "Take screenshot",
  "props": [
   {
    "p": "url",
    "v": "https://nodered.org",
    "vt": "str"
   }
  ],
  "repeat": "",
  "once": false,
  "topic": "",
  "x": 140,
  "y": 100,
  "wires": [
   [
    "cs_prep"
   ]
  ]
 },
 {
  "id": "cs_prep",
  "type": "function",
  "name": "Build request",
  "func": "// Put your key in an env var (Settings > Environment) named CURLSHOT_KEY\nconst q = new URLSearchParams({\n  url: msg.url,\n  format: 'png',          // png, jpeg, webp or pdf\n  full_page: 'true',\n  block_cookie_banners: 'true',\n  block_ads: 'true'\n});\nmsg.url = 'https://curlshot.com/api/v1/screenshot?' + q.toString();\nmsg.headers = { 'X-Access-Key': env.get('CURLSHOT_KEY') };\nmsg.filename = '/tmp/screenshot-' + Date.now() + '.png';\nreturn msg;",
  "outputs": 1,
  "x": 330,
  "y": 100,
  "wires": [
   [
    "cs_http"
   ]
  ]
 },
 {
  "id": "cs_http",
  "type": "http request",
  "name": "CurlShot API",
  "method": "GET",
  "ret": "bin",
  "paytoqs": "ignore",
  "url": "",
  "persist": false,
  "senderr": false,
  "x": 510,
  "y": 100,
  "wires": [
   [
    "cs_switch"
   ]
  ]
 },
 {
  "id": "cs_switch",
  "type": "switch",
  "name": "200?",
  "property": "statusCode",
  "propertyType": "msg",
  "rules": [
   {
    "t": "eq",
    "v": "200",
    "vt": "num"
   },
   {
    "t": "else"
   }
  ],
  "checkall": "true",
  "repair": false,
  "outputs": 2,
  "x": 670,
  "y": 100,
  "wires": [
   [
    "cs_file"
   ],
   [
    "cs_debug"
   ]
  ]
 },
 {
  "id": "cs_file",
  "type": "file",
  "name": "Save file",
  "filename": "filename",
  "filenameType": "msg",
  "appendNewline": false,
  "createDir": true,
  "overwriteFile": "true",
  "encoding": "none",
  "x": 830,
  "y": 80,
  "wires": [
   []
  ]
 },
 {
  "id": "cs_debug",
  "type": "debug",
  "name": "Error",
  "active": true,
  "tosidebar": true,
  "complete": "payload",
  "targetType": "msg",
  "x": 830,
  "y": 130,
  "wires": []
 }
]

Options are documented at Documentation | CurlShot . Feedback welcome, especially on pages that come out wrong.

I would think about the naming, CurlShot reminded me not to search for the words "come" and "shot" in combined form. CurlShot, I guess is taken from curl and screenshot, what curl has to do with is beyond me since I'm not taking a screenshot of curl, I'm retrieving data with curl.

Perhaps something like site shot or open graph or even screenshot guru. Or webshot or pageshot or htmlshot (for the t3kk1es out there). I.e. from the naming it's clear of what I'm taking a screenshot of. Obviously don't be using headshot because you're not retrieving only the <head> of the page.

Plus-points for a clear privacy page on your website. :smiley:

Of course, the service only works with web pages your service can get to which, when combined with Node-RED which may very often be used on a private network only, somewhat limits its use.

Agree with gregorious (doesn't always happen :wink: ) that the naming could be a little better perhaps.

The final point is something of a bugbear of mine. Your price points jump from free to nearly $200 per year. This can be a major put-off for people who find themselves wanting to use multiple cloud services since the costs very quickly spiral out of control. Personally, I prefer to see services have some intermediate prices, perhaps $5pm to cater for people who might want just a little more than your free tier. Though the free tier is pretty good I think.

I can see that some people may find this useful and I wish you all the best in running the service.