如何使用 Flask 和 SQLite 制作 URL Shortener

作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。

介绍

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

在本教程中,您将构建一个 _URL 缩写器,该服务可以使用任何 URL,并生成更短,更易于阅读的版本,如 bit.ly

Hashids是一个从整数中生成短的唯一ID的库,例如,您可以使用它将一个数字,如12转换为1XcId这样的唯一字符串。

您可以使用独特的字符串来生成视频共享网站上的标识符或服务上的图像标识符来上传图像。 这种独特的字符串为您提供不可预测的标识符;因此,如果用户可以在「your_domain/image/J32Fr」访问图像,他们无法预测其他图像的位置。

您将使用 Flask、SQLite 和 Hashids 库来构建您的 URL 缩写器. 您的应用程序将允许用户输入 URL 并生成更短的版本,以及一个统计页面,用户可以查看 URL 被点击的次数。

前提条件

步骤一:建立依赖性

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

首先,如果您尚未启用您的编程环境:

1source env/bin/activate

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

1pip install flask hashids

然后创建一个名为schema.sql的数据库架构文件,其中包含 SQL 命令,以创建一个urls表。

1nano schema.sql

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

1[label flask_shortener/schema.sql]
2DROP TABLE IF EXISTS urls;
3
4CREATE TABLE urls (
5    id INTEGER PRIMARY KEY AUTOINCREMENT,
6    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
7    original_url TEXT NOT NULL,
8    clicks INTEGER NOT NULL DEFAULT 0
9);

在方案文件中,如果它已经存在的话,首先删除urls表,从而避免另一个名为urls的表存在的可能性,这可能会导致混淆的行为;例如,如果它有不同的列。

然后,您可以创建与下列列的表:

  • id: URL 的 ID,这将是每个 URL 输入的唯一整数值。您将使用它来从哈希字符串中获取原始 URL。
  • created: URL 被缩短的日期。
  • original_url: 您将重定向用户的原始长 URL.
  • click: URL 被点击的次数。

保存并关闭文件。

要运行schema.sql文件来创建urls表,请在您的flask_shortener目录中打开名为init_db.py的文件:

1nano init_db.py

然后添加以下代码:

 1[label flask_shortener/init_db.py]
 2import sqlite3
 3
 4connection = sqlite3.connect('database.db')
 5
 6with open('schema.sql') as f:
 7    connection.executescript(f.read())
 8
 9connection.commit()
10connection.close()

在这里,你连接到一个名为 database.db 的文件,你的程序会创建一旦你运行这个程序. 这个文件是数据库,将持有你的应用程序的所有数据。 然后,你打开 schema.sql 文件,并使用 executescript() 方法运行它,同时执行多个 SQL 陈述. 这将创建 urls 表. 最后,你承诺更改并关闭连接。

保存并关闭文件。

运行这个程序:

1python init_db.py

執行後,一個名為「database.db」的新檔案會出現在您的「flask_shortener」目錄中。

通过此,您已经安装了Flask和Hashids库,创建了数据库方案,并创建了SQLite数据库,其中有一个名为urls的表格,以存储URL缩写器的原始URL。

步骤 2 — 创建缩短 URL 的索引页面

在此步骤中,您将为索引页创建一个Flask路线,这将允许用户输入一个URL,然后将其保存到数据库中。

首先,在您的「flask_shortener」目录中打开名为「app.py」的文件,这是主要的Flask应用程序文件:

1nano app.py

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

1[label flask_shortener/app.py]
2import sqlite3
3from hashids import Hashids
4from flask import Flask, render_template, request, flash, redirect, url_for
5
6def get_db_connection():
7    conn = sqlite3.connect('database.db')
8    conn.row_factory = sqlite3.Row
9    return conn

在此代码中,您首先导入sqlite3模块,从hashids库中导入Hashids类和Flask助手。

「get_db_connection()」函数打开了连接到「database.db」数据库文件,然后将 row_factory属性设置为「sqlite3.Row」。因此,您可以根据名称访问列;数据库连接将返回行为像常规Python字典的行。

接下来,添加以下内容:

1[label flask_shortener/app.py]
2. . .
3app = Flask(__name__)
4app.config['SECRET_KEY'] = 'this should be a secret random string'
5
6hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])

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

<$>[注] **注:**盐是一个随机的字符串,它被提供给 hashing 函数(即, hashids.encode()),以便根据盐进行 shuffle 结果的 hash. 这个过程确保你得到的 hash 是特定于你的盐,所以 hash 是独特的和不可预测的,就像一个秘密密密码,只有你可以用来编码和解密 hashes。

您创建一个哈希对象,指定一个哈希至少应该是4字符长,通过将一个值传递给min_length参数。

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

 1[label flask_shortener/app.py]
 2. . .
 3@app.route('/', methods=('GET', 'POST'))
 4def index():
 5    conn = get_db_connection()
 6
 7    if request.method == 'POST':
 8        url = request.form['url']
 9
10        if not url:
11            flash('The URL is required!')
12            return redirect(url_for('index'))
13
14        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
15                                (url,))
16        conn.commit()
17        conn.close()
18
19        url_id = url_data.lastrowid
20        hashid = hashids.encode(url_id)
21        short_url = request.host_url + hashid
22
23        return render_template('index.html', short_url=short_url)
24
25    return render_template('index.html')

index()函数是一个Flask _view函数,这是一个使用特殊的@app.route decorator装饰的函数,其返回值被转换成一个HTTP响应,一个HTTP客户端,如网页浏览器,显示。

index()视图函数中,您通过将methods=('GET', 'POST')传递到app.route()装饰器来接受 GET 和 POST 请求。

然后,如果请求是一个 GET 请求,它会跳过if request.method ==POST``条件,直到最后一行。

如果请求是 POST 请求,则 if request.method == 'POST' 条件是正确的,这意味着用户已提交了 URL. 您将 URL 存储在 url 变量中;如果用户提交了空格表单,您会闪烁的消息 URL 需要! 并重定向到索引页面。

如果用户已经提交了 URL,则使用INSERT INTO SQL 语句将提交的 URL 存储在 urls 表中。 您在 execute() 方法中添加了 ? 位数,并通过包含提交的 URL 的 tuple 来安全地将数据插入数据库。

在一个被称为url_id的变量中,您存储您在数据库中插入的 URL 的 ID. 您可以使用 lastrowid 属性访问 URL 的 ID,该属性为最后插入的行 ID。

您使用hashids.encode()方法构建一个哈希,将其传递为URL ID;您将结果保存为名为hashid的变量。

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

最后,您将index.html模板转换为short_url变量。

在所有添加后,文件将如下:

 1[label flask_shortener/app.py]
 2import sqlite3
 3from hashids import Hashids
 4from flask import Flask, render_template, request, flash, redirect, url_for
 5
 6def get_db_connection():
 7    conn = sqlite3.connect('database.db')
 8    conn.row_factory = sqlite3.Row
 9    return conn
10
11app = Flask(__name__)
12app.config['SECRET_KEY'] = 'this should be a secret random string'
13
14hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])
15
16@app.route('/', methods=('GET', 'POST'))
17def index():
18    conn = get_db_connection()
19
20    if request.method == 'POST':
21        url = request.form['url']
22
23        if not url:
24            flash('The URL is required!')
25            return redirect(url_for('index'))
26
27        url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
28                                (url,))
29        conn.commit()
30        conn.close()
31
32        url_id = url_data.lastrowid
33        hashid = hashids.encode(url_id)
34        short_url = request.host_url + hashid
35
36        return render_template('index.html', short_url=short_url)
37
38    return render_template('index.html')

保存并关闭文件。

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

在您的flask_shortener目录中,创建一个模板目录,并在其内部打开一个名为base.html的文件:

1mkdir templates
2nano templates/base.html

