如何使用 Flask 和 SQLite 制作 URL 缩短器

作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。

介绍

Flask是一个使用 Python 构建 Web 应用程序的框架,SQLite是一个数据库引擎,您可以与 Python 一起使用它来存储应用程序数据。

在本教程中,您将构建一个URL缩短服务,即采取任何URL,并生成一个短,更可读的版本像一个服务bit.ly

Hashids是一个从整数生成一个简短的唯一 ID 的库。例如,您可以使用它来将像这样的数字转换为121XcId. 您将使用 Hashids 为 URL ID 生成唯一的字符串。

您可以使用唯一字符串为视频共享站点上的视频生成 ID,或者为上传图像的服务上的图像生成 ID。这个唯一的字符串为您提供了不可预测的 ID;因此,如果用户可以访问 处的图像your_domain/image/J32Fr,则他们无法预测其他图像的位置。如果您在 URL 缩短器中使用整数 ID,这是不可能的——例如,your_domain/image/33允许用户预测其他图像的位置。不可预测的 URL 为您的服务增加了一种隐私形式,因为它们会阻止用户计算由其他用户缩短的不同 URL。

您将使用 Flask、SQLite 和Hashids库来构建您的 URL 缩短器。您的应用程序将允许用户输入一个 URL 并生成一个较短的版本,此外还有一个统计页面,用户可以在其中查看 URL 被点击的次数。您将使用Bootstrap工具包来设计您的应用程序。

先决条件

第 1 步 – 设置依赖项

在此步骤中,您将激活 Python 环境并使用 pip 包安装程序安装 Flask 和 Hashids 库。然后,您将创建用于存储 URL 的数据库。

首先,如果您还没有激活您的编程环境:

  • source env/bin/activate

激活编程环境后,使用以下命令安装 Flask 和 Hashids 库:

  • pip install flask hashids

然后创建一个名为 的数据库架构文件schema.sql,其中包含用于创建urls表的SQL 命令打开一个schema.sql在你的flask_shortener目录中调用的文件

  • nano schema.sql

在此文件中键入以下 SQL 命令:

flask_shortener/schema.sql
DROP TABLE IF EXISTS urls;

CREATE TABLE urls (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    original_url TEXT NOT NULL,
    clicks INTEGER NOT NULL DEFAULT 0
);

在架构文件中,urls如果表已存在,您首先将其删除这避免了另一个名为urlsexisting 的表的可能性,这可能会导致混淆行为;例如,如果它有不同的列。请注意,每当模式文件执行时,这将删除所有现有数据。

然后创建包含以下列的表:

  • id:URL 的 ID,这将是每个 URL 条目的唯一整数值​​。您将使用它从哈希字符串中获取原始 URL。
  • created: URL 被缩短的日期。
  • original_url:您将用户重定向到的原始长 URL。
  • clicks:URL 被点击的次数。初始值为0,每次重定向都会增加。

保存并关闭文件。

要执行该schema.sql文件以创建urls表,请init_db.py在您的flask_shortener目录中打开一个名为的文件

  • nano init_db.py

然后添加以下代码:

flask_shortener/init_db.py
import sqlite3

connection = sqlite3.connect('database.db')

with open('schema.sql') as f:
    connection.executescript(f.read())

connection.commit()
connection.close()

在这里,您连接到一个名为database.db您的程序将在您执行该程序后创建的文件该文件是保存应用程序所有数据的数据库。然后打开该schema.sql文件并使用executescript()一次执行多个 SQL 语句方法运行它这将创建urls表。最后,提交更改并关闭连接。

保存并关闭文件。

运行程序:

  • python init_db.py

执行后,一个名为的新文件database.db将出现在您的flask_shortener目录中。

有了这个,您已经安装了 Flask 和 Hashids 库,创建了数据库架构,并创建了 SQLite 数据库,其中包含一个表,urls用于存储 URL 缩短器的原始 URL。接下来,您将使用 Flask 创建索引页面,您的用户可以在其中输入 URL 以生成短 URL。

