Automated link preview images with Python & Flask
Tags: webdev, python
Published: 2020-10-28 14:33:02

In this post, I will show you how to generate link preview images for your blog posts so that they look good when you share them on social media.

Below, you can see how it's gonna look like:

![twitter-card-screenshot](https://dev-to-uploads.s3.amazonaws.com/i/ffu9ac2nln75e84qvnt3.png)

We will create a simple Flask endpoint that will accept a URL and return a generated image based on the website's metadata.

So let's get to work.

## Prepare a Jinja2 template for an HTML version of your card

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ title }}</title>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
    <style>
      @page {
        size: 1200px 628px;
        margin: 0;
        padding: 0;
      }

      html {
        font-size: 137.5%;
        -webkit-font-smoothing: antialiased;
      }

      body {
        padding: 3rem;
        font-family: 'Inter', sans-serif;
        color: #111111;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='18' viewBox='0 0 100 18'%3E%3Cpath fill='%231c64f2' fill-opacity='0.05' d='M61.82 18c3.47-1.45 6.86-3.78 11.3-7.34C78 6.76 80.34 5.1 83.87 3.42 88.56 1.16 93.75 0 100 0v6.16C98.76 6.05 97.43 6 96 6c-9.59 0-14.23 2.23-23.13 9.34-1.28 1.03-2.39 1.9-3.4 2.66h-7.65zm-23.64 0H22.52c-1-.76-2.1-1.63-3.4-2.66C11.57 9.3 7.08 6.78 0 6.16V0c6.25 0 11.44 1.16 16.14 3.42 3.53 1.7 5.87 3.35 10.73 7.24 4.45 3.56 7.84 5.9 11.31 7.34zM61.82 0h7.66a39.57 39.57 0 0 1-7.34 4.58C57.44 6.84 52.25 8 46 8S34.56 6.84 29.86 4.58A39.57 39.57 0 0 1 22.52 0h15.66C41.65 1.44 45.21 2 50 2c4.8 0 8.35-.56 11.82-2z'%3E%3C/path%3E%3C/svg%3E");
      }

      h1 {
        font-size: 3.052rem;
        margin-top: 0;
      }

      p {
        font-size: 1.563rem;
        line-height: 1.5;
      }
    </style>
  </head>

  <body>
    <h1>{{ title }}</h1>
    <p>{{ excerpt }}</p>
  </body>
</html>
```

## Create a Flask endpoint that will generate the preview image

```python
import io
import requests
from lxml import etree

@app.route('/preview.png')
def preview():
  	# Fetch the URL
    page_html = requests.get(request.args['url']).text

    # Parse the HTML response
    parser = etree.HTMLParser()
    tree = etree.parse(io.StringIO(page_html), parser)
    
    # Get the website's title and description from its metadata
    head = tree.xpath('/html/head')[0]
    title = head.xpath('meta[@property="og:title"]/@content')[0]
    description = head.xpath('meta[@property="og:description"]/@content')[0]

    # Render the HTML version of the preview
    preview_html = render_template('card.html', title=title, excerpt=description)
    
    # Use weasyprint to convert the HTML preview to PNG
    preview_img = weasyprint.HTML(string=preview_html).write_png(resolution=2 * 96)

    return Response(preview_img, mimetype='image/png')
```

Below are the steps one by one:

1. fetch the URL,
2. parse the HTML response,
3. get the website's title and description from its metadata,
4. render the HTML version of the preview,
5. use [`weasyprint`](https://weasyprint.org) to convert the HTML preview to PNG,
6. and return the generated image.

And that's it! Now you can use the endpoint in your website metadata so that it looks good when shared on social media:

```html
<meta property="og:image" content="https://YOUR_DOMAIN/preview.png?url=YOUR_URL"/>
```