作者选择了COVID-19 救济基金来接受捐赠,作为Write for DOnations计划的一部分。
介绍
Flask是一个使用 Python 构建 Web 应用程序的框架,SQLite是一个数据库引擎,您可以与 Python 一起使用它来存储应用程序数据。
在本教程中,您将构建一个URL缩短服务,即采取任何URL,并生成一个短,更可读的版本像一个服务bit.ly。
Hashids是一个从整数生成一个简短的唯一 ID 的库。例如,您可以使用它来将像这样的数字转换为12
像1XcId
. 您将使用 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工具包来设计您的应用程序。
先决条件
-
本地 Python 3 编程环境,请遵循如何为 Python 3系列安装和设置本地编程环境中的分发教程。在本教程中,我们将调用我们的项目目录
flask_shortener
。 -
了解基本的 Flask 概念,例如创建路由、呈现 HTML 模板和连接到 SQLite 数据库。如果您不熟悉这些概念,请查看如何在 Python 3 中使用 Flask 制作 Web 应用程序和如何在 Python 3 中使用 sqlite3 模块,但这不是必需的。
第 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 命令:
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
如果表已存在,您首先将其删除。这避免了另一个名为urls
existing 的表的可能性,这可能会导致混淆行为;例如,如果它有不同的列。请注意,每当模式文件执行时,这将删除所有现有数据。
然后创建包含以下列的表:
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
然后添加以下代码:
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
将以下代码添加到文件中:
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
模块、库中的Hashids
类hashids
和 Flask 助手。
该get_db_connection()
函数打开与database.db
数据库文件的连接,然后将row_factory
属性设置为sqlite3.Row
. 因此,您可以对列进行基于名称的访问;数据库连接将返回行为类似于常规 Python 字典的行。最后,该函数返回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'])
您创建 Flask 应用程序对象并设置密钥以保护会话。由于密钥是一个秘密的随机字符串,您还将使用它来为 Hashids 库指定一个盐;这将确保散列是不可预测的,因为每次盐改变时,散列也会改变。
注意: salt 是一个随机字符串,它提供给散列函数(即 ),hashids.encode()
以便根据 salt 对结果散列进行混洗。此过程可确保您获得的散列特定于您的盐,因此散列是唯一且不可预测的,就像一个只有您可以用来编码和解码散列的秘密密码。为了安全起见,请记住将其保密(这就是您使用应用程序密钥的原因)。
您hashids
可以4
通过将值传递给参数来创建一个对象,该对象指定散列的长度至少应为字符min_length
。您将应用程序的密钥用作盐。
接下来,将以下代码添加到文件末尾:
. . .
@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 INTO
SQL 语句将提交的 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.html
将short_url
变量传递给它的模板。
添加所有内容后,文件将如下所示:
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:
<!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
向其中添加以下代码:
{% 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
、定义标题并创建一个名为 的输入表单url
。该url
输入将允许用户输入网址缩短。它的值为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。
您创建了一个 Flask 应用程序,其页面接受 URL 并生成较短的 URL,但 URL 还没有做任何事情。在下一步中,您将添加一个从短 URL 中提取哈希值、找到原始 URL 并将用户重定向到它的路由。
第 3 步 – 添加重定向路由
在此步骤中,您将添加一个新路由,该路由采用应用程序生成的短散列并将散列解码为其整数值,即原始 URL 的 ID。您的新路由还将使用整数 ID 来获取原始 URL 并增加clicks
值。最后,您将用户重定向到原始 URL。
首先,打开app.py
添加一个新路由:
- nano 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。
然后使用SELECT
SQL 语句从urls
表中获取原始 URL 及其点击次数,其中 URL 的 ID 与您从哈希中提取的原始 ID 匹配。您使用该fetchone()
方法获取 URL 数据。接下来,您将数据提取到两个original_url
和clicks
变量中。
然后使用UPDATE
SQL 语句增加 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
将以下代码添加到文件末尾:
. . .
@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 列表中的每个项目。您创建一个名为的空列表,urls
并db_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
在其中输入以下代码:
{% 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。
接下来,在导航栏中添加一个Stats按钮。打开base.html
文件:
- nano 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 的更多项目和教程,请查看以下教程: