Py007-01-02内置urllib模块

urllib模块非爬虫的重点,核心为后面的request以及各种框架

内置urllib

概念:urllib是Python自带的一个用于爬虫的库,其主要作用就是可以通过代码模拟浏览器发送请求。其常被用到的子模块在Python3中的为urllib.request和urllib.parse,在Python2中是urllib和urllib2。

  • 作用:可以使用代码模拟浏览器发起请求。request parse

使用流程:

  1. 指定url
  2. 发请求
  3. 获取页面数据
  4. 持久化存储

需求1爬取搜狗首页的页面数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import urllib.request

#1.指定url
url = 'https://www.sogou.com/'

#2.发起请求:urlopen可以根据指定的url发起请求,切返回一个响应对象
response = urllib.request.urlopen(url=url)

#3.获取页面数据:read函数返回的就是响应对象中存储的页面数据(byte)
page_text = response.read()

#4.持久化存储
with open('./sougou.html','wb') as fp:
# fp.write(page_text)
# print(page_text) # 字节码
print(page_text.decode()) # 转化为字符串
fp.write(page_text.decode())#使用decode将page_text转成字符串类型
print('写入数据成功')

需求2搜狗搜关键字

注意:

  • urllib模块的get参数如果有中文要自行转义,否则报错UnicodeEncodeError
  • 使用urllib.parse.quote(“中文参数”) 转义中文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 需求:爬取指定词条所对应的页面数据

import urllib.request
import urllib.parse

#指定url
url = 'https://www.sogou.com/web?query='
#url特性:url不可以存在非ASCII编码的字符数据
word = urllib.parse.quote("人民币")
url += word #有效的url

#发请求
response = urllib.request.urlopen(url=url)

#获取页面数据
page_text = response.read()


with open('renminbi.html','wb') as fp:
fp.write(page_text)

'''
如果
word='人民币'
不经过转义则直接报错
UnicodeEncodeError: 'ascii' codec can't encode characters in position 15-17: ordinal not in range(128)
'''

是不是很麻烦如果多个参数就要多次处理?

使用UA来伪装

  • 反爬机制:网站检查请求的UA,如果发现UA是爬虫程序,则拒绝提供网站数据。
  • User-Agent(UA):请求载体的身份标识.
  • 反反爬机制:伪装爬虫程序请求的UA

爬去百度首页

  • 如果不用UA伪装,爬去的页面是不对的(百度已经做了反爬处理)
  • 获取UA打开浏览器
1
2
3
4
5
6
7
1.开发者工具
2.检查
3.Network
4.访问百度
5.找到Request Headers的请求信息里的User-Agent字符串
形如
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import urllib.request

url = 'https://www.baidu.com/'

#UA伪装
#1.自定制一个请求对象
headers = {
#存储任意的请求头信息
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36'

}
#该请求对象的UA进行了成功的伪装
request = urllib.request.Request(url=url,headers=headers)

#2.针对自定制的请求对象发起请求
response = urllib.request.urlopen(request)

print(response.read())

使用urllib发起post请求

需求:爬取百度翻译的翻译结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import urllib.request
import urllib.parse

#1.指定url
url = 'https://fanyi.baidu.com/sug'

#post请求携带的参数进行处理 流程:
#1.将post请求参数封装到字典
data = {
'kw':'西瓜'
}
#2.使用parse模块中的urlencode(返回值类型为str)进行编码处理
data = urllib.parse.urlencode(data)
#3.将步骤2的编码结果转换成byte类型
data = data.encode()

#2.发起post请求:urlopen函数的data参数表示的就是经过处理之后的post请求携带的参数
response = urllib.request.urlopen(url=url,data=data)

response.read()
res = response.read()
print(res)

Py007-01-01爬虫初识

python网络爬虫的简单介绍

什么是爬虫

爬虫就是通过编写程序模拟浏览器上网,然后让其去互联网上抓取数据的过程。

哪些语言可以实现爬虫

  1. php:可以实现爬虫。php被号称是全世界最优美的语言(当然是其自己号称的,就是王婆卖瓜的意思),但是php在实现爬虫中支持多线程和多进程方面做的不好。

  2. java:可以实现爬虫。java可以非常好的处理和实现爬虫,是唯一可以与python并驾齐驱且是python的头号劲敌。但是java实现爬虫代码较为臃肿,重构成本较大。

  3. c、c++:可以实现爬虫。但是使用这种方式实现爬虫纯粹是是某些人(大佬们)能力的体现,却不是明智和合理的选择。

  4. python:可以实现爬虫。python实现和处理爬虫语法简单,代码优美,支持的模块繁多,学习成本低,具有非常强大的框架(scrapy等)且一句难以言表的好!没有但是!

爬虫的分类

1. 通用爬虫:通用爬虫是搜索引擎(Baidu、Google、Yahoo等)“抓取系统”的重要组成部分。主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。 简单来讲就是尽可能的;把互联网上的所有的网页下载下来,放到本地服务器里形成备分,在对这些网页做相关处理(提取关键字、去掉广告),最后提供一个用户检索接口。