第 2 步 – 创建用于缩短 URL 的索引页面

在这一步中,您将为索引页创建一个 Flask 路由,这将允许用户输入一个 URL,然后您将其保存到数据库中。您的路由将使用 URL 的 ID 与 Hashids 库生成短字符串哈希,构造短 URL,然后将其作为结果呈现。

首先,打开一个app.py在你的flask_shortener目录中命名的文件这是主要的 Flask 应用程序文件:

  • nano app.py

将以下代码添加到文件中:

flask_shortener/app.py
import sqlite3
from hashids import Hashids
from flask import Flask, render_template, request, flash, redirect, url_for


def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn

在此代码中,您首先导入sqlite3模块、库中Hashidshashids和 Flask 助手。

get_db_connection()函数打开与database.db数据库文件的连接,然后将row_factory属性设置sqlite3.Row. 因此,您可以对列进行基于名称的访问;数据库连接将返回行为类似于常规 Python 字典的行。最后,该函数返回conn您将用于访问数据库连接对象。

接下来,添加以下内容:

flask_shortener/app.py
. . .
app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'

hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])

您创建 Flask 应用程序对象并设置密钥以保护会话由于密钥是一个秘密的随机字符串,您还将使用它来为 Hashids 库指定一个这将确保散列是不可预测的,因为每次盐改变时,散列也会改变。

注意: salt 是一个随机字符串,它提供给散列函数(即 ),hashids.encode()以便根据 salt 对结果散列进行混洗。此过程可确保您获得的散列特定于您的盐,因此散列是唯一且不可预测的,就像一个只有您可以用来编码和解码散列的秘密密码。为了安全起见,请记住将其保密(这就是您使用应用程序密钥的原因)。

hashids可以4通过将值传递给参数来创建一个对象,对象指定散列的长度至少应为字符min_length您将应用程序的密钥用作盐。

接下来,将以下代码添加到文件末尾:

flask_shortener/app.py
. . .
@app.route('/', methods=('GET', 'POST'))
def index():
    conn = get_db_connection()

    if request.method == 'POST':
        url = request.form['url']

        if not url:
            flash('The URL is required!')
            return redirect(url_for('index'))

        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                (url,))
        conn.commit()
        conn.close()

        url_id = url_data.lastrowid
        hashid = hashids.encode(url_id)
        short_url = request.host_url + hashid

        return render_template('index.html', short_url=short_url)

    return render_template('index.html')

这些index()函数是一个 Flask视图函数,它是一个使用特殊@app.route 装饰器装饰的函数它的返回值被转换为 HTTP 客户端(如 Web 浏览器)显示的 HTTP 响应。

index()视图函数中,您通过传递methods=('GET', 'POST')app.route()装饰器接受 GET 和 POST 请求您打开一个数据库连接。

然后如果请求是 GET 请求,它会跳过if request.method == 'POST'条件直到最后一行。这是您呈现一个名为 的模板的地方index.html,该模板将包含一个供用户输入要缩短的 URL 的表单。

如果请求是 POST 请求,则if request.method == 'POST'条件为真,这意味着用户已经提交了一个 URL。您将 URL 存储在url变量中;如果用户提交了一个空表单,您就会显示消息The URL is required!并重定向到索引页面。

如果用户提交了 URL,则使用INSERT INTOSQL 语句将提交的 URL 存储在urls表中。?execute()方法中包含占位符并传递包含提交的 URL 的元组以将数据安全地插入到数据库中。然后提交事务并关闭连接。

在名为 的变量中url_id,您存储插入到数据库中的 URL 的 ID。您可以使用lastrowid属性访问 URL 的 ID,该属性提供最后插入行的行 ID。

您使用该hashids.encode()方法构造一个散列,将 URL ID 传递给它;您将结果保存在名为hashid. 例如,根据您使用的盐,调用hashids.encode(1)可能会产生唯一的哈希值KJ34

然后使用 构造短 URL request.host_url,这是 Flask 的request对象提供的一个属性,用于访问应用程序主机的 URL。这将http://127.0.0.1:5000/在开发环境中并且your_domain如果您部署您的应用程序。例如,short_url变量将具有类似 的值http://127.0.0.1:5000/KJ34,它是一个短 URL,它将您的用户重定向到存储在数据库中的原始 URL,其 ID 与 hash 匹配KJ34

最后,渲染index.htmlshort_url变量传递给它模板

添加所有内容后,文件将如下所示:

flask_shortener/app.py
import sqlite3
from hashids import Hashids
from flask import Flask, render_template, request, flash, redirect, url_for


def get_db_connection():
    conn = sqlite3.connect('database.db')
    conn.row_factory = sqlite3.Row
    return conn


app = Flask(__name__)
app.config['SECRET_KEY'] = 'this should be a secret random string'

hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])


@app.route('/', methods=('GET', 'POST'))
def index():
    conn = get_db_connection()

    if request.method == 'POST':
        url = request.form['url']

        if not url:
            flash('The URL is required!')
            return redirect(url_for('index'))

        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                (url,))
        conn.commit()
        conn.close()

        url_id = url_data.lastrowid
        hashid = hashids.encode(url_id)
        short_url = request.host_url + hashid

        return render_template('index.html', short_url=short_url)

    return render_template('index.html')

保存并关闭文件。

接下来,您将创建一个基本模板和index.html模板文件。

在您的flask_shortener目录中,创建一个templates目录并打开一个base.html在其中调用的文件

  • mkdir templates
  • nano templates/base.html

在里面添加以下代码base.html请注意,对于样式,您也在此处使用Bootstrap如果您不熟悉 Flask 中的 HTML 模板,请参阅如何在 Python 3 中使用 Flask 制作 Web 应用程序的步骤 3

