hav-cs50-merge-02
哈佛 CS50 中文官方笔记(三)
第七讲
-
欢迎!
-
平面文件数据库
-
关系数据库
-
SELECT
-
INSERT
-
DELETE
-
UPDATE
-
IMDb
-
JOIN
-
索引
-
在 Python 中使用 SQL
-
竞争条件
-
SQL 注入攻击
-
总结
欢迎!
-
在前几周,我们向您介绍了 Python,这是一种高级编程语言,它使用了我们在 C 语言中学到的相同构建块。然而,我们引入这种新语言不是为了学习“另一种语言”。相反,我们这样做是因为某些工具更适合某些工作,而不太适合其他工作!
-
这周,我们将继续学习更多与 Python 相关的语法。
-
此外,我们将把这种知识与我们所学的内容结合起来。
-
最后,我们将讨论SQL或结构化查询语言,这是一种特定领域的方法,我们可以通过它来交互和修改数据。
-
总体而言,本课程的一个目标就是学习编程的一般知识——而不仅仅是学习本课程中描述的语言的编程。
平面文件数据库
-
如你所见,数据通常可以用列和行的模式来描述。
-
类似于在 Microsoft Excel 和 Google Sheets 中创建的电子表格可以输出为
csv或逗号分隔值文件。 -
如果你查看一个
csv文件,你会注意到文件是平的,因为我们的所有数据都存储在一个由文本文件表示的单个表中。我们称这种形式的数据为平面文件数据库。 -
所有数据都是按行存储的。每个列由逗号或其他值分隔。
-
Python 自带对
csv文件的原生支持。 -
首先,下载favorites.csv并将其上传到cs50.dev中的文件资源管理器内。其次,检查这些数据,注意第一行是特殊的,因为它定义了每一列。然后,每条记录按行存储。
-
在你的终端窗口中,输入
code favorites.py并编写以下代码:# Prints all favorites in CSV using csv.reader import csv # Open CSV file with open("favorites.csv", "r") as file: # Create reader reader = csv.reader(file) # Skip header row next(reader) # Iterate over CSV file, printing each favorite for row in reader: print(row[1])注意到已经导入了
csv库。此外,我们创建了一个reader,它将保存csv.reader(file)的结果。csv.reader函数从文件中读取每一行,在我们的代码中,我们将结果存储在reader中。因此,print(row[1])将打印出favorites.csv文件中的语言。 -
你可以按照以下方式改进你的代码:
# Stores favorite in a variable import csv # Open CSV file with open("favorites.csv", "r") as file: # Create reader reader = csv.reader(file) # Skip header row next(reader) # Iterate over CSV file, printing each favorite for row in reader: favorite = row[1] print(favorite)注意
favorite被存储并打印出来。此外,注意我们使用next函数跳到读者下一行。 -
上述方法的一个缺点是我们信任
row[1]始终是首选。然而,如果列被移动了,会发生什么呢? -
我们可以修复这个潜在的问题。Python 还允许你通过列表的键进行索引。按照以下方式修改你的代码:
# Prints all favorites in CSV using csv.DictReader import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Iterate over CSV file, printing each favorite for row in reader: favorite = row["language"] print(favorite)注意到这个例子直接在打印语句中使用了
language键。favorite索引到row["language"]的reader字典。 -
这可以进一步简化为:
# Prints all favorites in CSV using csv.DictReader import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Iterate over CSV file, printing each favorite for row in reader: print(row["language"]) -
要统计在
csv文件中表达的首选语言的数目,我们可以这样做:# Counts favorites using variables import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Counts scratch, c, python = 0, 0, 0 # Iterate over CSV file, counting favorites for row in reader: favorite = row["language"] if favorite == "Scratch": scratch += 1 elif favorite == "C": c += 1 elif favorite == "Python": python += 1 # Print counts print(f"Scratch: {scratch}") print(f"C: {c}") print(f"Python: {python}")注意到每种语言都是通过
if语句进行统计的。此外,注意那些if语句中的双等号==。 -
Python 允许我们使用字典来统计每种语言的
counts。考虑以下对我们代码的改进:# Counts favorites using dictionary import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Counts counts = {} # Iterate over CSV file, counting favorites for row in reader: favorite = row["language"] if favorite in counts: counts[favorite] += 1 else: counts[favorite] = 1 # Print counts for favorite in counts: print(f"{favorite}: {counts[favorite]}")注意到当
counts中存在favorite键时,其值会增加。如果它不存在,我们定义counts[favorite]并将其设置为 1。此外,格式化字符串已经得到改进,以展示counts[favorite]。 -
Python 也允许对
counts进行排序。按照以下方式改进你的代码:# Sorts favorites by key import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Counts counts = {} # Iterate over CSV file, counting favorites for row in reader: favorite = row["language"] if favorite in counts: counts[favorite] += 1 else: counts[favorite] = 1 # Print counts for favorite in sorted(counts): print(f"{favorite}: {counts[favorite]}")注意代码底部的
sorted(counts)。 -
如果你查看 Python 文档中
sorted函数的参数,你会发现它有许多内置参数。你可以利用一些这些内置参数,如下所示:# Sorts favorites by value using .get import csv # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Counts counts = {} # Iterate over CSV file, counting favorites for row in reader: favorite = row["language"] if favorite in counts: counts[favorite] += 1 else: counts[favorite] = 1 # Print counts for favorite in sorted(counts, key=counts.get, reverse=True): print(f"{favorite}: {counts[favorite]}")注意传递给
sorted的参数。key参数允许你告诉 Python 你希望用于排序项的方法。在这种情况下,使用counts.get按值排序。reverse=True告诉sorted从大到小排序。 -
Python 有许多库,我们可以在代码中利用这些库。其中之一是
collections,我们可以从中导入Counter。Counter将允许你访问每种语言的计数,而无需像我们之前的代码中看到的那样处理所有if语句。你可以按照以下方式实现:# Sorts favorites by value using .get import csv from collections import Counter # Open CSV file with open("favorites.csv", "r") as file: # Create DictReader reader = csv.DictReader(file) # Counts counts = Counter() # Iterate over CSV file, counting favorites for row in reader: favorite = row["language"] counts[favorite] += 1 # Print counts for favorite, count in counts.most_common(): print(f"{favorite}: {count}")注意到
counts = Counter()如何启用从collections导入的Counter类。
关系型数据库
-
Google、X 和 Meta 都使用关系型数据库来大规模存储他们的信息。
-
关系型数据库在称为 表格 的结构中以行和列的形式存储数据。
-
SQL 允许四种类型的命令:
Create Read Update Delete -
这四个操作被亲切地称为 CRUD。
-
我们可以使用 SQL 语法
CREATE TABLE table (column type, ...);创建一个数据库。但这个命令在哪里运行呢? -
sqlite3是一种具有本课程所需核心功能的关系型数据库。 -
我们可以在终端通过输入
sqlite3 favorites.db创建一个 SQL 数据库。在被提示时,我们将通过按y键同意创建favorites.db。 -
你会注意到提示符发生了变化,因为我们现在正在使用一个名为
sqlite的程序。 -
我们可以通过输入
.mode csv将sqlite置于csv模式。然后,我们可以通过输入.import favorites.csv favorites从我们的csv文件导入数据。看起来好像什么都没发生! -
我们可以输入
.schema来查看数据库的结构。 -
你可以使用
SELECT columns FROM table语法从表中读取项目。 -
例如,您可以输入
SELECT * FROM favorites;,这将打印favorites中的每一行。 -
您可以使用命令
SELECT language FROM favorites;来获取数据子集。 -
SQL 支持许多用于访问数据的命令,包括:
AVG COUNT DISTINCT LOWER MAX MIN UPPER -
例如,您可以输入
SELECT COUNT(*) FROM favorites;。此外,您可以输入SELECT DISTINCT language FROM favorites;以获取数据库中个别语言的列表。您甚至可以输入SELECT COUNT(DISTINCT language) FROM favorites;以获取这些语言的计数。 -
SQL 还提供了我们可以在查询中使用的附加命令:
WHERE -- adding a Boolean expression to filter our data LIKE -- filtering responses more loosely ORDER BY -- ordering responses LIMIT -- limiting the number of responses GROUP BY -- grouping responses together注意我们在 SQL 中使用
--来写注释。
SELECT
-
例如,我们可以执行
SELECT COUNT(*) FROM favorites WHERE language = 'C';。将显示计数。 -
此外,我们可以输入
SELECT COUNT(*) FROM favorites WHERE language = 'C' AND problem = 'Hello, World';。注意AND是如何用于缩小我们的结果的。 -
同样,我们可以执行
SELECT language, COUNT(*) FROM favorites GROUP BY language;。这将提供一个临时表,显示语言和计数。 -
我们可以通过输入以下命令来改进这一点:
SELECT language, COUNT(*) FROM favorites GROUP BY language ORDER BY COUNT(*);。这将按count对结果表进行排序。 -
同样,我们可以执行
SELECT COUNT(*) FROM favorites WHERE language = 'C' AND (problem = 'Hello, World' OR problem = 'Hello, It''s Me');。请注意,有两个''标记,以便以不混淆 SQL 的方式使用单引号。 -
此外,我们可以执行
SELECT COUNT(*) FROM favorites WHERE language = 'C' AND problem LIKE 'Hello, %';以找到以Hello,开头的任何问题(包括空格)。 -
我们还可以通过执行
SELECT language, COUNT(*) FROM favorites GROUP BY language;来按每种语言的值进行分组。 -
我们可以按以下方式排序输出:
SELECT language, COUNT(*) FROM favorites GROUP BY language ORDER BY COUNT(*) DESC;。 -
我们甚至可以在查询中创建别名,就像变量一样:
SELECT language, COUNT(*) AS n FROM favorites GROUP BY language ORDER BY n DESC;。 -
最后,我们可以限制输出为 1 个或多个值:
SELECT language, COUNT(*) AS n FROM favorites GROUP BY language ORDER BY n DESC LIMIT 1;。
INSERT
-
我们还可以使用
INSERT INTO table (column...) VALUES(value, ...);的形式将数据INSERT到 SQL 数据库中。 -
我们可以执行
INSERT INTO favorites (language, problem) VALUES ('SQL', 'Fiftyville');。 -
您可以通过执行
SELECT * FROM favorites;来验证此收藏夹的增加。
DELETE
DELETE允许您删除数据的一部分。例如,您可以DELETE FROM favorites WHERE Timestamp IS NULL;。这将删除任何Timestamp为NULL的记录。
UPDATE
-
我们还可以使用
UPDATE命令来更新数据。 -
例如,您可以执行
UPDATE favorites SET language = 'SQL', problem = 'Fiftyville';。这将覆盖所有之前将 C 和 Scratch 作为首选编程语言的语句。 -
注意,这些查询具有巨大的威力。因此,在实际环境中,您应该考虑谁有权限执行某些命令,以及您是否有可用的备份!
IMDb
-
我们可以想象一个我们可能想要创建的数据库,用于分类各种电视节目。我们可以创建一个包含诸如
title、star、star、star、star以及更多星星的电子表格。这种方法的缺点是它有很多浪费的空间。有些节目可能只有一个明星。而有些节目可能有几十个。 -
我们可以将数据库分成多个工作表。我们可以有一个
shows工作表、一个stars工作表和一个people工作表。在people工作表中,每个人可以有一个唯一的id。在shows工作表中,每个节目也可以有一个唯一的id。在名为stars的第三个工作表中,我们可以通过拥有show_id和person_id来关联每个节目对应的人员。虽然这是一个改进,但这并不是一个理想的数据库。 -
IMDb 提供了人员、节目、编剧、明星、类型和评分的数据库。这些表彼此之间如下相关:
![imdb relationships 代表各种 SQL 表的六个盒子,箭头指向每个盒子,显示它们彼此之间的多对多关系]()
-
下载完
shows.db后,你可以在终端窗口中执行sqlite3 shows.db。 -
让我们聚焦于数据库中名为
shows和ratings的两个表之间的关系。这两个表之间的关系可以如下表示:![imdb shows and ratings 一个称为 shows 的盒子和一个称为 ratings 的盒子]()
-
为了说明这些表之间的关系,我们可以执行以下命令:
SELECT * FROM ratings LIMIT 10;。检查输出后,我们可以执行SELECT * FROM shows LIMIT 10;。 -
检查
shows和rating,我们可以看到它们之间存在一对一的关系:一个节目有一个评分。 -
要了解数据库,在执行
.schema后,您不仅会发现每个表,还会发现每个字段中的各个字段。 -
更具体地说,您可以通过执行
.schema shows来了解shows内部的字段。您也可以执行.schema ratings来查看ratings内部的字段。 -
如您所见,
show_id存在于所有表中。在shows表中,它简单地被称为id。这个在所有字段之间都存在的公共字段被称为键。主键用于在表中标识唯一记录。外键用于通过指向另一个表中的主键来建立表之间的关系。您可以在ratings模式的方案中看到show_id是一个外键,它引用了shows中的id。 -
通过将数据存储在上述关系型数据库中,数据可以更有效地存储。
-
在 sqlite 中,我们有五种数据类型,包括:
BLOB -- binary large objects that are groups of ones and zeros INTEGER -- an integer NUMERIC -- for numbers that are formatted specially like dates REAL -- like a float TEXT -- for strings and the like -
此外,可以将列设置为添加特殊约束:
NOT NULL UNIQUE -
我们可以进一步利用这些数据来理解这些关系。执行
SELECT * FROM ratings;。这里有很多评分! -
我们可以通过执行
SELECT show_id FROM ratings WHERE rating >= 6.0 LIMIT 10;进一步限制这些数据。从这个查询中,你可以看到有 10 个节目被展示。然而,我们不知道每个show_id代表什么节目。 -
你可以通过执行
SELECT * FROM shows WHERE id = 626124;来发现这些节目是什么。 -
通过执行以下命令,我们可以使查询更高效:
SELECT title FROM shows WHERE id IN ( SELECT show_id FROM ratings WHERE rating >= 6.0 LIMIT 10 )注意,这个查询嵌套了两个查询。内部查询被外部查询使用。
JOINs
-
我们正在从
shows和ratings表中获取数据。注意shows和ratings都有一个共同的id。 -
我们如何临时合并表?可以使用
JOIN命令将表连接在一起。 -
执行以下命令:
SELECT * FROM shows JOIN ratings on shows.id = ratings.show_id WHERE rating >= 6.0 LIMIT 10;注意,这会产生一个比我们之前看到的更宽的表。
-
在之前的查询中已经展示了这些键之间的一对一关系,让我们来检查一些一对多关系。关注
genres表,执行以下命令:SELECT * FROM genres LIMIT 10;注意,这为我们提供了对原始数据的感觉。你可能注意到一个节目有三个值。这是一个一对多关系。
-
通过输入
.schema genres,我们可以了解更多关于genres表的信息。 -
执行以下命令来了解更多关于数据库中各种喜剧的信息:
SELECT title FROM shows WHERE id IN ( SELECT show_id FROM genres WHERE genre = 'Comedy' LIMIT 10 );注意,这产生了一个包括猫怪在内的喜剧列表。
-
要了解更多关于 Catweazle 的信息,可以通过连接各种表来实现:
SELECT * FROM shows JOIN genres ON shows.id = genres.show_id WHERE id = 63881;注意,这会产生一个临时表。拥有一个重复的表是可以的。
-
与一对一和一对多关系相比,可能存在多对多关系。
-
通过执行以下命令,我们可以了解更多关于节目办公室及其演员的信息:
SELECT name FROM people WHERE id IN (SELECT person_id FROM stars WHERE show_id = (SELECT id FROM shows WHERE title = 'The Office' AND year = 2005));注意,这会产生一个包含通过嵌套查询的各种明星名字的表。
-
我们找到史蒂夫·卡瑞尔主演的所有节目:
SELECT title FROM shows WHERE id IN (SELECT show_id FROM stars WHERE person_id = (SELECT id FROM people WHERE name = 'Steve Carell'));这会产生一个史蒂夫·卡瑞尔主演的节目标题列表。
-
这也可以用这种方式表达:
SELECT title FROM shows, stars, people WHERE shows.id = stars.show_id AND people.id = stars.person_id AND name = 'Steve Carell'; -
可以使用通配符
%运算符来查找所有名字以Steve C开头的人,可以使用以下语法:SELECT * FROM people WHERE name LIKE 'Steve C%';。
索引
-
虽然关系数据库比使用
CSV文件具有更快和更健壮的能力,但可以使用索引在表中优化数据。 -
索引可以被用来加速我们的查询。
-
我们可以通过在
sqlite3中执行.timer on来跟踪查询的速度。 -
要了解索引如何加速查询,运行以下命令:
SELECT * FROM shows WHERE title = 'The Office';注意查询执行后显示的时间。 -
然后,我们可以使用以下语法创建索引:
CREATE INDEX title_index ON shows (title);。这告诉sqlite3创建一个索引并对此列title进行一些特殊的底层优化。 -
这将创建一个名为B 树的数据结构,其外观类似于二叉树。然而,与二叉树不同,可以有超过两个子节点。
![b tree 从顶部有一个节点,该节点有四个子节点,下面有三个子节点来自一个节点,两个来自另一个节点,另外两个来自另一个节点,三个来自另一个节点]()
-
此外,我们可以创建索引如下:
CREATE INDEX name_index ON people (name); CREATE INDEX person_index ON stars (person_id); -
运行查询后,你会注意到查询运行得更快!
SELECT title FROM shows WHERE id IN (SELECT show_id FROM stars WHERE person_id = (SELECT id FROM people WHERE name = 'Steve Carell')); -
不幸的是,索引所有列将导致使用更多的存储空间。因此,在提高速度和存储空间之间有一个权衡。
在 Python 中使用 SQL
-
为了帮助在这个课程中处理 SQL,可以在你的代码中使用 CS50 库如下:
from cs50 import SQL -
与之前对 CS50 库的使用类似,这个库将帮助你在 Python 代码中利用 SQL 的复杂步骤。
-
你可以在文档中了解更多关于 CS50 库的 SQL 功能。
-
使用我们对 SQL 的新知识,我们现在可以利用 Python。
-
按照以下方式修改你的
favorites.py代码:# Searches database popularity of a problem from cs50 import SQL # Open database db = SQL("sqlite:///favorites.db") # Prompt user for favorite favorite = input("Favorite: ") # Search for title rows = db.execute("SELECT COUNT(*) AS n FROM favorites WHERE language = ?", favorite) # Get first (and only) row row = rows[0] # Print popularity print(row["n"])注意,
db = SQL("sqlite:///favorites.db")为 Python 提供了数据库文件的位置。然后,以rows开头的行执行使用db.execute的 SQL 命令。确实,这个命令将引号内的语法传递给db.execute函数。我们可以使用这种语法发出任何 SQL 命令。此外,注意rows作为字典列表返回。在这种情况下,只有一个结果,一行,作为字典返回到rows列表中。
竞争条件
-
有时使用 SQL 可能会导致一些问题。
-
你可以想象一个场景,多个用户可能同时访问同一个数据库并执行命令。
-
这可能导致代码被其他人的行为中断,从而造成数据丢失。
-
内置的 SQL 功能如
BEGIN TRANSACTION、COMMIT和ROLLBACK有助于避免一些这些竞争条件问题。
SQL 注入攻击
-
现在,仍然考虑上面的代码,你可能想知道上面的
?问号的作用。在 SQL 的现实中应用中可能出现的一个问题是所谓的注入攻击。注入攻击是指恶意行为者可以输入恶意的 SQL 代码。 -
例如,考虑以下登录界面:
![harvard key login screen 哈佛密钥登录界面,包含用户名和密码字段]()
-
如果在我们的代码中没有适当的安全措施,恶意行为者可以运行恶意代码。考虑以下:
rows = db.execute("SELECT COUNT(*) FROM users WHERE username = ? AND password = ?", username, password)注意,因为
?符号的位置,在查询盲目接受之前,可以在favorite上运行验证。 -
你永远不希望在查询中使用上述格式化的字符串或盲目信任用户的输入。
-
利用 CS50 库,该库将净化并移除任何潜在的恶意字符。
总结
在本节课中,你学习了更多与 Python 相关的语法。此外,你学习了如何将这一知识整合到以平面文件和关系数据库形式存在的数据中。最后,你了解了SQL。具体来说,我们讨论了…
-
平面文件数据库
-
关系数据库
-
如
SELECT、CREATE、INSERT、DELETE和UPDATE等 SQL 命令。 -
主键和外键
-
JOINs -
索引
-
在 Python 中使用 SQL
-
竞态条件
-
SQL 注入攻击
次次见!
第八讲
-
欢迎光临!
-
互联网
-
路由器
-
DNS
-
DHCP
-
HTTPS
-
HTML
-
正则表达式
-
CSS
-
框架
-
JavaScript
-
总结
欢迎光临!
- 在前几周,我们向您介绍了 Python,这是一种高级编程语言,它使用了我们在 C 语言中学到的相同构建块。今天,我们将进一步扩展这些构建块,在 HTML、CSS 和 JavaScript 中。
互联网
-
互联网是我们所有人都使用的技术。
-
使用我们前几周学到的技能,我们可以构建自己的网页和应用。
-
ARPANET 将互联网上的第一个节点连接在一起。
-
两点之间的点可以被认为是 路由器。
路由器
-
为了将数据从一个地方路由到另一个地方,我们需要做出 路由决策。也就是说,有人需要编程数据如何从 A 点传输到 B 点。
-
你可以想象数据可以从 A 点到 B 点有多个路径,当路由器拥堵时,数据可以通过另一条路径流动。数据 数据包 从一个路由器传输到另一个路由器,从一个计算机传输到另一个计算机。
-
TCP/IP 是两种协议,允许计算机在互联网上相互传输数据。
-
IP 或 互联网协议 是一种计算机可以在互联网上相互识别的方式。每台计算机在世界上都有一个唯一的地址。地址的形式如下:
#.#.#.# -
数字范围从
0到255。IP 地址是 32 位,这意味着这些地址可以容纳超过 40 亿个地址。较新的 IP 地址版本,采用 128 位,可以容纳更多的计算机! -
在现实世界中,服务器为我们做了很多工作。
-
数据包的结构如下:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |Version| IHL |Type of Service| Total Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Identification |Flags| Fragment Offset | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Time to Live | Protocol | Header Checksum | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Destination Address | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -
数据包是标准化的。源地址和目标地址包含在每个数据包中。
-
TCP,或传输控制协议,有助于跟踪发送的数据包顺序。
-
此外,TCP 用于区分不同的网络服务。例如,
80用于表示 HTTP,443用于表示 HTTPS。这些数字是 端口号。 -
当信息从一个位置发送到另一个位置时,会发送源 IP 地址、目标 IP 地址和 TCP 端口号。
-
这些协议也被用来将大文件分成多个部分或数据包。例如,一张大猫的照片可以分成多个数据包发送。当一个数据包丢失时,TCP/IP 可以从原始服务器再次请求丢失的数据包。
-
TCP 将在所有数据都已传输和接收后进行确认。
DNS
-
如果你需要记住一个 IP 地址来访问一个网站,那将会非常麻烦。
-
DNS,或 域名系统,是互联网上一组服务器,用于将像 harvard.edu 这样的网站地址路由到特定的 IP 地址。
-
DNS 简单来说是一个将特定的、完全限定的域名与特定的 IP 地址链接起来的表或数据库。
DHCP
-
DHCP 是一个确定你的设备 IP 地址的协议。
-
此外,此协议定义了你的设备使用的默认网关和名称服务器。
HTTPS
-
HTTP 或 超文本传输协议 是开发者用来通过数据从一个地方传输到另一个地方来构建强大和有用事物的应用层协议。HTTPS 是此协议的安全版本。
-
当你看到一个地址如
https://www.example.com时,你实际上是在隐式地访问该地址,并在其末尾有一个/。 -
路径 是在斜杠之后存在的部分。例如,
https://www.example.com/folder/file.html访问example.com并浏览到folder目录,然后访问名为file.html的文件。 -
.com被称为 顶级域名,用于表示与该地址关联的位置或组织类型。 -
在此地址中的
https是用来连接该网页地址的协议。通过协议,我们指的是 HTTP 使用GET或POST请求 从服务器获取信息。例如,你可以启动 Google Chrome,右键点击,并点击inspect。当你打开开发者工具并访问Network,选择Preserve log,你会看到Request Headers。你会看到GET的提及。这在其他浏览器中也是可能的,使用稍微不同的方法。 -
例如,在发出 GET 请求时,你的电脑可能向服务器发送以下内容:
GET / HTTP/2 Host: www.harvard.edu注意,这是通过 HTTP 请求在 www.harvard.edu 上提供的内容。
-
通常,在向服务器发出请求后,你将在
Response Headers中收到以下内容:HTTP/2 200 Content-Type: text/html -
检查这些日志的方法可能比必要的要复杂一些。你可以在 cs50.dev 上分析 HTTP 协议的工作。例如,在你的终端窗口中输入以下内容:
curl -I https://www.harvard.edu/注意,此命令的输出返回了服务器响应的所有头部值。
-
通过你的网页浏览器的开发者工具,你可以看到浏览上述网站时所有的 HTTP 请求。
-
此外,在你的终端窗口中执行以下命令:
curl -I https://harvard.edu注意,你会看到一个
301响应,为浏览器提供了一个指向正确网站的提示。 -
类似地,在你的终端窗口中执行以下命令:
curl -I http://www.harvard.edu/注意,
https中的s已被移除。服务器响应将显示响应为301,这意味着网站已永久迁移。 -
与
301类似,404状态码意味着指定的 URL 未找到。还有许多其他的响应代码,例如:200 OK 301 Moved Permanently 302 Found 304 Not Modified 307 Temporary Redirect 401 Unauthorized 403 Forbidden 404 Not Found 418 I'm a Teapot 500 Internal Server Error 503 Service Unavailable -
值得注意的是,当
500错误涉及到你创建的产品或应用程序时,这总是作为开发者的你的责任。这将在下周的问题集中尤为重要,也许对你的最终项目也是如此!
HTML
-
HTML 或 超文本标记语言 由 标签 组成,每个标签可能有一些 属性 来描述它。
-
在你的终端中,输入
code hello.html并编写如下代码:<!DOCTYPE html> <!-- Demonstrates HTML --> <html lang="en"> <head> <title>hello, title</title> </head> <body> hello, body </body> </html>注意到
html标签既打开了又关闭了这个文件。此外,注意lang属性,它修改了html标签的行为。还要注意,既有head标签也有body标签。缩进不是必需的,但确实暗示了一个层次结构。 -
你可以通过输入
http-server来提供你的代码。现在,提供的内容可以通过一个非常长的 URL 访问。如果你点击它,你可以访问由你自己的代码生成的网站。 -
当你访问这个 URL 时,注意文件名
hello.html出现在这个 URL 的末尾。此外,根据 URL,注意服务器是通过端口 8080 提供服务的。 -
标签的层次结构可以表示如下:
![DOM html 代码旁边显示层次结构,显示父节点和子节点]()
-
了解这个层次结构将在我们学习 JavaScript 时非常有用。
-
浏览器将自上而下、从左到右读取你的 HTML 文件。
-
由于在 HTML 中空白和缩进实际上被忽略,你需要使用
<p>段落标签来打开和关闭一个段落。考虑以下:<!DOCTYPE html> <!-- Demonstrates paragraphs --> <html lang="en"> <head> <title>paragraphs</title> </head> <body> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus convallis scelerisque quam, vel hendrerit lectus viverra eu. Praesent posuere eget lectus ut faucibus. Etiam eu velit laoreet, gravida lorem in, viverra est. Cras ut purus neque. In porttitor non lorem id lobortis. Mauris gravida metus libero, quis maximus dui porta at. Donec lacinia felis consectetur venenatis scelerisque. Nulla eu nisl sollicitudin, varius velit sit amet, vehicula erat. Curabitur sollicitudin felis sit amet orci mattis, a tempus nulla pulvinar. Aliquam erat volutpat. </p> <p> Mauris ut dui in eros semper hendrerit. Morbi vel elit mi. Sed sit amet ex non quam dignissim dignissim et vel arcu. Pellentesque eget elementum orci. Morbi ac cursus ex. Pellentesque quis turpis blandit orci dapibus semper sed non nunc. Nulla et dolor nec lacus finibus volutpat. Sed non lorem diam. Donec feugiat interdum interdum. Vivamus et justo in enim blandit fermentum vel at elit. Phasellus eu ante vitae ligula varius aliquet. Etiam id posuere nibh. </p> <p> Aenean venenatis convallis ante a rhoncus. Nullam in metus vel diam vehicula tincidunt. Donec lacinia metus sem, sit amet egestas elit blandit sit amet. Nunc egestas sem quis nisl mattis semper. Pellentesque ut magna congue lorem eleifend sodales. Donec tortor tortor, aliquam vitae mollis sed, interdum ut lectus. Mauris non purus quis ipsum lacinia tincidunt. </p> <p> Integer at justo lacinia libero blandit aliquam ut ut dui. Quisque tincidunt facilisis venenatis. Nullam dictum odio quis lorem luctus, vel malesuada dolor luctus. Aenean placerat faucibus enim a facilisis. Maecenas eleifend quis massa sed eleifend. Ut ultricies, dui ac vulputate hendrerit, ex metus iaculis diam, vitae fermentum libero dui et ante. Phasellus suscipit, arcu ut consequat sagittis, massa urna accumsan massa, eu aliquet nulla lorem vitae arcu. Pellentesque rutrum felis et metus porta semper. Nam ac consectetur mauris. </p> <p> Suspendisse rutrum vestibulum odio, sed venenatis purus condimentum sed. Morbi ornare tincidunt augue eu auctor. Vivamus sagittis ac lectus at aliquet. Nulla urna mauris, interdum non nibh in, vehicula porta enim. Donec et posuere sapien. Pellentesque ultrices scelerisque ipsum, vel fermentum nibh tincidunt et. Proin gravida porta ipsum nec scelerisque. Vestibulum fringilla erat at turpis laoreet, nec hendrerit nisi scelerisque. </p> <p> Sed quis malesuada mi. Nam id purus quis augue sagittis pharetra. Nulla facilisi. Maecenas vel fringilla ante. Cras tristique, arcu sit amet blandit auctor, urna elit ultricies lacus, a malesuada eros dui id massa. Aliquam sem odio, pretium vel cursus eget, scelerisque at urna. Vestibulum posuere a turpis consectetur consectetur. Cras consequat, risus quis tempor egestas, nulla ipsum ornare erat, nec accumsan nibh lorem nec risus. Integer at iaculis lacus. Integer congue nunc massa, quis molestie felis pellentesque vestibulum. Nulla odio tortor, aliquam nec quam in, ornare aliquet sapien. </p> </body> </html>注意到段落从
<p>标签开始,并以</p>标签结束。 -
HTML 允许表示标题:
<!DOCTYPE html> <!-- Demonstrates headings (for chapters, sections, subsections, etc.) --> <html lang="en"> <head> <title>headings</title> </head> <body> <h1>One</h1> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus convallis scelerisque quam, vel hendrerit lectus viverra eu. Praesent posuere eget lectus ut faucibus. Etiam eu velit laoreet, gravida lorem in, viverra est. Cras ut purus neque. In porttitor non lorem id lobortis. Mauris gravida metus libero, quis maximus dui porta at. Donec lacinia felis consectetur venenatis scelerisque. Nulla eu nisl sollicitudin, varius velit sit amet, vehicula erat. Curabitur sollicitudin felis sit amet orci mattis, a tempus nulla pulvinar. Aliquam erat volutpat. </p> <h2>Two</h2> <p> Mauris ut dui in eros semper hendrerit. Morbi vel elit mi. Sed sit amet ex non quam dignissim dignissim et vel arcu. Pellentesque eget elementum orci. Morbi ac cursus ex. Pellentesque quis turpis blandit orci dapibus semper sed non nunc. Nulla et dolor nec lacus finibus volutpat. Sed non lorem diam. Donec feugiat interdum interdum. Vivamus et justo in enim blandit fermentum vel at elit. Phasellus eu ante vitae ligula varius aliquet. Etiam id posuere nibh. </p> <h3>Three</h3> <p> Aenean venenatis convallis ante a rhoncus. Nullam in metus vel diam vehicula tincidunt. Donec lacinia metus sem, sit amet egestas elit blandit sit amet. Nunc egestas sem quis nisl mattis semper. Pellentesque ut magna congue lorem eleifend sodales. Donec tortor tortor, aliquam vitae mollis sed, interdum ut lectus. Mauris non purus quis ipsum lacinia tincidunt. </p> <h4>Four</h4> <p> Integer at justo lacinia libero blandit aliquam ut ut dui. Quisque tincidunt facilisis venenatis. Nullam dictum odio quis lorem luctus, vel malesuada dolor luctus. Aenean placerat faucibus enim a facilisis. Maecenas eleifend quis massa sed eleifend. Ut ultricies, dui ac vulputate hendrerit, ex metus iaculis diam, vitae fermentum libero dui et ante. Phasellus suscipit, arcu ut consequat sagittis, massa urna accumsan massa, eu aliquet nulla lorem vitae arcu. Pellentesque rutrum felis et metus porta semper. Nam ac consectetur mauris. </p> <h5>Five</h5> <p> Suspendisse rutrum vestibulum odio, sed venenatis purus condimentum sed. Morbi ornare tincidunt augue eu auctor. Vivamus sagittis ac lectus at aliquet. Nulla urna mauris, interdum non nibh in, vehicula porta enim. Donec et posuere sapien. Pellentesque ultrices scelerisque ipsum, vel fermentum nibh tincidunt et. Proin gravida porta ipsum nec scelerisque. Vestibulum fringilla erat at turpis laoreet, nec hendrerit nisi scelerisque. </p> <h6>Six</h6> <p> Sed quis malesuada mi. Nam id purus quis augue sagittis pharetra. Nulla facilisi. Maecenas vel fringilla ante. Cras tristique, arcu sit amet blandit auctor, urna elit ultricies lacus, a malesuada eros dui id massa. Aliquam sem odio, pretium vel cursus eget, scelerisque at urna. Vestibulum posuere a turpis consectetur consectetur. Cras consequat, risus quis tempor egestas, nulla ipsum ornare erat, nec accumsan nibh lorem nec risus. Integer at iaculis lacus. Integer congue nunc massa, quis molestie felis pellentesque vestibulum. Nulla odio tortor, aliquam nec quam in, ornare aliquet sapien. </p> </body> </html>注意到
<h1>、<h2>和<h3>表示不同的标题级别。 -
我们也可以在 HTML 中创建无序列表:
<!DOCTYPE html> <!-- Demonstrates (ordered) lists --> <html lang="en"> <head> <title>list</title> </head> <body> <ul> <li>foo</li> <li>bar</li> <li>baz</li> </ul> </body> </html>注意到
<ul>标签创建了一个包含三个项目的无序列表。 -
我们也可以在 HTML 中创建有序列表:
<!DOCTYPE html> <!-- Demonstrates (ordered) lists --> <html lang="en"> <head> <title>list</title> </head> <body> <ol> <li>foo</li> <li>bar</li> <li>baz</li> </ol> </body> </html>注意到
<ol>标签创建了一个包含三个项目的有序列表。 -
我们也可以在 HTML 中创建一个表格:
<!DOCTYPE html> <!-- Demonstrates table --> <html lang="en"> <head> <title>table</title> </head> <body> <table> <tr> <td>1</td> <td>2</td> <td>3</td> </tr> <tr> <td>4</td> <td>5</td> <td>6</td> </tr> <tr> <td>7</td> <td>8</td> <td>9</td> </tr> <tr> <td>*</td> <td>0</td> <td>#</td> </tr> </table> </body> </html>表格也有打开和关闭每个元素的标签。此外,注意 HTML 中注释的语法。
-
图像也可以在 HTML 中使用:
<!DOCTYPE html> <!-- Demonstrates image --> <html lang="en"> <head> <title>image</title> </head> <body> <img alt="photo of bridge" src="bridge.png"> </body> </html>注意到
src="bridge.png"指示了图像文件可以找到的路径。 -
视频也可以包含在 HTML 中:
<!DOCTYPE html> <!-- Demonstrates video --> <html lang="en"> <head> <title>video</title> </head> <body> <video controls muted> <source src="video.mp4" type="video/mp4"> </video> </body> </html>注意到
type属性指定这是一个mp4类型的视频。此外,注意controls和muted是如何传递给video的。 -
你也可以在各个网页之间建立链接:
<!DOCTYPE html> <!-- Demonstrates link --> <html lang="en"> <head> <title>link</title> </head> <body> Visit <a href="https://www.harvard.edu">Harvard</a>. </body> </html>注意到
<a>或 锚点 标签用于使Harvard成为可链接的文本。 -
你也可以创建类似于 Google 搜索的表单:
<!DOCTYPE html> <!-- Demonstrates form --> <html lang="en"> <head> <title>search</title> </head> <body> <form action="https://www.google.com/search" method="get"> <input name="q" type="search"> <input type="submit" value="Google Search"> </form> </body> </html>注意到
form标签打开并提供它将采取的action属性。input字段被包含在内,传递名称q和类型为search。 -
我们可以如下改进这个搜索:
<!DOCTYPE html> <!-- Demonstrates additional form attributes --> <html lang="en"> <head> <title>search</title> </head> <body> <form action="https://www.google.com/search" method="get"> <input autocomplete="off" autofocus name="q" placeholder="Query" type="search"> <button>Google Search</button> </form> </body> </html>注意到
autocomplete被设置为off。autofocus被启用。 -
我们已经看到了许多你可以添加到网站上的 HTML 元素中的一部分。如果你有关于要添加到网站上的想法(我们还没有看到,比如按钮、音频文件等),尝试在 Google 上搜索“X in HTML”以找到正确的语法!同样,你可以使用 cs50.ai 来帮助你发现更多的 HTML 功能!
正则表达式
-
正则表达式 或 regexes 是一种确保用户提供的数据符合特定格式的手段。
-
我们可以自己实现一个利用正则表达式的注册页面,如下所示:
<!DOCTYPE html> <!-- Demonstrates type="email" --> <html lang="en"> <head> <title>register</title> </head> <body> <form> <input autocomplete="off" autofocus name="email" placeholder="Email" type="email"> <button>Register</button> </form> </body> </html>注意,
input标签包含属性指定这是email类型的。浏览器知道要双重检查输入是否为电子邮件地址。 -
虽然浏览器使用这些内置属性来检查电子邮件地址,但我们可以添加一个
pattern属性来确保只有特定的数据出现在电子邮件地址中:<!DOCTYPE html> <!-- Demonstrates pattern attribute --> <html lang="en"> <head> <title>register</title> </head> <body> <form> <input autocomplete="off" autofocus name="email" pattern=".+@.+\.edu" placeholder="Email" type="email"> <button>Register</button> </form> </body> </html>注意,
pattern属性被传递了一个正则表达式,表示电子邮件地址必须包含一个@符号和一个.edu。 -
您可以从 Mozilla 的文档 中了解更多关于正则表达式的信息。此外,您可以访问 cs50.ai 获取提示。
CSS
-
CSS,或 层叠样式表,是一种标记语言,允许您微调 HTML 文件的审美。 -
CSS 中充满了 属性,它们包括键值对。
-
在您的终端中,键入
code home.html并编写如下代码:<!DOCTYPE html> <!-- Demonstrates inline CSS with P tags --> <html lang="en"> <head> <title>css</title> </head> <body> <p style="font-size: large; text-align: center;"> John Harvard </p> <p style="font-size: medium; text-align: center;"> Welcome to my home page! </p> <p style="font-size: small; text-align: center;"> Copyright © John Harvard </p> </body> </html>注意,一些
style属性被提供给<p>标签。font-size被设置为large、medium或small。然后text-align被设置为居中。 -
虽然正确,但上述设计并不理想。我们可以通过修改代码来去除冗余,如下所示:
<!DOCTYPE html> <!-- Removes outer DIV --> <html lang="en"> <head> <title>css</title> </head> <body style="text-align: center"> <div style="font-size: large"> John Harvard </div> <div style="font-size: medium"> Welcome to my home page! </div> <div style="font-size: small"> Copyright © John Harvard </div> </body> </html>注意,
<div>标签被用来将这个 HTML 文件划分为特定的区域。text-align: center被应用于整个 HTML 的主体部分。因为body内部的所有内容都是body的子元素,所以center属性会级联到这些子元素。 -
结果表明,HTML 中包含了一些新的语义标签。我们可以如下修改我们的代码:
<!DOCTYPE html> <!-- Uses semantic tags instead of DIVs --> <html lang="en"> <head> <title>css</title> </head> <body style="text-align: center"> <header style="font-size: large"> John Harvard </header> <main style="font-size: medium"> Welcome to my home page! </main> <footer style="font-size: small"> Copyright © John Harvard </footer> </body> </html>注意,
header和footer都被分配了不同的样式。 -
将样式和信息都放在同一个位置的做法并不好。我们可以将样式元素移动到文件顶部,如下所示:
<!-- Demonstrates class selectors --> <html lang="en"> <head> <style> .centered { text-align: center; } .large { font-size: large; } .medium { font-size: medium; } .small { font-size: small; } </style> <title>css</title> </head> <body class="centered"> <header class="large"> John Harvard </header> <main class="medium"> Welcome to my home page! </main> <footer class="small"> Copyright © John Harvard </footer> </body> </html>注意,所有的样式标签都被放置在
head部分的style标签包装器中。此外,注意我们已经为我们的元素分配了名为centered、large、medium和small的 类,并且我们通过在名称前放置一个点来选择这些类,例如.centered。 -
结果表明,我们可以将所有的样式代码移动到一个特殊的文件中,称为 CSS 文件。我们可以创建一个名为
style.css的文件,并将我们的类粘贴在那里:.centered { text-align: center; } .large { font-size: large; } .medium { font-size: medium; } .small { font-size: small; }注意,这正是出现在我们的 HTML 文件中的内容。
-
然后,我们可以告诉浏览器在哪里找到这个 HTML 文件的 CSS:
<!DOCTYPE html> <!-- Demonstrates external stylesheets --> <html lang="en"> <head> <link href="style.css" rel="stylesheet"> <title>css</title> </head> <body class="centered"> <header class="large"> John Harvard </header> <main class="medium"> Welcome to my home page! </main> <footer class="small"> Copyright © John Harvard </footer> </body> </html>注意,
style.css被链接到这个 HTML 文件作为样式表,告诉浏览器在哪里找到我们创建的样式。
框架
-
与我们可以在 Python 中利用的第三方库类似,还有称为 框架 的第三方库,我们可以利用这些框架与我们的 HTML 文件一起使用。
-
Bootstrap是我们可以使用来美化我们的 HTML 并轻松完善设计元素的框架之一,这样我们的页面就更容易阅读。
-
通过在 HTML 文件的
head部分添加以下link标签,可以使用 Bootstrap:<head> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <title>bootstrap</title> </head> -
考虑以下 HTML 代码:
<!DOCTYPE html> <!-- Demonstrates table --> <html lang="en"> <head> <title>phonebook</title> </head> <body> <table> <thead> <tr> <th>Name</th> <th>Number</th> </tr> </thead> <tbody> <tr> <td>Carter</td> <td>+1-617-495-1000</td> </tr> <tr> <td>David</td> <td>+1-617-495-1000</td> </tr> <tr> <td>John</td> <td>+1-949-468-2750</td> </tr> </tbody> </table> </body> </html>注意,当查看这个页面的服务版本时,它相当简单。
-
现在考虑以下实现 Bootstrap 使用的 HTML 代码:
<!DOCTYPE html> <!-- Demonstrates table with Bootstrap --> <html lang="en"> <head> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <title>phonebook</title> </head> <body> <table class="table"> <thead> <tr> <th scope="col">Name</th> <th scope="col">Number</th> </tr> </thead> <tbody> <tr> <td>Carter</td> <td>+1-617-495-1000</td> </tr> <tr> <td>David</td> <td>+1-949-468-2750</td> </tr> </tbody> </table> </body> </html>注意,现在这个网站看起来多么漂亮。
-
类似地,考虑以下我们之前创建的搜索页面的扩展:
<!DOCTYPE html> <!-- Demonstrates layout with Bootstrap --> <html lang="en"> <head> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <title>search</title> </head> <body> <div class="container-fluid"> <ul class="m-3 nav"> <li class="nav-item"> <a class="nav-link text-dark" href="https://about.google/">About</a> </li> <li class="nav-item"> <a class="nav-link text-dark" href="https://store.google.com/">Store</a> </li> <li class="nav-item ms-auto"> <a class="nav-link text-dark" href="https://www.google.com/gmail/">Gmail</a> </li> <li class="nav-item"> <a class="nav-link text-dark" href="https://www.google.com/imghp">Images</a> </li> <li class="nav-item"> <a class="nav-link text-dark" href="https://www.google.com/intl/en/about/products"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-grid-3x3-gap-fill" viewBox="0 0 16 16"> <path d="M1 2a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V2zM1 7a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1V7zM1 12a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-2zm5 0a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-2z"/> </svg> </a> </li> <li class="nav-item"> <a class="btn btn-primary" href="https://accounts.google.com/ServiceLogin" role="button">Sign in</a> </li> </ul> <div class="text-center"> <!-- https://knowyourmeme.com/memes/happy-cat --> <img alt="Happy Cat" class="img-fluid w-25" src="cat.gif"> <form action="https://www.google.com/search" class="mt-4" method="get"> <input autocomplete="off" autofocus class="form-control form-control-lg mb-4 mx-auto w-50" name="q" placeholder="Query" type="search"> <button class="btn btn-light">Google Search</button> <button class="btn btn-light" name="btnI">I'm Feeling Lucky</button> </form> </div> </div> </body> </html>这个版本的页面非常风格化,多亏了 Bootstrap。
-
你可以在Bootstrap 文档中了解更多相关信息。
JavaScript
-
JavaScript 是另一种允许在网页中进行交互的编程语言。
-
考虑以下
hello.html的实现,它包含了 JavaScript 和 HTML:<!DOCTYPE html> <!-- Demonstrates onsubmit --> <html lang="en"> <head> <script> function greet() { alert('hello, ' + document.querySelector('#name').value); } </script> <title>hello</title> </head> <body> <form onsubmit="greet(); return false;"> <input autocomplete="off" autofocus id="name" placeholder="Name" type="text"> <input type="submit"> </form> </body> </html>注意,这个表单使用了一个
onsubmit属性来触发文件顶部的script。该脚本使用alert创建一个弹出警告。#name.value指向页面上的文本框,并获取用户输入的值。 -
通常,将
onsubmit和 JavaScript 混合使用被认为是不良的设计。我们可以将我们的代码改进如下:<!DOCTYPE html> <!-- Demonstrates DOMContentLoaded --> <html lang="en"> <head> <script> document.addEventListener('DOMContentLoaded', function() { document.querySelector('form').addEventListener('submit', function(e) { alert('hello, ' + document.querySelector('#name').value); e.preventDefault(); }); }); </script> <title>hello</title> </head> <body> <form> <input autocomplete="off" autofocus id="name" placeholder="Name" type="text"> <input type="submit"> </form> </body> </html>注意,这个版本的代码创建了一个
addEventListener来监听表单submit事件的触发。注意DOMContentLoaded确保在执行 JavaScript 之前整个页面已经加载完成。 -
我们可以将此代码改进如下:
<!DOCTYPE html> <!-- Demonstrates keyup and template literals --> <html lang="en"> <head> <script> document.addEventListener('DOMContentLoaded', function() { let input = document.querySelector('input'); input.addEventListener('keyup', function(event) { let name = document.querySelector('p'); if (input.value) { name.innerHTML = `hello, ${input.value}`; } else { name.innerHTML = 'hello, whoever you are'; } }); }); </script> <title>hello</title> </head> <body> <form> <input autocomplete="off" autofocus placeholder="Name" type="text"> </form> <p></p> </body> </html>注意,当用户输入一个名字时,内存中的 DOM 会动态更新。如果
input中有值,在键盘的keyup事件发生时,DOM 会更新。否则,会显示默认文本。 -
JavaScript 允许你动态地读取和修改加载到内存中的 HTML 文档,这样用户就不需要重新加载来查看更改。
-
考虑以下 HTML 代码:
<!DOCTYPE html> <!-- Demonstrates programmatic changes to style --> <html lang="en"> <head> <title>background</title> </head> <body> <button id="red">R</button> <button id="green">G</button> <button id="blue">B</button> <script> let body = document.querySelector('body'); document.querySelector('#red').addEventListener('click', function() { body.style.backgroundColor = 'red'; }); document.querySelector('#green').addEventListener('click', function() { body.style.backgroundColor = 'green'; }); document.querySelector('#blue').addEventListener('click', function() { body.style.backgroundColor = 'blue'; }); </script> </body> </html>注意,JavaScript 监听特定按钮的点击事件。在点击时,页面上的某些样式属性会发生变化。
body被定义为页面的主体。然后,一个事件监听器等待按钮之一被点击。然后,body.style.backgroundColor被改变。 -
类似地,考虑以下:
<!DOCTYPE html> <html lang="en"> <head> <script> // Toggles visibility of greeting function blink() { let body = document.querySelector('body'); if (body.style.visibility == 'hidden') { body.style.visibility = 'visible'; } else { body.style.visibility = 'hidden'; } } // Blink every 500ms window.setInterval(blink, 500); </script> <title>blink</title> </head> <body> hello, world </body> </html>这个例子在设定的时间间隔闪烁文本。注意,
window.setInterval接受两个参数:一个要调用的函数和函数调用之间的等待期(以毫秒为单位)。 -
考虑以下实现自动完成文本的 JavaScript 代码:
<!DOCTYPE html> <html lang="en"> <head> <title>autocomplete</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="text"> <ul></ul> <script src="large.js"></script> <script> let input = document.querySelector('input'); input.addEventListener('keyup', function(event) { let html = ''; if (input.value) { for (word of WORDS) { if (word.startsWith(input.value)) { html += `<li>${word}</li>`; } } } document.querySelector('ul').innerHTML = html; }); </script> </body> </html>这是一个自动完成的 JavaScript 实现。它从一个名为
large.js的文件中获取数据(此处未展示),该文件是一个单词列表。 -
JavaScript 的功能很多,可以在JavaScript 文档中找到。
总结
在本课中,你学习了如何创建自己的 HTML 文件,为其添加样式,利用第三方框架,以及使用 JavaScript。具体来说,我们讨论了…
-
TCP/IP
-
DNS
-
HTML
-
正则表达式
-
CSS
-
框架
-
JavaScript
下次再见!
第九讲
-
欢迎!
-
http-server
-
Flask
-
表单
-
模板
-
请求方法
-
Frosh IMs
-
Flask 和 SQL
-
Cookies 和 Session
-
购物车
-
显示
-
APIs
-
JSON
-
总结
欢迎光临!
-
在之前的几周中,你已经学习了多种编程语言、技术和策略。
-
事实上,这门课程远不止是 C 语言课程 或 Python 课程,而更多的是一门 编程课程,这样你就可以继续追随未来的趋势。
-
在过去的几周中,你已经学习了 如何学习 编程。
-
今天,我们将从 HTML 和 CSS 转向结合 HTML、CSS、SQL、Python 和 JavaScript,以便你可以创建自己的 Web 应用程序。
-
你可以考虑使用这周学到的技能来创建你的最终项目。
http-server
-
到目前为止,你所看到的 HTML 都是预先编写和静态的。
-
在过去,当你访问一个页面时,浏览器会下载一个 HTML 页面,你能够查看它。这些被认为是 静态 页面,因为在 HTML 中编程的内容正是用户看到的和下载到他们互联网浏览器中的内容。
-
动态页面指的是 Python 和类似语言创建 HTML 的即时能力。因此,你可以拥有由代码根据用户输入或行为在服务器端生成的 Web 页面。
-
你过去使用
http-server来提供你的网页服务。今天,我们将利用一个新的服务器,它可以解析出网址并根据提供的 URL 执行操作。 -
此外,上周,你看到了以下这样的 URL:
https://www.example.com/folder/file.html注意到
file.html是位于example.com的folder文件夹中的一个 HTML 文件。
Flask
-
这周,我们介绍了与 路由(如
https://www.example.com/route?key=value)交互的能力,其中特定的功能可以通过 URL 中提供的键和值在服务器上生成。 -
Flask 是一个第三方库,它允许你使用 Flask 框架或微框架在 Python 中托管 Web 应用程序。
-
你可以在终端窗口中执行
flask run来运行 Flask,在 cs50.dev。 -
要这样做,你需要一个名为
app.py的文件和另一个名为requirements.txt的文件。app.py包含代码,告诉 Flask 如何运行你的 Web 应用程序。requirements.txt包含了运行 Flask 应用程序所需的库列表。 -
这里是
requirements.txt的一个示例:Flask注意到在这个文件中只有
Flask出现。这是因为 Flask 是运行 Flask 应用程序所必需的。 -
这里是一个非常简单的
app.pyFlask 应用程序:# Says hello to world by returning a string of text from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return "hello, world"注意到
/路由简单地返回文本hello, world。 -
我们还可以创建实现 HTML 的代码:
# Says hello to world by returning a string of HTML from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return '<!DOCTYPE html><html lang="en"><head><title>hello</title></head><body>hello, world</body></html>'注意到它不是返回简单的文本,而是提供了 HTML。
-
改进我们的应用程序,我们还可以通过创建一个名为
templates的文件夹并创建一个包含以下代码的index.html文件来根据模板提供 HTML:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> hello, {{ name }} </body> </html>注意双大括号
{{ name }},它是为我们 Flask 服务器稍后提供的某个内容的占位符。 -
然后,在
templates文件夹所在的同一文件夹中,创建一个名为app.py的文件,并添加以下代码:# Uses request.args.get from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): name = request.args.get("name", "world") return render_template("index.html", name=name)注意,这段代码将
app定义为 Flask 应用程序。然后,它定义了app的/路由,返回包含name参数的index.html内容。默认情况下,request.args.get函数将查找用户提供的name。如果没有提供名称,它将默认为world。"@app.route" 通常被称为装饰器。 -
您可以通过在终端窗口中输入
flask run来运行这个网络应用程序。如果 Flask 没有运行,请确保上述每个文件中的语法都正确。此外,如果 Flask 无法运行,请确保您的文件组织如下:/templates index.html app.py requirements.txt -
一旦运行起来,您将被提示点击一个链接。一旦您导航到该网页,请尝试在浏览器 URL 栏的基本 URL 中添加
?name=[Your Name]。
表单
-
在改进我们的程序时,我们知道大多数用户不会在地址栏中输入参数。相反,程序员依赖于用户在网页上填写表单。因此,我们可以按如下方式修改
index.html:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> <form action="/greet" method="get"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Greet</button> </form> </body> </html>注意现在创建了一个表单,它接受用户的姓名,并将其传递给名为
/greet的路由。"autocomplete" 已关闭。此外,包含文本name的placeholder也被包含在内。此外,注意meta标签是如何被用来使网页响应式的。 -
此外,我们还可以按如下方式修改
app.py:# Adds a form, second route from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/greet") def greet(): return render_template("greet.html", name=request.args.get("name", "world"))注意默认路径将显示一个表单,让用户输入他们的姓名。
/greet路由将name传递到该网页。 -
为了完成这个实现,您需要在
templates文件夹中创建一个名为greet.html的模板,如下所示:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> hello, {{ name }} </body> </html>注意,现在这个路由将向用户显示问候语,然后是他们的名字。
模板
-
我们的网页
index.html和greet.html有很多相同的数据。如果允许主体内容独特,但复制相同的布局从页面到页面,岂不是很好? -
首先,创建一个新的模板
layout.html并编写如下代码:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>hello</title> </head> <body> {% block body %}{% endblock %} </body> </html>注意到
{% block body %}{% endblock %}允许从其他 HTML 文件中插入其他代码。 -
然后,按如下方式修改您的
index.html:{% extends "layout.html" %} {% block body %} <form action="/greet" method="get"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Greet</button> </form> {% endblock %}注意到
{% extends "layout.html" %}这行代码告诉服务器从哪里获取此页面的布局。然后,{% block body %}{% endblock %}告诉要插入layout.html的代码。 -
最后,按如下方式修改
greet.html:{% extends "layout.html" %} {% block body %} hello, {{ name }} {% endblock %}注意这段代码更短、更紧凑。
请求方法
-
您可以想象一些场景,在这些场景中不能安全地使用
get,因为用户名和密码会出现在 URL 中。 -
我们可以通过修改
app.py来利用post方法帮助解决这个问题:# Switches to POST from flask import Flask, render_template, request app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") @app.route("/greet", methods=["POST"]) def greet(): return render_template("greet.html", name=request.form.get("name", "world"))注意到在
/greet路由中添加了POST,并且我们使用request.form.get而不是request.args.get。 -
这告诉服务器深入查看虚拟信封,不要在 URL 中暴露
post中的项目。 -
尽管如此,我们可以通过使用单个路由来同时处理
get和post来进一步改进此代码。为此,修改app.py如下:# Uses a single route from flask import Flask, render_template, request app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": return render_template("greet.html", name=request.form.get("name", "world")) return render_template("index.html")注意到
get和post都是在单个路由中完成的。然而,request.method被用来根据用户请求的路由类型正确路由。 -
因此,你可以按照以下方式修改你的
index.html:{% extends "layout.html" %} {% block body %} <form action="/" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Greet</button> </form> {% endblock %}注意到表单的
action已被更改。 -
尽管如此,此代码中仍然存在一个错误。根据我们的新实现,当有人在没有输入名称的情况下填写表单时,会显示没有名称的
Hello,。我们可以通过以下方式编辑app.py来改进我们的代码:# Moves default value to template from flask import Flask, render_template, request app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def index(): if request.method == "POST": return render_template("greet.html", name=request.form.get("name")) return render_template("index.html")注意到
name=request.form.get("name"))已被更改。 -
最后,按照以下方式修改
greet.html:{% extends "layout.html" %} {% block body %} hello, {% if name -%} {{ name }} {%- else -%} world {%- endif %} {% endblock %}注意到
hello, {{ name }}是如何改变以允许在没有识别到名称时输出默认值。 -
由于我们已经更改了许多文件,你可能希望将你的最终代码与我们的最终代码进行比较。
Frosh IMs
-
Frosh IMs 或froshims是一个允许学生注册校内体育活动的网络应用程序。
-
关闭所有与
hello相关的窗口,并在终端窗口中输入mkdir froshims创建一个文件夹。然后,输入cd froshims浏览到这个文件夹。在此文件夹内,通过输入mkdir templates创建一个名为templates的目录。 -
接下来,在
froshims文件夹中,输入code requirements.txt并编写如下代码:Flask如前所述,运行 Flask 应用程序需要 Flask。
-
最后,输入
code app.py并编写如下代码:# Implements a registration form using a select menu, validating sport server-side from flask import Flask, render_template, request app = Flask(__name__) SPORTS = [ "Basketball", "Soccer", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/register", methods=["POST"]) def register(): # Validate submission if not request.form.get("name") or request.form.get("sport") not in SPORTS: return render_template("failure.html") # Confirm registration return render_template("success.html")注意到提供了一个
failure选项,如果name或sport字段填写不正确,将向用户显示错误信息。 -
接下来,通过输入
code templates/index.html在templates文件夹中创建一个名为index.html的文件,并编写如下代码:{% extends "layout.html" %} {% block body %} <h1>Register</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <select name="sport"> <option disabled selected value="">Sport</option> {% for sport in sports %} <option value="{{ sport }}">{{ sport }}</option> {% endfor %} </select> <button type="submit">Register</button> </form> {% endblock %} -
接下来,通过输入
code templates/layout.html创建一个名为layout.html的文件,并编写如下代码:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>froshims</title> </head> <body> {% block body %}{% endblock %} </body> </html> -
第四,在
templates目录下创建一个名为success.html的文件,如下所示:{% extends "layout.html" %} {% block body %} You are registered! {% endblock %} -
最后,在
templates目录下创建一个名为failure.html的文件,如下所示:{% extends "layout.html" %} {% block body %} You are not registered! {% endblock %} -
执行
flask run并检查此阶段的程序。 -
你可以想象我们如何使用单选按钮查看各种注册选项。我们可以通过以下方式改进
index.html:{% extends "layout.html" %} {% block body %} <h1>Register</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> {% for sport in sports %} <input name="sport" type="radio" value="{{ sport }}"> {{ sport }} {% endfor %} <button type="submit">Register</button> </form> {% endblock %}注意到
type已被更改为radio。 -
再次执行
flask run,你可以看到界面现在已更改。 -
你可以想象我们如何接受许多不同注册者的注册。我们可以通过以下方式改进
app.py:# Implements a registration form, storing registrants in a dictionary, with error messages from flask import Flask, redirect, render_template, request app = Flask(__name__) REGISTRANTS = {} SPORTS = [ "Basketball", "Soccer", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/register", methods=["POST"]) def register(): # Validate name name = request.form.get("name") if not name: return render_template("error.html", message="Missing name") # Validate sport sport = request.form.get("sport") if not sport: return render_template("error.html", message="Missing sport") if sport not in SPORTS: return render_template("error.html", message="Invalid sport") # Remember registrant REGISTRANTS[name] = sport # Confirm registration return redirect("/registrants") @app.route("/registrants") def registrants(): return render_template("registrants.html", registrants=REGISTRANTS)注意到使用了一个名为
REGISTRANTS的字典来记录REGISTRANTS[name]选定的sport。同时,注意registrants=REGISTRANTS将字典传递给此模板。 -
此外,我们可以实现
error.html:{% extends "layout.html" %} {% block body %} <h1>Error</h1> <p>{{ message }}</p> <img alt="Grumpy Cat" src="/static/cat.jpg"> {% endblock %} -
此外,创建一个新的模板,名为
registrants.html,如下所示:{% extends "layout.html" %} {% block body %} <h1>Registrants</h1> <table> <thead> <tr> <th>Name</th> <th>Sport</th> </tr> </thead> <tbody> {% for name in registrants %} <tr> <td>{{ name }}</td> <td>{{ registrants[name] }}</td> </tr> {% endfor %} </tbody> </table> {% endblock %}注意到
{% for name in registrants %}...{% endfor %}会遍历每个注册者。能够在动态网页上迭代是非常强大的功能! -
最后,在
app.py所在的同一文件夹中创建一个名为static的文件夹。在那里,上传以下文件:一个 猫 的文件。 -
执行
flask run并与该应用程序互动。 -
现在你已经拥有了一个网络应用程序!然而,存在一些安全漏洞!因为所有内容都在客户端,攻击者可以更改 HTML 并黑客网站。此外,如果服务器关闭,这些数据将不会持久化。我们是否有办法让数据在服务器重启时也能持久化?
Flask 和 SQL
-
正如我们所见,Python 可以与 SQL 数据库交互,我们可以结合 Flask、Python 和 SQL 的力量来创建一个数据将持久化的网络应用程序!
-
要实现这一点,你需要采取多个步骤。
-
首先,将以下 SQL 数据库下载到你的
froshims文件夹中。 -
在终端中执行
sqlite3 froshims.db并输入.schema以查看数据库文件的内容。进一步输入SELECT * FROM registrants;以了解内容。你会注意到文件中目前没有任何注册信息。 -
接下来,按如下方式修改
requirements.txt:cs50 Flask -
按如下方式修改
index.html:{% extends "layout.html" %} {% block body %} <h1>Register</h1> <form action="/register" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> {% for sport in sports %} <input name="sport" type="checkbox" value="{{ sport }}"> {{ sport }} {% endfor %} <button type="submit">Register</button> </form> {% endblock %} -
按如下方式修改
layout.html:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>froshims</title> </head> <body> {% block body %}{% endblock %} </body> </html> -
确保
error.html显示如下:{% extends "layout.html" %} {% block body %} <h1>Error</h1> <p>{{ message }}</p> <img alt="Grumpy Cat" src="/static/cat.jpg"> {% endblock %} -
修改
registrants.html以如下所示:{% extends "layout.html" %} {% block body %} <h1>Registrants</h1> <table> <thead> <tr> <th>Name</th> <th>Sport</th> <th></th> </tr> </thead> <tbody> {% for registrant in registrants %} <tr> <td>{{ registrant.name }}</td> <td>{{ registrant.sport }}</td> <td> <form action="/deregister" method="post"> <input name="id" type="hidden" value="{{ registrant.id }}"> <button type="submit">Deregister</button> </form> </td> </tr> {% endfor %} </tbody> </table> {% endblock %}注意到包含了一个隐藏值
registrant.id,这样就可以在app.py中稍后使用这个id。 -
最后,按如下方式修改
app.py:# Implements a registration form, storing registrants in a SQLite database, with support for deregistration from cs50 import SQL from flask import Flask, redirect, render_template, request app = Flask(__name__) db = SQL("sqlite:///froshims.db") SPORTS = [ "Basketball", "Soccer", "Ultimate Frisbee" ] @app.route("/") def index(): return render_template("index.html", sports=SPORTS) @app.route("/deregister", methods=["POST"]) def deregister(): # Forget registrant id = request.form.get("id") if id: db.execute("DELETE FROM registrants WHERE id = ?", id) return redirect("/registrants") @app.route("/register", methods=["POST"]) def register(): # Validate name name = request.form.get("name") if not name: return render_template("error.html", message="Missing name") # Validate sports sports = request.form.getlist("sport") if not sports: return render_template("error.html", message="Missing sport") for sport in sports: if sport not in SPORTS: return render_template("error.html", message="Invalid sport") # Remember registrant for sport in sports: db.execute("INSERT INTO registrants (name, sport) VALUES(?, ?)", name, sport) # Confirm registration return redirect("/registrants") @app.route("/registrants") def registrants(): registrants = db.execute("SELECT * FROM registrants") return render_template("registrants.html", registrants=registrants)注意到使用了
cs50库。包含了一个用于register的post方法的路由。这个路由将获取注册表单中的姓名和运动项目,并执行一个 SQL 查询将name和sport添加到registrants表中。deregister路由将执行一个 SQL 查询,获取用户的id并利用这些信息注销该个人。 -
你可以执行
flask run并检查结果。 -
如果你想下载我们的
froshims实现,你可以在这里下载。 -
你可以在Flask 文档中了解更多关于 Flask 的信息。
Cookies 和 Session
-
app.py被视为控制器。视图被认为是用户所看到的内容。模型是数据存储和操作的方式。三者合称为 MVC(模型,视图,控制器)。 -
虽然之前的
froshims实现从管理角度来看很有用,其中后台管理员可以向数据库添加和删除个人,但可以想象这段代码在公共服务器上实现并不安全。 -
首先,不良分子可能会通过点击注销按钮代表其他用户做出决定——实际上是从服务器删除他们记录的答案。
-
像谷歌这样的网络服务使用登录凭证来确保用户只能访问正确的数据。
-
实际上,我们可以使用 cookies 来实现这一点。Cookies 是存储在您计算机上的小文件,这样您的计算机就可以与服务器通信,并有效地表示,“我是一个已经登录的授权用户。” 这种通过 cookie 的授权称为 会话。
-
Cookies 可以按照以下方式存储:
GET / HTTP/2 Host: accounts.google.com Cookie: session=value在这里,一个
sessionid 被存储,并带有特定的value,表示该会话。 -
以最简单的方式,我们可以通过创建一个名为
login的文件夹,然后添加以下文件来实现这一点。 -
首先,创建一个名为
requirements.txt的文件,其内容如下:Flask Flask-Session注意,除了
Flask,我们还包含了Flask-Session,这是支持登录会话所必需的。 -
第二,在
templates文件夹中创建一个名为layout.html的文件,其内容如下:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>login</title> </head> <body> {% block body %}{% endblock %} </body> </html>注意这提供了一个非常简单的布局,包括标题和正文。
-
第三,在
templates文件夹中创建一个名为index.html的文件,其内容如下:{% extends "layout.html" %} {% block body %} {% if name -%} You are logged in as {{ name }}. <a href="/logout">Log out</a>. {%- else -%} You are not logged in. <a href="/login">Log in</a>. {%- endif %} {% endblock %}注意这个文件会检查
session["name"]是否存在(在下面的app.py中进一步阐述)。如果存在,它将显示欢迎信息。如果不存在,它将建议您浏览到登录页面。 -
第四,创建一个名为
login.html的文件,并添加以下代码:{% extends "layout.html" %} {% block body %} <form action="/login" method="post"> <input autocomplete="off" autofocus name="name" placeholder="Name" type="text"> <button type="submit">Log In</button> </form> {% endblock %}注意这是基本登录页面的布局。
-
最后,创建一个名为
app.py的文件,并编写以下代码:from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configure app app = Flask(__name__) # Configure session app.config["SESSION_PERMANENT"] = False app.config["SESSION_TYPE"] = "filesystem" Session(app) @app.route("/") def index(): return render_template("index.html", name=session.get("name")) @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": session["name"] = request.form.get("name") return redirect("/") return render_template("login.html") @app.route("/logout") def logout(): session.clear() return redirect("/")注意文件顶部的修改后的 导入,包括
session,这将允许您支持会话。最重要的是,注意session["name"]在login和logout路由中的使用。login路由会将提供的登录名分配给session["name"]。然而,在logout路由中,注销是通过清除session的值来实现的。 -
session抽象允许您确保只有特定用户可以访问我们应用程序中的特定数据和功能。它允许您确保没有人代表另一个用户行事,无论是好是坏! -
如果您愿意,您可以下载我们的实现。
-
你可以在Flask 文档中了解更多关于会话的信息。
购物车
-
接下来,我们将通过一个最终示例来展示如何利用 Flask 的会话启用功能。
-
我们检查了
app.py中的store代码。以下代码被展示:from cs50 import SQL from flask import Flask, redirect, render_template, request, session from flask_session import Session # Configure app app = Flask(__name__) # Connect to database db = SQL("sqlite:///store.db") # Configure session app.config["SESSION_PERMANENT"] = False app.config["SESSION_TYPE"] = "filesystem" Session(app) @app.route("/") def index(): books = db.execute("SELECT * FROM books") return render_template("books.html", books=books) @app.route("/cart", methods=["GET", "POST"]) def cart(): # Ensure cart exists if "cart" not in session: session["cart"] = [] # POST if request.method == "POST": book_id = request.form.get("id") if book_id: session["cart"].append(book_id) return redirect("/cart") # GET books = db.execute("SELECT * FROM books WHERE id IN (?)", session["cart"]) return render_template("cart.html", books=books)注意到
cart是通过列表实现的。可以使用books.html中的Add to Cart按钮向此列表添加项目。点击此类按钮时,将调用post方法,其中将项目id附加到cart上。在查看购物车时,调用get方法,执行 SQL 以显示购物车中的书籍列表。 -
我们还看到了
books.html的内容:{% extends "layout.html" %} {% block body %} <h1>Books</h1> {% for book in books %} <h2>{{ book["title"] }}</h2> <form action="/cart" method="post"> <input name="id" type="hidden" value="{{ book['id'] }}"> <button type="submit">Add to Cart</button> </form> {% endfor %} {% endblock %}注意到这是如何通过
for book in books为每本书创建Add to Cart功能的。 -
你可以在源代码中查看驱动此
flask实现的其余文件。
显示
-
我们在
app.py中查看了一个名为shows的预设计程序:# Searches for shows using LIKE from cs50 import SQL from flask import Flask, render_template, request app = Flask(__name__) db = SQL("sqlite:///shows.db") @app.route("/") def index(): return render_template("index.html") @app.route("/search") def search(): shows = db.execute("SELECT * FROM shows WHERE title LIKE ?", "%" + request.args.get("q") + "%") return render_template("search.html", shows=shows)注意到
search路由允许通过一种方式来搜索show。此搜索查找与用户提供的标题LIKE匹配的标题。 -
我们还检查了
index.html:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>shows</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="text"> <ul></ul> <script> let input = document.querySelector('input'); input.addEventListener('input', async function() { let response = await fetch('/search?q=' + input.value); let shows = await response.json(); let html = ''; for (let id in shows) { let title = shows[id].title.replace('<', '<').replace('&', '&'); html += '<li>' + title + '</li>'; } document.querySelector('ul').innerHTML = html; }); </script> </body> </html>注意到 JavaScript
script创建了一个自动完成的实现,其中匹配input的标题会被显示出来。 -
你可以在源代码中查看此实现的其他文件。
API
-
应用程序程序接口或API是一系列规范,允许你与另一个服务进行交互。例如,我们可以利用 IMDB 的 API 来与其数据库进行交互。我们甚至可以集成用于处理从服务器下载的特定类型数据的 API。
-
在对
shows进行改进的同时,查看app.py的改进,我们看到了以下内容:# Searches for shows using Ajax from cs50 import SQL from flask import Flask, render_template, request app = Flask(__name__) db = SQL("sqlite:///shows.db") @app.route("/") def index(): return render_template("index.html") @app.route("/search") def search(): q = request.args.get("q") if q: shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%") else: shows = [] return render_template("search.html", shows=shows)注意到
search路由执行了一个 SQL 查询。 -
查看
search.html,你会注意到它非常简单:{% for show in shows %} <li>{{ show["title"] }}</li> {% endfor %}注意到它提供了一个项目符号列表。
-
最后,查看
index.html,注意到AJAX代码被用来驱动搜索:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>shows</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="search"> <ul></ul> <script> let input = document.querySelector('input'); input.addEventListener('input', async function() { let response = await fetch('/search?q=' + input.value); let shows = await response.text(); document.querySelector('ul').innerHTML = shows; }); </script> </body> </html>注意到使用事件监听器动态查询服务器以提供与提供的标题匹配的列表。这将定位 HTML 中的
ul标签并相应地修改网页以包含匹配的列表。 -
你可以在AJAX 文档中了解更多信息。
JSON
-
JavaScript 对象表示法或JSON是一个包含键和值的字典文本文件。这是一种原始且对计算机友好的方式来获取大量数据。
-
JSON 是从服务器获取数据的一种非常有用的方式。
-
你可以在我们共同检查的
index.html中看到这一操作:<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="initial-scale=1, width=device-width"> <title>shows</title> </head> <body> <input autocomplete="off" autofocus placeholder="Query" type="text"> <ul></ul> <script> let input = document.querySelector('input'); input.addEventListener('input', async function() { let response = await fetch('/search?q=' + input.value); let shows = await response.json(); let html = ''; for (let id in shows) { let title = shows[id].title.replace('<', '<').replace('&', '&'); html += '<li>' + title + '</li>'; } document.querySelector('ul').innerHTML = html; }); </script> </body> </html>虽然上述内容可能有些晦涩,但它为你提供了一个起点,让你可以自己研究 JSON,看看它如何在你的网络应用程序中实现。
-
此外,我们还检查了
app.py以了解如何获取 JSON 响应:# Searches for shows using Ajax with JSON from cs50 import SQL from flask import Flask, jsonify, render_template, request app = Flask(__name__) db = SQL("sqlite:///shows.db") @app.route("/") def index(): return render_template("index.html") @app.route("/search") def search(): q = request.args.get("q") if q: shows = db.execute("SELECT * FROM shows WHERE title LIKE ? LIMIT 50", "%" + q + "%") else: shows = [] return jsonify(shows)注意到
jsonify是如何被用来将结果转换为当代网络应用可接受的易读格式的。 -
你可以在JSON 文档中了解更多信息。
-
总结来说,你现在可以使用 Python、Flask、HTML 和 SQL 来完成自己的网络应用程序。
总结
在本节课中,你学习了如何利用 Python、SQL 和 Flask 来创建 Web 应用程序。具体来说,我们讨论了……
-
Flask
-
表单
-
模板
-
请求方法
-
Flask 和 SQL
-
Cookie 和会话
-
API
-
JSON
下次再见,我们将在这里举行本学期的最后一堂课,地点是Sanders Theatre!







浙公网安备 33010602011771号