1
2
3
4
搜索引擎如何抓取互联网上的网站数据?
门户网站主动向搜索引擎公司提供其网站的url
搜索引擎公司与DNS服务商合作,获取网站的url
门户网站主动挂靠在一些知名网站的友情链接中

2.聚焦爬虫:聚焦爬虫是根据指定的需求抓取网络上指定的数据。例如:获取豆瓣上电影的名称和影评,而不是获取整张页面中所有的数据值。

robots.txt协议

1
如果自己的门户网站中的指定页面中的数据不想让爬虫程序爬取到的话,那么则可以通过编写一个robots.txt的协议文件来约束爬虫程序的数据爬取。robots协议的编写格式可以观察淘宝网的robots(访问www.taobao.com/robots.txt即可)。但是需要注意的是,该协议只是相当于口头的协议,并没有使用相关技术进行强制管制,所以该协议是防君子不防小人。但是我们在学习爬虫阶段编写的爬虫程序可以先忽略robots协议。

反爬虫

  • 门户网站通过相应的策略和技术手段,防止爬虫程序进行网站数据的爬取。

反反爬虫

  • 爬虫程序通过相应的策略和技术手段,破解门户网站的反爬虫手段,从而爬取到相应的数据。

Py008-01-05选择排序和插入排序

选择排序(体育老师一指禅法)

思路:

在一个列表里每次把最小的值取出来。