flask_shortener/templates/base.html
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %} {% endblock %}</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-light bg-light">
        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskShortener</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
            <li class="nav-item active">
                <a class="nav-link" href="#">About</a>
            </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        {% for message in get_flashed_messages() %}
            <div class="alert alert-danger">{{ message }}</div>
        {% endfor %}
        {% block content %} {% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

前面部分中的大部分代码是标准 HTML 和 Bootstrap 所需的代码。<meta>标签提供了Web浏览器的信息,该<link>标签链接引导CSS文件和<script>标签链接的JavaScript代码,允许一些额外的引导功能。查看Bootstrap 文档以获取更多信息。

<title>{% block title %} {% endblock %}</title>标签允许继承模板定义自定义标题。您可以使用for message in get_flashed_messages()循环来显示闪现的消息(警告、警报等)。{% block content %} {% endblock %}占位符就是继承模板放置内容,让所有模板已经访问了这个基本模板,从而避免重复。

保存并关闭文件。

接下来,创建index.html将扩展此base.html文件的文件:

  • nano templates/index.html

向其中添加以下代码:

flask_shortener/templates/index.html
{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Welcome to FlaskShortener {% endblock %}</h1>
    <form method="post">
    <div class="form-group">
        <label for="url">URL</label>
        <input type="text" name="url"
               placeholder="URL to shorten" class="form-control"
               value="{{ request.form['url'] }}" autofocus></input>
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
    </form>

    {% if short_url %}
    <hr>
    <span>{{ short_url }}</span>
    {% endif %}
{% endblock %}

在这里,您可以扩展base.html、定义标题并创建一个名为 的输入表单urlurl输入将允许用户输入网址缩短。它的值为request.form['url'],在提交失败的情况下存储数据;也就是说,如果用户没有提供 URL。您还添加了一个提交按钮。

然后检查short_url变量是否有任何值——如果表单提交并且短 URL 生成成功,则为真。如果条件为真,则在表单下显示短 URL。

设置 Flask 需要的环境变量并使用以下命令运行应用程序:

  • export FLASK_APP=app
  • export FLASK_ENV=development
  • flask run

FLASK_APP环境变量指定要运行(该应用程序app.py文件)。FLASK_ENV环境变量指定的模式。development意味着应用程序将在调试器运行的情况下以开发模式运行。请记住避免在生产中使用此模式。使用flask run命令运行应用程序

打开浏览器并输入 URL http://127.0.0.1:5000/您将找到欢迎使用 FlaskShortener页面。

瓶起酥油索引页面

提交一个 URL,您将收到一个简短的 URL。

显示在 URL 输入框下方的 Flask 缩短的 URL

您创建了一个 Flask 应用程序,其页面接受 URL 并生成较短的 URL,但 URL 还没有做任何事情。在下一步中,您将添加一个从短 URL 中提取哈希值、找到原始 URL 并将用户重定向到它的路由。

第 3 步 – 添加重定向路由

在此步骤中,您将添加一个新路由,该路由采用应用程序生成的短散列并将散列解码为其整数值,即原始 URL 的 ID。您的新路由还将使用整数 ID 来获取原始 URL 并增加clicks值。最后,您将用户重定向到原始 URL。

首先,打开app.py添加一个新路由:

  • nano app.py

将以下代码添加到文件末尾:

flask_shortener/app.py
. . .

@app.route('/<id>')
def url_redirect(id):
    conn = get_db_connection()

    original_id = hashids.decode(id)
    if original_id:
        original_id = original_id[0]
        url_data = conn.execute('SELECT original_url, clicks FROM urls'
                                ' WHERE id = (?)', (original_id,)
                                ).fetchone()
        original_url = url_data['original_url']
        clicks = url_data['clicks']

        conn.execute('UPDATE urls SET clicks = ? WHERE id = ?',
                     (clicks+1, original_id))

        conn.commit()
        conn.close()
        return redirect(original_url)
    else:
        flash('Invalid URL')
        return redirect(url_for('index'))

这个新路由id通过 URL接受一个值并将其传递给url_redirect()视图函数。例如,访问http://127.0.0.1:5000/KJ34会将字符串传递'KJ34'id参数。

在视图函数中,您首先打开一个数据库连接。然后使用对象decode()方法hashids将哈希转换为其原始整数值并将其存储在original_id变量中。你检查original_id有一个值——意味着解码哈希成功。如果它有值,则从中提取 ID。当该decode()方法返回一个元组时,您使用 获取元组中的第一个值original_id[0],即原始 ID。

然后使用SELECTSQL 语句从urls表中获取原始 URL 及其点击次数,其中 URL 的 ID 与您从哈希中提取的原始 ID 匹配。您使用该fetchone()方法获取 URL 数据接下来,您将数据提取到两个original_urlclicks变量中。

然后使用UPDATESQL 语句增加 URL 的点击次数

您提交事务并关闭连接,然后使用redirect()Flask 辅助函数重定向到原始 URL

如果解码哈希失败,您会闪现一条消息,通知用户 URL 无效,并将他们重定向到索引页面。

保存并关闭文件。

运行你的开发服务器:

  • flask run

使用浏览器转到http://127.0.0.1:5000/输入一个 URL 并访问生成的短 URL;您的应用程序会将您重定向到原始 URL。

您创建了一个新路由,将用户从短 URL 重定向到原始 URL。接下来,您将添加一个页面来显示每个 URL 的访问次数。

第 4 步 – 添加统计页面

在此步骤中,您将为统计页面添加一个新路由,该页面显示每个 URL 的点击次数。您还将在导航栏上添加一个链接到页面的按钮。

允许用户查看每个缩短的链接收到的访问次数将提供每个 URL 受欢迎程度的可见性,这对于营销广告活动等项目很有用。您还可以将此工作流用作向现有 Flask 应用程序添加功能的示例。

打开app.py为统计页面添加新路由:

  • nano app.py

将以下代码添加到文件末尾:

flask_shortener/app.py
. . .

@app.route('/stats')
def stats():
    conn = get_db_connection()
    db_urls = conn.execute('SELECT id, created, original_url, clicks FROM urls'
                           ).fetchall()
    conn.close()

    urls = []
    for url in db_urls:
        url = dict(url)
        url['short_url'] = request.host_url + hashids.encode(url['id'])
        urls.append(url)

    return render_template('stats.html', urls=urls)

在这个视图函数中,您打开一个数据库连接。然后获取表中所有条目的 ID、创建日期、原始 URL 和点击次数urls您使用该fetchall()方法获取所有行的列表。然后将此数据保存在db_urls变量中并关闭连接。

要显示每个条目的短 URL,您需要构建它并将其添加到您从数据库 ( db_urls)获取的 URL 列表中的每个项目您创建一个名为的空列表,urlsdb_urls使用for url in db_urls.

您可以使用dict()Python 函数将sqlite3.Row对象转换为字典以允许赋值。short_url向字典中添加一个新键,其值为request.host_url + hashids.encode(url['id']),这是您之前在索引视图函数中构造短 URL 时使用的键。您将此词典附加到urls列表中。

最后,渲染一个名为 的模板文件stats.html,将urls列表传递给它。

保存并关闭文件。

接下来,创建新的stats.html模板文件:

  • nano templates/stats.html

在其中输入以下代码:

flask_shortener/templates/stats.html
{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} FlaskShortener Statistics {% endblock %}</h1>
    <table class="table">
        <thead>
            <tr>
            <th scope="col">#</th>
            <th scope="col">Short</th>
            <th scope="col">Original</th>
            <th scope="col">Clicks</th>
            <th scope="col">Creation Date</th>
            </tr>
        </thead>
        <tbody>
            {% for url in urls %}
                <tr>
                    <th scope="row">{{ url['id'] }}</th>
                    <td>{{ url['short_url'] }}</td>
                    <td>{{ url['original_url'] }}</td>
                    <td>{{ url['clicks'] }}</td>
                    <td>{{ url['created'] }}</td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

{% endblock %}

在这里,您base.html通过指定标题并定义包含以下列的表来扩展基本模板:

  • #:URL 的 ID。
  • Short: 短网址。
  • Original: 原网址。
  • Clicks:访问短网址的次数。
  • Creation Date:短网址的创建日期。

每行都使用一个for循环填充,该循环遍历urls列表并显示每个 URL 的每列的值。

使用以下命令运行开发服务器:

  • flask run

使用浏览器转到http://127.0.0.1:5000/stats您将在表中找到所有 URL。

包含 URL 列表和点击次数的统计页面

接下来,在导航栏中添加一个Stats按钮。打开base.html文件:

  • nano templates/base.html

按照以下突出显示的行编辑文件:

flask_shortener/templates/base.html
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

    <title>{% block title %} {% endblock %}</title>
  </head>
  <body>
    <nav class="navbar navbar-expand-md navbar-light bg-light">
        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskTodo</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav">
            <li class="nav-item active">
                <a class="nav-link" href="#">About</a>
            </li>

            <li class="nav-item active">
                <a class="nav-link" href="{{ url_for('stats')}}">Stats</a>
            </li>
            </ul>
        </div>
    </nav>
    <div class="container">
        {% for message in get_flashed_messages() %}
            <div class="alert alert-danger">{{ message }}</div>
        {% endfor %}
        {% block content %} {% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>

在这里,您将一个新<li>项目合并到导航栏中。您可以使用该url_for()函数链接到stats()视图函数。您现在可以从导航栏访问统计页面。

您的统计信息页面显示有关每个 URL 的信息,包括其较短的等效项和访问次数。

您可以重用此代码来监控其他上下文中的点击次数,例如跟踪帖子在社交媒体网站上被点赞或更新的次数,或者照片/视频被查看的次数。

您可以从此存储库访问应用程序的完整代码

结论

您已经创建了一个 Flask 应用程序,它允许用户输入一个长 URL 并生成一个较短的版本。您已将整数转换为短字符串哈希,将用户从一个链接重定向到另一个链接,并设置了一个统计页面,以便您可以监控缩短的 URL。有关使用 Flask 的更多项目和教程,请查看以下教程:

觉得文章有用?

点个广告表达一下你的爱意吧 !😁