在「base.html」中添加以下代码: 请注意,您也在这里使用 Bootstrap(https://getbootstrap.com/)。 如果您不熟悉 Flask 中的 HTML 模板,请参阅 How To Make A Web Application Using Flask in Python 3:

 1[label flask_shortener/templates/base.html]
 2<!doctype html>
 3<html lang="en">
 4  <head>
 5    <!-- Required meta tags -->
 6    <meta charset="utf-8">
 7    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 8
 9    <!-- Bootstrap CSS -->
10    <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">
11
12    <title>{% block title %} {% endblock %}</title>
13  </head>
14  <body>
15    <nav class="navbar navbar-expand-md navbar-light bg-light">
16        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskShortener</a>
17        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
18            <span class="navbar-toggler-icon"></span>
19        </button>
20        <div class="collapse navbar-collapse" id="navbarNav">
21            <ul class="navbar-nav">
22            <li class="nav-item active">
23                <a class="nav-link" href="#">About</a>
24            </li>
25            </ul>
26        </div>
27    </nav>
28    <div class="container">
29        {% for message in get_flashed_messages() %}
30            <div class="alert alert-danger">{{ message }}</div>
31        {% endfor %}
32        {% block content %} {% endblock %}
33    </div>
34
35    <!-- Optional JavaScript -->
36    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
37    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
38    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
39    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
40  </body>
41</html>

上一块的大部分代码是标准的HTML和对Bootstrap所需的代码。<meta>标签为Web浏览器提供信息,<link>标签链接到Bootstrap CSS文件,而<script>标签是链接到JavaScript代码,允许一些额外的Bootstrap功能。

标签 <title>{% block title %} {% endblock %}</title> 允许继承模板定义自定义标题. 您可以使用 for message in get_flashed_messages() 循环来显示闪存消息(警告、警告等)。

保存并关闭文件。

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

1nano templates/index.html

添加以下代码:

 1[label flask_shortener/templates/index.html]
 2{% extends 'base.html' %}
 3
 4{% block content %}
 5    <h1>{% block title %} Welcome to FlaskShortener {% endblock %}</h1>
 6    <form method="post">
 7    <div class="form-group">
 8        <label for="url">URL</label>
 9        <input type="text" name="url"
10               placeholder="URL to shorten" class="form-control"
11               value="{{ request.form['url'] }}" autofocus></input>
12    </div>
13
14    <div class="form-group">
15        <button type="submit" class="btn btn-primary">Submit</button>
16    </div>
17    </form>
18
19    {% if short_url %}
20    <hr>
21    <span>{{ short_url }}</span>
22    {% endif %}
23{% endblock %}

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

然后,您检查short_url变量是否具有任何值,如果表单提交并成功生成短 URL,则此值是正确的。

设置环境变量 Flask 需求,然后使用以下命令运行应用程序:

1export FLASK_APP=app
2export FLASK_ENV=development
3flask run

FLASK_APP环境变量指定你要运行的应用程序(app.py文件)。 FLASK_ENV环境变量指定了模式。 development意味着应用程序将在开发模式下运行,而调试器正在运行。

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

Flask Shortener Index page

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

Flask Shortened URL displayed beneath the URL input box

您创建了一个 Flask 应用程序,该页面接受 URL 并生成更短的 URL,但这些 URL 尚未完成任何工作,在下一步中,您将添加一条路线,从短的 URL 提取哈希,找到原始 URL,并将用户重定向到它。

步骤 3 – 添加重定向路径

在此步骤中,您将添加一个新的路径,该路径采用应用程序生成的短哈希,并将哈希解码为其整数值,即原始 URL 的 ID。

首先,打开 app.py 以添加新的路线:

1nano app.py

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

 1[label flask_shortener/app.py]
 2. . .
 3
 4@app.route('/<id>')
 5def url_redirect(id):
 6    conn = get_db_connection()
 7
 8    original_id = hashids.decode(id)
 9    if original_id:
10        original_id = original_id[0]
11        url_data = conn.execute('SELECT original_url, clicks FROM urls'
12                                ' WHERE id = (?)', (original_id,)
13                                ).fetchone()
14        original_url = url_data['original_url']
15        clicks = url_data['clicks']
16
17        conn.execute('UPDATE urls SET clicks = ? WHERE id = ?',
18                     (clicks+1, original_id))
19
20        conn.commit()
21        conn.close()
22        return redirect(original_url)
23    else:
24        flash('Invalid URL')
25        return redirect(url_for('index'))

此新路线通过 URL 接受一个值 id,并将其传输到 url_redirect() 视图函数. 例如,访问 http://127.0.0.1:5000/KJ34 将字符串 'KJ34'' 传送到 id` 参数。

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

然后,您使用SELECT SQL 语句从urls表中获取原始 URL 和其点击次数,其中 URL 标识匹配您从哈希中提取的原始 ID。

然后,您将 URL 的点击次数增加到更新 SQL 语句。

您发起交易并关闭连接,并使用redirect() Flask 帮助函数重定向到原始 URL。

如果解密哈希失败,你会闪烁一个消息,通知用户URL是无效的,并将其重定向到索引页面。

保存并关闭文件。

运行您的开发服务器:

1flask run

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

您创建了一个新路线,将用户从短的 URL 重定向到原始 URL。

步骤 4 – 添加统计页面

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

允许用户查看每个缩短链接所接收的访问次数将为每个 URL 的受欢迎性提供可见性,这对项目有用,例如营销广告活动。

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

1nano app.py

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

 1[label flask_shortener/app.py]
 2. . .
 3
 4@app.route('/stats')
 5def stats():
 6    conn = get_db_connection()
 7    db_urls = conn.execute('SELECT id, created, original_url, clicks FROM urls'
 8                           ).fetchall()
 9    conn.close()
10
11    urls = []
12    for url in db_urls:
13        url = dict(url)
14        url['short_url'] = request.host_url + hashids.encode(url['id'])
15        urls.append(url)
16
17    return render_template('stats.html', urls=urls)

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

要显示每个条目的短 URL,您需要构建它,并将其添加到您从数据库中提取的 URL 列表中的每个条目中(‘db_urls’)。

您使用 Python 函数 dict()sqlite3.Row 对象转换为字典,以允许分配. 您将名为 short_url 的新密钥添加到字典中,该值为 request.host_url + hashids.encode(url['id]),这是您以前在索引视图函数中构建短的 URL 所使用的。

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

保存并关闭文件。

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

1nano templates/stats.html

输入以下代码:

 1[label flask_shortener/templates/stats.html]
 2{% extends 'base.html' %}
 3
 4{% block content %}
 5    <h1>{% block title %} FlaskShortener Statistics {% endblock %}</h1>
 6    <table class="table">
 7        <thead>
 8            <tr>
 9            <th scope="col">#</th>
10            <th scope="col">Short</th>
11            <th scope="col">Original</th>
12            <th scope="col">Clicks</th>
13            <th scope="col">Creation Date</th>
14            </tr>
15        </thead>
16        <tbody>
17            {% for url in urls %}
18                <tr>
19                    <th scope="row">{{ url['id'] }}</th>
20                    <td>{{ url['short_url'] }}</td>
21                    <td>{{ url['original_url'] }}</td>
22                    <td>{{ url['clicks'] }}</td>
23                    <td>{{ url['created'] }}</td>
24                </tr>
25            {% endfor %}
26        </tbody>
27    </table>
28
29{% endblock %}

在这里,您可以通过指定标题和用以下列定义表来扩展「base.html」基础模板:

  • #: URL 的 ID.
  • Short: 短 URL.
  • Original: 原始 URL.
  • Click: 短 URL 被访问的次数.
  • Creation Date: 短 URL 的创建日期。

每个行都使用一个for循环填写,它通过urls列表,并显示每个URL的每个列的值。

运行开发服务器如下:

1flask run

使用您的浏览器前往http://127.0.0.1:5000/stats。 您将在一张表中找到所有URL。

Statistics page with list of URLs and number of clicks

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

1nano templates/base.html

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

 1[label flask_shortener/templates/base.html]
 2<!doctype html>
 3<html lang="en">
 4  <head>
 5    <!-- Required meta tags -->
 6    <meta charset="utf-8">
 7    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
 8
 9    <!-- Bootstrap CSS -->
10    <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">
11
12    <title>{% block title %} {% endblock %}</title>
13  </head>
14  <body>
15    <nav class="navbar navbar-expand-md navbar-light bg-light">
16        <a class="navbar-brand" href="{{ url_for('index')}}">FlaskTodo</a>
17        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
18            <span class="navbar-toggler-icon"></span>
19        </button>
20        <div class="collapse navbar-collapse" id="navbarNav">
21            <ul class="navbar-nav">
22            <li class="nav-item active">
23                <a class="nav-link" href="#">About</a>
24            </li>
25
26            <li class="nav-item active">
27                <a class="nav-link" href="{{ url_for('stats')}}">Stats</a>
28            </li>
29            </ul>
30        </div>
31    </nav>
32    <div class="container">
33        {% for message in get_flashed_messages() %}
34            <div class="alert alert-danger">{{ message }}</div>
35        {% endfor %}
36        {% block content %} {% endblock %}
37    </div>
38
39    <!-- Optional JavaScript -->
40    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
41    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
42    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
43    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
44  </body>
45</html>

在这里,您将新的<li>项嵌入导航栏中. 您使用url_for()函数链接到stats()视图函数. 您现在可以从导航栏访问统计页面。

您的统计页面显示了有关每个URL的信息,包括其较短的对应,以及它被访问了多少次。

您可以重复使用此代码来监控其他环境中的点击次数,例如跟踪社交媒体网站上的一篇文章被喜欢或更新了多少次,或者一张照片或视频被观看了多少次。

您可以从 此存储库获取该应用程序的完整代码。

结论

你已经创建了一个Flask应用程序,允许用户输入长的URL并生成更短的版本. 你已经将整数转化为短串哈希,将用户从一个链接转移到另一个链接,并设置了一个统计页面,以便你可以监控缩短的URL。

Published At
Categories with 技术
comments powered by Disqus