简易版本(不建议用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
li = [3,2,4,6,5,1,8,7,9]

def select_sort1(li):
list_new = []
for i in range(len(li)):
min_val = min(li)
list_new.append(min_val)
li.remove(min_val)

return list_new

res = select_sort1(li)
print(res)

'''
我们知道 冒泡排序是交换彼此直接的位置,属于原地排序

而这个版本的选择排序
每次都对原始数据进行删除 假如是长度10000的集合 如果满足条件的值在中间5000的位置 那么后边的要依次 索引-1
'''

优化版

1
2
3
4
5
6
7
8
9
10
11
def select_sort2(li):
for i in range(len(li)-1): # i是第几趟
min_loc = i
for j in range(i+1,len(li)):
if li[j]<li[min_loc]:
min_loc = j
li[i],li[min_loc] = li[min_loc],li[i]
print(li)

res2 = select_sort2(li)
print(res2)
  • 一趟排序记录最小的数,放到第一个
  • 再一趟排序记录 列表无序区最小的数,放到第二个位置
  • 。。。。
  • 关键点:有序区 和 无序区,无序区最小值的位置

时间复杂度:O(n*n)

插入排序(起扑克牌法)

你玩过扑克吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
你是如何摸牌的?
比如 arr = [5,1,3,2,4]

()内为你摸到的牌

第一次摸到5
(5) [1,3,2,4]
第二次摸到1 1与5比=>小 交换位置
(1,5) [3,2,4]
第三次摸到3 3与5比=>小 交换位置 3与1比=>大 不交换
(1,3,5) [2,4]
第四次摸到2 2与5比=>小 交换位置 2与3比=>小 交换位置 2与1比=>大 不交换
(1,2,3,5) [4]
第五次摸到4 4与5比=>小 交换位置 4与3比=>大 不交换

插入排序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
def insert_sort(li):
for i in range(1,len(li)): # 表示摸到的牌的下标
tmp = li[i]
j = i-1 # 手里牌的下标
while j>=0 and li[j]>tmp:
li[j+1] = li[j]
j -= 1
li[j+1] = tmp
print(li)

li = [3,2,4,6,5,1,8,7,9]

insert_sort(li)

时间复杂度: O(n*n)

Py008-01-04排序

排序

  • 排序:将一组‘无序’的记录序列调整为‘有序’的记录列表
  • 列表排序
    输入:列表
    输出:有序列表

  • 生序和降序

  • 内置排序函数:sort()

常见排序

low B 三人组

  • 冒泡排序
  • 选择排序
  • 插入排序

NB 三人组

  • 快速排序
  • 堆排序
  • 归并排序

其他排序

  • 希尔
  • 计数
  • 基数

冒泡排序 (Bubble sort) 体育委员两两比较法

  • 列表每两个相邻的数,如果后面的比前面的大,则交换这两个数
  • 一趟排序完成后,则无序区减少一个数,有序区增加一个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
初始列表
5 2 3 1 4

第一趟
5 2 3 1 4
第一次
2 5 3 1 4
第二次
2 3 5 1 4
第三次
2 3 1 5 4
第四次
2 3 1 4 5 此时最大的数5已经站定了

第二趟
2 3 1 4
第一次
2 3 1 4
第二次
2 1 3 4
第三次
2 1 3 4 此时最大的数4已经站定了

第三趟
2 1 3
第一次
1 2 3
第二次
1 2 3 此时最大的数3已经站定了

第四趟
1 2
第一次
1 2 此时最大的数2已经站定了

最后 1 2 3 4 5

冒泡排序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def bubble_sort(li):
for i in range(len(li)-1): # 第几趟
for j in range(len(li) - i - 1):
if li[j] > li[j+1]:# 大就交换
li[j],li[j+1] = li[j+1],li[j]
print(li)

li = [3,2,4,6,5,1,8,7,9]

bubble_sort(li)

'''
结果如下
[2, 3, 4, 5, 1, 6, 7, 8, 9]
[2, 3, 4, 1, 5, 6, 7, 8, 9]
[2, 3, 1, 4, 5, 6, 7, 8, 9]
[2, 1, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
'''

时间复杂度:O(n*n)

冒泡排序的优化

如果一趟列表中没有发生交换则代表已经排好序了 则后面无需进行那么多次的比较了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def bubble_sort2(li):
for i in range(len(li)-1): # 第几趟
exchange = False
for j in range(len(li) - i - 1):
if li[j] > li[j+1]:# 大就交换
li[j],li[j+1] = li[j+1],li[j]
exchange = True
print(li)
if not exchange:
return

li = [3,2,4,6,5,1,8,7,9]

bubble_sort2(li)

'''
[2, 3, 4, 5, 1, 6, 7, 8, 9]
[2, 3, 4, 1, 5, 6, 7, 8, 9]
[2, 3, 1, 4, 5, 6, 7, 8, 9]
[2, 1, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
'''

Py008-01-03查找算法

查找

在一些数据里,通过一定的方法找出与给定关键字相同的数据的元素的过程

列表查找(线性表查找):从列表中查找指定元素

  • 输入:列表/待查找元素
  • 输出:元素下标(未找到元素时一般返回None或-1)

内置列表查找函数:index()

也叫线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止

时间复杂度:O(n)

1
2
3
4
5
6
7
def linear_search(data_set,value):
for i,v in enumerate(data_set):
if v == value:
return i
return None

index = linear_search([1,2,3,5,4],4) # 4

二分查找

折半查找,从有序列表的初始候选区arr[0:n]开始,通过对待查找的值与候选区中间的值的比较,可以使候选区减少一半

猜物品价格 0-1000 假设物品840元

1
2
3
4
5
6
7
8
9
第一次 500  低了  + (1000-500)/2 = +250
第二次 750 低了 + (1000-750)/2 = +125
第三次 875 高了 - (875-750)/2 = -62.5
第四次 813 低了 + (875-813)/2 = +31
第五次 844 高了 - (844-813)/2 = -11.5
第六次 833 低了 + (844-833)/2 = +5.5
第七次 838 低了 + (844-838)/2 = +3
第七次 841 高了 - (841-838)/2 = -1.5
第八次 840 对了

二分查找算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def binary_search(li,val):
left = 0
right = len(li) - 1
while left <= right: # 候选区有值
# 中间值
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val: # 待查找的值在mid左侧
right = mid - 1
else: # li[mid] < val # 待查找的值在mid右侧
left = mid + 1
else:
return None

a = binary_search([1,2,3,4,5,6,7,8,9],3) # 2

时间复杂度: O(log n)

二分查找比线性查找速度快

1
内置函数index() 就是线性查找,因为二分查找的要求就是有序列表

Py008-01-02递归和汉诺塔

递归

特点

  • 调用自身
  • 结束条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def fn1(x):
print(x)
fn1(x-1)


def fn2(x):
if x>0:
print(x)
fn2(x+1)

# 合法递归 fn3(3) ==>3 2 1
def fn3(x):
if x>0:
print(x)
fn3(x-1)

# 合法递归 fn4(3) ==>1 2 3
def fn4(x):
if x>0:
fn4(x-1)
print(x)

汉诺塔问题

  • 三个柱子 A B C
  • A上有3个盘子 大盘子在最下 小盘子在最上
  • 只能在 三个柱子上移动
  • 实现 把A上的盘子移动到C
1
2
3
4
5
6
7
8
9
10
n=2时
1. 把小圆盘从A移动到B
2. 把小圆盘从A移动到C
3. 把小圆盘从B移动到C


n=n时
1. 把n-1个盘子从A经过C移动到B
2. 把第n个圆盘从A移动到C
3. 把n-1个圆盘从B经过A移动到C

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'''
a:从那个盘子
b:经过那个盘子
c:到那个盘子
'''
def hanoi(n,a,b,c):
if n>0:
hanoi(n-1,a,c,b)
print('moving from %s to %s'%(a,c))
hanoi(n-1,b,a,c)


hanoi(3,'A','B','C')

'''
moving from A to C
moving from A to B
moving from C to B
moving from A to C
moving from B to A
moving from B to C
moving from A to C
'''

汉诺塔移动次数的递推式:h(x)=2h(x-1)+1
h(64)=18446744073709551615

假设每秒搬一个盘子,则需要5800亿年!

Py008-01-01算法基础

算法入门

  • 什么是算法 Algorithm

一个计算过程,解决问题的方法

Niklaus Wirth:程序=数据结构+算法

时间复杂度

生活中一些事情,估计时间

1
2
3
4
5
6
7
8
9
10
11
眨眼     一瞬间

口算 29+68 几秒钟

烧一壶水 几分钟

睡一觉 几小时

完成一个项目 几天/几周/几个月

飞船从地球到太阳系 几年

如下代码那个运行最短?

用来评估算法运行效率的一个式子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#时间复杂度   O(1)
print('hi')


#时间复杂度 O(n)
for i in range(n):
print('hi')


#时间复杂度 O(n*n)
for i in range(n):
for j in range(n):
print('hi')

#时间复杂度 O(n*n*n)
for i in range(n):
for j in range(n):
for k in range(n):
print('hi')

继续看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#时间复杂度   不是O(3)  而是 O(1)
print('hi')
print('hi')
print('hi')
# why?
# 比如生活角度的烧水 没人回答你 3个3分钟 而是回答 几分钟

#时间复杂度 不是O(n*n+n) 而是O(n*n)
for i in range(n):
print('hi')
for j in range(n):
print('hi')

# why?
# 比如生活角度的睡觉 没人回答你 7个小时零20分 而是回答 几小时

再看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
while n > 1:
print(n)
n = n // 2

n=64输出
64
32
16
8
4
2

每次少一半 (对数形式)
2*2*2*2*2*2 = 64
log64 = 6
时间复杂度为 O(log n)

时间复杂度小结

  • 用来估计算法运行时间的式子
  • 时间复杂度高的算法比复杂度低的算法慢
  • 常见时间复杂度(按效率排序)
    O(1)<O(log n)<O(n)<O(n log n)<O(nn)<O(nn log n)<O(nnn)

  • 复杂的时间复杂度
    O(n!) O(2的n次方) O(n的n次方) …

如何简单快速判断算法复杂度

常见情况

  • 确定问题规模的n
  • 循环减半过程 –> log n
  • k层关于n的循环 –> n的k次方

复杂情况:根据算法执行过程判断

空间复杂度

  • 评估算法内存占用大小的式子
  • 空间复杂度的表达方式和时间复杂度完全一样
    算法使用了几个变量O(1)
    算法使用了长度为n的一维列表 O(n)
    算法使用了长度为m行n列的一维列表 O(m*n)
  • 空间换时间

由于现在内存和硬件水平的提升所以空间已经不是那么重要了

Py006-02-07中间件

中间件(django里非常非常重要的一个内容)

中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。

Django默认的7个Middleware(中间件):

settings.py里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# 通过导入模块查看中间件源码
from django.middleware.security import SecurityMiddleware

# 点进去发现基本都有这两个方法
'''
def process_request(self,request):...

def process_response(self,request,response):...
'''

一次请求的完成过程

1
2
3
django接受请求时  中间件时  依次执行  process_request

django响应时 中间件时 逆向依次执行 process_response

自定义中间件

中间件中一共有四个方法:

1
2
3
4
process_request
process_view
process_exception
process_response

我们也可以自己定义一个中间件,我们可以自己写一个类,但是必须继承MiddlewareMixin
需要导入

1
from django.utils.deprecation import MiddlewareMixin

step001 第一步创建一个项目建立一个应用app01

在app01目录下建立自己的中间件

my_middlewares.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 自定义中间件 必须继承MiddlewareMixin
# 自定义中间件 必须继承MiddlewareMixin
# 自定义中间件 必须继承MiddlewareMixin

from django.utils.deprecation import MiddlewareMixin

class MyMiddleWare1(MiddlewareMixin):
def process_request(self,request):
print('my middleware 001 request')

def process_response(self,request,response):
print('my middleware 001 response')
return response

class MyMiddleWare2(MiddlewareMixin):
def process_request(self,request):
print('my middleware 002 request')

def process_response(self, request, response):
print('my middleware 002 response')
return response

step002 第二步settings.py里引入自定义的中间件

1
2
3
4
5
6
7
8
9
10
11
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'app01.my_middleware1.MyMiddleWare1',
'app01.my_middleware1.MyMiddleWare2'
]

访问 http://localhost:8000/index/

1
2
3
4
5
6
# 打印如下信息
my middleware 001 request
my middleware 002 request
index.html
my middleware 002 response
my middleware 001 response

为什么process_request没返回值

return一下试试

给 MyMiddleWare1的process_request()添加一个返回值

1
2
3
4
5
6
7
from django.shortcuts import render,HttpResponse

class MyMiddleWare1(MiddlewareMixin):

def process_request(self,request):
print('my middleware 001 request')
return HttpResponse('被禁止的请求')

访问 http://localhost:8000/index/

1
2
3
4
5
6
# 控制台打印
my middleware 001 request
my middleware 001 response

# 页面显示
被禁止的请求

第一个中间件由于return一个HttpResponse(‘xxx’)

导致不会再去下一个中间件而提前返回给客户端

process_view

1
process_view(self, request, callback, callback_args, callback_kwargs)

中间件修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyMiddleWare1(MiddlewareMixin):

def process_request(self,request):
print('my middleware 001 request')

def process_response(self,request,response):
print('my middleware 001 response')
return response

def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view view")

class MyMiddleWare2(MiddlewareMixin):

def process_request(self,request):
print('my middleware 002 request')

def process_response(self, request, response):
print('my middleware 002 response')
return response

def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view2 view")

访问 http://localhost:8000/index/

1
2
3
4
5
6
7
my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
index.html
my middleware 002 response
my middleware 001 response

当最后一个中间的process_request到达路由关系映射之后,返回到中间件1的process_view,然后依次往下,到达views函数,最后通过process_response依次返回到达用户。

process_view可以用来调用视图函数:

在第二个中间件里做如下处理

  • 直接返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyMiddleWare2(MiddlewareMixin):
...
def process_view(self, request, callback, callback_args, callback_kwargs):

print("Md1view2 view")
# 直接返回响应体内容
return HttpResponse('view2 response')
'''
# 就不会进入路由views.index的视图函数了
my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
my middleware 002 response
my middleware 001 response
'''
  • 提前获取路由里 index函数的响应
1
2
3
4
5
6
7
8
class MyMiddleWare2(MiddlewareMixin):
...

def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view2 view")
# 拿到视图函数的结果提前响应
res = callback(request, *callback_args, **callback_kwargs)
return res
1
2
3
4
5
6
7
my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
index.html
my middleware 002 response
my middleware 001 response

注意:process_view如果有返回值,会越过其他的process_view以及视图函数,但是所有的process_response都还会执行。

process_exception

正常是不会执行process_exception里的内容,只有报错的时候才会执行

1
process_exception(self, request, exception)

添加process_exception函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class MyMiddleWare1(MiddlewareMixin):

def process_request(self,request):
print('my middleware 001 request')

def process_response(self,request,response):
print('my middleware 001 response')
return response

def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view view")

def process_exception(self,request,exception):
print("md1 process_exception...")

class MyMiddleWare2(MiddlewareMixin):

def process_request(self,request):
print('my middleware 002 request')

def process_response(self, request, response):
print('my middleware 002 response')
return response

def process_view(self, request, callback, callback_args, callback_kwargs):
print("Md1view2 view")

def process_exception(self,request,exception):
print("md2 process_exception...")


'''
访问 http://localhost:8000/index/

my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
index.html
my middleware 002 response
my middleware 001 response
'''

由于没有错误所以不会执行process_exception的内容

修改views.py 添加一个未初始化的变量

1
2
3
4
def index(request):
print('index.html')
aaa
return render(request,'index.html')
1
2
3
4
5
6
7
8
9
10
my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
index.html
md2 process_exception...
md1 process_exception...
....错误信息
my middleware 002 response
my middleware 001 response

系统返回的错误信息难看了,想自己返回一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# return响应信息
class MyMiddleWare2(MiddlewareMixin):
...
def process_exception(self,request,exception):
print("md2 process_exception...")
return HttpResponse('error')

'''
my middleware 001 request
my middleware 002 request
Md1view view
Md1view2 view
index.html
md2 process_exception...
my middleware 002 response
my middleware 001 response
'''

流程如下

三、应用案例

  1. 做IP访问频率限制

某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。

  1. URL访问过滤

如果用户访问的是login视图(放过)
如果访问其他视图,需要检测是不是有session认证,已经有了放行,没有返回login,这样就省得在多个视图函数上写装饰器了!

中间件是个双刃剑

1
2
如果80个视图函数70个不需要校验
如果用中间件处理 反而影响了效率

作为延伸扩展内容,有余力的同学可以尝试着读一下以下两个自带的中间件:

1
2
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',

Py006-02-06用户认证组件

用户认证组件

之前是用django的session设置键值对

但是存在个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
假如你有两个账号  
你用a账号在chrome登陆了 xx站
此时session里生成了 session{'00001':{user:'a'}}

此时你在用b账号在chrome里登陆 xx站
此时session里不会进行重新生成
而是更新 session{'00001':{user:'b'}}
这样相当于覆盖

假设 每个账号权限不一样
a是超级管理员
b是普通用户
此时b登陆时可能数据里还存留a的一些权限数据 这样就容易数据混淆

我们的目标

1
2
3
a登陆生成  a的信息
b登陆生成 b的信息
相互独立

auth模块

1
2
# 需要导入这个模块才可以使用
from django.contrib import auth

django.contrib.auth中提供了许多方法,这里主要介绍其中的三个:

  • 1.1 、authenticate()

提供了用户认证,即验证用户名以及密码是否正确,一般需要username password两个关键字参数
如果认证信息有效,会返回一个 User 对象。authenticate()会在User 对象上设置一个属性标识那种认证后端认证了该用户,且该信息在后面的登录过程中是需要的。当我们试图登陆一个从数据库中直接取出来不经过authenticate()的User对象会报错的!!

1
user = authenticate(username='someone',password='somepassword')
  • 1.2 、login(HttpRequest, user)  

该函数接受一个HttpRequest对象,以及一个认证了的User对象
此函数使用django的session框架给某个已认证的用户附加上session id等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.contrib.auth import authenticate, login

def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
  • 1.3 、logout(request) 注销用户
1
2
3
4
5
from django.contrib.auth import logout

def logout_view(request):
logout(request)
# Redirect to a success page.

该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。

使用auth模块的大前提

1
2
3
4
5
6
7
8
9
10
11
12
13
用户认证组件:
功能:用session记录登陆状态
前提:用户表:django自带的auth_user表

创建超级用户:python3 manage.py createsuperuser

创建普通用户:python3 manage.py createuser

# 根据 is_supperuser区分 1代表超级用户

api

# 之前使用django里的session 使用的是django_session表

step001新建项目不创建表直接进行数据库迁移

用户表不用自己写了使用 django自带的auth_user表

1
2
python3 manage.py makemigrations
python3 manage.py migrate

step002创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
python3 manage.py createsuperuser
# 输入用户名
hjx
# 输入邮箱
直接回车
# 输入密码
111
# 再次输入密码
111

# 此时去看表里生成了一条数据
但是这个密码 已经被加密了 不是你输入的111

我们创建两个账号
|username |password
-----------------------
|hjx |111
|aaa |222

step003 初始代码

urls.py

1
2
3
4
5
6
7
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.login),
path('index/', views.index),
path('logout/', views.logout)
]

login.html

1
2
3
4
5
6
<form action="" method="post">
{% csrf_token %}
用户名 <input type="text" name="user">
密码 <input type="text" name="pwd">
<input type="submit" value="submit">
</form>

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from django.shortcuts import render,HttpResponse,redirect

# step001 导入模块
from django.contrib import auth

def login(request):

if request.method=='POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
'''
step002 调用
user = auth.authenticate(username=用户名,password=密码)
有一个返回值 如果存在就是这个用户对象 反之None
'''

cur_user = auth.authenticate(username=user,password=pwd)

'''
# 注意虽然用户orm是这个类构造的 但是存在表的时候 密码是加密的 所以无法匹配
from django.contrib.auth.models import User
User.objects.filter(username=user,password=pwd)
'''

if cur_user:
auth.login(request,cur_user) # request.user 当前登陆对象
# 此时访问任意请求的时候 都可以访问到这个用户对象
return redirect('/index/')


return render(request,'login.html')

def index(request):
# 用户对象
print('request.user',request.user)
# 用户id
print('request.user', request.user.id)
# 是否是匿名的
print('request.user', request.user.is_anonymous)

if request.user.is_anonymous:
return redirect('/login/')

username = request.user.username
return render(request,'index.html',{"username":username})

def logout(request):
# 注销 实质就是request.session.flush()
auth.logout(request)

return redirect('/login/')

index.html

1
2
3
4
5
6
7
<body>
<h3>hi {{ username }}</h3>
{#也可以在模版里直接使用#}
<h4>hi {{ request.user.username }}</h4>

<a href="/logout/">注销</a>
</body>

User对象

User 对象属性:username, password(必填项)password用哈希算法保存到数据库

2.1 、user对象的 is_authenticated()

如果是真正的 User 对象,返回值恒为 True 。 用于检查用户是否已经通过了认证。

通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。 这个方法很重要, 在后台用request.user.is_authenticated()判断用户是否已经登录,如果true则可以向前台展示request.user.name

要求:

  1. 用户登陆后才能访问某些页面,

  2. 如果用户没有登录就访问该页面的话直接跳到登录页面

  3. 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址

  • 方法1:
1
2
3
def my_view(request):
if not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
  • 方法2:
django已经为我们设计好了一个用于此种情况的装饰器:login_requierd()
注意:未登陆要跳转的默认页码要在settings.py进行设置
1
2
3
4
5
# 这个值本身是没有的
LOGIN_URL = '/login/'

# 默认会跳
accounts/login/?next=/index/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.contrib.auth.decorators import login_required

@login_required
def other(request):
# 不用在做验证登陆的重定向了
# ...


def login(request):

if user:
auth.login(request,user)
# 从失效页面返回到login时 url会变成 login/?next=/other/
next_url = request.GET.get('next','/index/')
return redirect(next_url)

若用户没有登录,则会跳转到django默认的 登录URL ‘/accounts/login/ ‘ (这个值可以在settings文件中通过LOGIN_URL进行修改)。并传递 当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。

1
2
3
4
5
6
7
8
9
10
11
12
# 之前判断是否登陆是
def index(request):
# 通过is_anonymous判断
if request.user.is_anonymous:
return redirect('/login/')

username = request.user.username
return render(request,'index.html',{"username":username})

# 通过 is_authenticated是否登陆
if not request.user.is_authenticated:
return redirect('/login/')

2.2 、创建用户

1
2
3
4
5
6
7
8
9
10
11
from django.contrib.auth.models import User
user = User.objects.create_user(username='',password='')

'''
# 注意不要直接用这个方式
# 注意不要直接用这个方式
# 注意不要直接用这个方式
user = User.objects.create(username='',password='')
因为auth_user里的password是加密处理后的
这种方式创建没有经过加密
'''

注册用户的需求

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def reg(request):
if request.method=='POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')

'''
# 千万别用
User.objects.create(username=user,password=pwd)
# 因为密码是 加密的
'''
from django.contrib.auth.models import User
user = User.objects.create_user(username=user, password=pwd)

return redirect('/login/')

return render(request,'reg.html')

reg.html

1
2
3
4
5
6
7
<h3>注册</h3>
<form action="" method="post">
{% csrf_token %}
用户名 <input type="text" name="user">
密码 <input type="text" name="pwd">
<input type="submit" value="submit">
</form>

2.3 、check_password(passwd)

用户需要修改密码的时候 首先要让他输入原来的密码 ,如果给定的字符串通过了密码检查,返回 True

2.4 、修改密码

使用 set_password() 来修改密码

1
2
3
user = User.objects.get(username='')
user.set_password(password='')
user.save

2.5 、简单示例

  • 注册:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def sign_up(request):

state = None
if request.method == 'POST':

password = request.POST.get('password', '')
repeat_password = request.POST.get('repeat_password', '')
email=request.POST.get('email', '')
username = request.POST.get('username', '')
if User.objects.filter(username=username):
state = 'user_exist'
else:
new_user = User.objects.create_user(username=username, password=password,email=email)
new_user.save()

return redirect('/book/')
content = {
'state': state,
'user': None,
}
return render(request, 'sign_up.html', content)
  • 修改密码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@login_required
def set_password(request):
user = request.user
state = None
if request.method == 'POST':
old_password = request.POST.get('old_password', '')
new_password = request.POST.get('new_password', '')
repeat_password = request.POST.get('repeat_password', '')
if user.check_password(old_password):
if not new_password:
state = 'empty'
elif new_password != repeat_password:
state = 'repeat_error'
else:
user.set_password(new_password)
user.save()
return redirect("/log_in/")
else:
state = 'password_error'
content = {
'user': user,
'state': state,
}
return render(request, 'set_password.html', content)

总结

  • api常用的
1
2
3
4
5
6
7
8
9
10
11
12
13
from django.contrib import auth
1. if验证成功返回user对象,否则返回None
user=auth.authenticate(username='',password='')

2. auth.login(request,user) # request.user # 当前登陆对象

3. auth.logout(request)

from django.contrib.auth.models import User # User(orm模型) = auth_user表

4. request.user.is_authenticated()

5. user = User.objects.create_user(username='',password='')
  • 补充
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
匿名用户
class models.AnonymousUser

django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点:

id 永远为None。
username 永远为空字符串。
get_username() 永远返回空字符串。
is_staff 和 is_superuser 永远为False。
is_active 永远为 False。
groups 和 user_permissions 永远为空。
is_anonymous() 返回True 而不是False。
is_authenticated() 返回False 而不是True。
set_password()、check_password()、save() 和delete() 引发 NotImplementedError。
New in Django 1.8:
新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。
  • 总结
1
2
3
4
5
6
7
8
9
如果没登录
request.user = 匿名用户对象

如果登录了

request.user = 登录对象

request.user 是一个全局变量
可以在任何视图和模版里使用

Py006-02-05cookie和session实例

详情请看我之前的知识总结

cookie

session

前置准备

models.py

1
2
3
4
from django.db import models
class UserInfo(models.Model):
user = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)

urls.py

1
2
3
4
5
6
7
8
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.login),
path('index/',views.index)
]

login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>
用户名<input type="text" id="user" name="user">
</p>
<p>
密码<input type="text" id="pwd" name="pwd">
</p>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>

django中使用cookie

设置cookie:

1
2
3
rep = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect()  
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐',...)

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class HttpResponseBase:
def set_cookie(self, key, 键
             value='', 值
             max_age=None, 超长时间
                              cookie需要延续的时间(以秒为单位)
                              如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止。
             expires=None, 超长时间
                          expires默认None ,cookie失效的实际日期/时间。
                            
             path='/', Cookie生效的路径,
浏览器只会把cookie回传给带有该路径的页面,这样可以避免将
cookie传给站点中的其他的应用。
/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
            
domain=None, Cookie生效的域名

你可用这个参数来构造一个跨站cookie。
如, domain=".example.com"
所构造的cookie对下面这些站点都是可读的:
www.example.com 、 www2.example.com
                         和an.other.sub.domain.example.com 。
如果该参数设置为 None ,cookie只能由设置它的站点读取。
             secure=False, 如果设置为 True ,浏览器将通过HTTPS来回传cookie。
             httponly=False 只能http协议传输,无法被JavaScript获取
(不是绝对,底层抓包可以获取到也可以被覆盖)
          ): pass

获取cookie:

1
request.COOKIES

删除cookie:

1
response.delete_cookie("cookie_key",path="/",domain=name)

需求登陆的时候设置cookie,存储登陆状态和用户名,进入index的时候显示欢迎,用户xxx

1
2
3
数据库迁徙别忘了执行这个
python3 manage.py makemigrations
python3 manage.py migrate

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from django.shortcuts import render,HttpResponse,redirect
from app01.models import UserInfo
# 自行插入两条用户数据
# user:hjx pwd: 111
# user:aaa pwd: 111

def login(request):
if request.method=='POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
u = UserInfo.objects.filter(user=user,pwd=pwd).first()

if u:
# 登陆成功
'''
响应体有三种
return HttpResponse
return redirect()
return render()

必须通过响应体设置cookie
'''
response = HttpResponse('登陆成功')
response.set_cookie('is_login',True)
response.set_cookie('user_name', u.user)
return response

else:
# 失败
pass

return render(request,'login.html')


# 如果已经登陆了 进入首页显示对应的用户名 反之进入登陆页
def index(request):
print(request.COOKIES)

is_login = request.COOKIES.get('is_login')

if is_login:
username = request.COOKIES.get('user_name')
return render(request,'index.html',{'user_name':username})
else:
return redirect('/login/')

cookie的参数

设置cookie失效时间

  • max_age 毫秒值(靠谱)
  • expires 指定某个时刻(不靠谱,客户端设置2099年)
1
2
3
4
5
6
7
8
9
10
11
12
response.set_cookie('is_login',True,max_age=15)
response.set_cookie('user_name', u.user)

# is_login 15秒后失效
# user_name不会15秒后失效 只要浏览器不关就会一直存在

# -------------------------
# 另一种时间设置 日期类型
# 过期时间
import datetime
date = datetime.datetime(year=2020,month=5,day=1,hour=14,minute=32,second=10)
response.set_cookie('xxx','111',expires=date)

有效路径设置

  • path 默认 ‘/‘就是根目录下所有路径都可以使用
1
2
response.set_cookie('user_name', u.user,path='/index/')
# 此时只有 路由index下的可以访问cookie user_name

django中使用session

Session是服务器端技术,利用这个技术,服务器在运行时可以 为每一个用户的浏览器创建一个其独享的session对象,由于 session为用户浏览器独享,所以用户在访问服务器的web资源时 ,可以把各自的数据放在各自的session中,当用户再去访问该服务器中的其它web资源时,其它web资源再从用户各自的session中 取出数据为用户服务

django中session语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1、设置Sessions值
request.session['session_name'] ="admin"
2、获取Sessions值
session_name = request.session["session_name"]
3、删除Sessions值
del request.session["session_name"]
4、flush()
删除当前的会话数据并删除会话的Cookie。
这用于确保前面的会话数据不可以再次被用户的浏览器访问
5、get(key, default=None)
fav_color = request.session.get('fav_color', 'red')
6、pop(key)
fav_color = request.session.pop('fav_color')
7、keys()
8、items()
9、setdefault()
10 用户session的随机字符串
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查 用户session的随机字符串 在数据库中是否
request.session.exists("session_key")

# 删除当前用户的所有Session数据
request.session.delete("session_key")

request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。

session 登陆实例

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def login_session(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")

user = UserInfo.objects.filter(user=user, pwd=pwd).first()

if user:
import datetime

now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 设置session
request.session["is_login"] = True
request.session["username"] = user.user
request.session["last_visit_time"] = now

'''
if request.COOKIE.get("sessionid"):
更新

在django—session表中创建一条记录:
session-key session-data
ltv8zy1kh5lxj1if1fcs2pqwodumr45t 更新数据
else:
1 生成随机字符串 ltv8zy1kh5lxj1if1fcs2pqwodumr45t
2 response.set_cookie("sessionid",ltv8zy1kh5lxj1if1fcs2pqwodumr45t)
3 在django—session表中创建一条记录:
session-key session-data
ltv8zy1kh5lxj1if1fcs2pqwodumr45t {"is_login":True,"username":"yuan"}

'''
return HttpResponse("登录成功!")

return render(request, "login.html")


def index_session(request):
print("is_login:", request.session.get("is_login"))

'''
1 request.COOKIE.get("session") # ltv8zy1kh5lxj1if1fcs2pqwodumr45t
2 django-session表中过滤纪录:
在django—session表中创建一条记录:
session-key session-data
ltv8zy1kh5lxj1if1fcs2pqwodumr45t {"is_login":True,"username":"yuan"}

obj=django—session.objects .filter(session-key=ltv8zy1kh5lxj1if1fcs2pqwodumr45t).first()

3 obj.session-data.get("is_login")
'''
is_login = request.session.get("is_login")
if not is_login:
return redirect("/login_session/")

username = request.session.get("username")
last_visit_time = request.session.get("last_visit_time")

return render(request, "index.html", {"username": username, "last_visit_time": last_visit_time})

# 删除session
def logout(request):
# del request.session["is_login"]

request.session.flush()

'''
1 randon_str=request.COOKIE.get("sessionid")
# 删除整条session记录
2 django-session.objects.filter(session-key=randon_str).delete()
# 同时删除cookie记录 那个sessionid
3 response.delete_cookie("sessionid",randon_str)

'''

return redirect("/login/")

session配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。

a. 配置 settings.py

SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)

SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 2*7*24*3600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)


# ------------------
SESSION_COOKIE_AGE = 2*7*24*3600 # 2周后过期
'''
假如2018-01-01登陆 15号失效
如果你设置了
SESSION_SAVE_EVERY_REQUEST = True
此时你 在 2018-01-02登陆了 则导致 16号失效
此时你 在 2018-01-03登陆了 则导致 17号失效

'''