Python复习教程(重点)
目录导航:
- 目录导航:
- 一、Python基础
- 二、Python基础
- 三、Web前端
- 1.1 Web前端开发介绍
- 1.2 HTML基础语法 (缺)
- 1.3 HTML常用标签介绍
- 1.4 CSS层叠样式表介绍
- 1.5 CSS的常用选择符
- 1.6 CSS常用属性
- 1.6.1 color颜色属性:
- 1.6.2 字体属性:font
- 1.6.3 文本属性:
- 1.6.4 背景属性:background
- 1.6.5 *边框(盒模型):
- 1.6.6 *内补白(内补丁)
- 1.6.7 *外补白(外补丁)
- 1.6.8 Position定位
- 1.6.9. Layout布局
- 1.6.10 Flexible Box 弹性盒子(了解见手册)
- 1.6.11. 用户界面 User Interface
- 1.6.12 多栏 Multi-column
- 1.6.13 表格相关属性
- 1.6.14 过渡 Transition:
- 1.6.15. 动画 Animation
- 1.6.16. 2D变换 2D Transform:
- 1.6.17. Media Queries Properties媒体查询
- 1.7 网页布局实战
- 1.8 JavaScript语言
- 1.8.1 JavaScript基础
- 1.8.2 JavaScript函数和对象
- 1.8.3 JavaScript的内置对象
- 1.8.4 JavaScript的事件
- 1.8.5 JavaScript的BOM
- 1.8.6 讲解HTML DOM
- 1.8.7 继续讲解DOM(重点讲XML DOM)
- 1.8.8 Ajax
- 1.9 jQuery
- 1.10 BootStrap案例实战
- 四、Django框架
- 五、Django商城项目开发(上)
- 六、Django商城项目开发(下)
- 七、Python网络爬虫基础(上)
- 八、Python网络爬虫基础(下)
- 九、Python网络爬虫进阶实战(上)
- 十、Python网络爬虫进阶实战(中)
- 十一、Python网络爬虫进阶实战(下)
一、Python基础
1.1 Python安装和使用
1.1.1 Python环境搭建
- Python可应用于多平台包括Windows、 Linux/Unix 和 Mac OS。
Python下载
- Python最新源码,二进制文档,新闻资讯等可以在Python的官网查看到:
- Python官网:http://www.python.org/
- 你可以在以下链接中下载 Python 的文档,你可以下载 HTML、PDF 和 PostScript 等格式的文档。
- Python文档下载地址:www.python.org/doc/
Unix & Linux 平台安装 Python:(源码式安装)
- 以下为在Unix & Linux 平台上安装 Python 的简单步骤:
- 打开WEB浏览器访问http://www.python.org/download/
- 选择适用于Unix/Linux的源码压缩包。
- 下载及解压压缩包。
- 如果你需要自定义一些选项修改Modules/Setup
- 执行 ./configure 脚本
- make
- make install
- 执行以上操作后,Python会安装在 /usr/local/bin 目录中,Python库安装在/usr/local/lib/pythonXX,XX为你使用的Python的版本号。
通过ubuntu官方的apt工具包安装
$ sudo apt-get install python
$ sudo apt-get install python2.7
$ sudo apt-get install python3.6
Mac安装Python3
$ brew sreach python
$ brew install python3
//在/usr/local/Cellar/这个目录下
Windows下直接下载安装就可以了
- 首先访问http://www.python.org/download/去下载最新的python版本
- 安装下载包,一路next,注意选择安装pip


- 为计算机添加安装目录搭到环境变量,如图把python的安装目录添加到pth系统变量中即可。

1.1.2 运行Python
- 有三种方式可以运行Python:
(1) 交互式解释器
- 你可以通过命令行窗口进入python并开在交互式解释器中开始编写Python代码。
- 你可以在Unix,DOS或任何其他提供了命令行或者shell的系统进行python编码工作。
$ python # Unix/Linux
或者
C:>python # Windows/DOS
- 以下为Python命令行参数:
| 选项 | 描述 |
|---|---|
| -d | 在解析时显示调试信息 |
| -O | 生成优化代码 ( .pyo 文件 ) |
| -S | 启动时不引入查找Python路径的位置 |
| -V | 输出Python版本号 |
| -X | 从 1.6版本之后基于内建的异常(仅仅用于字符串)已过时。 |
| -c cmd | 执行 Python 脚本,并将运行结果作为 cmd 字符串。 |
| file | 在给定的python文件执行python脚本。 |
(2) 命令行脚本
- 在你的应用程序中通过引入解释器可以在命令行中执行Python脚本,如下所示:
$ python script.py # Unix/Linux
或者
C:>python script.py # Windows/DOS
(3) 集成开发环境(IDE:Integrated Development Environment): PyCharm
- PyCharm 是由 JetBrains 打造的一款 Python IDE,支持 macOS、 Windows、 Linux 系统。
- PyCharm 功能 : 调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制……
- PyCharm 下载地址 : https://www.jetbrains.com/pycharm/download/

1.2 Python基础语法
1.2.1 输入和输出
input()输入:
- input()的小括号中放入的是,提示信息,用来在获取数据之前给用户的一个简单提示
- input()在从键盘获取了数据以后,会存放到等号右边的变量中
- input()会把用户输入的任何值都作为字符串来对待
- 注意:在python2中还有一个raw_input()输入,但到python3中没有了
#!/usr/bin/python3
str = input("请输入:");
print ("你输入的内容是: ", str)
- 这会产生如下的对应着输入的结果:
请输入:Hello Python!
你输入的内容是: Hello Python!
Print()输出:
- print 默认输出是换行的,如果要实现不换行需要在变量末尾加上 end="":
#!/usr/bin/python3
x="a"
y="b"
# 换行输出
print( x )
print( y )
print('---------')
# 不换行输出
print( x, end=" " )
print( y, end=" " )
print()
# 同时输出多个变量
print(x,y)
format的格式化函数(了解)
- 格式化字符串的函数 str.format(),它增强了字符串格式化的功能。
- 基本语法是通过 {} 和 : 来代替以前的 % 。
>>>"{} {}".format("hello", "world") # 不设置指定位置,按默认顺序
'hello world'
>>> "{0} {1}".format("hello", "world") # 设置指定位置
'hello world'
>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'
>>> print("网站名:{name}, 地址 {url}".format(name="百度", url="www.baidu.com")) #指定参数名
'网站名:百度, 地址 www.baidu.com'
>>>site = {"name": "百度", "url": "www.baidu.com"}
>>>print("网站名:{name}, 地址 {url}".format(**site)) # 通过字典设置参数
'网站名:百度, 地址 www.baidu.com'
>>>my_list = ['百度', 'www.baidu.com']
>>>print("网站名:{0[0]}, 地址 {0[1]}".format(my_list)) # "0" 是必须的 通过列表索引设置参数
'网站名:百度, 地址 www.baidu.com'
>>> print("{:.2f}".format(3.1415926)); #数字格式化
3.14
| 数字 | 格式 | 输出 | 描述 |
|---|---|---|---|
| 3.1415926 | 3.14 | 保留小数点后两位 | |
| 3.1415926 | +3.14 | 带符号保留小数点后两位 | |
| -1 | -1.00 | 带符号保留小数点后两位 | |
| 2.71828 | 3 | 不带小数 | |
| 5 | 05 | 数字补零 (填充左边, 宽度为2) | |
| 5 | 5xxx | 数字补x (填充右边, 宽度为4) | |
| 10 | 10xx | 数字补x (填充右边, 宽度为4) | |
| 1000000 | 1,000,000 | 以逗号分隔的数字格式 | |
| 0.25 | 25.00% | 百分比格式 | |
| 1000000000 | 1.00e+09 | 指数记法 | |
| 13 | 13 | 右对齐 (默认, 宽度为10) | |
| 13 | 13 | 左对齐 (宽度为10) | |
| 13 | 13 | 中间对齐 (宽度为10) | |
| 11 | '{:b}'.format(11) '{:d}'.format(11) '{😮}'.format(11) '{:x}'.format(11) '{:#x}'.format(11) '{:#X}'.format(11) | 1011 11 13 b 0xb 0XB | 进制 |
1.2.2 注释
Python中的注释有单行注释和多行注释:
- python中单行注释采用 # 开头。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:test.py
# 第一个注释
print "Hello, Python!"; # 第二个注释
输出结果:
Hello, Python!
注释可以在语句或表达式行末:
name = "Madisetti" # 这是一个注释
- python 中多行注释使用三个单引号(''')或三个双引号(""")。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:test.py
'''
这是多行注释,使用单引号。
这是多行注释,使用单引号。
这是多行注释,使用单引号。
'''
"""
这是多行注释,使用双引号。
这是多行注释,使用双引号。
这是多行注释,使用双引号。
"""
1.2.3 标识符
-
在
Python里,标识符: 由字母、数字、下划线组成,但不能以数字开头。 -
Python 中的标识符是区分大小写的。
-
特殊标识符:
- 以下划线开头的标识符是有特殊意义的。以单下划线开头
_foo的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用from xxx import *而导入; - 以双下划线开头的
__foo代表类的私有成员;以双下划线开头和结尾的__foo__代表 Python 里特殊方法专用的标识,如__init__()代表类的构造函数。
- 以下划线开头的标识符是有特殊意义的。以单下划线开头
-
python保留字: 保留字即关键字,我们不能把它们用作任何标识符名称。Python 的标准库提供了一个 keyword 模块,可以输出当前版本的所有关键字:
>>> import keyword >>> keyword.kwlist ['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'if', 'return','try', 'while', 'with', 'yield']
1.2.4 变量
- Python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
- 在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。
- 等号(=)用来给变量赋值。
- 等号(=)运算符左边是一个变量名,等号(=)运算符右边是存储在变量中的值。例如:
- 实例(Python 3.0+)
#!/usr/bin/python3
counter = 100 # 整型变量
miles = 1000.0 # 浮点型变量
name = "demo" # 字符串
print (counter)
print (miles)
print (name)
-
执行以上程序会输出如下结果:
100 1000.0 demo
多个变量赋值
-
Python允许你同时为多个变量赋值。例如:
a = b = c = 1 -
以上实例,创建一个整型对象,值为1,三个变量被分配到相同的内存空间上。
-
您也可以为多个对象指定多个变量。例如:
a, b, c = 1, 2, "demo" -
以上实例,两个整型对象 1 和 2 的分配给变量 a 和 b,字符串对象 "demo" 分配给变量 c。
1.2.5 行与缩进
- python最具特色的就是使用缩进来表示代码块,不需要使用大括号({})。
- 缩进的空格数是可变的,但是同一个代码块的语句必须包含相同的缩进空格数。实例如下:
if True:
print ("True")
else:
print ("False")
- 以下代码最后一行语句缩进数的空格数不一致,会导致运行错误:
if True:
print ("Answer")
print ("True")
else:
print ("Answer")
print ("False") # 缩进不一致,会导致运行错误
- 以上程序由于缩进不一致,执行后会出现类似以下错误:
File "test.py", line 6
print ("False") # 缩进不一致,会导致运行错误
^
IndentationError: unindent does not match any outer indentation level
多行语句
- Python 通常是一行写完一条语句,但如果语句很长,我们可以使用反斜杠()来实现多行语句,例如:
total = item_one + \
item_two + \
item_three
- 在 [], {}, 或 () 中的多行语句,不需要使用反斜杠(),例如:
total = ['item_one', 'item_two', 'item_three',
'item_four', 'item_five']
空行
- 函数之间或类的方法之间用空行分隔,表示一段新的代码的开始。类和函数入口之间也用一行空行分隔,以突出函数入口的开始。
- 空行与代码缩进不同,空行并不是Python语法的一部分。书写时不插入空行,Python解释器运行也不会出错。但是空行的作用在于分隔两段不同功能或含义的代码,便于日后代码的维护或重构。
- 记住:空行也是程序代码的一部分。
1.3 Python运算符
- 本章节主要说明Python的运算符。举个简单的例子 4 +5 = 9 。 例子中,4 和 5 被称为操作数,"+" 称为运算符。
- Python语言支持以下类型的运算符:
- 算术运算符
- 比较(关系)运算符
- 赋值运算符
- 逻辑运算符
- 位运算符
- 成员运算符
- 身份运算符
- 运算符优先级
(1) Python算术运算符
- 以下假设变量a为10,变量b为21:
| 运算符 | 描述 | 实例 |
|---|---|---|
| + | 加 - 两个对象相加 | a + b 输出结果 31 |
| - | 减 - 得到负数或是一个数减去另一个数 | a - b 输出结果 -11 |
| * | 乘 - 两个数相乘或是返回一个被重复若干次字串 | a * b 输出结果 210 |
| / | 除 - x 除以 y | b / a 输出结果 2.1 |
| % | 取模 - 返回除法的余数 | b % a 输出结果 1 |
| ** | 幂 - 返回x的y次幂 | a**b 为10的21次方 |
| // | 取整除 - 返回商的整数部分 | 9//2 输出结果 4 , 9.0//2.0 输出结果 4.0 |
(2) Python比较运算符
- 以下假设变量a为10,变量b为20:
| 运算符 | 描述 | 实例 |
|---|---|---|
| == | 等于 - 比较对象是否相等 | (a == b) 返回 False。 |
| != | 不等于 - 比较两个对象是否不相等 | (a != b) 返回 True。 |
| > | 大于 - 返回x是否大于y | (a > b) 返回 False。 |
| < | 小于 - 返回x是否小于y。返回1表示真,返回0表示假。 这分别与特殊的变量True和False等价。注意,这些变量名的大写。 | (a < b) 返回 True。 |
| >= | 大于等于 - 返回x是否大于等于y。 | (a >= b) 返回 False。 |
| <= | 小于等于 - 返回x是否小于等于y。 | (a <= b) 返回 True。 |
(3) Python赋值运算符
- 以下假设变量a为10,变量b为20:
| 运算符 | 描述 | 实例 |
|---|---|---|
| = | 简单的赋值运算符 | c = a + b 将 a + b 的运算结果赋值为 c |
| += | 加法赋值运算符 | c += a 等效于 c = c + a |
| -= | 减法赋值运算符 | c -= a 等效于 c = c - a |
*= |
乘法赋值运算符 | c *= a 等效于 c = c * a |
| /= | 除法赋值运算符 | c /= a 等效于 c = c / a |
| %= | 取模赋值运算符 | c %= a 等效于 c = c % a |
**= |
幂赋值运算符 | c **= a 等效于 c = c ** a |
| //= | 取整除赋值运算符 | c //= a 等效于 c = c // a |
(4) Python位运算符
- 按位运算符是把数字看作二进制来进行计算的。Python中的按位运算法则如下:
- 下表中变量 a 为 60,b 为 13二进制格式如下:
>>> a=18
>>> bin(a) # 将变量a的数值转成二进制数值输出
'0b10010'
>>> b = 0b10010 #将二进制的数值赋给变量b
>>> b
18
# 下面是二进制运算
a = 0011 1100
b = 0000 1101
-----------------
a&b = 0000 1100
a|b = 0011 1101
a^b = 0011 0001
~a = 1100 0011
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| l | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 | (a l b) 输出结果 61 ,二进制解释: 0011 1101 |
| ^ | 按位异或运算符:当两对应的二进位相异时,结果为1 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
| ~ | 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
| << | 左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
| >> | 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
(5) Python逻辑运算符
- Python语言支持逻辑运算符,以下假设变量 a 为 10, b为 20:
| 运算符 | 逻辑表达式 | 描述 | 实例 |
|---|---|---|---|
| and | x and y | 布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。 | (a and b) 返回 20。 |
| or | x or y | 布尔"或" - 如果 x 是 True,它返回 x 的值,否则它返回 y 的计算值。 | (a or b) 返回 10。 |
| not | not x | 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 | not(a and b) 返回 False |
(6) Python成员运算符
- 除了以上的一些运算符之外,Python还支持成员运算符,测试实例中包含了一系列的成员,包括字符串,列表或元组。
| 运算符 | 描述 | 实例 |
|---|---|---|
| in | 如果在指定的序列中找到值返回 True,否则返回 False。 | x 在 y 序列中 , 如果 x 在 y 序列中返回 True。 |
| not in | 如果在指定的序列中没有找到值返回 True,否则返回 False。 | x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。 |
#!/usr/bin/python3
a = 10
b = 20
list = [1, 2, 3, 4, 5 ];
if ( a in list ):
print ("1 - 变量 a 在给定的列表中 list 中")
else:
print ("1 - 变量 a 不在给定的列表中 list 中")
if ( b not in list ):
print ("2 - 变量 b 不在给定的列表中 list 中")
else:
print ("2 - 变量 b 在给定的列表中 list 中")
# 修改变量 a 的值
a = 2
if ( a in list ):
print ("3 - 变量 a 在给定的列表中 list 中")
else:
print ("3 - 变量 a 不在给定的列表中 list 中")
以上实例输出结果:
1 - 变量 a 不在给定的列表中 list 中
2 - 变量 b 不在给定的列表中 list 中
3 - 变量 a 在给定的列表中 list 中
(7) Python身份运算符
- 身份运算符用于比较两个对象的存储单元
| 运算符 | 描述 | 实例 |
|---|---|---|
| is | is是判断两个标识符是不是引用自一个对象 | x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False |
| is not | is not 是判断两个标识符是不是引用自不同对象 | x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果 True,否则返回 False。 |
- 注:
id()函数用于获取对象内存地址。
#!/usr/bin/python3
a = 20
b = 20
if ( a is b ):
print ("1 - a 和 b 有相同的标识")
else:
print ("1 - a 和 b 没有相同的标识")
if ( id(a) == id(b) ):
print ("2 - a 和 b 有相同的标识")
else:
print ("2 - a 和 b 没有相同的标识")
# 修改变量 b 的值
b = 30
if ( a is b ):
print ("3 - a 和 b 有相同的标识")
else:
print ("3 - a 和 b 没有相同的标识")
if ( a is not b ):
print ("4 - a 和 b 没有相同的标识")
else:
print ("4 - a 和 b 有相同的标识")
- 以上实例输出结果:
1 - a 和 b 有相同的标识
2 - a 和 b 有相同的标识
3 - a 和 b 没有相同的标识
4 - a 和 b 没有相同的标识
is 与 == 区别:
# is 用于判断两个变量引用对象是否为同一个, == 用于判断引用变量的值是否相等。
>>>a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b == a
True
>>> b = a[:] # 其中[:]表示复制传值
>>> b is a
False
>>> b == a
True
(8) Python运算符优先级
- 以下表格列出了从最高到最低优先级的所有运算符:
| 运算符 | 描述 |
|---|---|
| ** | 指数 (最高优先级) |
| ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) |
| * / % // | 乘,除,取模和取整除 |
| + - | 加法减法 |
| >> << | 右移,左移运算符 |
| & | 位 'AND' |
| ^ l | 位运算符 |
| <= < > >= | 比较运算符 |
| <> == != | 等于运算符 |
| = %= /= //= -= += *= **= | 赋值运算符 |
| is is not | 身份运算符 |
| in not in | 成员运算符 |
| not or and | 逻辑运算符 |
1.4 Python数据类型
1.4.1 标准数据类型
- Python3 中有六个标准的数据类型:
- Number(数字)
- int
- bool
- float
- complex(复数)
- String(字符串)
- List(列表)
- Tuple(元组)
- Sets(集合)
- Dictionary(字典)
- Number(数字)
(1) Number(数字)
- Python3 支持 int、float、bool、complex(复数)。
- 在Python 3里,只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。
- 像大多数语言一样,数值类型的赋值和计算都是很直观的。
- 内置的 type() 函数可以用来查询变量所指的对象类型。
>>> a, b, c, d = 20, 5.5, True, 4+3j
>>> print(type(a), type(b), type(c), type(d))
<class 'int'> <class 'float'> <class 'bool'> <class 'complex'>
- 此外还可以用 isinstance 来判断:
>>>a = 111
>>> isinstance(a, int)
True
>>>
- isinstance 和 type 的区别在于:
class A:
pass
class B(A):
pass
isinstance(A(), A) # returns True
type(A()) == A # returns True
isinstance(B(), A) # returns True
type(B()) == A # returns False
- 区别就是:
- type()不会认为子类是一种父类类型。
- isinstance()会认为子类是一种父类类型。
注意:在 Python2 中是没有布尔型的,它用数字 0 表示 False,用 1 表示 True。
到 Python3 中,把 True 和 False 定义成关键字了,但它们的值还是 1 和 0,它们可以和数字相加。
- 当你指定一个值时,Number 对象就会被创建:
var1 = 1
var2 = 10
- 您也可以使用del语句删除一些对象引用。
- del语句的语法是:
- del var1[,var2[,var3[....,varN]]]]
- 您可以通过使用del语句删除单个或多个对象。例如
del var
del var_a, var_b
整数的进制:
# 输出其他进制数值
>>> bin(255) #255的二进制
'0b11111111'
>>> oct(255) #255的八进制
'0o377'
>>> hex(255) #255的十六进制
'0xff'
>>> a=0b10 #赋值二进制数值
>>> a
2
>>> a=0o10 #赋值八进制数值
>>> a
8
>>> a=0x10 #赋值十六进制数值
>>> a
16
(2) String(字符串)
- Python中的字符串用单引号(')或双引号(")括起来,同时使用反斜杠()转义特殊字符。
- 字符串的截取的语法格式如下:
变量[头下标:尾下标]
- 索引值以 0 为开始值,-1 为从末尾的开始位置。
- 加号 (+) 是字符串的连接符, 星号 (*) 表示复制当前字符串,紧跟的数字为复制的次数。实例如下:
#!/usr/bin/python3
str = 'zhangsan'
print (str) # 输出字符串
print (str[0:-1]) # 输出第一个到倒数第二个的所有字符
print (str[0]) # 输出字符串第一个字符
print (str[2:5]) # 输出从第三个开始到第五个的字符
print (str[2:]) # 输出从第三个开始的后的所有字符
print (str * 2) # 输出字符串两次
print (str + "TEST") # 连接字符串
- 输出结果:
zhangsan
zhangsa
z
ang
angsan
zhangsanzhangsan
zhangsanTEST
- Python 使用反斜杠()转义特殊字符,如果你不想让反斜杠发生转义,可以在字符串前面添加一个
r,表示原始字符串:
>>> print('Ru\noob')
Ru
oob
>>> print(r'Ru\noob')
Ru\noob
>>>
- 另外,反斜杠()可以作为续行符,表示下一行是上一行的延续。也可以使用 """...""" 或者 '''...''' 跨越多行。
- 注意,Python 没有单独的字符类型,一个字符就是长度为1的字符串。
>>>word = 'Python'
>>> print(word[0], word[5])
P n
>>> print(word[-1], word[-6])
n P
- 与 C 字符串不同的是,Python 字符串不能被改变。向一个索引位置赋值,比如word[0] = 'm'会导致错误。
- 注意:
- 1、反斜杠可以用来转义,使用
r可以让反斜杠不发生转义。 - 2、字符串可以用+运算符连接在一起,用
*运算符重复。 - 3、Python中的字符串有两种索引方式,从左往右以0开始,从右往左以-1开始。
- 4、Python中的字符串不能改变。
- 1、反斜杠可以用来转义,使用
(3) List(列表)
- List(列表) 是 Python 中使用最频繁的数据类型。
- 列表可以完成大多数集合类的数据结构实现。列表中元素的类型可以不相同,它支持数字,字符串甚至可以包含列表(所谓嵌套)。
- 列表是写在方括号
[]之间、用逗号分隔开的元素列表。 - 和字符串一样,列表同样可以被索引和截取,列表被截取后返回一个包含所需元素的新列表。
- 列表截取的语法格式如下:
变量[头下标:尾下标]
- 索引值以 0 为开始值,-1 为从末尾的开始位置。
- 加号(+)是列表连接运算符,星号(*)是重复操作。如下实例:
#!/usr/bin/python3
list = [ 'abcd', 786 , 2.23, 'demo', 70.2 ]
tinylist = [123, 'demo']
print (list) # 输出完整列表
print (list[0]) # 输出列表第一个元素
print (list[1:3]) # 从第二个开始输出到第三个元素
print (list[2:]) # 输出从第三个元素开始的所有元素
print (tinylist * 2) # 输出两次列表
print (list + tinylist) # 连接列表
-
以上实例输出结果:
['abcd', 786, 2.23, 'demo', 70.2] abcd [786, 2.23] [2.23, 'demo', 70.2] [123, 'demo', 123, 'demo'] ['abcd', 786, 2.23, 'demo', 70.2, 123, 'demo'] -
与Python字符串不一样的是,列表中的元素是可以改变的:
>>>a = [1, 2, 3, 4, 5, 6]
>>> a[0] = 9
>>> a[2:5] = [13, 14, 15]
>>> a
[9, 2, 13, 14, 15, 6]
>>> a[2:5] = [] # 将对应的元素值设置为 []
>>> a
[9, 2, 6]
- List内置了有很多方法,例如append()、pop()等等,这在后面会讲到。
*注意:
* 1、List写在方括号之间,元素用逗号隔开。
* 2、和字符串一样,list可以被索引和切片。
* 3、List可以使用+操作符进行拼接。
* 4、List中的元素是可以改变的。
(4) Tuple(元组)
- 元组(tuple)与列表类似,不同之处在于元组的元素不能修改。元组写在小括号(())里,元素之间用逗号隔开。
- 元组中的元素类型也可以不相同:
#!/usr/bin/python3
tuple = ( 'abcd', 786 , 2.23, 'demo', 70.2 )
tinytuple = (123, 'demo')
print (tuple) # 输出完整元组
print (tuple[0]) # 输出元组的第一个元素
print (tuple[1:3]) # 输出从第二个元素开始到第三个元素
print (tuple[2:]) # 输出从第三个元素开始的所有元素
print (tinytuple * 2) # 输出两次元组
print (tuple + tinytuple) # 连接元组
-
以上实例输出结果:
('abcd', 786, 2.23, 'demo', 70.2) abcd (786, 2.23) (2.23, 'demo', 70.2) (123, 'demo', 123, 'demo') ('abcd', 786, 2.23, 'demo', 70.2, 123, 'demo') -
元组与字符串类似,可以被索引且下标索引从0开始,-1 为从末尾开始的位置。
-
也可以进行截取(看上面,这里不再赘述)。
-
其实,可以把字符串看作一种特殊的元组。
>>>tup = (1, 2, 3, 4, 5, 6)
>>> print(tup[0])
1
>>> print(tup[1:5])
(2, 3, 4, 5)
>>> tup[0] = 11 # 修改元组元素的操作是非法的
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>>
- 虽然tuple的元素不可改变,但它可以包含可变的对象,比如list列表。
- 构造包含 0 个或 1 个元素的元组比较特殊,所以有一些额外的语法规则:
tup1 = () # 空元组
tup2 = (20,) # 一个元素,需要在元素后添加逗号
string、list和tuple都属于sequence(序列)。
- 注意:
- 1、与字符串一样,元组的元素不能修改。
- 2、元组也可以被索引和切片,方法一样。
- 3、注意构造包含0或1个元素的元组的特殊语法规则。
- 4、元组也可以使用+操作符进行拼接。
(5) Set(集合)
- 集合(set)是一个无序不重复元素的序列。
- 基本功能是进行成员关系测试和删除重复元素。
- 可以使用大括号
{ }或者set()函数创建集合,注意:创建一个空集合必须用set()而不是{ },因为{ }是用来创建一个空字典。 - 创建格式:
parame = {value01,value02,...}
或者
set(value)
- 实例:
#!/usr/bin/python3
student = {'Tom', 'Jim', 'Mary', 'Tom', 'Jack', 'Rose'}
print(student) # 输出集合,重复的元素被自动去掉
# 成员测试
if('Rose' in student) :
print('Rose 在集合中')
else :
print('Rose 不在集合中')
# set可以进行集合运算
a = set('abracadabra')
b = set('alacazam')
print(a)
print(a - b) # a和b的差集
print(a | b) # a和b的并集
print(a & b) # a和b的交集
print(a ^ b) # a和b中不同时存在的元素
- 以上实例输出结果:
{'Mary', 'Jim', 'Rose', 'Jack', 'Tom'}
Rose 在集合中
{'b', 'a', 'c', 'r', 'd'}
{'b', 'd', 'r'}
{'l', 'r', 'a', 'c', 'z', 'm', 'b', 'd'}
{'a', 'c'}
{'l', 'r', 'z', 'm', 'b', 'd'}
(6) Dictionary(字典)
- 字典(dictionary)是Python中另一个非常有用的内置数据类型。
- 列表是有序的对象结合,字典是无序的对象集合。两者之间的区别在于:字典当中的元素是通过键来存取的,而不是通过偏移存取。
- 字典是一种映射类型,字典用"{ }"标识,它是一个无序的键(key) : 值(value)对集合。
- 键(key)必须使用不可变类型。
- 在同一个字典中,键(key)必须是唯一的。
#!/usr/bin/python3
dict = {}
dict['one'] = "1 - Python教程"
dict[2] = "2 - Python工具"
tinydict = {'name': 'demo','code':1, 'site': 'www.demo.com'}
print (dict['one']) # 输出键为 'one' 的值
print (dict[2]) # 输出键为 2 的值
print (tinydict) # 输出完整的字典
print (tinydict.keys()) # 输出所有键
print (tinydict.values()) # 输出所有值
以上实例输出结果:
1 - Python教程
2 - Python工具
{'name': 'demo', 'site': 'www.demo.com', 'code': 1}
dict_keys(['name', 'site', 'code'])
dict_values(['demo', 'www.demo.com', 1])
- 构造函数 dict() 可以直接从键值对序列中构建字典如下:
- 实例
>>>dict([('demo', 1), ('Google', 2), ('Taobao', 3)])
{'Taobao': 3, 'demo': 1, 'Google': 2}
>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}
>>> dict(demo=1, Google=2, Taobao=3)
{'Taobao': 3, 'demo': 1, 'Google': 2}
- 另外,字典类型也有一些内置的函数,例如clear()、keys()、values()等。
- 注意:
- 1、字典是一种映射类型,它的元素是键值对。
- 2、字典的关键字必须为不可变类型,且不能重复。
- 3、创建空字典使用 { }。
1.4.2 Python数据类型转换
- 有时候,我们需要对数据内置的类型进行转换,数据类型的转换,你只需要将数据类型作为函数名即可。
- 以下几个内置的函数可以执行数据类型之间的转换。这些函数返回一个新的对象,表示转换的值
| 函数 | 描述 |
|---|---|
| int(x [,base]) | 将x转换为一个整数 |
| float(x) | 将x转换到一个浮点数 |
| complex(real [,imag]) | 创建一个复数 |
| str(x) | 将对象 x 转换为字符串 |
| repr(x) | 将对象 x 转换为表达式字符串 |
| eval(str) | 用来计算在字符串中的有效Python表达式,并返回一个对象 |
| tuple(s) | 将序列 s 转换为一个元组 |
| list(s) | 将序列 s 转换为一个列表 |
| set(s) | 转换为可变集合 |
| dict(d) | 创建一个字典。d 必须是一个序列 (key,value)元组。 |
| frozenset(s) | 转换为不可变集合 |
| chr(x) | 将一个整数转换为一个字符 |
| unichr(x) | 将一个整数转换为Unicode字符 |
| ord(x) | 将一个字符转换为它的整数值 |
| hex(x) | 将一个整数转换为一个十六进制字符串 |
| oct(x) | 将一个整数转换为一个八进制字符串 |
数据类型转换分类:
- 数据类型转换一共分为2类:自动数据类型转换(隐式转换)和强制数据类型转换(显示转换)
自动数据类型转换/隐式转换
- 自动类型转换是程序根据运算要求进行的转换,不需要人工干预 1.自动类型转换不需要人工干预 2.自动类型转换多发生在运算或者判断过程中 3.转化时向着更加精确的类型转换
强制类型转换/显示转换
- 根据程序需要,由编写程序人员人为改变数据类型的方式就是强制数据类型转换。
- int() 将其他类型转化为整型
1.数字整型转化之后,还是原来的味道
2.浮点类型转化之后,舍去小数部分
3.布尔值转化之后 True -> 1 False->0
4.字符串转换,仅纯整型字符串可以转化(浮点型或者带有其他字符都不可以转化)
5.复数不可以转换
- float() 将其他类型转化为浮点型
1.整型转换之后变为浮点型,后面+.0
2.浮点数不需要转化,转化也不会变化
3.布尔值转化 True->1.0 False ->0.0
4.字符串,纯整型字符串和纯浮点型字符串可以转换,其他都不可以
- complex() 将其他数据转化为复数
1.整型转换之后变为 (整型+0j)
2.浮点型转换之后变为(浮点型 + 0j)
3.布尔值转化之后 True->(1+0j) False(0j)
4.字符串,纯整型和浮点型字符串可以转化,其他都不行
5.复数,无需转换
- bool() 将其他类型转化为布尔值
#下面转化为布尔值false的情况
1.整型 0
2.浮点型 0.0
3.复数 0+0j
4.布尔 False
5.字符串 '' 空字符串
6.列表 [] 空列表
7.元组 ()空元组
8.字典 {} 空字典
9.集合 set() 空集合
- str() 将其他类型转化为字符串
- 所有转换均改变类型为字符串,表示方式依旧不变
- list() 将其他类型转化为列表类型
- 在python中有5中可迭代序列,可以互相转换,他们分别是:
- 字符串,列表,元组,字典,集合
var = ('张三','李四','王老五')
newvar = list(var)
newvar的值为 ['张三','李四','王老五']
注意:- 1.字符串转换时每个字符变成列表中的一个值
- 2.字典类型转换时,仅将字典的键部分转换成列表,忽略值部分
- tuple() 将其他类型转化为元组类型
var = {'张三','李四','王老五'}
newvar = tuple(var)
newvar的值为 ('张三','李四','王老五')
-
注意:- 1.字符串转换时每个字符变成元组中的一个值
- 2.字典类型转换时,仅将字典的键部分转换成元组,忽略值部分
-
set() 将其他类型转化为集合类型
var = ['张三','李四','王老五']
newvar = set(var)
newvar的值为 {'张三','李四','王老五'} #值的顺序不定
-
注意:
- 1.字符串转换时每个字符变成集合中的一个值
- 2.字典类型转换时,仅将字典的键部分转换集合,忽略值部分
-
dict() 将其他类型转换为字典类型
-
其他类型转化为字典时需要按照指定的格式才可以转化:(列表和元组的组合可以)
[['cat', '黑猫警长'], ['mouse', '一只耳'], ['next', '请看夏季']]
[('cat', '黑猫警长'), ('mouse', '一只耳'), ('next', '请看夏季')]
1.5 Python分支结构
1.5.1 流程控制
流程: 计算机执行代码的顺序就是流程流程控制: 对计算机代码执行顺序的管理就是流程控制流程分类: 流程控制一共分为三类:- 顺序结构
- 分支结构/选择结构
- 循环结构
1.5.2 分支/选择结构
- 分支结构一共分为4类:
- 单项分支
- 双项分支
- 多项分支
- 巢状分支
(1) 单项分支
if 条件表达式:
一条python语句...
一条python语句...
...
- 特征:
- if条件表达式结果为真,则执行if之后所控制代码组,如果为假,则不执行后面的代码组(
:后面的N行中有相同缩进的代码) :之后下一行的内容必须缩进,否则语法错误!- if之后的代码中如果缩进不一致,则不会if条件表达式是的控制,也不是单项分支的内容,是顺序结构的一部分
- if
:后面的代码是在条件表达式结果为真的情况下执行,所以称之为真区间或者if区间、
- if条件表达式结果为真,则执行if之后所控制代码组,如果为假,则不执行后面的代码组(
(2) 双项分支
if 条件表达式:
一条python语句...
一条python语句...
...
else:
一条python语句...
一条python语句...
...
- 特征:
- 1.双项分支有2个区间:分别是True控制的if区间和False控制的else区间(假区间)
- 2.if区间的内容在双项分支中必须都缩进,否则语法错误!
(2) 多项分支
if 条件表达式:
一条python语句...
一条python语句...
...
elif 条件表达式:
一条python语句...
一条python语句...
...
elif 条件表达式:
一条python语句...
一条python语句...
...
...
else:
一条python语句...
一条python语句...
- 特征:
- 1.多项分支可以添加无限个elif分支,无论如何只会执行一个分支
- 2.执行完一个分支后,分支结构就会结束,后面的分支都不会判断也不会执行
- 3.多项分支的判断顺序是自上而下逐个分支进行判断
- 4.在Python中没有switch – case语句。
- 实例-演示了狗的年龄计算判断:
#!/usr/bin/python3
age = int(input("请输入你家狗狗的年龄: "))
print("")
if age < 0:
print("你是在逗我吧!")
elif age == 1:
print("相当于 14 岁的人。")
elif age == 2:
print("相当于 22 岁的人。")
elif age > 2:
human = 22 + (age -2)*5
print("对应人类年龄: ", human)
### 退出提示
input("点击 enter 键退出")
(4) 巢状分支
- 巢状分支是其他分支结构的嵌套结构,无论哪个分支都可以嵌套
# !/usr/bin/python3
num=int(input("输入一个数字:"))
if num%2==0:
if num%3==0:
print ("你输入的数字可以整除 2 和 3")
else:
print ("你输入的数字可以整除 2,但不能整除 3")
else:
if num%3==0:
print ("你输入的数字可以整除 3,但不能整除 2")
else:
print ("你输入的数字不能整除 2 和 3")
- 将以上程序保存到 test_if.py 文件中,执行后输出结果为:
$ python3 test.py
输入一个数字:6
你输入的数字可以整除 2 和 3
1.6 Python循环结构
- 循环结构就是为了将相似或者相同的代码操作变得更见简洁,使得代码可以重复利用
- 循环结构分为2类:
while循环 和for..in循环
1.6.1 while型循环
格式1:
while 条件表达式:
循环的内容
[变量的变化]
格式2:
while 条件表达式:
循环的内容
[变量的变化]
else:
python语句..
- 注意:while循环中的else是在while条件表达式为假的情况下执行的代码内容,一般用于判断起始条件是否为假等相关操作。
- 实例使用了 while 来计算 1 到 100 的总和:
#!/usr/bin/env python3
n = 100
sum = 0
counter = 1
while counter <= n:
sum = sum + counter
counter += 1
print("1 到 %d 之和为: %d" % (n,sum))
- 执行结果如下:
1 到 100 之和为: 5050
死循环:
- 死循环就是循环不会终止的循环类型,通过将用于判断的条件表达式设置为永远为True来实现。
while True:
python代码...
python代码...
...
#!/usr/bin/python3
var = 1
while var == 1 : # 表达式永远为 true
num = int(input("输入一个数字 :"))
print ("你输入的数字是: ", num)
print ("Good bye!")
- 你可以使用 CTRL+C 来退出当前的无限循环
- 执行以上脚本,输出结果如下:
输入一个数字 :5
你输入的数字是: 5
输入一个数字 :
1.6.2 for ... in 循环
- for...in 循环用于遍历容器类的数据(字符串,列表,元组,字典,集合)
格式:
for 变量 in 容器:
python代码,可以在此使用变量
格式2:
for 变量1,变量2 in 容器:
python代码,可以在此使用变量1和变量2
- 要求遍历的容器必须是一下几种格式:
- [(),(),()] 列表中有元组
- [[],[],[]] 列表中有列表
- ((),(),()) 元组中有元组
- {(),(),()} 集合中有元组
- 字典的特殊使用
格式3:
for 变量 in 容器:
python代码,可以在此使用变量
else:
循环结束是执行的代码!
>>>languages = ["C", "C++", "Perl", "Python"]
>>> for x in languages:
... print (x)
...
C
C++
Perl
Python
>>>
1.6.3 range()函数
- 如果你需要遍历数字序列,可以使用内置range()函数。它会生成数列,例如:
>>>for i in range(5):
... print(i)
...
0
1
2
3
4
>>>for i in range(5,9) :
print(i)
5
6
7
8
>>>
>>>for i in range(0, 10, 3) :
print(i)
0
3
6
9
>>>
>>>for i in range(-10, -100, -30) :
print(i)
-10
-40
-70
>>>
- 您可以结合range()和len()函数以遍历一个序列的索引,如下所示:
>>>a = ['Google', 'Baidu', 'Runoob', 'Taobao', 'QQ']
>>> for i in range(len(a)):
... print(i, a[i])
...
0 Google
1 Baidu
2 Runoob
3 Taobao
4 QQ
>>>
输出乘法口诀:
for i in range(1,10):
for j in range(1,i+1):
print(str(i)+'*'+str(j)+"="+str(i*j),end="")
print()
1.3.7 练习:逆向输出乘法口诀
for i in range(9,0,-1):
for j in range(i,0,-1):
print(str(i)+'*'+str(j)+"="+str(i*j),end="\t")
print()
1.6.4 break和continue语句及循环中的else子句
break语句:
- break作用:在循环中break的作用是终止当前循环结构的后续操作,一旦程序运行了break,循环也就终止了!
- break 语句可以跳出 for 和 while 的循环体。如果你从 for 或 while 循环中终止,任何对应的循环 else 块将不执行。 实例如下:
#!/usr/bin/python3
for letter in 'Runoob': # 第一个实例
if letter == 'b':
break
print ('当前字母为 :', letter)
var = 10 # 第二个实例
while var > 0:
print ('当期变量值为 :', var)
var = var -1
if var == 5:
break
print ("Good bye!")
- 执行以上脚本输出结果为:
当前字母为 : R
当前字母为 : u
当前字母为 : n
当前字母为 : o
当前字母为 : o
当期变量值为 : 10
当期变量值为 : 9
当期变量值为 : 8
当期变量值为 : 7
当期变量值为 : 6
Good bye!
continue语句:
- continue语句被用来告诉Python跳过当前循环块中的剩余语句,然后继续进行下一轮循环。
#!/usr/bin/python3
for letter in 'Runoob': # 第一个实例
if letter == 'o': # 字母为 o 时跳过输出
continue
print ('当前字母 :', letter)
var = 10 # 第二个实例
while var > 0:
var = var -1
if var == 5: # 变量为 5 时跳过输出
continue
print ('当前变量值 :', var)
print ("Good bye!")
- 执行以上脚本输出结果为:
当前字母 : R
当前字母 : u
当前字母 : n
当前字母 : b
当前变量值 : 9
当前变量值 : 8
当前变量值 : 7
当前变量值 : 6
当前变量值 : 4
当前变量值 : 3
当前变量值 : 2
当前变量值 : 1
当前变量值 : 0
Good bye!
- 循环语句可以有 else 子句,它在穷尽列表(以for循环)或条件变为 false (以while循环)导致循环终止时被执行,但循环被break终止时不执行。
- 如下实例用于查询质数的循环例子:
#!/usr/bin/python3
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, '等于', x, '*', n//x)
break
else:
# 循环中没有找到元素
print(n, ' 是质数')
- 执行以上脚本输出结果为:
2 是质数
3 是质数
4 等于 2 * 2
5 是质数
6 等于 2 * 3
7 是质数
8 等于 2 * 4
9 等于 3 * 3
pass 语句:
- Python pass是空语句,是为了保持程序结构的完整性。
- pass 不做任何事情,一般用做占位语句,如下实例
>>>while True:
... pass # 等待键盘中断 (Ctrl+C)
最小的类:
>>>class MyEmptyClass:
... pass
1.7 Python函数
1.7.1 认识Python函数
- 函数的本质就是功能的封装。使用函数可以大大提高编程效率与程序的可读性。
- 函数是能够实现特定功能的计算机代码而已,他是一种特定的代码组结构。
- 函数的作用
- 1.提升代码的重复利用率,避免重复开发相同代码
- 2.提高程序开发效率
- 3.便于程序维护
定义一个函数:
- 你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
1.7.2 函数的定义格式
- 基本函数格式
- 带有参数的函数格式
- 带有默认值的参数
- 关键字参数
- 收集参数(带*)
- 多种参数混合
(1) 基本函数格式
def 函数名():
函数功能代码...
函数功能代码...
...
调用函数: 函数名()
- 特征:函数定义之后不会自动执行,必须在调用函数后函数才会执行.
- 函数名的命名规则:和变量基本一样
- 1.推荐使用英文或者拼音,禁止使用中文
- 2.可以使用数字,但是不能用数字开头
- 3.不可以使用特殊字符,除了_
- 4.函数名严格区分大小写
- 5.函数名必须要有意义。
- 6.不能和系统已经存在的保留关键字冲突!
- 7.禁止使用和系统提供函数相同的函数名
- 让我们使用函数来输出"Hello World!":
>>> def hello() :
print("Hello World!")
>>> hello()
Hello World!
>>>
(2) 带有参数的函数格式
def 函数名(参数,参数...):
函数功能代码...
函数功能代码...
...
调用函数:函数名(参数,参数...)
形参:形式上的参数,声明函数时()中的参数是形参
实参:实际上的参数,调用函数时()中的参数是实参
注意:实参将值传递给形参的过程本质上就是简单的变量赋值仅此而已
- 更复杂点的应用,函数中带上参数变量:
#!/usr/bin/python3
# 计算面积函数
def area(width, height):
return width * height
def print_welcome(name):
print("Welcome", name)
print_welcome("Python")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
- 以上实例输出结果:
Welcome Python
width = 4 height = 5 area = 20
- 参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
- 调用printme()函数,你必须传入一个参数,不然会出现语法错误:
#!/usr/bin/python3
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str);
return;
#调用printme函数
printme();
- 以上实例输出结果:
Traceback (most recent call last):
File "test.py", line 10, in <module>
printme();
TypeError: printme() missing 1 required positional argument: 'str'
(3) 带有默认值的参数
def 函数名(形参=默认值,形参=默认值...):
函数功能代码...
函数功能代码...
...
调用函数:
函数名() 调用函数时所有形参采用默认值操作
函数名(实参,实参...) 调用时形参使用实参的值而抛弃默认值
- 注意:在此情况下使用实参值覆盖原有形参的默认值,本质上就是变量的重新赋值操作
- 调用函数时,如果没有传递参数,则会使用默认参数。以下实例中如果没有传入 age 参数,则使用默认值:
#!/usr/bin/python3
#可写函数说明
def printinfo( name, age = 35 ):
"打印任何传入的字符串"
print ("名字: ", name);
print ("年龄: ", age);
return;
#调用printinfo函数
printinfo( age=50, name="runoob" );
print ("------------------------")
printinfo( name="runoob" );
- 输出结果
名字: runoob
年龄: 50
------------------------
名字: runoob
年龄: 35
(4) 关键字参数
def 函数名(形参=默认值,形参=默认值...):
函数功能代码...
函数功能代码...
...
调用函数:函数名(形参=实参,形参=实参...)
- 关键字参数就是调用函数时,在实参前面指定形参的做法,为了防止参数按照位置传递出现的错误
- 关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
- 使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
- 以下实例在函数 printme() 调用时使用参数名:
#!/usr/bin/python3
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print (str);
return;
#调用printme函数
printme( str = "Python教程");
- 以上实例输出结果:
Python教程
- 以下实例中演示了函数参数的使用不需要使用指定顺序:
#!/usr/bin/python3
#可写函数说明
def printinfo( name, age ):
"打印任何传入的字符串"
print ("名字: ", name);
print ("年龄: ", age);
return;
#调用printinfo函数
printinfo( age=50, name="runoob" );
- 以上实例输出结果:
名字: runoob
年龄: 50
(5). 收集参数
- 1.非关键字收集参数
def 函数名(*参数名):
函数功能代码...
函数功能代码...
...
调用函数:函数名(实参,实参...) 没有数量限制
- 特征:
- 1.非关键字收集参数,在形参前添加一个*即可
- 2.非关键字收集参数收集实参组成一个元组
- 3.非关键字收集参数,仅收集没有任何形参接受的非关键字实参
- 4.非关键字收集参数和普通的形参可以共存
#!/usr/bin/python3
# 可写函数说明
def printinfo( arg1, *vartuple ):
"打印任何传入的参数"
print ("输出: ")
print (arg1)
for var in vartuple:
print (var)
return;
# 调用printinfo 函数
printinfo( 10 );
printinfo( 70, 60, 50 );
- 以上实例输出结果:
输出:
10
输出:
70
60
50
- 2.关键字收集参数
def 函数名(**参数名):
函数功能代码...
函数功能代码...
...
调用函数: 函数名(形参=实参,形参=实参...) 没有数量限制
- 特征:
- 1.关键字收集参数,在形参前添加两个**即可
- 2.关键字收集参数,收集的结果组成一个字典,关键字成为字典的键,实参成为值
- 3.关键字收集参数,仅收集没有任何形参接受的关键字参数
- 4.关键字参数可以和普通的形参共存
#定义
def func(country,province,**kwargs):
print(country,province,kwargs)
#使用
func("China","Sichuan",city = "Chengdu", section = "JingJiang")
# 结果
# China Sichuan {'city': 'Chengdu', 'section': 'JingJiang'}
(6) 多种参数混合
- 定义函数时尽量避免多种参数格式混合(普通参数/非关键字参数,关键字参数,非关键字收集参数,关键字收集参数)
- 1.普通参数(非关键字参数)和关键字参数必须在两种收集参数之前
- 2.非关键字收集参数,必须在关键字收集参数之前
- 3.如果多种参数在一起,必须注意进制参数多次赋值操作(相同赋值赋值之后,关键字参数在此赋值!)
**关于返回值的问题
- 函数根据执行完毕是否可以得到一个结果,将函数分为2个类型:
- 执行过程函数:print()
- 函数执行完毕之后,不会有任何结果可以被接受的函数。
- 具有返回值的函数:id(),type()
- 函数执行完毕之后,会产生一个结果,可以被变量接受或者使用的函数
- 执行过程函数:print()
格式:
def 函数名(参数....):
函数功能代码...
函数功能代码...
...
[return 语句]
- return的特征:
- 1.具有return语句的函数称为具有返回值的函数
- 2.return可以为当前函数执行完毕返回一个结果,这样的函数调用可以被接受
- 3.return执行之后,函数则会终止,所有return之后的语句不会被执行
- 4.一个函数可以书写多个return语句,但是一般会放入分支结构当中。
- 5.一个函数如果需要返回多个数据,需要借助复合数据类型(list,tuple,set,dict)来操作即可!
- 6.不带参数值的return语句返回None。之前的例子都没有示范如何返回数值.
#!/usr/bin/python3
# 可写函数说明
def sum( arg1, arg2 ):
# 返回2个参数的和."
total = arg1 + arg2
print ("函数内 : ", total)
return total;
# 调用sum函数
total = sum( 10, 20 );
print ("函数外 : ", total)
- 以上实例输出结果:
函数内 : 30
函数外 : 30
1.7.3 函数文档
- 函数文档就是用来查看当前函数相关信息介绍的一个特定格式而已。
- 查看函数文档的方法:
help(函数名称)
直接输出显示函数文档的内容字符串
函数名.__doc__
直接输出显示函数文档的内容元字符串(转义字符不转义)
- 定义函数的文档:
def 函数名(参数...):
'''
在此处声明函数文档
'''
函数功能代码...
函数功能代码...
。。。
或者:
def 函数名(参数...):
"""
在此处声明函数文档
"""
函数功能代码...
函数功能代码...
。。。
- 注意:函数文档的作用是对函数进行说明,便于阅读和快速掌握函数的使用,通常函数文档需要具有以下信息:
- 函数的作用
- 函数的参数介绍(个数,数据类型)
- 函数的返回值(数据和类型)
1.7.4 局部变量与全局变量
- 定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
- 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
#!/usr/bin/python3
total = 0; # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
#返回2个参数的和."
total = arg1 + arg2; # total在这里是局部变量.
print ("函数内是局部变量 : ", total)
return total;
#调用sum函数
sum( 10, 20 );
print ("函数外是全局变量 : ", total)
- 以上实例输出结果:
函数内是局部变量 : 30
函数外是全局变量 : 0
1.7.5 函数其他使用
(1) 匿名函数
- python 使用 lambda 来创建匿名函数。
- 所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
- 语法:
- lambda 函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
- 如下实例:
#!/usr/bin/python3
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2;
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
- 以上实例输出结果:
相加后的值为 : 30
相加后的值为 : 40
1.8 Python数据类型的操作
1.8.1 Number数字
- 之前我们已经介绍了Nember类型的基本操作,下面介绍他们的操作函数(了解)
数学函数
| 函数 | 返回值 ( 描述 ) |
|---|---|
| abs(x) | 返回数字的绝对值,如abs(-10) 返回 10 |
| ceil(x) | 返回数字的上入整数,如math.ceil(4.1) 返回 5 |
| cmp(x, y) | 如果 x < y 返回 -1, 如果 x == y 返回 0, 如果 x > y 返回 1。 Python 3 已废弃 。使用 使用 (x>y)-(x<y) 替换。 |
| exp(x) | 返回e的x次幂(ex),如math.exp(1) 返回2.718281828459045 |
| fabs(x) | 返回数字的绝对值,如math.fabs(-10) 返回10.0 |
| floor(x) | 返回数字的下舍整数,如math.floor(4.9)返回 4 |
| log(x) | 如math.log(math.e)返回1.0,math.log(100,10)返回2.0 |
| log10(x) | 返回以10为基数的x的对数,如math.log10(100)返回 2.0 |
| max(x1, x2,...) | 返回给定参数的最大值,参数可以为序列。 |
| min(x1, x2,...) | 返回给定参数的最小值,参数可以为序列。 |
| modf(x) | 返回x的整数部分与小数部分,两部分的数值符号与x相同,整数部分以浮点型表示。 |
| pow(x, y) | x**y 运算后的值。 |
| round(x [,n]) | 返回浮点数x的四舍五入值,如给出n值,则代表舍入到小数点后的位数。 |
| sqrt(x) | 返回数字x的平方根。 |
随机数函数
| 函数 | 描述 |
|---|---|
| choice(seq) | 从序列的元素中随机挑选一个元素,比如random.choice(range(10)),从0到9中随机挑选一个整数。 |
| randrange ([start,] stop [,step]) | 从指定范围内,按指定基数递增的集合中获取一个随机数,基数缺省值为1 |
| random() | 随机生成下一个实数,它在[0,1)范围内。 |
| seed([x]) | 改变随机数生成器的种子seed。如果你不了解其原理,你不必特别去设定seed,Python会帮你选择seed。 |
| shuffle(lst) | 将序列的所有元素随机排序 |
| uniform(x, y) | 随机生成下一个实数,它在[x,y]范围内。 |
三角函数
| 函数 | 描述 |
|---|---|
| acos(x) | 返回x的反余弦弧度值。 |
| asin(x) | 返回x的反正弦弧度值。 |
| atan(x) | 返回x的反正切弧度值。 |
| atan2(y, x) | 返回给定的 X 及 Y 坐标值的反正切值。 |
| cos(x) | 返回x的弧度的余弦值。 |
| hypot(x, y) | 返回欧几里德范数 sqrt(xx + yy)。 |
| sin(x) | 返回的x弧度的正弦值。 |
| tan(x) | 返回x弧度的正切值。 |
| degrees(x) | 将弧度转换为角度,如degrees(math.pi/2) , 返回90.0 |
| radians(x) | 将角度转换为弧度 |
数学常量
| 常量 | 描述 |
|---|---|
| pi | 数学常量 pi(圆周率,一般以π来表示) |
| e | 数学常量 e,e即自然常数(自然常数)。 |
1.8.2 String字符串
(1) Python转义字符
| 转义字符 | 描述 |
|---|---|
| (在行尾时) | 续行符 |
| \ | 反斜杠符号 |
| ' | 单引号 |
| " | 双引号 |
| \a | 响铃 |
| \b | 退格(Backspace) |
| \e | 转义 |
| \000 | 空 |
| \n | 换行 |
| \v | 纵向制表符 |
| \t | 横向制表符 |
| \r | 回车 |
| \f | 换页 |
| \oyy | 八进制数,yy代表的字符,例如:\o12代表换行 |
| \xyy | 十六进制数,yy代表的字符,例如:\x0a代表换行 |
| \other | 其它的字符以普通格式输出 |
(2) Python字符串运算符
- 下表实例变量a值为字符串 "Hello",b变量值为 "Python":
| 操作符 | 描述 | 实例 |
|---|---|---|
| + | 字符串连接 | a + b 输出结果: HelloPython |
| * | 重复输出字符串 | a*2 输出结果:HelloHello |
| [] | 通过索引获取字符串中字符 | a[1] 输出结果 e |
| [ : ] | 截取字符串中的一部分 | a[1:4] 输出结果 ell |
| in | 成员运算符 - 如果字符串中包含给定的字符返回 True | H in a 输出结果 1 |
| not in | 成员运算符 - 如果字符串中不包含给定的字符返回 True | M not in a 输出结果 1 |
| r/R | 原始字符串 - 原始字符串:所有的字符串都是直接按照字面 的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母"r"(可以大 小写)以外,与普通字符串有着几乎完全相同的语法。 | print r'\n' prints \n 和 print R'\n' prints \n |
| % | 格式字符串 | 请看下一节内容。 |
#!/usr/bin/python3
a = "Hello"
b = "Python"
print("a + b 输出结果:", a + b)
print("a * 2 输出结果:", a * 2)
print("a[1] 输出结果:", a[1])
print("a[1:4] 输出结果:", a[1:4])
if( "H" in a) :
print("H 在变量 a 中")
else :
print("H 不在变量 a 中")
if( "M" not in a) :
print("M 不在变量 a 中")
else :
print("M 在变量 a 中")
print (r'\n')
print (R'\n')
- 以上实例输出结果为:
a + b 输出结果: HelloPython
a * 2 输出结果: HelloHello
a[1] 输出结果: e
a[1:4] 输出结果: ell
H 在变量 a 中
M 不在变量 a 中
\n
\n
(3) Python字符串格式化
- Python 支持格式化字符串的输出 。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。
- 在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。
#!/usr/bin/python3
print ("我叫 %s 今年 %d 岁!" % ('小明', 10))
# 以上实例输出结果:
# 我叫 小明 今年 10 岁!
*python字符串格式化符号:
| 符 号 | 描述 |
|---|---|
| %c | 格式化字符及其ASCII码 |
| %s | 格式化字符串 |
| %d | 格式化整数 |
| %u | 格式化无符号整型 |
| %o | 格式化无符号八进制数 |
| %x | 格式化无符号十六进制数 |
| %X | 格式化无符号十六进制数(大写) |
| %f | 格式化浮点数字,可指定小数点后的精度 |
| %e | 用科学计数法格式化浮点数 |
| %E | 作用同%e,用科学计数法格式化浮点数 |
| %g | %f和%e的简写 |
| %G | %f 和 %E 的简写 |
| %p | 用十六进制数格式化变量的地址 |
- 格式化操作符辅助指令:
| 符号 | 功能 |
|---|---|
| * | 定义宽度或者小数点精度 |
| - | 用做左对齐 |
| + | 在正数前面显示加号( + ) |
| 在正数前面显示空格 | |
| # | 在八进制数前面显示零('0'),在十六进制前面显示'0x'或者'0X'(取决于用的是'x'还是'X') |
| 0 | 显示的数字前面填充'0'而不是默认的空格 |
| % | '%%'输出一个单一的'%' |
| (var) | 映射变量(字典参数) |
| m.n. | m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话) |
- Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。
(4) Python 的字符串内建函数
| 序号 | 名称 | 描述 |
|---|---|---|
| 1 | capitalize() | 将字符串的第一个字符转换为大写 |
| 2 | center(width, fillchar) | 返回一个指定的宽度 width 居中的字符串,fillchar 为填充的字符,默认为空格。 |
| 3 | count(str, beg= 0,end=len(string)) | 返回 str 在 string 里面出现的次数,如果 beg 或者 end 指定则返回指定范围内 str 出现的次数 |
| 4 | bytes.decode(encoding="utf-8", errors="strict") | Python3 中没有 decode 方法,但我们可以使用 bytes 对象的 decode() 方法来解码给定的 bytes 对象,这个 bytes 对象可以由 str.encode() 来编码返回。 |
| 5 | encode(encoding='UTF-8',errors='strict') | 以 encoding 指定的编码格式编码字符串,如果出错默认报一个ValueError 的异常,除非 errors 指定的是'ignore'或者'replace' |
| 6 | endswith(suffix, beg=0, end=len(string)) | 检查字符串是否以 obj 结束,如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束,如果是,返回 True,否则返回 False. |
| 7 | expandtabs(tabsize=8) | 把字符串 string 中的 tab 符号转为空格,tab 符号默认的空格数是 8 。 |
| 8 | find(str, beg=0 end=len(string)) | 检测 str 是否包含在字符串中,如果指定范围 beg 和 end ,则检查是否包含在指定范围内,如果包含返回开始的索引值,否则返回-1 |
| 9 | index(str, beg=0, end=len(string)) | 跟find()方法一样,只不过如果str不在字符串中会报一个异常. |
| 10 | isalnum() | 如果字符串至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False |
| 11 | isalpha() | 如果字符串至少有一个字符并且所有字符都是字母则返回 True, 否则返回 False |
| 12 | isdigit() | 如果字符串只包含数字则返回 True 否则返回 False.. |
| 13 | islower() | 如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False |
| 14 | isnumeric() | 如果字符串中只包含数字字符,则返回 True,否则返回 False |
| 15 | isspace() | 如果字符串中只包含空白,则返回 True,否则返回 False. |
| 16 | istitle() | 如果字符串是标题化的(见 title())则返回 True,否则返回 False |
| 17 | isupper() | 如果字符串中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False |
| 18 | join(seq) | 以指定字符串作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串 |
| 19 | len(string) | 返回字符串长度 |
| 20 | ljust(width[, fillchar]) | 返回一个原字符串左对齐,并使用 fillchar 填充至长度 width 的新字符串,fillchar 默认为空格。 |
| 21 | lower() | 转换字符串中所有大写字符为小写. |
| 22 | lstrip() | 截掉字符串左边的空格或指定字符。 |
| 23 | maketrans() | 创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。 |
| 24 | max(str) | 返回字符串 str 中最大的字母。 |
| 25 | min(str) | 返回字符串 str 中最小的字母。 |
| 26 | replace(old, new [, max]) | 把 将字符串中的 str1 替换成 str2,如果 max 指定,则替换不超过 max 次。 |
| 27 | rfind(str, beg=0,end=len(string)) | 类似于 find()函数,不过是从右边开始查找. |
| 28 | rindex( str, beg=0, end=len(string)) | 类似于 index(),不过是从右边开始. |
| 29 | rjust(width,[, fillchar]) | 返回一个原字符串右对齐,并使用fillchar(默认空格)填充至长度 width 的新字符串 |
| 30 | rstrip() | 删除字符串字符串末尾的空格. |
| 31 | split(str="", num=string.count(str)) | num=string.count(str)) 以 str 为分隔符截取字符串,如果 num 有指定值,则仅截取 num 个子字符串 |
| 32 | splitlines([keepends]) | 按照行('\r', '\r\n', \n')分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。 |
| 33 | startswith(str, beg=0,end=len(string)) | 检查字符串是否是以 obj 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查。 |
| 34 | strip([chars]) | 在字符串上执行 lstrip()和 rstrip() |
| 35 | swapcase() | 将字符串中大写转换为小写,小写转换为大写 |
| 36 | title() | 返回"标题化"的字符串,就是说所有单词都是以大写开始,其余字母均为小写(见 istitle()) |
| 37 | translate(table, deletechars="") | 根据 str 给出的表(包含 256 个字符)转换 string 的字符, 要过滤掉的字符放到 deletechars 参数中 |
| 38 | upper() | 转换字符串中的小写字母为大写 |
| 39 | zfill (width) | 返回长度为 width 的字符串,原字符串右对齐,前面填充0 |
| 40 | isdecimal() | 检查字符串是否只包含十进制字符,如果是返回 true,否则返回 false。 |
1.8.3 List列表
- 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。
- 列表操作实例:
#!/usr/bin/python3
# 定义
list0 = [] # 创建一个空列表 或者 变量 = list()
list1 = ['Google', 'Python', 1997, 2000];
list2 = [1, 2, 3, 4, 5, 6, 7 ];
#输出
print ("list1[0]: ", list1[0]) # Google
print ("list2[1:5]: ", list2[1:5]) # [2, 3, 4, 5]
#更新其中的值
print ("第三个元素为 : ", list[2]) # 2000
list[2] = 2001
print ("更新后的第三个元素为 : ", list[2]) # 2001
#删除其中一个元素
del list[2]
print ("删除第三个元素 : ", list) # ['Google', 'Python', 2000]
(1) 列表的遍历操作:
1.使用for...in 遍历列表
for 变量 in 列表:
使用变量
2.使用while循环遍历列表
i = 0
while i<len(列表):
使用列表中的元素(列表[i])
i += 1
3.同等长度二级列表的遍历
列表 = [[值1,值2],[值1,值2],....]
for 变量1,变量2 in 列表:
使用变量1和变量2
注意:变量1取二级列表中的第一个值,变量2取第二个值
4.非同等长度的二级列表的遍历
列表 = [[值1,值2],[值1,值2,值3],[值]...]
for 变量1 in 列表:
for 变量2 in 变量1:
使用变量2(变量2是二级列表中的每个值)
(2) Python列表脚本操作符
- 列表对 + 和 的操作符与字符串相似。+ 号用于组合列表, 号用于重复列表。
| Python 表达式 | 结果 | 描述 |
|---|---|---|
| len([1, 2, 3]) | 3 | 长度 |
| [1, 2, 3] + [4, 5, 6] | [1, 2, 3, 4, 5, 6] | 组合 |
| ['Hi!'] * 4 | ['Hi!', 'Hi!', 'Hi!', 'Hi!'] | 重复 |
| 3 in [1, 2, 3] | True | 元素是否存在于列表中 |
| for x in [1, 2, 3]: print(x, end=" ") | 1 2 3 | 迭代 |
(3) Python列表截取与拼接
- Python的列表截取与字符串操作类型,如下所示:L=['Google', 'Python', 'Taobao']
| Python | 表达式 结果 | 描述 |
|---|---|---|
| L[2] | 'Taobao' | 读取第三个元素 |
| L[-2] | 'Python' | 从右侧开始读取倒数第二个元素: count from the right |
| L[1:] | ['Python', 'Taobao'] | 输出从第二个元素开始后的所有元素 |
- 使用嵌套列表即在列表里创建其它列表,例如:
>>>a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
>>> x[0]
['a', 'b', 'c']
>>> x[0][1]
'b'
(4) Python列表函数&方法
- Python包含以下函数:
| 序号 | 函数名称 | 说明 |
|---|---|---|
| 1 | len(list) | 列表元素个数 |
| 2 | max(list) | 返回列表元素最大值 |
| 3 | min(list) | 返回列表元素最小值 |
| 4 | list(seq) | 将元组转换为列表 |
- Python包含以下方法:
| 序号 | 方法名称 | 说明 |
|---|---|---|
| 1 | list.append(obj) | 在列表末尾添加新的对象 |
| 2 | list.count(obj) | 统计某个元素在列表中出现的次数 |
| 3 | list.extend(seq) | 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) |
| 4 | list.index(obj) | 从列表中找出某个值第一个匹配项的索引位置 |
| 5 | list.insert(index, obj) | 将对象插入列表 |
| 6 | list.pop(obj=list[-1]) | 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
| 7 | list.remove(obj) | 移除列表中某个值的第一个匹配项 |
| 8 | list.reverse() | 反向列表中元素 |
| 9 | list.sort([func]) | 对原列表进行排序 |
| 10 | list.clear() | 清空列表 |
| 11 | list.copy() | 复制列表 |
1.8.4 Tuple元组
- Python 的元组与列表类似,不同之处在于元组的元素不能修改。
- 元组使用小括号,列表使用方括号。
- 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
# 定义元组的方式:
tup0 = () #定义一个空元组 或者 变量 = tuple()
tup1 = ('Google', 'Python', 1997, 2000)
tup2 = (1, 2, 3, 4, 5 )
tup3 = "a", "b", "c", "d"
# 输出元组:
print ("tup1[0]: ", tup1[0]) # tup1[0]: Google
print ("tup2[1:5]: ", tup2[1:5]) # tup2[1:5]: (2, 3, 4, 5)
# 注意下面这种定义不加逗号,类型为整型
tup4 = (50)
print(type(tup4)) # <class 'int'>
# 正确定义方式加上逗号,类型为元组
tup5 = (50,)
print(type(tup5)) # <class 'tuple'>
# 以下修改元组元素操作是非法的。
# tup1[0] = 100
#元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组
del tup0;
(1) 元组运算符
- 与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。
| Python 表达式 | 结果 | 描述 |
|---|---|---|
| len((1, 2, 3)) | 3 | 长度 |
| (1, 2, 3) + (4, 5, 6) | (1, 2, 3, 4, 5, 6) | 组合 |
| ('Hi!') * 4 | ('Hi!', 'Hi!', 'Hi!', 'Hi!') | 重复 |
| 3 in (1, 2, 3) | True | 元素是否存在于元组中 |
| for x in (1, 2, 3): print(x, end=" ") | 1 2 3 | 迭代 |
(2)元组索引,截取
- 因为元组也是一个序列,所以我们可以访问元组中的指定位置的元素,也可以截取索引中的一段元素
- 如下所示:L=('Google', 'Python', 'Taobao')
| Python | 表达式 结果 | 描述 |
|---|---|---|
| L[2] | 'Taobao' | 读取第三个元素 |
| L[-2] | 'Python' | 从右侧开始读取倒数第二个元素: count from the right |
| L[1:] | ('Python', 'Taobao') | 输出从第二个元素开始后的所有元素 |
(3) 元组内置函数
- Python元组包含了以下内置函数:
| 序号 | 函数名称 | 说明 |
|---|---|---|
| 1 | len(tuple) | 元组元素个数 |
| 2 | max(tuple) | 返回元组元素最大值 |
| 3 | min(tuple) | 返回元组元素最小值 |
| 4 | tuple(seq) | 将元组转换为元组 |
list1= ['Google', 'Taobao', 'Runoob', 'Baidu']
tuple1=tuple(list1)
print(tuple1)
#('Google', 'Taobao', 'Runoob', 'Baidu')
1.8.5 Sets集合
- 集合(set)是一个无序不重复元素的序列。
- 基本功能是进行成员关系测试和删除重复元素。
- 可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。
# 集合的定义
set1 = set() #定义一个空的集合
set2 = {1,2,3}
# 增加一个元素
set1.add(5)
#增加多个:
set1.update([5,6,7,8])
#删除某个值
set1.remove(1)
#查:无法通过下标索引
#改:不可变类型无法修改元素
a={10,20,30}
b={20,50}
print(a - b) # a和b的差集
print(a | b) # a和b的并集
print(a & b) # a和b的交集
print(a ^ b) # a和b中不同时存在的元素
(1) 集合的遍历
1.普通序列的遍历
for 变量 in 集合:
使用变量
2.多级集合
集合 = {(值,值..),(值,值..)。。。}
for 变量1,变量2 in 集合:
使用变量1和变量2
(2) 集合的序列函数
- len() 计算集合的长度
- max() 获取集合中的最大值
- min() 获取集合中的最小值
- set() 创建空集合或者将其他数据转换为集合
(3) 集合中的方法
- add -- 增加集合元素
name = {'d', 's'}
name.add('d')
name
返回结果:{'d', 's'}
name.add('sd')
name
返回结果:{'sd', 'd', 's'}
- update--更新已有集合
name = {'sd', 'd', 's'}
name.update('df')
name
返回结果:{'sd', 'd', 'f', 's'}
- remove--移除指定集合元素
name = {'sd','d','s'}
name.remove('s')
返回结果:name
{'sd', 'd'}
- discard--移除元素
name = {'sd', 'd', 's'}
name.discard('s')
返回结果:name
{'sd', 'd'}
# remove移除非成员值会报错,discard移除非成员值,啥也不错!
- clear--清空集合元素
name = {'d', 's'}
name.clear()
name
返回结果:{}
- copy--浅拷贝
name = {'sd', 'd', 's'}
li = name.copy()
返回结果:li
{'sd', 'd', 's'}
- difference -- 求差集
name.difference(li)
set()
>>> name.difference()
{'sd', 'd', 's'}
- union--并集,创建新的对象
name = {'sd', 'd', 's'}
li = {'s', 'd','h'}
name.union(li)
返回结果:{'h', 's', 'd', 'sd'}
- difference_update---删除当前set中的所有包含在 new set 里的元素
li = ('s', 'd')
name = {'sd', 'd', 's'}
name.difference_update(li)
name
返回结果:{'sd'}
- intersection--取交集,建立新的set集合
li = ('s', 'd')
name = {'sd', 'd', 's'}
name.intersection(li)
返回结果:{'d', 's'}
- intersection_update--取交集,更新原来的set集合
li = ('s', 'd')
name = {'sd', 'd', 's'}
name.intersection_update(li)
返回结果:{'d', 's'}
- isdisjoint--判断没有交集,返回True,否则,返回False
li = {'s', 'd'}
name = {'sd', 'd', 's'}
name.isdisjoint(li)
- issubset--判断是否是子集
li = {'s', 'd'}
name = {'sd', 'd', 's'}
name.issubset(li) #判断name是不是li的子集
返回结果:False
li.issubset(name) #判断li是不是name的子集
返回结果:True
- issuperset--判断是否是父集
li = {'s', 'd'}
name = {'sd', 'd', 's'}
name.issuperset(li) #判断name是不是li的父集
返回结果:True
li.issuperset(name) #判断li是不是name的父集
返回结果:False
- pop--移除集合元素
name = {'sd', 'd', 's'}
name.pop()
返回结果:'sd' #同一个集合,删除集合元素的顺序固定
se1 = {'a','s','sb'}
se1.pop()
返回结果:'sb'
- symmetric_difference--去两个集合的差集,建立新的set集合对象
name = {'sd', 'd', 's'}
li = {'s', 'd'}
name.symmetric_difference(li)
返回结果:{'sd'}
- symmetric_difference_update--去两个集合的差集,更新原来的集合对象
name = {'sd', 'd', 's'}
li = {'s', 'd'}
name.symmetric_difference_update(li)
返回结果:{'sd'}
1.8.6 Dictionary字典
- 字典是另一种可变容器模型,且可存储任意类型对象。
- 字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,格式如下所示:
d = {key1 : value1, key2 : value2 }
- 键必须是唯一的,但值则不必。
(1) 创建字典
1.创建空字典
变量 = {} 或者 变量 = dict()
2.创建多个元素的字典:
方法1:
变量 = {键:值,键:值....}
方法2:
变量 = dict({键:值,键:值....})
方法3:
变量 = dict(键=值,键=值...)
注意:该方式键作为形参名使用不可以添加引号,必须符合变量规则
方法4:
变量 = dict([(键,值),(键,值)...])
变量 = dict([[键,值],[键,值]...])
变量 = dict(((键,值),(键,值)...))
方法5:
变量 = dict(zip((键,键...),(值,值...)))
(2) 字典的基本操作
访问字典的元素:
变量[键]
添加字典的元素
变量[新键] = 值
修改字典元素
变量[键] = 新值
删除字典元素
del 变量[键]
- 案例:
#!/usr/bin/python3
# 定义一个字典
dict = {'Name': 'Python', 'Age': 17, 'Class': 'First'}
# 输出子典中的信息
print ("dict['Name']: ", dict['Name']) #Python
print ("dict['Age']: ", dict['Age']) #17
# 输出错误信息:KeyError: 'Alice'
#print ("dict['Alice']: ", dict['Alice'])
# 修改和添加内容
dict['Age'] = 18; # 更新 Age
dict['School'] = "云课堂" # 添加信息
# 删除信息
del dict['Name'] # 删除键 'Name'一个元素值
dict.clear() # 清空字典
del dict # 删除字典
字典的遍历:
1.键的遍历
for 变量i in 字典:
使用i遍历所有的键,有键就可以通过变量访问其值
2.键值遍历
for 变量i,变量j in 字典.items():
使用变量i遍历所有键,通过变量j遍历所有值
字典内涵/字典推导式:
1.普通的字典内涵
变量= {key:value for key,value in 字典.items()}
2.带有判断条件的字典内涵
变量= {key:value for key,value in 字典.items() if 条件表达式}
3,多个循环的字典内涵
变量 = {i+x:j+y for i,j in 字典1.items for x,y in 字典2.items()}
4.带有判断条件的多个循环的字典内涵
变量 = {i+x:j+y for i,j in 字典1.items for x,y in 字典2.items() if 条件表达式}
字典内置函数&方法:
- Python字典包含了以下内置函数:
| 序号 | 函数名称 | 描述 | 实例 |
|---|---|---|---|
| 1 | len(dict) | 计算字典元素个数, 即键的总数。 | >>> dict = {'Name': 'py', 'Age': 7, 'Class': 'First'} >>> len(dict) 3 |
| 2 | str(dict) | 输出字典, 以可打印的字符串表示。 | >>> dict = {'Name': 'py', 'Age': 7, 'Class': 'First'} >>> str(dict) "{'Name': 'py', 'Class': 'First', 'Age': 7}" |
| 3 | type(variable) | 返回输入的变量类型, 如果变量是字典 就返回字典类型。 | >>> dict = {'Name': 'py', 'Age': 7, 'Class': 'First'} >>> type(dict) <class 'dict'> |
- Python字典包含了以下内置方法:
| 序号 | 方法名称 | 描述 |
|---|---|---|
| 1 | radiansdict.clear() | 删除字典内所有元素 |
| 2 | radiansdict.copy() | 返回一个字典的浅复制 |
| 3 | radiansdict.fromkeys() | 创建一个新字典,以序列seq中元素做字典的键,val为字典所有键对应的初始值 |
| 4 | radiansdict.get(key, default=None) | 返回指定键的值,如果值不在字典中返回default值 |
| 5 | key in dict | 如果键在字典dict里返回true,否则返回false |
| 6 | radiansdict.items() | 以列表返回可遍历的(键, 值) 元组数组 |
| 7 | radiansdict.keys() | 以列表返回一个字典所有的键 |
| 8 | radiansdict.setdefault(key, default=None) | 和get()类似, 但如果键不存在于字典中,将会添加键并将值设为default |
| 9 | radiansdict.update(dict2) | 把字典dict2的键/值对更新到dict里 |
| 10 | radiansdict.values() | 以列表返回字典中的所有值 |
| 11 | pop(key[,default]) | 删除字典给定键 key 所对应的值,返回值为被删除的值。key值必须给出。 否则,返回default值。 |
| 12 | popitem() | 随机返回并删除字典中的一对键和值(一般删除末尾对)。 |
1.9 Python文件操作
- 文件操作主要讲解以下内容:
- 1.文件本身的操作(python内置)
- 2.系统中文件和文件夹的操作(os和shutil模块当中)
- 3.系统路径相关操作(os模块中的子模块 os.path)
(1) 文件的基本操作
- open() 打开或者创建一个文件
格式:open('文件路径','打开模式')
返回值:文件io对象
打开模式一共N种:
w模式 写模式write 文件不存在时会创建文件,如果文件已存在则会清空文件
r模式 读模式read 文件不存在就报错,存在则准备读取文件
a模式 追加模式 append 文件不存在则新建,文件存在则在文件末尾追加内容
x模式 抑或模式 xor 文件存在则报错,文件 不存在则新建文件
b模式 二进制模式 binary 辅助模式不能单独使用
+模式 增强模式plus 也是辅助模式不能单独使用
- 以上模式可以互相组合:wrax不可以互相组合:
| 模式 | 说明 |
|---|---|
| r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
| w | 打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。 |
| wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
| r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
| w+ | 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
| rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。 |
| wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。 |
| ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
- close() 关闭文件
格式:文件io对象.close()
返回值:None
- 示例如下:
# 新建一个文件,文件名为:test.txt
f = open('test.txt', 'w')
# 关闭这个文件
f.close()
读写函数:
- read() 读取文件
格式:文件io对象.read()
返回值:整个文件的字符
格式:文件io对象.read(字符长度)
返回值:指定长度的字符
f = open('test.txt', 'r')
content = f.read(5)
print(content)
print("-"*30)
content = f.read()
print(content)
f.close()
- readline() 读取一行文件
格式:文件io对象.readline()
返回值:一行内容的字符串
格式:文件io对象.readline(字符长度)
返回值:一行内容的字符串
注意:字符长度<当前行内内容,则读取指定长度的字符串,并且下次再读取还是在
这个一行中获取没有读取的内容。字符长度>=当前行内容,直接读取当前行
#coding=utf-8
f = open('test.txt', 'r')
content = f.readline()
print("1:%s"%content)
content = f.readline()
print("2:%s"%content)
f.close()
- readlines() 将文件中的内容读取到序列当中。
格式:文件io对象.readlines()
返回值:列表
格式:文件io对象.readlines(字符长度)
返回值:列表
注意:读取的行数由字符长度决定,如果字符长度读取了N行后,还有指定长度的字符
没有读取,则直接读取下一行进来
#coding=utf-8
f = open('test.txt', 'r')
content = f.readlines()
print(type(content))
i=1
for temp in content:
print("%d:%s"%(i, temp))
i+=1
f.close()
- write() 写入文件
格式:文件io对象.write(字符串)
返回值:写入字符串的长度
- writelines() 将序列写入文件中
格式:文件io对象.writelines(序列)
返回值:None
- truncate() 字符串截取操作
格式:文件io对象.truncate(字节长度)
返回值:截取的字节长度
(2) OS模块
- OS -- 操作系统的简称
- os模块就是对操作系统进行操作
- 使用该模块必须先导入模块:
import os
os模块中的函数:
| 序号 | 函数名称 | 描述 | 格式 |
|---|---|---|---|
| 1 | getcwd() | 获取当前的工作目录 | 格式:os.getcwd() 返回值:路径字符串 |
| 2 | chdir() | 修改当前工作目录 | 格式:os.chdir() 返回值:None |
| 3 | listdir() | 获取指定文件夹中的 所有文件和文件夹组成的列表 | 格式:os.listdir(目录路径) 返回值:目录中内容名称的列表 |
| 4 | mkdir() | 创建一个目录/文件夹 | 格式:os.mkdir(目录路径) 返回值:None |
| 5 | makedirs() | 递归创建文件夹 | 格式:os.makedirs(路径) |
| 6 | rmdir() | 移除一个目录(必须是空目录) | 格式:os.rmdir(目录路径) 返回值:None |
| 7 | removedirs() | 递归删除文件夹 | 格式:os.removedirs(目录路径) 返回值:None 注意最底层目录必须为空 |
| 8 | rename() | 修改文件和文件夹的名称 | 格式:os.rename(源文件或文件夹,目标文件或文件夹) 返回值:None |
| 9 | stat() | 获取文件的相关 信息 | 格式:os.stat(文件路径) 返回值:包含文件信息的元组 |
| 10 | system() | 执行系统命令 | 格式:os.system() 返回值:整型 慎用! 玩意来个rm -rf 你就爽了! |
| 11 | getenv() | 获取系统环境变量 | 格式:os.getenv(获取的环境变量名称) 返回值:字符串 |
| 12 | putenv() | 设置系统环境变量 | 格式:os.putenv('环境变量名称',值) 返回值:无 注意:无法正常的getenv检测到。 |
| 13 | exit() | 推出当前执行命令,直接关闭当前操作 | 格式:exit() 返回值:无 |
当前os模块的值:
| 序号 | 函数名称 | 描述 |
|---|---|---|
| 1 | curdir | os.curdir 获取当前路径 都是. |
| 2 | pardir | os.pardir 获取上层目录路径 都是.. |
| 3 | path | os.path os中的一个子模块,操作非常多 |
| 4 | name | os.name 当前系统的内核名称 win->nt linux/unix->posix |
| 5 | sep | os.sep 获取当前系统的路径分割符号 window -> \ linux/unix -> / |
| 6 | extsep | os.extsep 获取当前系统中文件名和后缀之间的分割符号,所有系统都是. |
| 7 | linesep | os.linesep 获取当前系统的换行符号 window -> \r\n linux/unix -> \n |
os.environ模块
- os.environ 可以直接获取所有环境变量的信息组成的字典,如果希望更改环境变量,并且可以查询得到,就需要对os.environ进行操作
- 该模块的所有方法均是字典的方法,可以通过字典的os.environ的结果进行操作。
- 注意:无论使用os.getenv,putenv 还是使用os.environ进行环境变量的操作,都是只对当前脚本,临时设置而已,无法直接更新或者操作系统的环境变量设置。
os.path模块
- os.path是os模块中的子模块,包含很多和路径相关的操作
- 函数部分:
| 序号 | 函数名称 | 描述 | 格式 |
|---|---|---|---|
| 1 | abspath() | 将一个相对路径转化为绝对路径 | 格式:os.path.abspath(相对路径) 返回值:绝对路径字符串 |
| 2 | basename() | 获取路径中的文件夹或者文件名称 (只要路径的最后一部分) | 格式:os.path.basename(路径) 返回值:路径的最后一部分(可能是文件名也可能是文件夹名) |
| 3 | dirname() | 获取路径中的路径部分(出去最后一部分) | 格式:os.path.dirname(路径) 返回值:路径中除了最后一部分的内容字符串 |
| 4 | join() | 将2个路径合成一个路径 | 格式:os.path.join(路径1,路径2) 返回值:合并之后的路径 |
| 5 | split() | 将一个路径切割成文件夹和文件名部分 | 格式:os.path.split(路径) 返回值:元组 |
| 6 | splitext() | 将一个文件名切成名字和后缀两个部分 | 格式:os.path.splitext(文件名称) 返回值:元组 (名称,后缀) |
| 7 | getsize() | 获取一个文件的大小 | 格式:os.path.getsize(路径) 返回值:整数 |
| 8 | isfile() | 检测一个路径是否是一个文件 | 格式:os.path.isfile(路径) 返回值:布尔值 |
| 9 | isdir() | 检测一个路径是否是一个文件夹 | 格式:os.path.isdir(路径) 返回值:布尔值 |
| 10 | getctime() | 获取文件的创建时间! get create time | 格式:os.path.getctime(文件路径) 返回值:时间戳浮点数 |
| 11 | getmtime() | 获取文件的修改时间! get modify time | 格式:os.path.getmtime(文件路径) 返回值:时间戳浮点数 |
| 12 | getatime() | 获取文件的访问时间! get active time | 格式:os.path.getatime(文件路径) 返回值:时间戳浮点数 |
| 13 | exists() | 检测指定的路径是否存在 | 格式:os.path.exists(路径) 返回值:布尔值 |
| 14 | isabs() | 检测一个路径是否是绝对路径 | 格式:os.path.isabs(路径) 返回值:布尔值 |
| 15 | islink() | 检测一个路径是否是链接 | 格式:os.path.islink(路径) 返回值:布尔值 |
| 16 | samefile() | 检测2个路径是否指向同一个文件 | 格式:os.path.samefile(路径1,路径2) 返回值:布尔值 |
1.10 综合案例实战
使用python学习内容实现一个在线学员信息管理操作
- 数据临时存放在变量列表中
- 实现学生信息的添加,删除和查询操作。
初识界面:

添加学员信息

浏览学员信息

删除学员信息

退出操作

参考程序代码如下:
# 学员信息在线管理
# 定义一个用于存放学员信息的列表变量
stulist=[
{'name':'zhangsan','age':20,'classid':'python02'},
{'name':'lisi','age':22,'classid':'python03'},
{'name':'wangwu','age':25,'classid':'python04'}]
#定义一个学生信息的输出函数
def showStu(stulist):
'''
学生信息的输出函数
'''
if len(stulist)==0:
print("========== 没有学员信息可以输出!=============")
return
print("|{0:<5}| {1:<10}| {2:<5}| {3:<10}|".format("sid","name","age","classid"))
print("-"*40)
for i in range(len(stulist)):
print("|{0:<5}| {1:<10}| {2:<5}| {3:<10}|".format(i+1,stulist[i]['name'],stulist[i]['age'],stulist[i]['classid']))
while True:
# 输出初始界面
print("="*12,"学员管理系统","="*14)
print("{0:1} {1:13} {2:15}".format(" ","1. 查看学员信息","2. 添加学员信息"))
print("{0:1} {1:13} {2:15}".format(" ","3. 删除学员信息","4. 退出系统"))
print("="*40)
key = input("请输入对应的选择:")
# 根据键盘值,判断并执行对应的操作
if key == "1":
print("="*12,"学员信息浏览","="*14)
showStu(stulist)
input("按回车键继续:")
elif key == "2":
print("="*12,"学员信息添加","="*14)
stu={}
stu['name']=input("请输入要添加的姓名:")
stu['age']=input("请输入要添加的年龄:")
stu['classid']=input("请输入要添加的班级号:")
stulist.append(stu)
showStu(stulist)
input("按回车键继续:")
elif key == "3":
print("="*12,"学员信息删除","="*14)
showStu(stulist)
sid = input("请输入你要删除的信息id号:")
del stulist[int(sid)-1]
showStu(stulist)
input("按回车键继续:")
elif key == "4":
print("="*12,"再见","="*14)
break
else:
print("======== 无效的键盘输入! ==========")
二、Python基础
1.11 Python面向对象编程
- Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的.
面向对象技术简介
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。实例变量:定义在方法中的变量,只作用于当前实例的类。继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。实例化:创建一个类的实例,类的具体对象。方法:类中定义的函数。对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
1.11.2 类和对象
3
class ClassName:
<statement-1>
.
.
.
<statement-N>
- 实例:
#!/usr/bin/python3
# 类的定义
class MyClass:
i = 12345
def f(self):
return 'hello world'
# 实例化类
x = MyClass()
# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())
# 结果:
# MyClass 类的属性 i 为: 12345
# MyClass 类的方法 f 输出为: hello world
1.11.3 构造函数
- 很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 init() 的特殊方法(构造方法),像下面这样:
def __init__(self):
self.data = []
- 类定义了 init() 方法的话,类的实例化操作会自动调用 init() 方法。所以在下例中,可以这样创建一个新的实例:
x = MyClass()
- 当然, init() 方法可以有参数,参数通过 init() 传递到类的实例化操作上。例如:
#!/usr/bin/python3
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5
self代表类的实例,而非类
- 类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
- 以上实例执行结果为:
<__main__.Test instance at 0x100771878>
__main__.Test
- 从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。
- self 不是 python 关键字,我们可以换成其他变量名
1.11.4 类的属性和方法
类的方法:
- 在类地内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
# 实例化类
p = people('runoob',10,30)
p.speak()
- 执行以上程序输出结果为:
runoob 说: 我 10 岁。
类属性与方法:
-
类的私有属性- private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 self.private_attrs。
-
类的方法- 在类地内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。
- self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。
-
类的私有方法- private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类地外部调用。self.private_methods。
1.11.5 继承与重载
- Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:
class DerivedClassName(BaseClassName1):
<statement-1>
.
.
.
<statement-N>
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
# 执行以上程序输出结果为:
# ken 说: 我 10 岁了,我在读 3 年级
多继承:
- Python同样有限的支持多继承形式。多继承的类定义形如下例:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
- 需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
#!/usr/bin/python3
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
#另一个类,多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s,我是一个演说家,我演讲的主题是 %s"%(self.name,self.topic))
#多重继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中排前地父类的方法
# 执行以上程序输出结果为:
# 我叫 Tim,我是一个演说家,我演讲的主题是 Python
方法重写:
- 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:
#!/usr/bin/python3
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
# 执行以上程序输出结果为:
# 调用子类方法
1.11.6 其他
- 类的专有方法:
__init__: 构造函数,在生成对象时调用__del__: 析构函数,释放对象时使用__repr__: 打印,转换__setitem__: 按照索引赋值__getitem__: 按照索引获取值__len__: 获得长度__cmp__: 比较运算__call__: 函数调用__add__: 加运算__sub__: 减运算__mul__: 乘运算__div__: 除运算__mod__: 求余运算__pow__: 乘方
- 运算符重载
- Python同样支持运算符重载,我们可以对类的专有方法进行重载,实例如下:
#!/usr/bin/python3
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
# 以上代码执行结果如下所示:
# Vector(7,8)
1.12 Python中的异常处理
1.12.1 异常介绍
- 即便Python程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。
- 大多数的异常都不会被程序处理,都以错误信息的形式展现在这里:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly
- 异常以不同的类型出现,这些类型都作为信息的一部分打印出来: 例子中的类型有 ZeroDivisionError,NameError 和 TypeError。
- Python的一些內建异常:
Exception 常规错误的基类
AttributeError 对象没有这个属性
IOError 输入/输出操作失败
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
NameError 未声明/初始化对象 (没有属性)
SyntaxError Python 语法错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
ZeroDivisionError 除(或取模)零 (所有数据类型)
附加:Python—内建异常体系结构
Python异常体系结构如下图:
常见的异常如下表所示:
| 异常名称 | 描述 |
|---|---|
| BaseException | 所有异常的基类 |
| SystemExit | 解释器请求退出 |
| KeyboardInterrupt | 用户中断执行(通常是输入^C) |
| Exception | 常规错误的基类 |
| StopIteration | 迭代器没有更多的值 |
| GeneratorExit | 生成器(generator)发生异常来通知退出 |
| StandardError | 所有的内建标准异常的基类 |
| ArithmeticError | 所有数值计算错误的基类 |
| FloatingPointError | 浮点计算错误 |
| OverflowError | 数值运算超出最大限制 |
| ZeroDivisionError | 除(或取模)零 (所有数据类型) |
| AssertionError | 断言语句失败 |
| AttributeError | 对象没有这个属性 |
| EOFError | 没有内建输入,到达EOF 标记 |
| EnvironmentError | 操作系统错误的基类 |
| IOError | 输入/输出操作失败 |
| OSError | 操作系统错误 |
| WindowsError | 系统调用失败 |
| ImportError | 导入模块/对象失败 |
| LookupError | 无效数据查询的基类 |
| IndexError | 序列中没有此索引(index) |
| KeyError | 映射中没有这个键 |
| MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
| NameError | 未声明/初始化对象 (没有属性) |
| UnboundLocalError | 访问未初始化的本地变量 |
| ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
| RuntimeError | 一般的运行时错误 |
| NotImplementedError | 尚未实现的方法 |
| SyntaxError | Python 语法错误 |
| IndentationError | 缩进错误 |
| TabError | Tab 和空格混用 |
| SystemError | 一般的解释器系统错误 |
| TypeError | 对类型无效的操作 |
| ValueError | 传入无效的参数 |
| UnicodeError | Unicode 相关的错误 |
| UnicodeDecodeError | Unicode 解码时的错误 |
| UnicodeEncodeError | Unicode 编码时错误 |
| UnicodeTranslateError | Unicode 转换时错误 |
| Warning | 警告的基类 |
| DeprecationWarning | 关于被弃用的特征的警告 |
| FutureWarning | 关于构造将来语义会有改变的警告 |
| OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
| PendingDeprecationWarning | 关于特性将会被废弃的警告 |
| RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
| SyntaxWarning | 可疑的语法的警告 |
| UserWarning | 用户代码生成的警告 |
# 注意:
BaseException是异常的顶级类。但是这个类不能当作是由用户定义的类直接继承的,而是要继承Exception。Exception类是与应用相关的异常的顶层根超类,除了系统退出事件类之外(SystemExit、KeyboardInterrupt和GeneratorExit),几乎所有的用户定义的类都应该继承自这个类,而不是BaseException
1.12.2 异常处理
- 没有异常处理的:特点是出现异常会终止程序执行。
print("start.....")
x = int(input("Please enter a number: "))
print("number:",x)
print("ok....")
print("end.....")
- 使用了有异常处理的代码,程序会执行到最后
print("start.....")
try:
x = int(input("Please enter a number: "))
print("number:",x)
print("ok....")
except ValueError:
print("Oops! That was no valid number. Try again")
print("end.....")
- try语句按照如下方式工作;
- 首先,执行try子句(在关键字try和关键字except之间的语句)
- 如果没有异常发生,忽略except子句,try子句执行后结束。
- 如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的except子句将被执行。最后执行 try 语句之后的代码。
- 如果一个异常没有与任何的except匹配,那么这个异常将会传递给上层的try中。
- 一个 try 语句可能包含多个except子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。
- 处理程序将只针对对应的try子句中的异常进行处理,而不是其他的 try 的处理程序中的异常。
- 一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:
except (RuntimeError, TypeError, NameError):
pass
- 最后一个except子句可以忽略异常的名称,它将被当作通配符使用。你可以使用这种方法打印一个错误信息,然后再次把异常抛出。
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
异常处理实例
- 一个 try 语句可能包含多个except子句,分别来处理不同的特定的异常。最多只有一个分支会被执行
- 最后一个except子句可以忽略异常的名称,它将被当作通配符使用
print("start.....")
try:
x = int(input("Please enter a number: "))
print("number:",x)
print(100/x)
print("ok....")
except ValueError:
print("非纯数字错误!")
except ZeroDivisionError:
print("不可以为零错误!")
except:
print("可选的未知错误!")
print("end.....")
- 一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组
print("start.....")
try:
x = int(input("Please enter a number: "))
print("number:",x)
print(100/x)
print("ok....")
except (ValueError,ZeroDivisionError):
print("非纯数字或不可以为零错误!")
except:
print("可选的未知错误!")
raise #重新抛出这个异常
print("end.....")
1.12.3 抛出异常(自行抛出异常)
- Python 使用 raise 语句抛出一个指定的异常。例如:
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: HiThere
- raise 唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。
- 如果你只想知道这是否抛出了一个异常,并不想去处理它,那么一个简单的 raise 语句就可以再次把它抛出。
>>> try:
raise NameError('HiThere')
except NameError:
print('An exception flew by!')
raise
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in ?
NameError: HiThere
1.13 魔术方法、属性和迭代器
1.13.1 魔术方法:
- 在Python中的面向对象中有很多魔术方法如:
__init__: 构造函数,在生成对象时调用
__del__: 析构函数,释放对象时使用
__str__: 使用print(对象)或者str(对象)的时候触发
__repr__: 在使用repr(对象)的时候触发
__setitem__ : 按照索引赋值:每当属性被赋值的时候都会调用该方法:self.__dict__[name] = value
__getitem__: 按照索引获取值:当访问不存在的属性时会调用该方法
_delitem__(self,name): 当删除属性时调用该方法
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__div__: 除运算
__mod__: 求余运算
__pow__: 乘方
...
- 注意:
__setitem__: 每当属性被赋值的时候都会调用该方法,因此不能再该方法内赋值 self.name = value 会死循环 __str__函数用于处理打印实例本身的时候的输出内容。如果没有覆写该函数,则默认输出一个对象名称和内存地址。
class Stu:
name= '张三'
age = 20
def __str__(self):
return "姓名:%s; 年龄:%d"%(self.name,self.age)
s = Stu()
print(s)
- 析构魔术方法,当对象从内存被释放前调用的方法,目的是做一些释放销毁工作。
class Demo:
def __init__(self,x):
self.x = x;
print("create demo...",self.x)
def __del__(self):
print("del demo....",self.x)
d1 = Demo(1)
d2 = Demo(2)
d3 = Demo(3)
del d3
__add__: 加运算
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
# 以上代码执行结果如下所示:
# Vector(7,8)
1.13.2 属性和方法
property() 函数的作用是在新式类中返回属性值
* ```class property([fget[, fset[, fdel[, doc]]]])```
* fget -- 获取属性值的函数
* fset -- 设置属性值的函数(可选)
* fdel -- 删除属性值函数(可选)
* doc -- 属性描述信息(可选)
# 定义一个矩形类,假象有一个size特性访问器方法
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self,size):
self.width,self.height=size
def getSize(self):
return self.width,self.height
#测试
rt = Rectangle()
#rt.width=100
#rt.height=50
rt.setSize((200,100)) #赋值
print(rt.getSize()) #(200,100)
- 对应上面的实例,
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self,size):
self.width,self.height=size
def getSize(self):
return self.width,self.height
size = property(getSize,setSize,fdel)
rt = Rectangle()
#rt.width=100
#rt.height=50
rt.setSize((200,100))
print(rt.getSize())
rt.size = 10,5
print(rt.size)
静态方法和类成员方法(区别是有无带参数)
- 使用staticmethod()和classmethod()函数 或使用@staticmethod和@classmethod装饰器
class MyClass1:
def smeth():
print('这是一个静态方法')
smeth = staticmethod(smeth)
def cmeth(cls):
print("这是一个类成员方法",cls)
cmeth = classmethod(cmeth)
MyClass1.smeth()
MyClass1.cmeth()
#或使用使用@staticmethod和@classmethod装饰器
class MyClass2:
@staticmethod
def smeth():
print('这是一个静态方法')
@classmethod
def cmeth(cls):
print("这是一个类成员方法",cls)
MyClass2.smeth()
MyClass2.cmeth()
- 使用hasattr()函数判读对象中是否存在指定的非私有属性和方法:
class B:
name="zhangsan"
__age=20
def bb(self):
print("bbbbb")
def __cc(self):
print("cccc")
b = B()
print(hasattr(b,"name")) #True
print(hasattr(b,"__age")) #False
print(hasattr(b,"sex")) #False
print(hasattr(b,"bb")) #True
print(hasattr(b,"__cc")) #False
print()
1.13.3 迭代器:
- 指定数据创建迭代器(使用iter()和next() )
x = [1, 2, 3] #定义一个列表:<class 'list'>
y = iter(x) #创建一个可迭代对象:<class 'list_iterator'>
#print(next(y)) # 1
#print(next(y)) # 2
#print(next(y)) # 3
#print(next(y)) # 迭代结束会后返回异常StopIteration错误
for i in y:
print(i,end=" ")
print()
# 1 2 3
- 迭代对象:定义魔术方法:
__next__()和__iter__()
class A:
def __init__(self):
self.x=0
def __next__(self):
self.x += 1
if self.x>10:
raise StopIteration
return self.x
def __iter__(self):
return self
a = A()
print(list(a))
#for i in a:
# print(i)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1.14 Python模块实战
1.14.1 什么是Python模块
- Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。
- 模块让你能够有逻辑地组织你的 Python 代码段。
- 把相关的代码分配到一个模块里能让你的代码更好用,更易懂。
- 模块能定义函数,类和变量,模块里也能包含可执行的代码。
1.14.2 Python模块的导入
(1) import 语句
- 想使用 Python 源文件,只需在另一个源文件里执行 import 语句,语法如下:
import module1[, module2[,... moduleN]
import random
random.choice([0,1,2,3,4,5]) #随机从列表中获取一个
random.randrange(1,10) #1~9随机一个
import time
# 格式化成2016-03-20 11:45:39形式
print (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
(2) from…import 语句
- Python的from语句让你从模块中导入一个指定的部分到当前命名空间中,语法如下:
from modname import name1[, name2[, ... nameN]]
from time import strftime,localtime
# 格式化成2016-03-20 11:45:39形式
print (strftime("%Y-%m-%d %H:%M:%S", localtime()))
1.14.3 第三方模块的安装
- 使用pip命令安装
- pip install 模块名
- pip uninstall 模块名
- pip freeze --查看都安装了哪些模块名
- whl下载安装的方式
- 网址: https://www.lfd.uci.edu/~gohlke/pythonlibs/ 下载
- 安装:pip install 文件包名
- 直接复制的方式
- anaconda
1.14.4 自定义Python模块
- 在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。
- 目录只有包含一个叫做
__init__.py的文件才会被认作是一个包,主要是为了避免一些滥俗的名字(比如叫做string)不小心的影响搜索路径中的有效模块。 - 最简单的情况,放一个空的 :
file:__init__.py就可以了。当然这个文件中也可以包含一些初始化代码或者为(将在后面介绍的)__all__变量赋值。
1.15 MySQL数据库基础
1.15.1 MySQL简介
- Mysql是最流行的RDBMS(Relational Database Management System:关系数据库管理系统),特别是在WEB应用方面。
- 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,
- 每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据。
- 所谓的关系型数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。
- RDBMS即关系数据库管理系统(Relational Database Management System)的特点:
- 1.数据以表格的形式出现
- 2.每行为各种记录名称
- 3.每列为记录名称所对应的数据域
- 4.许多的行和列组成一张表单
- 5.若干的表单组成database
- 在我们开始学习MySQL 数据库前,让我们先了解下RDBMS的一些术语:
数据库: 数据库是一些关联表的集合。.
数据表: 表是数据的矩阵。在一个数据库中的表看起来像一个简单的电子表格。
列: 一列(数据元素) 包含了相同的数据, 例如邮政编码的数据。
行:一行(=元组,或记录)是一组相关的数据,例如一条用户订阅的数据。
冗余:存储两倍数据,冗余降低了性能,但提高了数据的安全性。
主键:主键是唯一的。一个数据表中只能包含一个主键。你可以使用主键来查询数据。
外键:外键用于关联两个表。
复合键:复合键(组合键)将多个列作为一个索引键,一般用于复合索引。
索引:使用索引可快速访问数据库表中的特定信息。索引是对数据库表中一列或多列的值进行排序的一种结构。类似于书籍的目录。
参照完整性: 参照的完整性要求关系中不允许引用不存在的实体。与实体完整性是关系模型必须满足的完整性约束条件,目的是保证数据的一致性。
SQL:
- SQL: 结构化查询语言(Structured Query Language)简称SQL,是最重要的关系数据库操作语言.
- 有上百种数据库产品都支持SQL,如:MySQL、DB2、ORACLE、INGRES、SYBASE、SQLSERVER...
- 结构化查询语言包含6个部分:
1. 数据查询语言(DQL:Data Query Language):SELECT
2. 数据操作语言(DML:Data Manipulation Language):INSERT,UPDATE和DELETE
3. 事务处理语言(TPL):BEGIN TRANSACTION,COMMIT和ROLLBACK
4. 数据控制语言(DCL):GRANT(授权)或REVOKE(回收权限)
5. 数据定义语言(DDL):CREATE、ALTER和DROP
6. 指针控制语言(CCL):DECLARE CURSOR,FETCH INTO和UPDATE WHERE CURRENT用于对一个或多个表单独行的操作
- 在本节中,会让大家快速掌握Mysql的基本知识,并轻松使用Mysql数据库。
mysql数据库的安装:
- 网址:https://www.mysql.com/downloads/ 下载,但是已经是商业版了
- 可下载 MariaDB 开源的 https://downloads.mariadb.org
- 建议Window上可以安装一个集成环境如:XAMPP:https://www.apachefriends.org/zh_cn/download.html
- Ubuntu系统安装:sudo apt-get install mysql-server mysql-client
- 服务的启动和停止
- 配置文件:windows下是:my.ini Linux下:mysqld.conf
连接数据库:
mysql -h 主机名 -u 用户名 -p密码 库名
C:\>mysql --采用匿名账号和密码登陆本机服务
C:\>mysql -h localhost -u root -proot --采用root账号和root密码登陆本机服务
C:\>mysql -u root -p --推荐方式默认登陆本机
Enter password: ****
C:\>mysql -u root -p mydb --直接进入mydb数据库的方式登陆
SQL语句中的快捷键
\G 格式化输出(文本式,竖立显示)
\s 查看服务器端信息
\c 结束命令输入操作
\q 退出当前sql命令行模式
\h 查看帮助
1.15.2. SQL的基本操作
数据库操作:
mysql> show databases; --查看当前用户下的所有数据库
mysql> create database [if not exists] 数据库名; --创建数据库
mysql> use test; --选择进入test数据库
mysql> show create database 数据库名\G --查看建数据库语句
mysql> select database(); --查看当前所在的数据库位置
mysql> drop database [if exists] 数据库名; --删除一个数据库
数据表操作:
mysql> show tables; --查看当前库下的所有表格
mysql> desc tb1; --查看tb1的表结构。
mysql> show create table 表名\G --查看表的建表语句。
mysql> create table demo( --创建demo表格
-> name varchar(16) not null,
-> age int,
-> sex enum('w','m') not null default 'm');
Query OK, 0 rows affected (0.05 sec)
mysql> show columns from demo; --查看表结构
mysql> desc demo; --查看表结构
+-------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| name | varchar(16) | NO | | NULL | |
| age | int(11) | YES | | NULL | |
| sex | enum('w','m') | NO | | m | |
+-------+---------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql>drop table if exists mytab; -- 尝试删除mytab表格
数据操作:
--添加一条数据
mysql> insert into demo(name,age,sex) values('zhangsan',20,'w');
Query OK, 1 row affected (0.00 sec)
--不指定字段名来添加数据
mysql> insert into demo values('lisi',22,'m');
Query OK, 1 row affected (0.00 sec)
--指定部分字段名来添加数据
mysql> insert into demo(name,age) values('wangwu',23);
Query OK, 1 row affected (0.00 sec)
--批量添加数据
mysql> insert into demo(name,age,sex) values('aaa',21,'w'),("bbb",22,'m');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> select * from demo; --查询数据
mysql> update demo set age=24 where name='aaa'; --修改
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> delete from demo where name='bbb'; --删除
Query OK, 1 row affected (0.00 sec)
1.15.3 MySQL数据结构类型及操作:
MySQL的数据类型分为三个类:数值类型、字串类型、日期类型 。 还有一个特殊的值:NULL。
1 数值类型:
*tinyint(1字节) 0~255 -128~127
smallint(2字节)
mediumint(3字节)
*int(4字节)
bigint(8字节)
*float(4字节) float(6,2)
*double(8字节)
decimal(自定义)字串形数值
2 字串类型
普通字串
*char 定长字串 char(8)
*varchar 可变字串 varchar(8)
二进制类型
tinyblob
blob
mediumblob
longblob
文本类型
tinytext
*text 常用于<textarea></textarea>
mediumtext
longtext
*enum枚举
set集合
3 时间和日期类型:
date 年月日
time 时分秒
*datetime 年月日时分秒
timestamp 时间戳
year 年
4 NULL值
NULL意味着“没有值”或“未知值”
可以测试某个值是否为NULL
不能对NULL值进行算术计算
对NULL值进行算术运算,其结果还是NULL
0或NULL都意味着假,其余值都意味着真
MySQL的运算符:
算术运算符:+ - * / %
比较运算符:= > < >= <= <> !=
数据库特有的比较:in,not in, is null,is not null,like, between and
逻辑运算符:and or not
表的字段约束:
unsigned 无符号(正数)
zerofill 前导零填充
auto_increment 自增
default 默认值
not null 非空
PRIMARY KEY 主键 (非null并不重复)
unique 唯一性 (可以为null但不重复)
index 常规索引
建表语句格式:
create table 表名(
字段名 类型 [字段约束],
字段名 类型 [字段约束],
字段名 类型 [字段约束],
...
);
mysql> create table stu(
-> id int unsigned not null auto_increment primary key,
-> name varchar(8) not null unique,
-> age tinyint unsigned,
-> sex enum('m','w') not null default 'm',
-> classid char(6)
-> );
Query OK, 0 rows affected (0.05 sec)
mysql> desc stu;
+---------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(8) | NO | UNI | NULL | |
| age | tinyint(3) unsigned | YES | | NULL | |
| sex | enum('m','w') | NO | | m | |
| classid | char(6) | YES | | NULL | |
+---------+---------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> show create table stu\G --查看建表的语句
*************************** 1. row ***************************
Table: stu
Create Table: CREATE TABLE `stu` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(8) NOT NULL,
`age` tinyint(3) unsigned default NULL,
`sex` enum('m','w') NOT NULL default 'm',
`classid` char(6) default NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
修改表结构:
格式: alter table 表名 action(更改选项);
更改选项:
1. 添加字段:alter table 表名 add 字段名信息
例如:
-- 在user表的最后追加一个num字段 设置为int not null
mysql> alter table user add num int not null;
-- 在user表的email字段后添加一个age字段,设置int not null default 20;
mysql> alter table user add age int not null default 20 after email;
-- 在user表的最前面添加一个aa字段设置为int类型
mysql> alter table user add aa int first;
2. 删除字段:alter table 表名 drop 被删除的字段名
例如:-- 删除user表的aa字段
mysql> alter table user drop aa;
3. 修改字段:alter table 表名 change[modify] 被修改后的字段信息
其中:change可以修改字段名, modify 不修改
例如:
-- 修改user表中age字段信息(类型),(使用modify关键字的目的不修改字段名)
mysql> alter table user modify age tinyint unsigned not null default 20;
-- 修改user表的num字段改为mm字段并添加了默认值(使用change可以改字段名)
mysql> alter table user change num mm int not null default 10;
4. 添加和删除索引
-- 为user表中的name字段添加唯一性索引,索引名为uni_name;
mysql> alter table user add unique uni_name(name);
-- 为user表中的email字段添加普通索引,索引名为index_eamil
mysql> alter table user add index index_email(email);
-- 将user表中index_email的索引删除
mysql> alter table user drop index index_email;
5. 更改表名称:
ALTER TABLE 旧表名 RENAME AS 新表名
6. 更改AUTO_INCREMENT初始值:
ALTER TABLE 表名称 AUTO_INCREMENT=1
7. 更改表类型:
ALTER TABLE 表名称 ENGINE="InnoDB"
MySQL数据库中的表类型一般常用两种:MyISAM和InnoDB
区别:MyISAM类型的数据文件有三个frm(结构)、MYD(数据)、MYI(索引)
MyISAM类型中的表数据增 删 改速度快,不支持事务,没有InnoDB安全。
InnoDB类型的数据文件只有一个 .frm
InnoDB类型的表数据增 删 改速度没有MyISAM的快,但支持事务,相对安全。
1.15.4 数据的DML操作:添加数据,修改数据,删除数据
添加数据:
格式: insert into 表名[(字段列表)] values(值列表...);
--标准添加(指定所有字段,给定所有的值)
mysql> insert into stu(id,name,age,sex,classid) values(1,'zhangsan',20,'m','lamp138');
Query OK, 1 row affected (0.13 sec)
mysql>
--指定部分字段添加值
mysql> insert into stu(name,classid) value('lisi','lamp138');
Query OK, 1 row affected (0.11 sec)
-- 不指定字段添加值
mysql> insert into stu value(null,'wangwu',21,'w','lamp138');
Query OK, 1 row affected (0.22 sec)
-- 批量添加值
mysql> insert into stu values
-> (null,'zhaoliu',25,'w','lamp94'),
-> (null,'uu01',26,'m','lamp94'),
-> (null,'uu02',28,'w','lamp92'),
-> (null,'qq02',24,'m','lamp92'),
-> (null,'uu03',32,'m','lamp138'),
-> (null,'qq03',23,'w','lamp94'),
-> (null,'aa',19,'m','lamp138');
Query OK, 7 rows affected (0.27 sec)
Records: 7 Duplicates: 0 Warnings: 0
修改操作:
格式:update 表名 set 字段1=值1,字段2=值2,字段n=值n... where 条件
-- 将id为11的age改为35,sex改为m值
mysql> update stu set age=35,sex='m' where id=11;
Query OK, 1 row affected (0.16 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 将id值为12和14的数据值sex改为m,classid改为lamp92
mysql> update stu set sex='m',classid='lamp92' where id=12 or id=14 --等价于下面
mysql> update stu set sex='m',classid='lamp92' where id in(12,14);
Query OK, 2 rows affected (0.09 sec)
Rows matched: 2 Changed: 2 Warnings: 0
删除操作:
格式:delete from 表名 [where 条件]
-- 删除stu表中id值为100的数据
mysql> delete from stu where id=100;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值为20到30的数据
mysql> delete from stu where id>=20 and id<=30;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值为20到30的数据(等级于上面写法)
mysql> delete from stu where id between 20 and 30;
Query OK, 0 rows affected (0.00 sec)
-- 删除stu表中id值大于200的数据
mysql> delete from stu where id>200;
Query OK, 0 rows affected (0.00 sec)
1.15.5 数据的DQL操作:数据查询
格式:
select [字段列表]|* from 表名
[where 搜索条件]
[group by 分组字段 [having 子条件]]
[order by 排序 asc|desc]
[limit 分页参数]
各种查询:
mysql> select * from stu;
+----+----------+-----+-----+---------+
| id | name | age | sex | classid |
+----+----------+-----+-----+---------+
| 1 | zhangsan | 20 | m | lamp138 |
| 2 | lisi | 20 | m | lamp138 |
| 3 | wangwu | 21 | w | lamp138 |
| 4 | zhaoliu | 25 | w | lamp94 |
| 5 | uu01 | 26 | m | lamp94 |
| 6 | uu02 | 28 | w | lamp92 |
| 7 | qq02 | 24 | m | lamp92 |
| 8 | uu03 | 32 | m | lamp138 |
| 9 | qq03 | 23 | w | lamp94 |
| 10 | aa | 19 | m | lamp138 |
| 11 | sad | 35 | m | lamp94 |
| 12 | tt | 25 | m | lamp92 |
| 13 | wer | 25 | w | lamp94 |
| 14 | xx | 25 | m | lamp92 |
| 15 | kk | 0 | w | lamp94 |
+----+----------+-----+-----+---------+
15 rows in set (0.00 sec)
1. where条件查询
1. 查询班级为lamp138期的学生信息
mysql> select * from stu where classid='lamp138';
2. 查询lamp138期的男生信息(sex为m)
mysql> select * from stu where classid='lamp138' and sex='m';
3. 查询id号值在10以上的学生信息
mysql> select * from stu where id>10;
4. 查询年龄在20至25岁的学生信息
mysql> select * from stu where age>=20 and age<=25;
mysql> select * from stu where age between 20 and 25;
5. 查询年龄不在20至25岁的学生信息
mysql> select * from stu where age not between 20 and 25;
mysql> select * from stu where age<20 or age>25;
6. 查询id值为1,8,4,10,14的学生信息
select * from stu where id in(1,8,4,10,14);
mysql> select * from stu where id=1 or id=8 or id=4 or id=10 or id=14;
7. 查询lamp138和lamp94期的女生信息
mysql> select * from stu where classid in('lamp138','lamp94') and sex='w';
mysql> select * from stu where (classid='lamp138' or classid='lamp94') and sex='w
1.15.6 数据库授权、备份和恢复
授权:
格式:grant 允许操作 on 库名.表名 to 账号@来源 identified by '密码';
--实例:创建zhangsan账号,密码123,授权lamp61库下所有表的增/删/改/查数据,来源地不限
mysql> grant select,insert,update,delete on lamp61.* to zhangsan@'%' identified by '123';
mysql> grant all on *.* to zhangsan@'%' identified by '123';
Query OK, 0 rows affected (0.00 sec)
-- 授权一个用户(zhangsan)密码123,可以对所有的库,所有的表做所有操作。
mysql> grant all on *.* to zhangsan@'%' identified by '123';
Query OK, 0 rows affected (0.17 sec)
--刷新生效,否则就要重启MySQL服务才可以。
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
--浏览当前MySQL用户信息
mysql> select user,host,password from mysql.user;
+----------+-----------------+-------------------------------------------+
| user | host | password |
+----------+-----------------+-------------------------------------------+
| root | localhost | *23AE809DDACAF96AF0FD78ED04B6A265E05AA257 |
| root | 127.0.0.1 | |
| | localhost | |
| zhangsan | % | *23AE809DDACAF96AF0FD78ED04B6A265E05AA257 |
| admin | 192.168.112.132 | *23AE809DDACAF96AF0FD78ED04B6A265E05AA257 |
+----------+-----------------+-------------------------------------------+
5 rows in set (0.00 sec)
-- 移除一些权限
-- revoke:只删除了用户权限,但没有删除这个用户
mysql> revoke insert,delete on *.* from admin@192.168.112.132 identified by'123';
-- 查看指定用户的权限信息
mysql> show grants for xbb@localhost;
+------------------------------------------------------------------------------------------------------------+
| Grants for xbb@localhost |
+------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'xbb'@'localhost' IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' |
+------------------------------------------------------------------------------------------------------------+
--drop user:删除了整个用户及其权限(包括数据字典中的数据)
mysql> drop user 'xbb'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql> select user,host from mysql.user;
+------------------+-----------+
| user | host |
+------------------+-----------+
| root | 127.0.0.1 |
| debian-sys-maint | localhost |
| root | localhost |
| root | wangxg |
+------------------+-----------+
4 rows in set (0.00 sec)
备份与恢复(导入和导出)
-- 将lamp138库导出
D:\>mysqldump -u root -p lamp138 >lamp138.sql
Enter password:
---- 将lamp138库中的stu表导出
D:\>mysqldump -u root -p lamp138 stu >lamp138_stu.sql
Enter password:
-- 将lamp138库导入
D:\>mysql -u root -p lamp138<lamp138.sql
Enter password:
-- 将lamp138库中stu表导入
D:\>mysql -u root -p lamp138<lamp138_stu.sql
Enter password:
1.15.7 MySQL的多表联查
- 表之间的关系有:1对1 1对多 多对多
1. 嵌套查询:一个查询的结果是另外sql查询的条件:
如:查询stu表中年龄最大的是谁?
mysql> select * from stu where age=(select max(age) from stu);
mysql> select * from stu where age in(select max(age) from stu); --(子查询结果是多条时使用in查询)
+----+------+------+-----+----------+
| id | name | age | sex | classid |
+----+------+------+-----+----------+
| 14 | abc | 33 | w | python01 |
+----+------+------+-----+----------+
1 row in set (0.01 sec)
2. where关联查询
已知:员工personnel表和部门department表,其中员工表中的did字段为部门表id主键关联。
查询所有员工信息,并显示所属部门名称
要求:显示字段:员工id 部门 姓名
mysql> select p.id,d.name,p.name from personnel p,department d where p.did = d.id;
+----+-----------+-----------+
| id | name | name |
+----+-----------+-----------+
| 2 | 人事部 | 李玉刚 |
| 10 | 人事部 | 阿杜 |
| 4 | 市场部 | 刘欢 |
。。。。
3. 连接join查询
左联:left join
右联:right join
内联:inner join
已知如下表所示,商品类别信息表(具有两层类别关系,通过pid表示,0表示一级类别)
mysql> select * from type;
+----+-----------+------+
| id | name | pid |
+----+-----------+------+
| 1 | 服装 | 0 |
| 2 | 数码 | 0 |
| 3 | 男装 | 1 |
| 4 | 手机 | 2 |
| 5 | 相机 | 2 |
| 6 | 电脑 | 2 |
| 7 | 女装 | 1 |
| 8 | 童装 | 1 |
| 9 | 食品 | 0 |
| 10 | 零食 | 9 |
| 11 | 特产 | 9 |
| 12 | 休闲装 | 1 |
+----+-----------+------+
12 rows in set (0.00 sec)
mysql> desc type;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(16) | NO | | NULL | |
| pid | int(10) unsigned | YES | | NULL | |
+-------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
-- 查询二级类别信息,并关联出他们的父类别名称
mysql> select t1.id,t1.name,t2.name from type t1,type t2 where t1.pid!=0 and t1.pid=t2.id;
+----+-----------+--------+
| id | name | name |
+----+-----------+--------+
| 3 | 男装 | 服装 |
| 4 | 手机 | 数码 |
| 5 | 相机 | 数码 |
| 6 | 电脑 | 数码 |
| 7 | 女装 | 服装 |
| 8 | 童装 | 服装 |
| 10 | 零食 | 食品 |
| 11 | 特产 | 食品 |
| 12 | 休闲装 | 服装 |
+----+-----------+--------+
9 rows in set (0.01 sec)
--统计每个一级类别下都有多少个子类别。
mysql> select t1.id,t1.name,count(t2.id) from type t1,type t2 where t1.pid=0 and t1.id=t2.pid group by t1.id;
+----+--------+--------------+
| id | name | count(t2.id) |
+----+--------+--------------+
| 1 | 服装 | 4 |
| 2 | 数码 | 3 |
| 9 | 食品 | 2 |
+----+--------+--------------+
3 rows in set (0.00 sec)
1.15.8 MySQL的其他操作
1. MySQL的表复制
复制表结构
mysql> create table 目标表名 like 原表名;
复制表数据
mysql> insert into 目标表名 select * from 原表名;
2. 数据表的索引
创建索引
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
删除索引
DROP INDEX index_name ON talbe_name
3. mysql视图
创建视图:
mysql> create view v_t1 as select * from t1 where id>4 and id<11;
Query OK, 0 rows affected (0.00 sec)
view视图的帮助信息:
mysql> ? view
ALTER VIEW
CREATE VIEW
DROP VIEW
查看视图:
mysql> show tables;
删除视图v_t1:
mysql> drop view v_t1;
4. MySQL的内置函数
字符串处理函数
---------------------------------------------
*concat(s1,s2,…Sn) 连接s1,s2..Sn为一个字符串
insert(str,x,y,instr)将字符串str从第xx位置开始,y字符串的子字符串替换为字符串str
lower(str)将所有的字符串变为小写
upper(str)将所有的字符串变为大写
left(str,x)返回字符串中最左边的x个字符
rigth(str,y)返回字符串中最右边的x个字符
lpad(str,n,pad)用字符串pad对str最左边进行填充,直到长度为n个字符串长度
rpad(str,n,pad)用字符串pad对str最右边进行填充,直到长度为n个字符串长度
trim(str) 去掉左右两边的空格
ltrim(str) 去掉字符串str左侧的空格
rtrim(str) 去掉字符串str右侧的空格
repeat(str,x) 返回字符串str重复x次
replace(str,a,b)将字符串的的a替换成b
strcmp(s1,s2) 比较字符串s1和s2
substring(s,x,y)返回字符串指定的长度
*length(str) 返回值为字符串str 的长度
数值函数
-----------------------------------------------------
*abs(x) 返回x的绝对值
ceil(x) 返回大于x的最小整数值
floor(x) 返回小于x的最大整数值
mod(x,y) 返回x/y的取余结果
rand() 返回0~1之间的随机数
*round(x,y)返回参数x的四舍五入的有y位小数的值
truncate(x,y) 返回x截断为y位小数的结果
日期和时间函数
---------------------------------------------------
curdate() 返回当前日期,按照’YYYY-MM-DD’格式
curtime() 返回当前时间,当前时间以'HH:MM:SS'
*now() 返回当前日期和时间,
*unix_timestamp(date) 返回date时间的unix时间戳
from_unixtime(unix_timestamp[,format]) 返回unix时间的时间
week(date) 返回日期是一年中的第几周
year(date) 返回日期的年份
hour(time) 返回time的小时值
minute(time) 返回日time的分钟值
monthname(date) 返回date的月份
*date_fomat(date,fmt) 返回按字符串fmt格式化日期date值
date_add(date,INTERVAL,expr type) 返回一个日期或者时间值加上一个时间间隔的时间值
*datediff(expr,expr2) 返回起始时间和结束时间的间隔天数
//统计时间戳647583423距离当前时间相差天数(生日天数(不考虑年份))
mysql> select datediff(date_format(from_unixtime(647583423),"2017-%m-%d %h:%i:%s"),now());
其他常用函数
------------------------------------------------------
*database() 返回当前数据库名
version() 返回当前服务器版本
user() 返回当前登陆用户名
inet_aton 返回当前IP地址的数字表示 inet_aton("192.168.80.250");
inet_ntoa(num) 返回当前数字表示的ip inet_ntoa(3232256250);
*password(str) 返回当前str的加密版本
*md5(str) 返回字符串str的md5值
5. MySQL的事务处理
关闭自动提交功能(开启手动事务)
mysql> set autocommit=0;
从表t1中删除了一条记录
mysql> delete from t1 where id=11;
此时做一个p1还原点:
mysql> savepoint p1;
再次从表t1中删除一条记录:
mysql> delete from t1 where id=10;
再次做一个p2还原点:
mysql> savepoint p2;
此时恢复到p1还原点,当然后面的p2这些还原点自动会失效:
mysql> rollback to p1;
退回到最原始的还原点:
mysql> rollback;
回滚
开启自动事务提交(关闭手动事务)
mysql> set autocommit=1;
6. MySQL的触发器
格式:1、触发器的定义:
CREATE TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt
说明:
# trigger_name:触发器名称
# trigger_time:触发时间,可取值:BEFORE或AFTER
# trigger_event:触发事件,可取值:INSERT、UPDATE或DELETE。
# tb1_name:指定在哪个表上
# trigger_stmt:触发处理SQL语句。
示例:
mysql> delimiter $$
mysql> create trigger del_stu before delete on stu for each row
-> begin
-> insert into stu_bak values(old.id,old.name,old.sex,old.age,old.addtime);
-> end;
-> $$
Query OK, 0 rows affected (0.05 sec)
mysql> delimiter ;
7. mysql日志
开启日志: 在mysql配置文件中开启:log-bin=mysql-bin
查看bin-log日志:
mysql>show binary logs;
查看最后一个bin-log日志:
mysql>show master status;
此时就会多一个最新的bin-log日志
mysql>flush logs;
查看最后一个bin日志.
mysql>show master status;
mysql>reset master;
清空所有的bin-log日志
执行查看bin-log日志
备份数据:
mysqldump -uroot -pwei test -l -F '/tmp/test.sql'
其中:-F即flush logs,可以重新生成新的日志文件,当然包括log-bin日志
// Linux关闭MySQL的命令
$mysql_dir/bin/mysqladmin -uroot -p shutdown
// linux启动MySQL的命令
$mysql_dir/bin/mysqld_safe &
8、有关慢查询操作:
开户和设置慢查询时间:
vi /etc/my.cnf
log_slow_queries=slow.log
long_query_time=5
查看设置后是否生效
mysql> show variables like "%quer%";
慢查询次数:
mysql> show global status like "%quer%";
9 数据库的恢复
1. 首先恢复最后一次的备份完整数据
[root@localhost mnt]# mysql -u root -p mydemo<mydemo_2017-7-26.sql
Enter password:
2. 查看bin-log日志
[root@localhost data]# mysqlbinlog --no-defaults mysql-bin.000009;
查找到恢复的节点
3. 执行bin-log日志文件,恢复最后一块的增量数据。
[root@localhost data]# mysqlbinlog --no-defaults --stop-position="802" mysql-bin.000009|mysql -u ro
1.16 Python的数据库支持
1.16.1. 什么是 PyMySQL?
- PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb。
- PyMySQL 遵循 Python 数据库 API v2.0 规范,并包含了 pure-Python MySQL 客户端库。
1.16.2. PyMySQL安装
- PyMySQL下载地址:https://github.com/PyMySQL/PyMySQL。
2.1 使用pip命令进行安装:
$ pip install PyMySQL
2.2 使用 git 命令下载安装包安装(你也可以手动下载):
$ git clone https://github.com/PyMySQL/PyMySQL
$ cd PyMySQL/
$ python3 setup.py install
1.16.3. 数据库连接
3.1 通过如下代码测试数据库连接
#!/usr/bin/python3
import pymysql
# 打开数据库连接
db = pymysql.connect(host="localhost",user="root",password="",db="mydb",charset="utf8")
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
# 使用 execute() 方法执行 SQL 查询
cursor.execute("SELECT VERSION()")
# 使用 fetchone() 方法获取单条数据.
data = cursor.fetchone()
print ("Database version : %s " % data)
# 关闭数据库连接
db.close()
3.2 执行数据查询
#!/usr/bin/python3
import pymysql
#打开数据库连接
db = pymysql.connect(host="localhost",user="root",password="",db="mydb",charset="utf8")
#使用cursor()方法创建一个游标对象cursor
cursor = db.cursor()
#定义查询sql语句
#sql = "select * from stu"
sql = "select * from stu where classid='%s'"%("python03")
try:
# 使用execute()方法执行SQL查询
cursor.execute(sql)
print("本次查询条数:",cursor.rowcount)
'''
# 使用fetchone()方法获取单条数据.
while True:
data = cursor.fetchone();
if data == None:
break;
print (data)
'''
#使用fetchall()获取所有结果
alist = cursor.fetchall()
for vo in alist:
print(vo)
except Exception as err:
print("SQL执行错误,原因:",err)
# 关闭数据库连接
db.close()
3.3 执行数据添加
#!/usr/bin/python3
import pymysql
#打开数据库连接
db = pymysql.connect(host="localhost",user="root",password="",db="mydb",charset="utf8")
#使用cursor()方法创建一个游标对象cursor
cursor = db.cursor()
#定义添加sql语句
data = ("uu100",28,'w','python05')
sql = "insert into stu(name,age,sex,classid) values('%s','%d','%s','%s')"%(data)
try:
# 使用execute()方法执行SQL
m = cursor.execute(sql)
# 事务提交
db.commit()
print("成功操作条数:",m)
#print("成功操作条数:",cursor.rowcount)
except Exception as err:
#事务回滚
db.rollback()
print("SQL执行错误,原因:",err)
# 关闭数据库连接
db.close()
3.4 执行删除操作
#!/usr/bin/python3
import pymysql
#打开数据库连接
db = pymysql.connect(host="localhost",user="root",password="",db="mydb",charset="utf8")
#使用cursor()方法创建一个游标对象cursor
cursor = db.cursor()
#定义删除sql语句
sql = "delete from stu where id=%d"%(100)
try:
# 使用execute()方法执行SQL
cursor.execute(sql)
# 事务提交
db.commit()
print("成功删除条数:",cursor.rowcount)
except Exception as err:
#事务回滚
db.rollback()
print("SQL执行错误,原因:",err)
# 关闭数据库连接
db.close()
数据库查询操作:
- Python查询Mysql使用 fetchone() 方法获取单条数据, 使用fetchall() 方法获取多条数据。
- fetchone(): 该方法获取下一个查询结果集。结果集是一个对象,最后返回None结束
- fetchall(): 接收全部的返回结果行.
- rowcount: 这是一个只读属性,并返回执行execute()方法后影响的行数。
附录:pip命令
------------------------------------------------------------
列出已安装的包:
$ pip list
$ pip freeze # 查看自己安装的
安装软件(安装特定版本的package,通过使用==, >=, <=, >, <来指定一个版本号)**
$ pip install SomePackage
$ pip install 'Markdown<2.0'
$ pip install 'Markdown>2.0,<2.0.3'
卸载软件pip uninstall SomePackage
$ pip uninstall SomePackage
下载所需的软件包:
$ pip download SomePackage -d directory
例如下载PyMySQL软件包
$ pip download PyMySQL -d D:/pypackage
安装下载好的软件包文件
$ pip install 目录/软件包文件名
如安装PyMySQL软件包
$ pip3.6 install D:/pypackage/PyMySQL-0.7.11-py2.py3-none-any.whl
1.17 图形用户界面实战
- 本节介绍如何创建Python程序的图形用户界面(GUI),也就是那些带有按钮和文本框的窗口。
- 目前支持Python的所谓"GUI工具包"有很多,但没有一个被认为是标准的,也好,选择空间大
- GUI工具包:
| 工具包名 | 介绍 | URL地址 |
|---|---|---|
| Tkinter | 使用Tk平台。很容易得到。半标准 | http://wiki.python.org/moin/TkInter |
| wxpython | 基于wxWindows。跨平台越来越流行 | http://wxpython.org |
| PythonWin | 只能在Windows上使用。 | http://starship.python.net/crew/mhammond |
| Java Swing | 只能用于Python。使用本机的Java GUI | http://java.sun.com/docs/books/tutorial/uiswing |
| PyGTK | 使用GTK平台,在Linux上很流行 | http://pygtk.org |
| PyQt | 使用Qt平台,跨平台 | http://wiki.python.org/moin/PyQt |
1.17.1 安装:wxpython
pip install -U wxpython
--Installing collected packages: six, wxpython
--Successfully installed six-1.11.0 wxpython-4.0.1
- 开发步骤:
1. 导入wx模块
2. 定义一个应用程序对象
3. 创建wx.Frame主窗口对象,设置窗口标题和大小
4. 创建组件、布局、添加事件处理等操作
5. 通过Frame的show()方法显示窗体
6. 进入应用程序事件主循环
1.17.2 创建并且显示一个框架
# 导入wxPython模块
import wx
# 创建应用程序对象
app = wx.App()
win = wx.Frame(None) #创建一个单独的窗口
btn=wx.Button(win) #创建一个按钮组件
win.Show() #设置可见
#进入应用程序事件主循环
app.MainLoop()
1.17.3 设置标题,添加按钮
import wx
app = wx.App()
win = wx.Frame(None,title="我的记事本") #创建一个单独的窗口
loadButton = wx.Button(win,label="Open")
saveButton = wx.Button(win,label="Save")
win.Show()
app.MainLoop() #进入应用程序事件主循环
1.17.4 设置标题,添加按钮,并简单布局
import wx
app = wx.App()
win = wx.Frame(None,title="我的记事本",size=(410,335)) #创建一个单独的窗口
loadButton = wx.Button(win,label="Open",pos=(225,5),size=(80,25))
saveButton = wx.Button(win,label="Save",pos=(315,5),size=(80,25))
filename = wx.TextCtrl(win,pos=(5,5),size=(210,25))
contents = wx.TextCtrl(win,pos=(5,35),size=(390,260),style=wx.TE_MULTILINE | wx.HSCROLL)
win.Show()
app.MainLoop() #进入应用程序事件主循环
1.17.5 组件布局
import wx
app = wx.App()
win = wx.Frame(None,title="我的记事本",size=(410,335)) #创建一个单独的窗口
bkg = wx.Panel(win)
#创建组件
loadButton = wx.Button(bkg,label="Open")
saveButton = wx.Button(bkg,label="Save")
filename = wx.TextCtrl(bkg)
contents = wx.TextCtrl(bkg,style=wx.TE_MULTILINE | wx.HSCROLL)
#布局容器
hbox=wx.BoxSizer() #默认水平布局
hbox.Add(filename,proportion=1,flag=wx.EXPAND)
hbox.Add(loadButton,proportion=0,flag=wx.LEFT,border=5)
hbox.Add(saveButton,proportion=0,flag=wx.LEFT,border=5)
#布局容器
vbox=wx.BoxSizer(wx.VERTICAL) #垂直布局
vbox.Add(hbox,proportion=0,flag=wx.EXPAND|wx.ALL,border=5)
vbox.Add(contents,proportion=1,flag=wx.EXPAND|wx.LEFT|wx.BOTTOM|wx.RIGHT,border=50)
bkg.SetSizer(vbox)
win.Show()
app.MainLoop() #进入应用程序事件主循环
1.17.6 为按钮添加事件并完成其事件处理操作
import wx
#按钮事件处理函数
def load(event):
'''加载文件内容'''
file=open(filename.GetValue(),"r")
contents.SetValue(file.read())
file.close()
def save(event):
'''保持文件内容'''
file=open(filename.GetValue(),"w")
file.write(contents.GetValue())
file.close()
#
app = wx.App()
win = wx.Frame(None,title="我的记事本",size=(410,335)) #创建一个单独的窗口
win.Show()
loadButton = wx.Button(win,label="Open",pos=(225,5),size=(80,25))
saveButton = wx.Button(win,label="Save",pos=(315,5),size=(80,25))
loadButton.Bind(wx.EVT_BUTTON,load)
saveButton.Bind(wx.EVT_BUTTON,save)
filename = wx.TextCtrl(win,pos=(5,5),size=(210,25))
contents = wx.TextCtrl(win,pos=(5,35),size=(390,260),style=wx.TE_MULTILINE | wx.HSCROLL)
app.MainLoop() #进入应用程序事件主循环
1.18 阶段案例实战 :《飞机游戏》
- 本次开发需要安装一个Python的游戏模块:pygame。 方式:
pip install pygame
开发步骤如下:
1.18.1 创建游戏主页面窗口,并添加滚动背景。

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time
# 创建游戏主页面窗口,并添加滚动背景。
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.18.2 添加键盘事件处理函数

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time
# 添加键盘事件处理函数。
def key_control(hero_temp):
''' 键盘控制函数 '''
#获取事件,比如按键等
for event in pygame.event.get():
#判断是否是点击了退出按钮
if event.type == QUIT:
print("exit")
exit()
#获取按下的键(返回的是元组值)
pressed_keys = pygame.key.get_pressed()
#检测是否按下a或者left键
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
print('left')
#检测是否按下d或者right键
elif pressed_keys[K_RIGHT] or pressed_keys[K_d]:
print('right')
#检查是否是空格键
if pressed_keys[K_SPACE]:
print('space')
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
# 调用键盘控制函数
key_control(None)
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.18.3 放置玩家英雄飞机,并绑定键盘事件,实现飞机移动

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time
# 放置玩家英雄飞机,并绑定键盘事件,实现飞机移动
class HeroPlane:
''' 玩家飞机类(英雄) '''
def __init__(self, screen_temp):
self.x = 200
self.y = 400
self.screen = screen_temp
self.image = pygame.image.load("./images/me.png")
def display(self):
''' 绘制玩家到窗口中 '''
self.screen.blit(self.image, (self.x, self.y))
def move_left(self):
''' 左移动,并判断防止越界 '''
self.x -= 5
if self.x<0:
self.x=0
def move_right(self):
''' 右移动,并判断防止越界 '''
self.x += 5
if self.x > 406:
self.x = 406
def key_control(hero_temp):
''' 键盘控制函数 '''
#获取事件,比如按键等
for event in pygame.event.get():
#判断是否是点击了退出按钮
if event.type == QUIT:
print("exit")
exit()
#获取按下的键(返回的是元组值)
pressed_keys = pygame.key.get_pressed()
#检测是否按下a或者left键
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
print('left')
hero_temp.move_left()
#检测是否按下d或者right键
elif pressed_keys[K_RIGHT] or pressed_keys[K_d]:
print('right')
hero_temp.move_right()
#检查是否是空格键
if pressed_keys[K_SPACE]:
print('space')
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
#3. 创建一个玩家飞机对象
hero = HeroPlane(screen)
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
#显示英雄玩家
hero.display()
# 键盘控制(负责移动玩家)
key_control(hero)
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.18.4 添加玩家子弹,并实现发射

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time
# 添加玩家子弹,并实现发射。
class HeroPlane:
''' 玩家飞机类(英雄) '''
def __init__(self, screen_temp):
self.x = 200
self.y = 400
self.screen = screen_temp
self.image = pygame.image.load("./images/me.png")
self.bullet_list = [] #存储发射出去的子弹对象引用
def display(self):
''' 绘制玩家到窗口中 '''
#遍历移动子弹
for bullet in self.bullet_list:
bullet.display()
#移动子弹,并判断是否越界。
if bullet.move():
self.bullet_list.remove(bullet)
self.screen.blit(self.image, (self.x, self.y))
def move_left(self):
''' 左移动,并判断防止越界 '''
self.x -= 5
if self.x<0:
self.x=0
def move_right(self):
''' 右移动,并判断防止越界 '''
self.x += 5
if self.x > 406:
self.x = 406
def fire(self):
self.bullet_list.append(Bullet(self.screen, self.x, self.y))
print(len(self.bullet_list))
class Bullet:
''' 玩家子弹类 '''
def __init__(self, screen_temp, x, y):
self.x = x+51
self.y = y
self.screen = screen_temp
self.image = pygame.image.load("./images/pd.png")
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self):
self.y-=10
if self.y<-20:
return True
def key_control(hero_temp):
''' 键盘控制函数 '''
#获取事件,比如按键等
for event in pygame.event.get():
#判断是否是点击了退出按钮
if event.type == QUIT:
print("exit")
exit()
#获取按下的键(返回的是元组值)
pressed_keys = pygame.key.get_pressed()
#检测是否按下a或者left键
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
print('left')
hero_temp.move_left()
#检测是否按下d或者right键
elif pressed_keys[K_RIGHT] or pressed_keys[K_d]:
print('right')
hero_temp.move_right()
#检查是否是空格键
if pressed_keys[K_SPACE]:
print('space')
hero_temp.fire()
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
#3. 创建一个玩家飞机对象
hero = HeroPlane(screen)
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
#显示英雄玩家
hero.display()
# 键盘控制(负责移动玩家)
key_control(hero)
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.18.5 随机显示敌机

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time,random
# 显示敌机。
class HeroPlane:
''' 玩家飞机类(英雄) '''
def __init__(self, screen_temp):
self.x = 200
self.y = 400
self.screen = screen_temp
self.image = pygame.image.load("./images/me.png")
self.bullet_list = [] #存储发射出去的子弹对象引用
def display(self):
''' 绘制玩家到窗口中 '''
#遍历移动子弹
for bullet in self.bullet_list:
bullet.display()
#移动子弹,并判断是否越界。
if bullet.move():
self.bullet_list.remove(bullet)
self.screen.blit(self.image, (self.x, self.y))
def move_left(self):
''' 左移动,并判断防止越界 '''
self.x -= 5
if self.x<0:
self.x=0
def move_right(self):
''' 右移动,并判断防止越界 '''
self.x += 5
if self.x > 406:
self.x = 406
def fire(self):
self.bullet_list.append(Bullet(self.screen, self.x, self.y))
print(len(self.bullet_list))
class Bullet:
''' 玩家子弹类 '''
def __init__(self, screen_temp, x, y):
self.x = x+51
self.y = y
self.screen = screen_temp
self.image = pygame.image.load("./images/pd.png")
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self):
self.y-=10
if self.y<-20:
return True
class EnemyPlane:
"""敌机的类"""
def __init__(self, screen_temp):
self.x = random.choice(range(408))
self.y = -75
self.screen = screen_temp
self.image = pygame.image.load("./images/e2.png")
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self):
self.y += 4
def key_control(hero_temp):
''' 键盘控制函数 '''
#获取事件,比如按键等
for event in pygame.event.get():
#判断是否是点击了退出按钮
if event.type == QUIT:
print("exit")
exit()
#获取按下的键(返回的是元组值)
pressed_keys = pygame.key.get_pressed()
#检测是否按下a或者left键
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
print('left')
hero_temp.move_left()
#检测是否按下d或者right键
elif pressed_keys[K_RIGHT] or pressed_keys[K_d]:
print('right')
hero_temp.move_right()
#检查是否是空格键
if pressed_keys[K_SPACE]:
print('space')
hero_temp.fire()
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
#3. 创建一个玩家飞机对象
hero = HeroPlane(screen)
#4. 定义用于存放敌机列表
enemylist = []
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
#显示英雄玩家
hero.display()
# 键盘控制(负责移动玩家)
key_control(hero)
#随机输出敌机
if random.choice(range(50))==10:
enemylist.append(EnemyPlane(screen))
#遍历所有敌机,显示敌机,移动敌机
for em in enemylist:
em.display()
em.move()
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.18.6 实现敌机与子弹的碰撞检测

# -*- coding:utf-8 -*-
import pygame
from pygame.locals import * #pygame使用的各种常量
import time,random
# 实现敌机与子弹的碰撞检测。
class HeroPlane:
''' 玩家飞机类(英雄) '''
def __init__(self, screen_temp):
self.x = 200
self.y = 400
self.screen = screen_temp
self.image = pygame.image.load("./images/me.png")
self.bullet_list = [] #存储发射出去的子弹对象引用
def display(self):
''' 绘制玩家到窗口中 '''
#遍历移动子弹
for bullet in self.bullet_list:
bullet.display()
#移动子弹,并判断是否越界。
if bullet.move():
self.bullet_list.remove(bullet)
self.screen.blit(self.image, (self.x, self.y))
def move_left(self):
''' 左移动,并判断防止越界 '''
self.x -= 5
if self.x<0:
self.x=0
def move_right(self):
''' 右移动,并判断防止越界 '''
self.x += 5
if self.x > 406:
self.x = 406
def fire(self):
self.bullet_list.append(Bullet(self.screen, self.x, self.y))
print(len(self.bullet_list))
class Bullet:
''' 玩家子弹类 '''
def __init__(self, screen_temp, x, y):
self.x = x+51
self.y = y
self.screen = screen_temp
self.image = pygame.image.load("./images/pd.png")
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self):
self.y-=10
if self.y<-20:
return True
class EnemyPlane:
"""敌机的类"""
def __init__(self, screen_temp):
self.x = random.choice(range(408))
self.y = -75
self.screen = screen_temp
self.image = pygame.image.load("./images/e"+str(random.choice(range(3)))+".png")
def display(self):
self.screen.blit(self.image, (self.x, self.y))
def move(self,hero):
self.y += 4
#遍历玩家的子弹,并做碰撞检测
for bo in hero.bullet_list:
if bo.x>self.x+12 and bo.x<self.x+92 and bo.y>self.y+20 and bo.y<self.y+60:
hero.bullet_list.remove(bo)
return True
#判断敌机是否越界
if self.y>512:
return True;
def key_control(hero_temp):
''' 键盘控制函数 '''
#获取事件,比如按键等
for event in pygame.event.get():
#判断是否是点击了退出按钮
if event.type == QUIT:
print("exit")
exit()
#获取按下的键(返回的是元组值)
pressed_keys = pygame.key.get_pressed()
#检测是否按下a或者left键
if pressed_keys[K_LEFT] or pressed_keys[K_a]:
print('left')
hero_temp.move_left()
#检测是否按下d或者right键
elif pressed_keys[K_RIGHT] or pressed_keys[K_d]:
print('right')
hero_temp.move_right()
#检查是否是空格键
if pressed_keys[K_SPACE]:
print('space')
hero_temp.fire()
def main():
'''游戏的主程序执行函数'''
#1. 创建窗口:set_mode(分辨率=(0,0),标志=0,深度=0)
screen = pygame.display.set_mode((512,568),0,0)
#2. 创建一个游戏背景图片(512*1536)
background = pygame.image.load("./images/bg2.jpg")
m=-968 #初始化游戏背景图片标轴y的值
#3. 创建一个玩家飞机对象
hero = HeroPlane(screen)
#4.定义用于存放敌机列表
enemylist = []
while True:
#绘制位图
screen.blit(background,(0,m))
m+=2
if m>=-200:
m = -968
#显示英雄玩家
hero.display()
# 键盘控制(负责移动玩家)
key_control(hero)
#随机输出敌机
if random.choice(range(50))==10:
enemylist.append(EnemyPlane(screen))
#遍历所有敌机,显示敌机,移动敌机,并与玩家子弹碰撞检测
for em in enemylist:
em.display()
if em.move(hero):
enemylist.remove(em)
#更新屏幕显示
pygame.display.update()
# 定时睡眠(时钟)
time.sleep(0.04)
# 判断当前是否是主程序,若是就执行主程序。
if __name__ == "__main__":
main()
1.19 Python扩展内容
① python中yield关键字的使用:
yield是一个类似return的关键字,只是这个函数返回的是个生成器- 当你调用这个函数的时候,函数内部的代码并不立马执行 ,这个函数只是返回一个生成器对象
- 当你使用for进行迭代的时候,函数中的代码才会执行
生成器特点:可迭代;只能读取一次;实时生成数据,不全存在内存中。
def fun():
yield "aaa"
yield "bbb"
yield "ccc"
#返回可迭代对象(生成器)
a = fun()
print(a) # <generator object fun at 0x10f26e990>
#可以将迭代对象转成列表
# b = list(a)
# print(b) #['aaa', 'bbb', 'ccc']
#遍历(迭代)输出,注意:只能读取一次
for i in a:
print(i)
'''
aaa
bbb
ccc
'''
- 案例:
import json
#案例一、这是一段过程化代码编写:
str= '[{"name":"zhangsan","age":22},{"name":"lisi","age":19},{"name":"wangwu","age":24}]'
data = json.loads(str) #解码JSON数据
# 过滤出年龄大于20岁以上的信息,并输出
for item in data:
if item['age']>20:
#输出数据
print('-' * 20)
print(item['name'],":",item['age'])
'''
#输出结果:
--------------------
zhangsan : 22
--------------------
wangwu : 24
'''
#案例二:代码拆分(将数据的处理封装成函数):
def fun1():
str= '[{"name":"zhangsan","age":22},{"name":"lisi","age":19},{"name":"wangwu","age":24}]'
data = json.loads(str) #解码JSON数据
#过滤出年龄大于20岁以上的信息,并输出
dlist = []
for item in data:
if item['age']>20:
#将过滤出来的数据放置到dlist中
print('-' * 20)
dlist.append(item)
return dlist
# 使用(输出数据)
for i in fun1():
print(i['name'], ":", i['age'])
'''
#输出结果:
--------------------
--------------------
wangwu : 24
wangwu : 24
'''
#案例三:代码拆分(使用yield返回生成器):
def fun1():
str= '[{"name":"zhangsan","age":22},{"name":"lisi","age":19},{"name":"wangwu","age":24}]'
data = json.loads(str) #解码JSON数据
#过滤出年龄大于20岁以上的信息,并输出
for item in data:
if item['age']>20:
#将过滤出来的数据放置到dlist中
print('-' * 20)
yield item
# 使用(输出数据)
for i in fun1():
print(i['name'], ":", i['age'])
'''
#输出结果:
--------------------
zhangsan : 22
--------------------
wangwu : 24
'''
② 装饰器的使用:
-
python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,
-
使用python装饰器的好处就是在
不用更改原函数的代码前提下给函数增加新的功能。 -
无参数的装饰器实例
# 无参数的装饰器实例
def deco(dd):
def _deco():
print("start....")
dd()
print("end.....")
return _deco
@deco
def demo():
print("demo()............")
if __name__ == "__main__":
d = demo
d()
#demo()
- 输出结果
start....
demo()............
end.....
# 带参数的装饰器实例
def deco(func):
def _deco(a, b):
print("before myfunc() called.")
ret = func(a, b)
print(" after myfunc() called. result: %s" % ret)
return ret
return _deco
@deco
def myfunc(a, b):
print(" myfunc(%s,%s) called." % (a, b))
return a + b
if __name__ == "__main__":
myfunc(1, 2)
myfunc(3, 4)
- 输出结果
before myfunc() called.
myfunc(1,2) called.
after myfunc() called. result: 3
before myfunc() called.
myfunc(3,4) called.
after myfunc() called. result: 7
三、Web前端
1.1 Web前端开发介绍
1. Web网站介绍
- 网络系统软件开发包括两种结构: C/S是客户机(client)/服务器(server) B/S是浏览器(browser)/服务器。
- B/S最大的优点就是可以在任何地方进行操作而不用安装任何专门的软件
- B/S架构软件的优势与劣势: 维护和升级方式简单。 成本降低,选择更多。 应用服务器运行数据负荷较重。
- 目前比较流行的WEB技术:Python、PHP、JavaEE、Ruby与ASP.NET
- 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
- HTTP是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口(默认端口为80)的HTTP请求。
2. 动态网站开发所需的Web构件
- 客户端浏览器
- Web前端
- Web服务器
- 服务器端编程语言
- 数据库管理系统
3. Web的工作原理

1.2 HTML基础语法 (缺)
1.3 HTML常用标签介绍
1.3.1 文本标签:
<hn>...</hn>其中n为1--6的值。 标题标签(加粗、独立行)<i>...</i>斜体<em>...</em>强调斜体<b>...</b>加粗<strong>...</strong>强调加粗<cite></cite>作品的标题(引用)<sub>...</sub>下标<sup>...</sup>上标<del>...</del>删除线
1.3.2 格式化标签:
<br/>换行<p>...</p>换段<hr />水平分割线- 列表:
<ul>...</ul>无序列表<ol>...</ol>有序列表 其中type类型值:A a I i 1 start属性表示起始值<li>...</li>列表项<dl>...</dl>自定义列表<dt>...</dt>自定义列表头<dd>...</dd>自定义列表内容
<div>...</div>常用于组合块级元素,以便通过 CSS 来对这些元素进行格式化<span>...</span>常用于包含的文本,您可以使用 CSS 对它定义样式,或者使用 JavaScript 对它进行操作。
1.3.3 图片标签img*
<img /> 在网页中插入一张图片
属性:
src: 图片名及url地址
alt: 图片加载失败时的提示信息
title:文字提示属性
width:图片宽度
height:图片高度
border:边框线粗细
1.3.4 HTML中的超级链接*
<a href=" ">...</a> 超级链接标签
属性:
href: 必须,指的是链接跳转地址
target: 表示链接的打开方式:
_blank 新窗口
_parent 父窗口
_self 本窗口(默认)
_top 顶级窗口
framename 窗口名
title:文字提示属性(详情)
锚点链接:
定义一个锚点:<a id="a1"></a> 以前使用的是 <a name="a1"></a>
使用锚点: <a href="#a1">跳到a1处</a>
1.3.5 表格标签
<table>...</table> 表格标签: 属性:border width cellspacing cellpadding
<caption>...</caption> 表格标题
<tr>...</tr> 行标签
<th>...</th> 列头标签
<td>...</td> 列标签: 跨行属性rowspan 跨列属性:colspan
<thead>...</thead> 表头
<tbody>...</tbody> 表体
<tfoot>...</tfoot> 表尾
1.3.6 form表单标签 (其中1~5重点,其他了解)
(1) <form>...</form> 表单标签
form标签常用属性:
*action属性:提交的目标地址(URL)
*method属性:提交方式:get(默认)和post
get方式是URL地址栏可见,长度受限制(IE2k 火狐8k),相对不安全.
post方式是URL地址不可见,长度不受限制,相对安全.
enctype:提交类型
target: 在何处打开目标 URL。
name:属性为表单起个名字.在HTML5中使用 id 代替。
(2) <input> 表单项标签input定义输入字段,用户可在其中输入数据。在 HTML 5 中,type 属性有很多新的值。
具体在下面有详解:
如:<input type="text" name="username">
*type属性:表示表单项的类型:值如下:
text:单行文本框
password:密码输入框
checkbox:多选框 注意要提供value值
radio:单选框 注意要提供value值
file:文件上传选择框
button:普通按钮
submit:提交按钮
image:图片提交按钮
reset:重置按钮, 还原到开始(第一次打开时)的效果
hidden:主表单隐藏域,要是和表单一块提交的信息,但是不需要用户修改
email 用于应该包含 e-mail 地址的输入域
url 用于应该包含 URL 地址的输入域
number 用于应该包含数值的输入域。
max 规定允许的最大值
min 规定允许的最小值
step 规定合法的数字间隔(如果 step="3",则合法的数是 -3,0,3,6 等)
value 规定默认值
range 用于应该包含一定范围内数字值的输入域,显示为滑动条
max 规定允许的最大值
min 规定允许的最小值
step 规定合法的数字间隔(如果 step="3",则合法的数是 -3,0,3,6 等)
value 规定默认值
日期选择器 Date pickers
date - 选取日、月、年
month - 选取月、年
week - 选取周和年
time - 选取时间(小时和分钟)
datetime - 选取时间、日、月、年(UTC 时间)
datetime-local - 选取时间、日、月、年(本地时间)
search 用于搜索域,比如站点搜索或 Google 搜索
color 颜色选择
*name属性: 表单项名,用于存储内容值的
*value属性: 输入的值(默认指定值)
*placeholder: 预期值的简短的提示信息
size属性: 输入框的宽度值
maxlength属性: 输入框的输入内容的最大长度
readonly属性: 对输入框只读属性
*disabled属性: 禁用属性
*checked属性: 对选择框指定默认选项
accesskey属性: 指定快捷键(不常用) (IE:alt+键 火狐:alt+shift+键)
tabindex属性: 通过数字指定tab键的切换顺序(不常用)
src和alt是为图片按钮设置的
注意:reset重置按钮是将表单数据恢复到第一次打开时的状态,并不是清空
image图片按钮,默认具有提交表单功能。
(3) <select>...</select> 标签创建下拉列表。
name属性:定义名称,用于存储下拉值的
size:定义菜单中可见项目的数目,html5不支持
disabled 当该属性为 true 时,会禁用该菜单。
multiple 多选
<option>... </option> 下拉选择项标签,用于嵌入到<select>标签中使用的;
*value属性:下拉项的值
*selected属性:默认下拉指定项.
(4) <textarea>...</textarea> 多行的文本输入区域
name :定义名称,用于存储文本区域中的值。
cols :规定文本区内可见的列数。
rows :规定文本区内可见的行数。
disabled: 是否禁用
readonly: 只读
...
默认值是在两个标签之间
(5) <button>...</button> 标签定义按钮。
您可以在 button 元素中放置内容,比如文档或图像。这是该元素与由 input 元素创建的按钮的不同之处。
(6) <fieldset> --fieldset 元素可将表单内的相关元素分组。
disabled属性:定义 fieldset 是否可见。
form属性: 定义该 fieldset 所属的一个或多个表单。
(7) <legend></legend> -- 标签为 <fieldset>、<figure> 以及 <details> 元素定义标题。
<form>
<fieldset>
<legend>个人信息:</legend>
姓名:<input type="text" /><br/>
年龄:<input type="text" /><br/>
</fieldset>
<br/><br/>
<fieldset>
<legend>健康信息:</legend>
身高:<input type="text" /><br/>
体重:<input type="text" /><br/>
</fieldset>
</form>
(8) <optgroup> html5标签--<optgroup> 标签定义选项组。此元素允许您组合选项
城市:
<select name="city">
<optgroup label="河北省">
<option>石家庄</option>
<option>保定</option>
<option>廊坊</option>
</optgroup>
<optgroup label="河南省">
<option>郑州</option>
<option>安阳</option>
<option>周口</option>
</optgroup>
</select>
(9) <datalist> html5标签--<datalist>标签定义可选数据的列表。与 input 元素配合使用,就可以制作出输入值的下拉列表。
<form action="demo_form.php" method="get">
搜索:
<input type="search" list="namelist" name="keywords"/>
<datalist id="namelist">
<option value="zhangsan">
<option value="zhangsanfeng">
<option value="zhangwuji">
<option value="lisi">
<option value="lixiaolong">
</datalist>
</form>
1.3.7 <iframe>...</iframe> 行内框架
属性:src:规定在 iframe 中显示的文档的 URL
name:规定 iframe 的名称
height:规定 iframe 的高度。
width:定义 iframe 的宽度。
frameborder:规定是否显示框架周围的边框。
例如:<iframe src="1.html" name="myframe" width="700" height="500"></iframe>
1.3.8 多媒体标签(了解)
<audio src="./images/beidahuang.mp3" controls="controls">
你的浏览器不支持播放
</audio>
<br/>
<audio controls="controls">
<source src="./images/beidahuang.mp3" type="audio/mpeg" />
你的浏览器不支持播放
</audio>
<br/><br/>
<video controls="controls" width="400" height="400">
<source src="./images/fun.mp4" type="video/mp4" />
<source src="movie.webm" type="video/webm">
你的浏览器不支持视频播放
</video>
<video controls loop poster="tiao.jpg">
<source src="movie.webm" type="video/webm">
<source src="movie.ogg" type="video/ogg">
<source src="movie.mp4" type="video/mp4">
您的破浏览器该扔了,不支持视频标签
</video>
<br/><br/>
<embed src="./images/haowan.swf" width="300" height="300" />
1.4 CSS层叠样式表介绍
1.4.1 什么是CSS?
- CSS 指层叠样式表 (Cascading Style Sheets)
- 样式定义如何显示控制 HTML 元素,从而实现美化HTML网页。
- 样式通常存储在样式表中,目的也是为了解决内容与表现分离的问题
- 外部样式表(CSS文件)可以极大提高工作效率
- 多个样式定义可层叠为一,后者可以覆盖前者样式
1.4.2 CSS的语法:
格式: 选择器{属性:值;属性:值;属性:值;....}

- 案例如下:其中选择器也叫选择符
p{
color:red;
text-align:center;
}
1.4.3 CSS中的注释:
格式: /* ... */
1.4.4 在HTML中如何使用css样式(HTML中引入CSS的方式):
(1) 内联方式(行内样式)
- 就是在HTML的标签中使用style属性来设置css样式
- 格式:
<html标签 style="属性:值;属性:值;....">被修饰的内容</html标签>
<p style="color:blue;font-family:隶书">在HTML中如何使用css样式</p>
<!-- 特点:仅作用于本标签。-->
(2) 内部方式(内嵌样式)
- 就是在head标签中使用
<style type="text/css">....</style>标签来设置css样式
<style type="text/css">
....css样式代码
</style>
<!-- 特点:作用于当前整个页面 -->
(3) 外部导入方式(外部链入)
- 3.1(推荐)就是在head标签中使用标签导入一个css文件,在作用于本页面,实现css样式设置
<link href="文件名.css" type="text/css" rel="stylesheet"/>
- 3.2 还可以使用import在style标签中导入css文件。
<style type="text/css">
@import "style.css";
</style>
- 特点:作用于整个网站
- 优先级:当样式冲突时,就是采用就近原则,是值css属性离被修饰的内容最近的为主。
- 若没有样式冲突则采用叠加效果。
1.5 CSS的常用选择符
1.5.1 css2的选择符(重要)
(1). html选择符(标签选择器)
就是把html标签作为选择符使用
如 p{....} 网页中所有p标签采用此样式
h2{....} 网页中所有h2标签采用此样式
(2). class类选择符 (使用点.将自定义名(类名)来定义的选择符)
定义: .类名{样式....} 匿名类
其他选择符名.类名{样式....}
使用:<html标签 class="类名">...</html标签>
.mc{color:blue;} /* 凡是class属性值为mc的都采用此样式 */
p.ps{color:green;} /*只有p标签中class属性值为ps的才采用此样式*/
注意:类选择符可以在网页中重复使用
(3). Id选择符:
定义: #id名{样式.....}
使用:<html标签 id="id名">...</html标签>
注意:id选择符只在网页中使用一次.
- 选择符的优先级:从大到小 [ID选择符]->[class选择符]->[html选择符]->[html属性]
(4). 关联选择符(包含选择符)
-
格式:
选择符1 选择符2 选择符3 ...{样式....}table a{....} /*table标签里的a标签才采用此样式*/ h1 p{color:red} /*只有h1标签中的p标签才采用此样式*/
(5). 组合选择符(选择符组)
-
格式:
选择符1,选择符2,选择符3 ...{样式....}h3,h4,h5{color:green;} /*h3、h4和h5都采用此样式*/
(6). 伪类选(伪元素)择符:
-
格式:
标签名:伪类名{样式....}a:link {color: #FF0000; text-decoration: none} /* 未访问的链接 */ a:visited {color: #00FF00; text-decoration: none} /* 已访问的链接 */ a:hover {color: #FF00FF; text-decoration: underline} /* 鼠标在链接上 */ a:active {color: #0000FF; text-decoration: underline} /* 激活链接 */
1.5.2 CSS3中的选择器(熟悉)
(1). 关系选择器:
div>p 选择所有作为div元素的子元素p
div+p 选择紧贴在div元素之后p元素
div~p 选择div元素后面的所有兄弟元素p
(2). 属性选择器:
[attribute]选择具有attribute属性的元素。
[attribute=value]选择具有attribute属性且属性值等于value的元素。
[attribute~=value]选择具有attribute属性且属性值为一用空格分隔的字词列表,其中一个等于value的元素。
[attribute|=value]选择具有att属性且属性值为以val开头并用连接符"-"分隔的字符串的E元素。
[attibute^=value]匹配具有attribute属性、且值以valule开头的E元素
[attribute$=value]匹配具有attribute属性、且值以value结尾的E元素
[attribute*=value]匹配具有attribute属性、且值中含有value的E元素
(3). 结构性伪类选择器:
::first-letter设置对象内的第一个字符的样式。
::first-line设置对象内的第一行的样式。
:before设置在对象前(依据对象树的逻辑结构)发生的内容。
:after设置在对象后(依据对象树的逻辑结构)发生的内容。
:lang(language)匹配使用特殊语言的E元素。
:element1~element2:
:first-of-type匹配同类型中的第一个同级兄弟元素
:last-of-type匹配同类型中的最后一个同级兄弟元素
:only-of-type匹配同类型中的唯一的一个同级兄弟元素
:only-child匹配父元素仅有的一个子元素
:nth-child(n)匹配父元素的第n个子元素
:nth-last-child(n)匹配同类型中的倒数第n个同级兄弟元素
:last-child()匹配父元素的最后一个子元素
:root匹配元素在文档的根元素。在HTML中,根元素永远是HTML
:empty匹配没有任何子元素(包括text节点)的元素
(4). *状态伪类选择器
:link 设置超链接a在未被访问前的样式。
:visited 设置超链接a在其链接地址已被访问过时的样式
:active 设置元素在被用户激活(在鼠标点击与释放之间发生的事件)时的样式
*:hover 设置元素在其鼠标悬停时的样式
*:focus 设置元素在其获取焦点时的样式
:target 匹配相关URL指向的E元素
:enabled 匹配用户界面上处于可用状态的元素
:disabled 匹配用户界面上处于禁用状态的元素
:checked 匹配用户界面上处于选中状态的元素
:not(selector)匹配不含有selector选择符的元素
::selection 设置对象被选择时的样式
(5). 其他伪类选择器
E:not(s) : {attribute}
匹配所有不匹配简单选择符s的元素E
p:not(.bg) {background-color:#00FF00;}
1.6 CSS常用属性
1.6.1 color颜色属性:
a. HSL颜色: 通过对色调(H)、饱和度(S)、亮度(L)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色.
background-color: hsl(240,100%,50%);color:white;
b. HSLA颜色: 色调(H)、饱和度(S)、亮度(L)、透明度(A);
background-color: hsla(0,100%,50%,0.2);
*c. RGB颜色: 红(R)、绿(G)、蓝(B)三个颜色通道的变化
background-color: rgba(200,100,0);
d. RGBA颜色: 红(R)、绿(G)、蓝(B)、透明度(A)
background-color: rgba(0,0,0,0.5);
*e. 图片透明度的设置 img.opacity{ opacity:0.25;}
兼容IE8 filter:alpha(opacity=100);
1.6.2 字体属性:font
font:
*font-size: 字体大小:20px,60%基于父对象的百分比取值
*font-family: 字体:宋体,Arial
font-style: normal正常;italic斜体; oblique倾斜的字体
*font-weight: 字体加粗 :bold
font-variant: small-caps 小型的大写字母字体
font-stretch: [了解]文字的拉伸是相对于浏览器显示的字体的正常宽度(大部分浏览器不支持)。
1.6.3 文本属性:
text-indent: 首行缩进:text-indent:30px;
text-overflow: 文本的溢出是否使用省略标记(...)。clip|ellipsis(显示省略标记)
*text-align: 文本的位置:left center right
text-transform:对象中的文本的大小写:capitalize(首字母)|uppercase大写|lowercase小写
*text-decoration: 字体画线:none无、underline下画线,line-through贯穿线
text-decoration-line:[了解]文本装饰线条的位置(浏览器不兼容)
*text-shadow: 文本的文字是否有阴影及模糊效果
vertical-align: 文本的垂直对齐方式
direction:文字流方向。ltr | rtl
white-space:nowrap; /* 强制在同一行内显示所有文本*/
*letter-spacing: 文字或字母的间距
word-spacing:单词间距
*line-height:行高
*color: 字体颜色
1.6.4 背景属性:background
*background-color: 背景颜色
*background-image: 背景图片
*background-repeat:是否重复,如何重复?(平铺)
*background-position:定位
background-attachment: 是否固定背景,
scroll:默认值。背景图像是随对象内容滚动
fixed:背景图像固定
css3的属性:
*background-size: 背景大小,如 background-size:100px 140px;
多层背景:
background:url(test1.jpg) no-repeat scroll 10px 20px,url(test2.jpg) no-repeat scroll 50px 60px,url(test3.jpg) no-repeat scroll 90px 100px;
background-origin:content-box,content-box,content-box;
background-clip:padding-box,padding-box,padding-box;
background-size:50px 60px,50px 60px,50px 60px;
1.6.5 *边框(盒模型):
盒子模型:

border:宽度 样式 颜色;
border-color;
border-style; 边框样式:solid实现,dotted点状线,dashed虚线
border-width:
border-left-color;
border-left-style;
border-left-width:
...
CSS3的样式
border-radius:圆角处理
box-shadow: 设置或检索对象阴影
1.6.6 *内补白(内补丁)
padding: 检索或设置对象四边的内部边距,如padding:10px; padding:5px 10px;
padding-top: 检索或设置对象顶边的内部边距
padding-right: 检索或设置对象右边的内部边距
padding-bottom:检索或设置对象下边的内部边距
padding-left: 检索或设置对象左边的内部边距
1.6.7 *外补白(外补丁)
margin: 检索或设置对象四边的外延边距,如 margin:10px; margin:5px auto;
margin-top: 检索或设置对象顶边的外延边距
margin-right: 检索或设置对象右边的外延边距
margin-bottom: 检索或设置对象下边的外延边距
margin-left: 检索或设置对象左边的外延边距
1.6.8 Position定位
*position: 定位方式:absolute(绝对定位)、fixed(固定)(relative定位参考,可对内部相对absolute定位)
*z-index: 层叠顺序,值越大越在上方。
*top: 检索或设置对象与其最近一个定位的父对象顶部相关的位置
right: 检索或设置对象与其最近一个定位的父对象右边相关的位置
bottom: 检索或设置对象与其最近一个定位的父对象下边相关的位置
*left: 检索或设置对象与其最近一个定位的父对象左边相关的位置
1.6.9. Layout布局
*display: 是否及如何显示:none隐藏,block块状显示...
*float: 指出了对象是否及如何浮动:值none | left | right
*clear: 清除浮动:none | left | right | both两侧
visibility:设置或检索是否显示对象。visible|hidden|collapse。
与display属性不同,此属性为隐藏的对象保留其占据的物理空间
clip: 检索或设置对象的可视区域。区域外的部分是透明的。 rect(上-右-下-左)
如:clip:rect(auto 50px 20px auto);上和左不裁剪,右50,下20.
*overflow: 超出隐藏:hidden,visible:不剪切内容
overflow-x:内容超过其指定宽度时如何管理内容: visible | hidden | scroll | auto
overflow-y:内容超过其指定高度时如何管理内容
1.6.10 Flexible Box 弹性盒子(了解见手册)
box-orient: 设置或检索弹性盒模型对象的子元素的排列方式。horizontal(水平)|vertical(纵向)
box-pack 设置或检索弹性盒模型对象的子元素的对齐方式。
box-align 设置或检索弹性盒模型对象的子元素的对齐方式。
box-flex 设置或检索弹性盒模型对象的子元素如何分配其剩余空间。
box-flex-group 设置或检索弹性盒模型对象的子元素的所属组。
box-ordinal-group 设置或检索弹性盒模型对象的子元素的显示顺序。
box-direction 设置或检索弹性盒模型对象的子元素的排列顺序是否反转。
box-lines 设置或检索弹性盒模型对象的子元素是否可以换行显示。
1.6.11. 用户界面 User Interface
*cursor 鼠标指针采用何种系统预定义的光标形状。pointer小手,url自定义
zoom 设置或检索对象的缩放比例: normal|5倍|200%百分比
box-sizing 设置或检索对象的盒模型组成模式。content-box | border-box
content-box: padding和border不被包含在定义的width和height之内。
border-box: padding和border被包含在定义的width和height之内。
resize 设置或检索对象的区域是否允许用户缩放,调节元素尺寸大小。
none: 不允许用户调整元素大小。
both: 用户可以调节元素的宽度和高度。
horizontal: 用户可以调节元素的宽度
vertical: 用户可以调节元素的高度。
outline 复合属性:设置或检索对象外的线条轮廓
outline-width 设置或检索对象外的线条轮廓的宽度
outline-style 设置或检索对象外的线条轮廓的样式
outline-color 设置或检索对象外的线条轮廓的颜色
outline-offset 设置或检索对象外的线条轮廓偏移位置的数值
nav-index 设置或检索对象的导航顺序。
nav-up 设置或检索对象的导航方向。
nav-right 设置或检索对象的导航方向。
1.6.12 多栏 Multi-column
columns 设置或检索对象的列数和每列的宽度
column-width 设置或检索对象每列的宽度
column-count 设置或检索对象的列数
column-gap 设置或检索对象的列与列之间的间隙
column-rule 设置或检索对象的列与列之间的边框
column-rule-width 设置或检索对象的列与列之间的边框厚度
column-rule-style 设置或检索对象的列与列之间的边框样式
column-rule-color 对象的列与列之间的边框颜色
column-span 象元素是否横跨所有列
column-fill 对象所有列的高度是否统一
column-break-before 对象之前是否断行
column-break-after 对象之后是否断行
column-break-inside 对象内部是否断行
1.6.13 表格相关属性
table-layout 设置或检索表格的布局算法
border-collapse 设置或检索表格的行和单元格的边是合并在一起还是按照标准的HTML样式分开 separate | collapse
border-spacing 设置或检索当表格边框独立时,行和单元格的边框在横向和纵向上的间距
caption-side 设置或检索表格的caption对象是在表格的那一边 top | right | bottom | left
empty-cell 设置或检索当表格的单元格无内容时,是否显示该单元格的边框 hide | show
1.6.14 过渡 Transition:
transition 检索或设置对象变换时的过渡效果
transition-property 检索或设置对象中的参与过渡的属性
transition-duration 检索或设置对象过渡的持续时间
transition-timing-function 检索或设置对象中过渡的类型
transition-delay 检索或设置对象延迟过渡的时间
1.6.15. 动画 Animation
animation 检索或设置对象所应用的动画特效
animation-name 检索或设置对象所应用的动画名称
animation-duration 检索或设置对象动画的持续时间
animation-timing-function 检索或设置对象动画的过渡类型
animation-delay 检索或设置对象动画延迟的时间
animation-iteration-count 检索或设置对象动画的循环次数
animation-direction 检索或设置对象动画在循环中是否反向运动
animation-play-state 检索或设置对象动画的状态
animation-fill-mode 检索或设置对象动画时间之外的状态
1.6.16. 2D变换 2D Transform:
transform 检索或设置对象的变换
transform-origin 检索或设置对象中的变换所参照的原点
1.6.17. Media Queries Properties媒体查询
width 定义输出设备中的页面可见区域宽度
height 定义输出设备中的页面可见区域高度
device-width 定义输出设备的屏幕可见宽度
device-height 定义输出设备的屏幕可见高度
orientation 定义'height'是否大于或等于'width'。值portrait代表是,landscape代表否
aspect-ratio 定义'width'与'height'的比率
device-aspect-ratio 定义'device-width'与'device-height'的比率。如常见的显示器比率:4/3, 16/9, 16/10
device-aspect-ratio 定义'device-width'与'device-height'的比率。如常见的显示器比率:4/3, 16/9, 16/10
color 定义每一组输出设备的彩色原件个数。如果不是彩色设备,则值等于0
color-index 定义在输出设备的彩色查询表中的条目数。如果没有使用彩色查询表,则值等于0
monochrome 定义在一个单色框架缓冲区中每像素包含的单色原件个数。如果不是单色设备,则值等于0
resolution 定义设备的分辨率。如:96dpi, 300dpi, 118dpcm
scan 定义电视类设备的扫描工序
grid 用来查询输出设备是否使用栅格或点阵。只有1和0才是有效值,1代表是,0代表否
1.7 网页布局实战
具体详见授课文档
1.8 JavaScript语言
1.8.1 JavaScript基础
(1) JavaScript介绍:
- 什么是JavaScript?
- JavaScript 是一种客户端脚本语言(脚本语言是一种轻量级的编程语言)。
- JavaScript 通常被直接嵌入 HTML 页面。
- JavaScript 是一种解释性语言(就是说,代码执行不进行预编译)。
- 特点:弱类型和基于对象。(因为面向对象需要具有封装、继承、多态的特征)
- JavaScript语言中包含三个核心:ECMAScript基本语法、DOM、BOM
- 在HTML中如何使用JavaScript(三种)
1. 使用<script></script>标签:
属性:
charset(可选)字符集设置、
defer(可选执行顺序)值:defer、
language(已废除)、
src(可选)使用外部的js脚本文件
type(必选)类型:值:text/javascript
<script type="text/javascript">
<!--
javaScript语言
//-->
</script>
2. 在html标签的事件中,超级链接里。
<button onclick="javaScript语言"></button>
<a href="javascript:alert('aa');alert('bb')">点击</a>
3. 外部导入方式(推荐):
<script type="text/javascript" src="my.js"></script>
(2) JavaScript的语法:
-
区分大小写:变量名、函数名、运算符以及其他一切东西都是区分大小写的。
-
他和Python一样属于弱类型语言。
-
每行结尾的分号可有可无。(js中的每条语句之间的分割符可以是回车换行也可以是";"分号(推荐))
-
脚本注释:
// 单行注释和/* 多行注释 */ -
括号表示代码块:
{ } -
变量的定义:使用var关键字来声明。
变量的命名规范是:字母数字,$符和下划线构成,但是不可以以数字开始。 变量名不可以使用关键字. typeof函数获取一个变量的类型: * undefined - 如果变量是 Undefined 类型的 * boolean - 如果变量是 Boolean 类型的 * number - 如果变量是 Number 类型的 (整数、浮点数) * string - 如果变量是 String 类型的 (采用""、 '') * object - 如果变量是一种引用类型或 Null 类型的 如:new Array()/ new String()... * function -- 函数类型 -
JavaScript的数据类型:
undefined 类型 null 类型(对象) boolean 类型 number 类型 八进制数和十六进制数 012 浮点数 特殊的 Number 值 string 类型 var s = "hello"; document.write(s+"<br/>"); document.write(s[1]+"<br/>"); //使用下标可以取出对应的字符 document.write(s.length+"<br/>");//求长度 object引用类型 引用类型通常叫做类(class),也就是说,遇到引用值,所处理的就是对象。 Object 对象自身用处不大,不过在了解其他类之前,还是应该了解它。 因为 ECMAScript 中的 Object 对象与 Java 中的 java.lang.Object 相似, ECMAScript 中的所有对象都由这个对象继承而来,Object 对象中的所有属性 和方法都会出现在其他对象中,所以理解了 Object 对象,就可以更好地理解其他对象。 -
类型转换:
使用:Number()、parseInt() 和parseFloat() 做类型转换 Number()强转一个数值(包含整数和浮点数)。 *parseInt()强转整数, *parseFloat()强转浮点数 函数isNaN()检测参数是否不是一个数字。 is not a number ECMAScript 中可用的 3 种强制类型转换如下: Boolean(value) - 把给定的值转换成 Boolean 型; Number(value) - 把给定的值转换成数字(可以是整数或浮点数); String(value) - 把给定的值转换成字符串;
(3) JavaScript的运算符
-
一元运算符
delete:用于删除对象中属性的 如:delete o.name; //删除o对象中的name属性 void : void 运算符对任何值返回 undefined。没有返回值的函数真正返回的都是 undefined。 ++ -- : 一元加法和一元减法 -
位运算符
位运算 NOT ~ 位运算 AND & 位运算 OR | 位运算 XOR ^ (不同为1,相同则为0) 左移运算 << 右移运算 >> -
逻辑运算符
逻辑 NOT ! 运算符 非 逻辑 AND && 运算符 与 逻辑 OR || 运算符 或 -
乘性运算符:
*( 乘) /(除) %(取模)求余 -
加性运算符:
+ - *其中+号具有两重意思:字串连接和数值求和。 就是加号”+“两侧都是数值则求和,否则做字串连接 -
关系运算符:
> >= < <= -
等性运算符:
== === != !== -
条件运算符:
? : (三元运算符) -
赋值运算符:
= += -= *= /= %= >>= <<= -
逗号运算符
用逗号运算符可以在一条语句中执行多个运算。 var iNum1=1, iNum2=2, iNum3=3;
(4) javaScript(语句流程控制)
1. 判断语句 if语句; if... else ... if ... else if ... else...
2. 多分支语句: switch(){。 case :。。。。}
switch (i) {
case 20: alert("20");
break;
case 30: alert("30");
break;
case 40: alert("40");
break;
default: alert("other");
}
3. 循环语句(迭代语句)
for:
while
do...while
*for-in 语句: 语句是严格的迭代语句,用于枚举对象的属性。
var a = [10,20,30,40,50];
//迭代的是数组的下标。
for(i in a){
document.write(a[i]);
}
//输出: 1020304050
4. break 和 continue 语句对循环中的代码执行提供了更严格的控制。
5. with 语句用于设置代码在特定对象中的作用域。
(5) 本节扩充知识:
1. 在网页文档中获取一个节点对象(HTML标签)
document.getElementById("mid"); //获取标签id属性值为mid的节点对象
2. 定时相关函数:
setTimeout(vCode, iMilliSeconds [, sLanguage]) -- 单次定时执行指定函数
clearTimeout(iTimeoutID) -- 取消上面的单次定时
setInterval(vCode, iMilliSeconds [, sLanguage]) --无限次定时执行指定函数
clearInterval(iIntervalID)-- 取消上面的多次定时
本节练习:
```
一、理论练习:
1. 什么是JavaScript?
2. 在HTML中嵌入JavaScript的方法有几种?
3. JavaScript的常用类型都有哪些?
4. 常用的JavaScript的类型转换函数有那两个?
5. 我们使用哪个函数判断是否不是一个整数?
6. JavaScript语言的运算符都有哪些?
二、代码题:
1. 参考课程代码做一个简单的计算器
2. (选做)一个倒计时的按钮(10,9,8,7,...这是是个灰色不可点击的,当为0是会换成同意字样,并可点击。)
3. (选做) 做一个倒计时(时 分 秒)。可选带暂停效果
三、预习练习:
1. JavaScript的函数如何定义和调用。
2. 常用的系统函数都有哪些。
3. 对象的应用与声明。
```
1.8.2 JavaScript函数和对象
(1) JavaScript的函数:
标准格式: function 函数名([参数列表..]){
函数体。。。
[return 返回值;]
}
JavaScript三种定义函数方法:
*第一种是使用function语句定义函数
如上面格式
第二种是使用Function()构造函数来定义函数(不常用)
var 函数名 = new Function(“参数1”,”参数2”,”参数3”……”函数体”);
如:
var 函数名 = new Function(”x”,”y”,”var z=x+y;return z;”);
*第三种是在表达式中定义函数
var 函数名 = function(参数1,参数2,…){函数体};
//例如:
//定义
var add = function(a,b){
return a+b;
}
//调用函数
document.write(add(50,20));
arguments 对象
在函数代码中,使用特殊对象 arguments,开发者无需明确指出参数名,就能访问它们。
例如,在函数 sayHi() 中,第一个参数是 message。用 arguments[0]
也可以访问这个值,即第一个参数的值(第一个参数位于位置 0,
第二个参数位于位置 1,依此类推)。
关于变量和参数问题:
函数外面定义的变量是全局变量,函数内可以直接使用。
在函数内部没有使用var定义的=变量则为全局变量,
*在函数内使用var关键字定义的变量是局部变量,即出了函数外边无法获取。
js函数定义的参数没有默认值(目前只有最新的火狐浏览器支持)
(2) 系统中常用的内置函数:
escape() //字串编码
unescape() //字串反编码
*eval() //将参数字符串作为脚本代码来执行。
*isNaN() // is not a number (不是一个数值)
*parseInt()
*parseFloat()
(3) JavaScript的对象的定义和使用
1.使用原始的方式创建内置对象
var myObject = new Object();
myObject.name = “lijie”;
myObject.age = 20;
myObject.say = function(){...}
2.直接创建自定义对象
var 对象名 = {属性名1:属性值,属性名2:属性值2,…….}
3.使用自定义构造函数创建对象
function pen(name,color,price){
//对象的name属性
this.name = name;
//对象的color属性
this.color = color;
//对象的piece属性
this.price = price;
//对象的say方法
this.say = function(){};
}
var pen = new pen(“铅笔”,”红色”,20);
pen.say();
--------------------------------------------------------------
测试类型:
1.typeof() //global对象的其中一个方法,typeof()
2.对象.constructor; //查看当前对象的构造函数是谁
if(arr.constructor==Array){
alert("数组"); //数组推荐用这种方法,因为typeof得到是object
}
(4) 常用技巧函数:
HTML的标签(节点)操作:
document.write(""); //输出的
document.getElementById("id名"); //获取html页面标签中,标签id属性等于此值的对象。
如:var id = document.getElementById("hid"); //获取id值为hid的标签对象
document.getElementsByTagName("标签名"); //获取当前文档执行的标签对象
html标签对象的操作:
标签对象.innerHTML="内容";//在标签对象内放置指定内容
标签对象.style.css属性名="值" //改变标签对象的样式。
示例:id.style.color="red";
注意:属性名相当于变量名,所以css属性名中的减号要去掉,将后面的首字母大写。
如:font-size(css)---> fontSize(JS属性)
标签对象.value; //获取标签对象的value值
标签对象.value=”值“;//设置标签对象的value值
本节练习
```
一、理论练习:
1. JavaScript三种定义函数方法?
2. arguments对象的理解?
3. 简述JS中全局变量和局部变量的作用域?
4. 系统中常用的内置函数有哪些?
二、代码题:
1. 练习全选/全不选/反选的实例
2. 做一个文件进度条的特效。
3. 使用自定义构造函数创建对象
4. (选做) 做一个树形菜单的点击效果
三、预习练习:
1. JavaScript中常用内置对象都有哪些?
2. 如何使用Date获取当前时间。
3. 如何创建一个数组对象。
4. 如何使用对象的基本操作for..in。
```
1.8.3 JavaScript的内置对象
(1) for…in语句
for(var i in window){
document.write(i+”----”+window[i]);
}
这种语句可以遍历对象中的所有属性或数组中的所有元素。
(2) with语句
如果使用with语句,就可以简化对象属性调用的层次。
document.write(‘test1’);
document.write(‘test2’);
document.write(‘test3’);
可以使用with来简化:
with(document){
write(‘test1’);
write(‘test2’);
write(‘test3’);
}
(3) JavaScript内置对象
* Array(数组)
var a= new Array(); //创建一个空数组
a = new Array(10); //创建一个数组单元为10个的数组。
a = new Array(10,20,30);//创建一个指定数组单元的数组。
a=['a','b','c','d']; //快捷定义数组
常用属性:
length--获取长度。
常用方法:
toString() 把数组转换为字符串,并返回结果。
sort() 对数组的元素进行排序
join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
pop() 删除并返回数组的最后一个元素
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
。。。。。
* Boolean 布尔值包装类对象
方法:toSource() 返回该对象的源代码。
toString() 把逻辑值转换为字符串,并返回结果。
valueOf() 返回 Boolean 对象的原始值。
* Date
var dd = new Date();
getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31)。
getDay() 从 Date 对象返回一周中的某一天 (0 ~ 6)。
getMonth() 从 Date 对象返回月份 (0 ~ 11)。
getFullYear() 从 Date 对象以四位数字返回年份。
getYear() 请使用 getFullYear() 方法代替。
getHours() 返回 Date 对象的小时 (0 ~ 23)。
getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。
getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。
getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。
getTime() 返回 1970 年 1 月 1 日至今的毫秒数。
同上还有很多set方法用来设置。
* Math 对象用于执行数学任务。方法是静态的。
abs(x) 返回数的绝对值。
ceil(x) 对数进行上舍入。
floor(x) 对数进行下舍入。
random() 返回 0 ~ 1 之间的随机数。
round(x) 把数四舍五入为最接近的整数。
max(x,y) 返回 x 和 y 中的最高值。
min(x,y) 返回 x 和 y 中的最低值。
* Number
toString();
toFixed 把数字转换为字符串,结果的小数点后有指定位数的数字。
* String 子串处理对象
anchor() 创建 HTML 锚。
*charAt() 返回在指定位置的字符。
charCodeAt()返回在指定的位置的字符的 Unicode 编码。
*indexOf() 检索字符串。
*lastIndexOf() 从后向前搜索字符串。
match() 找到一个或多个正在表达式的匹配。
*replace() 替换与正则表达式匹配的子串。
search() 检索与正则表达式相匹配的值。
slice() 提取字符串的片断,并在新的字符串中返回被提取的部分。
split() 把字符串分割为字符串数组。
substr() 从起始索引号提取字符串中指定数目的字符。
*substring() 提取字符串中两个指定的索引号之间的字符。
toLocaleLowerCase() 把字符串转换为小写。
toLocaleUpperCase() 把字符串转换为大写。
*toLowerCase() 把字符串转换为小写。
*toUpperCase() 把字符串转换为大写。
* RegExp
exec()正则匹配
test()
match()
* Global
escape(string) -- 可对字符串进行编码
unescape(string) -- 函数可对通过 escape() 编码的字符串进行解码。
encodeURI(URIstring) -- 函数可把字符串作为 URI 进行编码。
decodeURI(URIstring) -- 函数可对 encodeURI() 函数编码过的 URI 进行解码。
*eval(string) -- 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
getClass(javaobj) -- 函数可返回一个 JavaObject 的 JavaClass。
*isNaN(x) -- 函数用于检查其参数是否是非数字值。
Number(object) --函数把对象的值转换为数字。
*parseFloat(string) -- 函数可解析一个字符串,并返回一个浮点数。
*parseInt(string, radix)
本节练习
一、理论练习:
1. JavaScript中常用内置对象都有哪些?
3. 如何创建一个数组对象方式有几种。
二、代码题:
1. 做一个注册的表单验证,
2. 实时问候:如:早上好!
3. 给你一个日期"2013-06-01 12:24:36"的时间戳值。
4. 显示中文的星期几。
5. 做一个随机点名系统(点名多次,也不会重复!);
三、预习练习:
1. JavaScript中常用事件都有哪些?
```
1.8.4 JavaScript的事件
(1) 事件处理过程:
一、事件源: 任何一个HTML元素(节点) body, div , button p, a, h1 .......
二、事件: 你的操作
鼠标:
* click 单击
dblclick 双击
contextmenu (在body) 文本菜单(鼠标右键使用)
要想屏蔽鼠标右键使用return false
window.document.oncontextmenu=function(ent){...}
* mouseover 放上(移入)
* mouseout 离开(移出)
mousedown 按下
mouseup 抬起
* mousemove 移动
键盘:
keypress 键盘事件
keydown 按下
文档:(主要使用在body标签中)
* load 加载
unload 关闭(为了兼容可使用下面函数)
beforeunload 关闭之前
表单:
* focus 焦点
* blur 失去焦点
* submit 提交事件
* change 改变(如下拉框选择事件)
其它:
* scroll 滚动事件(常用延迟加载、瀑布流技术)
window.onscroll=function(){
document.documentElement.scrollTop;//获取滚动条的上距离
document.documentElement.scrollLeft;//滚动条的左距离
}
selectd 事件
。。。。
三、事件处理程序
使用一个匿名或回调函数
(2) 三种方法加事件:
第一种:
格式: <tag on事件="事件处理程序" />
*第二种:
<script>
对象.on事件=事件处理程序
</script>
第三种:(火狐不兼容)
<script for="事件源ID" event="on事件">事件处理程序</script>
(3) 事件对象:
属性:
1. srcElement
2. keyCode 键盘值
事件 event window.event
1. srcElement 代表事件源对象
2. keyCode 事件发生时的键盘码 keypress , keydown keyup
3. clientX, clientY 坐标位置
4. screenX, screenY
5. returnValue
6. cancelBubble;
//为页面添加鼠标右点击事件
window.document.oncontextmenu=function(ent){
//兼容IE和火狐浏览器兼容
var event=ent || window.event;
//获取事件坐标位置
var x=event.clientX;
var y=event.clientY;
...
}
document.getElementById("one").offsetHeight; //获取one对应对象的高度
obj.offsetTop 指 obj 距离上方或上层控件的位置,整型,单位像素。
obj.offsetLeft 指 obj 距离左方或上层控件的位置,整型,单位像素。
obj.offsetWidth 指 obj 控件自身的宽度,整型,单位像素。
obj.offsetHeight 指 obj 控件自身的高度,整型,单位像素。
document.all ? 'IE' : 'others':在IE下document.all值为1,而其他浏览器下的值为0;
本节练习:
```
练习: 1. 完善注册的表单验证提示,
2. 横向的菜单特效
3. 为图片轮换播放添加按钮
5.(选做)滚动图片替换
6.(选做)键盘事件的处理(通过键盘事件移动图层)
预习:1.JavaScript中的其他事件:(滚动事件,键盘事件。。)
2.常用的BOM都有哪些?
3.HTML中的DOM都有哪些?
```
1.8.5 JavaScript的BOM
<button onclick="window.location=''">跳转</button>
1. window对象
常用的属性:
*document :对 Document 对象的只读引用
*history :对 History 对象的只读引用。
*location:用于窗口或框架的 Location 对象
Navigator: 对 Navigator 对象的只读引用
*parent: 返回父窗口。
length: 设置或返回窗口中的框架数量。
Screen: 对 Screen 对象的只读引用
status: 设置窗口状态栏的文本。
top: 返回最顶层的先辈窗口。
常用方法:
alert() 显示带有一段消息和一个确认按钮的警告框。
confirm() 显示带有一段消息以及确认按钮和取消按钮的对话框。
prompt() 显示可提示用户输入的对话框。
close() 关闭浏览器窗口。
open() 打开一个新的浏览器窗口或查找一个已命名的窗口。
scrollTo() 把内容滚动到指定的坐标。
setTimeout() 在指定的毫秒数后调用函数或计算表达式。
clearTimeout() 取消由 setTimeout() 方法设置的 timeout。
setInterval() 按照指定的周期(以毫秒计)来调用函数或计算表达式。
clearInterval() 取消由 setInterval() 设置的 timeout。
2. Navigator 对象
Navigator 对象包含的属性描述了正在使用的浏览器。可以使用这些属性进行平台专用的配置。
常用属性:
with(document) {
write ("你的浏览器信息:<ul>");
write ("<li>代码:"+navigator.appCodeName+"</li>");
write ("<li>名称:"+navigator.appName+"</li>");
write ("<li>版本:"+navigator.appVersion+"</li>");
write ("<li>语言:"+navigator.language+"</li>");
write ("<li>编译平台:"+navigator.platform+"</li>");
write ("<li>用户表头:"+navigator.userAgent+"</li>");
write ("</ul>");
}
3. Screen 对象包含有关客户端显示屏幕的信息。
常用属性:
document.write( "屏幕宽度:"+screen.width+"px<br />" );
document.write( "屏幕高度:"+screen.height+"px<br />" );
document.write( "屏幕可用宽度:"+screen.availWidth+"px<br />" );
document.write( "屏幕可用高度:"+screen.availHeight+"px" );
参考了解其他属性信息获取方式
网页可见区域宽: document.body.clientWidth
网页可见区域高: document.body.clientHeight
网页可见区域宽: document.body.offsetWidth (包括边线的宽)
网页可见区域高: document.body.offsetHeight (包括边线的高)
网页正文全文宽: document.body.scrollWidth
网页正文全文高: document.body.scrollHeight
网页被卷去的高: document.body.scrollTop
网页被卷去的左: document.body.scrollLeft
网页正文部分上: window.screenTop
网页正文部分左: window.screenLeft
屏幕分辨率的高: window.screen.height
屏幕分辨率的宽: window.screen.width
屏幕可用工作区高度: window.screen.availHeight
屏幕可用工作区宽度: window.screen.availWidth
4. History 对象包含用户(在浏览器窗口中)访问过的 URL。
5. Location 对象包含有关当前 URL 的信息。
//获取页面中第二form表单中,一个username输入框的值(7种方式)
//alert(document.forms[1].username.value);
alert(document.myform.username.value);
//alert(document.forms.myform.username.value);
//alert(document.forms.item(1).username.value);
//alert(document.forms['myform'].username.value);
//alert(document['myform'].username.value);
//alert(document.forms.item('myform').username.value); //火狐不兼容
1.8.6 讲解HTML DOM
(1)基本概念
HTML DOM 定义了访问和操作HTML文档的标准方法。
HTML DOM 把 HTML 文档呈现为带有元素、属性和文本的树结构(节点树)。
DOM 被分为不同的部分:
1.Core DOM
定义了一套标准的针对任何结构化文档的对象
2.XML DOM
定义了一套标准的针对 XML 文档的对象
3.HTML DOM
定义了一套标准的针对 HTML 文档的对象。
节点:根据 DOM,HTML 文档中的每个成分都是一个节点。
DOM 是这样规定的:
>整个文档是一个文档节点
>每个 HTML 标签是一个元素节点
>包含在 HTML 元素中的文本是文本节点
>每一个 HTML 属性是一个属性节点
>注释属于注释节点
节点彼此间都存在关系。
>除文档节点之外的每个节点都有父节点。
>大部分元素节点都有子节点。
>当节点分享同一个父节点时,它们就是同辈(同级节点)。
>节点也可以拥有后代,后代指某个节点的所有子节点,或者这些子节点的子节点
>节点也可以拥有先辈。先辈是某个节点的父节点,或者父节点的父节点
查找并访问节点
你可通过若干种方法来查找您希望操作的元素:
>通过使用 getElementById() 和 getElementsByTagName() 方法
>通过使用一个元素节点的 parentNode、firstChild 以及 lastChild childNodes属性
> nextSibling返回节点之后紧跟的同级节点。/ previousSibling返回节点之前紧跟的同级节点。
节点信息
每个节点都拥有包含着关于节点某些信息的属性。这些属性是:
nodeName(节点名称)
nodeValue(节点值)
nodeType(节点类型)
nodeName 属性含有某个节点的名称。
元素节点的 nodeName 是标签名称
属性节点的 nodeName 是属性名称
文本节点的 nodeName 永远是 #text
文档节点的 nodeName 永远是 #document
(2) HTML DOM 对象参考
Document: 代表整个 HTML 文档,可被用来访问页面中的所有元素
常用集合属性:forms
Anchor : 代表 <a> 元素
Area : 代表图像映射中的 <area> 元素
Base : 代表 <base> 元素
Body : 代表 <body> 元素
Button : 代表 <button> 元素
Event : 代表某个事件的状态
Form : 代表 <form> 元素
Frame : 代表 <frame> 元素
Frameset: 代表 <frameset> 元素
Iframe : 代表 <iframe> 元素
Image : 代表 <img> 元素
Input button : 代表 HTML 表单中的一个按钮
Input checkbox : 代表 HTML 表单中的复选框
Input file : 代表 HTML 表单中的文件上传
Input hidden : 代表 HTML 表单中的隐藏域
Input password : 代表 HTML 表单中的密码域
Input radio : 代表 HTML 表单中的单选按钮
Input reset : 代表 HTML 表单中的重置按钮
Input submit : 代表 HTML 表单中的确认按钮
Input text : 代表 HTML 表单中的文本输入域(文本框)
Link : 代表 <link> 元素
Meta : 代表 <meta> 元素
Object : 代表 <Object> 元素
Option : 代表 <option> 元素
Select : 代表 HTML 表单中的选择列表
Style : 代表单独的样式声明
Table : 代表 <table> 元素
TableData : 代表 <td> 元素
TableRow : 代表 <tr> 元素
Textarea : 代表 <textarea> 元素
1.8.7 继续讲解DOM(重点讲XML DOM)
(1) HTML的DOM
Object : 代表 <Object> 元素
Option : 代表 <option> 元素
Select : 代表 HTML 表单中的选择列表
Style : 代表单独的样式声明
Table : 代表 <table> 元素
TableData : 代表 <td> 元素
TableRow : 代表 <tr> 元素
Textarea : 代表 <textarea> 元素
(2) XML的DOM
DOM (Document Object Model) 文档对象模型
1. document 文档 HTML XML 文件 (标记语言)
<body>
<div>
<!-- -->
<a href="#">wwww</a>
</div>
</body>
节点:
将文档想成一个倒树, 每一个部分(根、元素、文本(内容), 属性, 注释)都是一节点。
根据 DOM,HTML 文档中的每个成分都是一个节点。
DOM 是这样规定的:
1. 整个文档是一个文档节点(根节点)
2. 每个 HTML 标签是一个元素节点
3. 包含在 HTML 元素中的文本是文本节点
4. 每一个 HTML 属性是一个属性节点
5. 注释属于注释节点
2. 父、子和同级节点
节点树中的节点彼此之间都有等级关系。
父、子和同级节点用于描述这种关系。父节点拥有子节点,位于相同层级上的子节点称为同级节点(兄弟或姐妹)。
1. 在节点树中,顶端的节点成为根节点
2. 根节点之外的每个节点都有一个父节点
3. 节点可以有任何数量的子节点
4. 叶子是没有子节点的节点
5. 同级节点是拥有相同父节点的节点
只要知道一个节点, 按关系找到其它节点
父节点: parentNode
子节点(第一个, 最后一个) childNodes firstChild lastChild
同胞(同辈)节点 (上一个, 下一个)nextSibling previousSibling
3。 获取节点的方式:
array getElementsByTagName("节点名"); //获取所对应节点名(所有),返回的是数组
object getElementById("id名"); //获取id名的唯一节点对象
示例:(找节点)
document.getElementsByTagName("li"); //所有所有li节点
document.getElementById("lid"); //获取id值为lid的唯一节点
document.getElementById("uid").getElementsByTagName("li");
//获取id值为uid中所有li子节点
document.getElementsByTagName("ul")[0].getElementsByTagName("li");
//获取第一个ul节点中所有li子节点
获取到的标记名(多个)、 id(唯一)、 name(多个)
4. 每个节点中的内容
节点类型nodeType、节点名nodeName,节点值nodeValue
节点名nodeName:
nodeName 是只读的
元素节点的 nodeName 与标签名相同
属性节点的 nodeName 是属性的名称
文本节点的 nodeName 永远是 #text
文档节点的 nodeName 永远是 #document
节点值nodeValue
元素节点的 nodeValue 是 undefined
文本节点的 nodeValue 是文本自身
属性节点的 nodeValue 是属性的值
nodeType(节点类型)
元素类型 节点类型
元素 1
属性 2
文本 3
注释 8
文档 9
4. Object 对象 (HTML元素 转成的对象(js对象))
注意: 如果使用js操作HTML文档, 就需要选将HTML文档结构转成Js对象
a. 操作属性:
nodeName(节点名称)
nodeValue(节点值)
nodeType(节点类型)
其他属性:(针对于节点)
childNodes 返回节点到子节点的节点列表。
firstChild 返回节点的首个子节点。
lastChild 返回节点的最后一个子节点。
nextSibling 返回节点之后紧跟的同级节点。
previousSibling 属性可返回某节点之前紧跟的节点(处于同一树层级)
parentNode 返回节点的父节点。
textContent设置或返回节点及其后代的文本内容。
b. 操作内容
innerText(IE) textContent(FF) //获取的是显示的内容,不包含HTML标签
innerHTML //获取的是显示的内容,会包含HTML
outerText
outerHTML
表单
value
c. 操作样式
aobj.style.backgroundColor="red";
aobj.style.fontSize="3cm";
className
aobj.className="test";
aobj.className+=" demo";
aobj.className="";
e. 操作节点:
appendChild() 向节点的子节点列表的结尾添加新的子节点。
cloneNode() 复制节点。
removeChild() 删除(并返回)当前节点的指定子节点。
replaceChild() 用新节点替换一个子节点。
hasAttributes() 判断当前节点是否拥有属性。
hasChildNodes() 判断当前节点是否拥有子节点。
insertBefore() 在指定的子节点前插入新的子节点。
f. 创建节点:
* createElement() 创建元素节点
createAttribute() 来创建属性节点 可以:元素节点.属性名=值;
createTextNode() 来创建新的文本节点 可以:元素节点.innerHTML=文本内容;
有了以上三点的操作之前先转成对象
转成对象的两种形式:
1. 标记名(多个)、 id(唯一)、 name(多个)
document中的三个方法
var objs=document.getElementsByTagName("div"); //获取多个
var objs=document.getElementById("one"); //获取一个
var objs=document.getElementsByName("two");
1.8.8 Ajax
(1) JavaScript版的Ajax实现步骤:
var xmlhttp;
1. 创建请求对象
if(window.XMLHttpRequest){
// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}else{
// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
2. 设置回调函数(监听)
xmlhttp.onreadystatechange=函数名;
或
xmlhttp.onreadystatechange=function(){
函数体。。。
}
3. 初始化:
xmlhttp.open("GET","gethint.php?q="+str,true); //异步以get方式发送到gethint.php
4. 发送:
xmlhttp.send();
(2) xmlhttp请求对象:
属性:
*readyState //请求状态:0,1,2,3,4
*responseText//响应内容
responseXML //xml响应对象
*status //浏览器响应状态:200正常, 404 请求地址不存在 ,,
statusText //状态内容
*onreadystatechange //回调函数属性
方法:
abort() //取消当前响应,关闭连接并且结束任何未决的网络活动。
getAllResponseHeaders() //把 HTTP 响应头部作为未解析的字符串返回。
getResponseHeader() //返回指定的 HTTP 响应头部的值
*open() //初始化 HTTP 请求参数
*send() //发送 HTTP 请求,使用传递给 open() 方法的参数
*setRequestHeader() //向一个打开但未发送的请求设置或添加一个 HTTP 请求。
模拟POST提交代码:
xmlhttp.open("POST","ajax_test.php",true);
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlhttp.send("fname=Bill&lname=Gates");
1.9 jQuery
- jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架)。
- jQuery设计的宗旨是“write Less,Do More”,即倡导写更少的代码,做更多的事情。
- 它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互。
- jQuery的使用:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>jQuery实例</title>
<script type="text/javascript" src="jquery-1.8.3.min.js"></script>
<script type="text/javascript">
//两种使用方式:
$(document).ready(function(){
// 在这里写你的代码...
});
//简写
$(function($) {
// 你可以在这里继续使用$作为别名...
});
</script>
</head>
<body>
<!-- HTML代码... -->
</body>
</html>
学习内容如下:具体使用文档详见《jQuery手册》
#### 1.9.1 选择器
#### 1.9.2 属性
#### 1.9.3 CSS样式
#### 1.9.4 文档操作
#### 1.9.5 筛选
#### 1.9.6 事件
#### 1.9.7 效果
#### 1.9.8 Ajax
1.10 BootStrap案例实战
关于BootStrap的文档详见: https://v3.bootcss.com/
四、Django框架
2.1 Django框架介绍与安装
(1). Web开发介绍:
-
目前Web开发属于Browser/Server模式,简称BS架构,开发语言有(Python、PHP、Java ...)。
-
基于Python的Web工作原理如下:
![img]()
(2). 框架介绍:
①. 什么是框架?
- 软件框架就是为实现或完成某种软件开发时,提供了一些基础的软件产品,
- 框架的功能类似于
基础设施,提供并实现最为基础的软件架构和体系 - 通常情况下我们依据框架来实现更为复杂的业务程序开发
- 二个字,框架就是程序的
骨架
②. 框架的优缺点
- 可重用
- 成熟,稳健
- 易扩展、易维护
③. Python中常见的框架
- 大包大揽 Django 被官方称之为完美主义者的Web框架。
- 力求精简 web.py和Tornado
- 新生代微框架 Flask和Bottle
④. Web框架中的一些概念
- MVC
- 大部分开发语言中都有MVC框架
- MVC框架的核心思想是:解耦
- 降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用
- m表示model,主要用于对数据库层的封装
- v表示view,用于向用户展示结果
- c表示controller,是核心,用于处理请求、获取数据、返回结果
- MVT
- Django是一款python的web开发框架
- 与MVC有所不同,属于MVT框架
- m表示model,负责与数据库交互
- v表示view,是核心,负责接收请求、获取数据、返回结果
- t表示template,负责呈现内容到浏览器
(3). Django框架介绍
-
Django是一个高级的Python Web框架,它鼓励快速开发和清洁,务实的设计。
-
由经验丰富的开发人员构建,它负责Web开发的许多麻烦,因此您可以专注于编写应用程序,而无需重新创建轮子。
-
它是免费的和开源的。
-
被官方称之为完美主义者的Web框架。
-
Django可以更快地构建更好的Web应用程序并减少代码。
① Django框架的特点:
- 快速开发:Django的宗旨在于帮助开发人员快速从概念到完成应用程序。
- 安全可靠:Django认真对待安全性,帮助开发人员避免许多常见的安全错误。
- 超可伸缩性:Web上的一些最繁忙的网站利用了Django快速灵活扩展的能力。
② Django可以使用什么Python版本?
| Django版本 | Python版本 |
|---|---|
| 1.8 | 2.7,3.2(直到2016年底),3.3, 3.4, 3.5 |
| 1.9, 1.10 | 2.7, 3.4, 3.5 |
| 1.11 | 2.7 , 3.4 , 3.5 , 3.6 |
| 2.0 | 3.5+ |
- Django的开发版本

(4). Django的安装
- 作为Python Web框架,Django需要Python,在安装Python同时需要安装pip。
在线安装Django,指定版本安装,目前1.11的最新版为1.11.11
pip install django==1.11.11
#默认会安装:Django==1.11.11 和 pytz==2018.3
检测当前是否安装Django及版本
python -m django --version
1.11.11
# 我们也可以先下载安装包:pip download django=1.11.11 -d ./
# 指定安装包安装:pip install Django-1.11.11-py2.py3-none-any.whl
2.2 Django的快速入门
第一步 项目的创建与运行
(1). 创建项目
如果这是你第一次使用Django,那么你必须要照顾一些初始设置。也就是说,您需要自动生成一些建立Django 项目的代码
从命令行cd到您要存储代码的目录,然后运行以下命令:
$ django-admin startproject mysite
我们来看看startproject创建的内容:
[root@localhost mysite]# tree
.
├── manage.py
└── mysite
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
关于上面自动生成的目录与文件解释如下:
- 外部mysite/根目录只是一个项目的容器。它的名字与Django无关; 您可以将其重命名为您喜欢的任何内容。
- manage.py:一个命令行实用程序,可以让您以各种方式与此Django项目进行交互。你可以阅读所有的细节 manage.py在Django的管理和manage.py。
- 内部mysite/目录是您的项目的实际Python包。它的名字是您需要用来导入其中的任何内容的Python包名称(例如mysite.urls)。
- mysite/init.py:一个空的文件,告诉Python这个目录应该被认为是一个Python包。
- mysite/settings.py:此Django项目的设置/配置。 Django设置会告诉你所有关于设置的工作原理。
- mysite/urls.py:该Django项目的URL声明; 您的Django动力网站的“目录”。
- mysite/wsgi.py:WSGI兼容的Web服务器为您的项目提供服务的入口点。
(2). 运行开发服务器
我们来验证您的Django项目的作品。更改为外部mysite目录,如果您还没有,并运行以下命令:
$ python manage.py runserver
注:若当前环境中有两个python环境,可使用python3或python3.6命令来调用3.6的环境,如:
$ python3 manage.py runserver
您将在命令行中看到以下输出:
Performing system checks...
System check identified no issues (0 silenced).
You have unapplied migrations; your app may not work properly until they are applied.
Run 'python manage.py migrate' to apply them.(注意:现在忽略关于未执行应用数据库迁移的警告)
August 07, 2017 - 15:50:53
Django version 1.11, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
以上说明已经开始使用Django的开发服务器,这是一个纯粹以Python编写的轻量级Web服务器。 我们将其与Django结合在一起,因此您可以快速开发,而无需处理配置生产服务器(如Apache),直到您准备好生产。
默认情况下,该runserver命令在端口8000的内部IP上启动开发服务器。当然也可以指定端口开启服务,如8080端口:
$ python manage.py runserver 8080
如果要更改服务器的IP,请将其与端口一起传递。例如:
$ python manage.py runserver 0:8000
注意:通过IP访问后报如下错误:
DisallowedHost at /polls
Invalid HTTP_HOST header: '192.168.*.*:8000'. You may need to add '192.168.*.*' to ALLOWED_HOSTS.
HTTP_HOST标头无效:'192.168.*.*:8000'。您可能需要将“192.168.*.*”添加到ALLOWED_HOSTS
如:ALLOWED_HOSTS = ['192.168.104.240']
(3). 创建一个应用程序
Django自带一个实用程序,可以自动生成应用程序的基本目录结构,因此您可以专注于编写代码而不是创建目录。
要创建您的应用程序,请确保您与目录位于同一目录,manage.py 并键入以下命令:
$ python manage.py startapp polls
注:若当前环境中有两个python环境,可使用python3或python3.6命令来调用3.6的环境,如:
$ python3 manage.py startapp polls
这将创建一个目录polls,其目录如下:此目录结构将容纳轮询应用程序。
[root@localhost demo]# tree mysite/
mysite/
├── manage.py
├── mysite
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── polls
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
(4). 定义我们的第一个视图
我们来写第一个视图。打开文件polls/views.py 并放入以下Python代码:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
这是Django中最简单的视图。要调用视图,我们需要将其映射到一个URL - 为此,我们需要一个URLconf。
要在polls目录中创建一个URLconf,创建一个名为urls.py。您的应用目录应该如下所示:
└── polls
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
├── urls.py
└── views.py
在polls/urls.py文件中包含以下代码:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
下一步是将根URLconf指向polls.urls模块。 在 mysite/urls.py添加一条import用于django.conf.urls.include和插入include()的urlpatterns列表,所以你必须:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^polls/', include('polls.urls')),
url(r'^admin/', admin.site.urls),
]
其中include()函数允许引用其他URLconfs。请注意,该include()函数的正则表达式 没有$(字符串匹配字符),而是尾部的斜杠。 每当Django遇到时 include(),它会排除与该点匹配的任何部分,并将剩余的字符串发送到随附的URLconf进行进一步处理。
背后的想法include()是使即插即用的URL变得容易。由于民意调查是在自己的URLconf(polls/urls.py)中,它们可以被放置在“/ polls /”下面,或者在“/ fun_polls /”下面,或者在“/ content / polls /”或其他路径根目录下,工作。
注:include()当您包含其他网址格式时,您应始终使用。 admin.site.urls是唯一的例外。
您现在已将index视图连接到URLconf中。让它验证它的工作,运行以下命令:
$ python manage.py runserver 0:8000
在浏览器中转到http:// localhost:8000 / polls /,您应该看到文本"Hello, world. You're at the polls index."
url() 函数
Django url() 可以接收四个参数,分别是两个必选参数:regex、view 和两个可选参数:kwargs、name,接下来详细介绍这四个参数。
regex: 正则表达式,与之匹配的 URL 会执行对应的第二个参数 view。view: 用于执行与正则表达式匹配的 URL 请求。kwargs: 视图使用的字典类型的参数。name: 用来反向获取 URL。
附录:
第二步 应用的创建和使用
(1). 创建一个应用程序
Django自带一个实用程序,可以自动生成应用程序的基本目录结构,因此您可以专注于编写代码而不是创建目录。
要创建您的应用程序,请确保您与目录位于同一目录,manage.py 并键入以下命令:
$ python manage.py startapp myapp
注:若当前环境中有两个python环境,可使用python3或python3.6命令来调用3.6的环境,如:
$ python3 manage.py startapp myapp
这将创建一个目录myapp,其目录如下:此目录结构将容纳轮询应用程序。
[root@localhost demo]# tree myweb/
myweb/
├── manage.py
├── myweb
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── myapp
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
└── views.py
(2). 编写我们的第一个视图
我们来写第一个视图。打开文件myapp/views.py 并放入以下Python代码:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the myapp index.")
这是Django中最简单的视图。要调用视图,我们需要将其映射到一个URL - 为此,我们需要一个URLconf。
要在myapp目录中创建一个URLconf,创建一个名为urls.py。您的应用目录应该如下所示:
└── myapp
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── tests.py
├── urls.py
└── views.py
在myapp/urls.py文件中包含以下代码:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
下一步是将根URLconf指向myapp.urls模块。 在 myweb/urls.py添加一条import用于django.conf.urls.include和插入include()的urlpatterns列表,所以你必须:
from django.conf.urls import include, url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^myapp/', include('myapp.urls')),
]
其中include()函数允许引用其他URLconfs。请注意,该include()函数的正则表达式 没有$(字符串匹配字符),而是尾部的斜杠。 每当Django遇到时 include(),它会排除与该点匹配的任何部分,并将剩余的字符串发送到随附的URLconf进行进一步处理。
背后的想法include()是使即插即用的URL变得容易。由于民意调查是在自己的URLconf(myapp/urls.py)中,它们可以被放置在“/ myapp /”下面,或者在“/ fun_myapp /”下面,或者在“/ content / myapp /”或其他路径根目录下,工作。
注:include()当您包含其他网址格式时,您应始终使用。 admin.site.urls是唯一的例外。
您现在已将index视图连接到URLconf中。让它验证它的工作,运行以下命令:
$ python manage.py runserver 0:8000
在浏览器中转到http:// localhost:8000 / myapp /,您应该看到文本"Hello, world. You're at the myapp index."
(3). 附录:url() 函数介绍
Django url() 可以接收四个参数,分别是两个必选参数:regex、view 和两个可选参数:kwargs、name,接下来详细介绍这四个参数。
regex: 正则表达式,与之匹配的 URL 会执行对应的第二个参数 view。view: 用于执行与正则表达式匹配的 URL 请求。kwargs: 视图使用的字典类型的参数。name: 用来反向获取 URL。
第三步 项目的模型
(1). 连接MySQL数据库设置
默认情况下,配置使用SQLite。若不使用SQLite作为数据库,则需要额外的设置,例如 USER,PASSWORD和HOST必须加入。
其中ENGINE设置为数据库后端使用。内置数据库后端有:
- 'django.db.backends.postgresql'
- 'django.db.backends.mysql'
- 'django.db.backends.sqlite3'
- 'django.db.backends.oracle'
在myweb/settings.py文件中,通过DATABASES项进行数据库设置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mydemo',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
}
}
注意:Django使用MySQL数据库需要安装PyMySQL,若已经安装请略过。
$ pip install pymysql
打开 myweb/__init__.py,写入以下代码导入pymysql:
import pymysql
pymysql.install_as_MySQLdb()
(2). 创建模型
在我们的简单的应用程序中,去创建一个stu表信息操作的Model类。
编辑 myapp/models.py文件
from django.db import models
# Create your models here.
class Stu(models.Model):
'''自定义Stu表对应的Model类'''
#定义属性:默认主键自增id字段可不写
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=16)
age = models.SmallIntegerField()
sex = models.CharField(max_length=1)
classid=models.CharField(max_length=8)
# 定义默认输出格式
def __str__(self):
return "%d:%s:%d:%s:%s"%(self.id,self.name,self.age,self.sex,self.classid)
# 自定义对应的表名,默认表名:myapp_stu
class Meta:
db_table="stu"
(3). 激活模型
要将该应用程序包括在我们的项目中,我们需要在设置中添加对其配置类的引用INSTALLED_APPS。
该 myappConfig班是在myapp/apps.py文件中,所以它的虚线路径'myapp.apps.myappConfig'。
编辑myweb/settings.py文件,并将该虚线路径添加到该INSTALLED_APPS设置。
INSTALLED_APPS = [
'django.contrib.admin' ,
'django.contrib.auth' ,
'django.contrib.contenttypes' ,
'django.contrib.sessions' ,
'django.contrib.messages' ,
'django.contrib.staticfiles' ,
'myapp.apps.MyappConfig', #或者直接写 myapp
]
(4). 使用(两种)
① 现在进入交互式的Python shell,并使用Django提供的免费API
C:\Users\张涛\Desktop\code\myweb>python manage.py shell
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)]
on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from myapp.models import Stu
>>> mod = Stu.objects
# 获取所有信息
>>> lists = mod.all()
>>> for v in lists:
... print(v)
...
1:zhangsan:22:m:python03
2:lisi:25:w:python04
3:wangwu:20:m:python03
4:zhaoliu:19:w:python04
5:qq01:20:m:python03
6:qq02:21:w:python04
7:qq03:20:m:python05
8:uu01:21:w:python04
9:uu02:20:m:python05
10:aa:29:w:python03
11:bb:20:m:python04
# 获取单条信息
>>> mod.get(id=1)
<Stu: 1:zhangsan:22:m:python03>
>>> mod.get(id=2)
<Stu: 2:lisi:25:w:python04>
>>> mod.get(id=3)
<Stu: 3:wangwu:20:m:python03>
>>>
② 在myapp应用的视图中使用
# 文件:myapp/views.py 文件代码
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Stu
# Create your views here.
def index(request):
return HttpResponse("Hello Django!")
def stu(request):
#获取所有stu表信息
lists = Stu.objects.all()
print(lists)
#获取单条学生信息
print(Stu.objects.get(id=1))
return HttpResponse("ok")
- 配置stu函数的访问路由
#在myapp/urls.py文件中配置
url(r'^stu$', views.stu),
启动服务后,在浏览器中访问,在命令行终端中查看输出效果: http://localhost:8000/myapp/stu
第四步 启用网站admin管理
(1). 数据迁移
Django框架中有一个非常强大的应用功能--自动管理界面,常被Web平台管理者使用,去管理整个Web平台。
默认情况下,在settings.py配置文件中INSTALLED_APPS包含以下应用程序,这些应用程序都是由Django提供:
- django.contrib.admin - 管理网站。你会很快使用它。
- django.contrib.auth - 认证系统。
- django.contrib.contenttypes - 内容类型的框架。
- django.contrib.sessions - 会话框架
- django.contrib.messages - 消息框架。
- django.contrib.staticfiles - 管理静态文件的框架。
为了后续的开发,默认这些应用程序都是包含在里面的。
使用这些Django自带的应用程序,需要我们在数据库中创建一些数据表对应,然后才能使用它们。为此,请运行以下命令(数据结构迁移):
$ python manage.py migrate
其中该migrate命令查看该INSTALLED_APPS设置,并根据myweb/settings.py文件中的数据库设置和应用程序随附的数据库迁移创建任何必需的数据库表(稍后将介绍)。您会看到适用于每个迁移的消息。
执行上面命令后的输出结果
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
# 默认情况下自动在我们的数据库中创建了10张表,具体如下:
MariaDB [mydemo]> show tables;
+----------------------------+
| Tables_in_mydemo |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
| stu |
+----------------------------+
11 rows in set (0.00 sec)
(2). 创建管理员用户
首先,我们需要创建一个可以登录管理站点的用户。运行以下命令:
$ python manage.py createsuperuser
# 输入您所需的用户名,然后按Enter键。
Username: admin
# 然后将提示您输入所需的电子邮件地址:
Email address: admin@example.com
# 最后一步是输入你的密码。您将被要求输入密码两次,第二次作为第一次的确认
Password: **********
Password (again): *********
Superuser created successfully.
(2). 启动开发服务器
默认情况下,Django管理员站点被激活。让我们开始开发服务器并探索它。
启动开发服务器命令如下:
$ python manage.py runserver 0:8000
现在,打开一个Web浏览器,访问地址: http://127.0.0.1:8000/admin/
(3). 设置时区和语言:
编辑myweb/settings.py配置文件:
...
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
...
(4). 将我们自定义的应用程序的加入到后台管理
但我们的自定义应用程序在哪里?并没有显示在后台管理索引页面上。
要做到这一点,打开myapp/admin.py 文件,并编辑代码如下:
from django.contrib import admin
from myapp.models import Stu
admin.site.register(Stu)
(5). 更深入设计后台管理
# 编辑myapp/models.py文件,在Stu类中添加如下信息,让后台管理显示中文字段。
class Stu(models.Model):
'''自定义Stu表对应的Model类'''
#定义属性:默认主键自增id字段可不写
id = models.AutoField("学号",primary_key=True)
name = models.CharField("姓名",max_length=16)
age = models.SmallIntegerField("年龄")
sex = models.CharField("性别",max_length=1)
classid=models.CharField("班级",max_length=8)
# 定义默认输出格式
def __str__(self):
return "%d:%s:%d:%s:%s"%(self.id,self.name,self.age,self.sex,self.classid)
# 自定义对应的表名,默认表名:myapp_stu
class Meta:
db_table="stu"
verbose_name = '浏览学生信息'
verbose_name_plural = '学生信息管理'
# 编辑myapp/admin.py 文件,实现信息管理的个性化定制
from django.contrib import admin
# Register your models here.
from myapp.models import Stu
#Stu模型的管理器(装饰器写法)
@admin.register(Stu)
class StuAdmin(admin.ModelAdmin):
#listdisplay设置要显示在列表中的字段(id字段是Django模型的默认主键)
list_display = ('id','name','age','sex','classid')
#设置哪些字段可以点击进入编辑界面
list_display_links = ('id','name')
#list_per_page设置每页显示多少条记录,默认是100条
list_per_page = 10
#ordering设置默认排序字段,负号表示降序排序
ordering = ('id',) #-id降序
#list_editable 设置默认可编辑字段
#list_editable = ['age','sex','classid']
#其他请详见手册文档说明
2.3 Django的URL路由
一个
干净优雅的URL方案是高质量Web应用程序中的一个重要细节。Django可以让你
自己设计URL,无论你想要什么,没有框架限制。要为应用程序设计URL,您可以非正式地
创建一个名为**URLconf**(URL配置)的Python模块。这个模块是纯Python代码,是
一个简单的Python模式(简单的正则表达式)到Python函数(您的视图)之间的映射。
(1). Django是如何处理一个请求?
当用户从Django的站点请求页面时,系统遵循以下步骤来执行的Python代码:
- 首先Django确定要使用的根URLconf模块,通过
ROOT_URLCONF来设置,具体在settings.py配置文件中。但是如果传入 HttpRequest对象具有urlconf 属性(由中间件设置),则其值将用于替换ROOT_URLCONF设置。 - Django加载该Python模块并查找该变量 urlpatterns。这应该是一个Python的django.conf.urls.url()实例列表。
- Django按顺序运行每个URL模式,并在匹配所请求的URL的第一个URL中停止。
- 一旦正则表达式匹配,Django将导入并调用给定的视图,这是一个简单的Python函数(或基于类的视图)。该视图通过以下参数传递:
- 一个实例HttpRequest。
- 如果匹配的正则表达式没有返回任何命名组,那么来自正则表达式的匹配将作为位置参数提供。
- 关键字参数由正则表达式匹配的任何命名组组成,由可选kwargs参数中指定的任何参数覆盖 django.conf.urls.url()。
- 如果没有正则表达式匹配,或者在此过程中的任何一点出现异常,Django将调用适当的错误处理视图。
示例
以下是一个URLconf示例:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
说明:
- 要从URL捕获一个值,只需将其括起来。
- 没有必要添加一个主要的斜杠,因为每个URL都有。例如articles,不是/articles。
- 正则中的'r'正面的每个正则表达式字符串的中是可选的,但推荐使用。它告诉Python一个字符串是“raw” - 字符串中没有任何内容应该被转义。请参阅Dive Into Python的解释。
示例请求:
- /articles/2005/03/将匹配列表中的第三个条目。Django会调用该函数 。views.month_archive(request, '2005', '03')
- /articles/2005/3/ 不符合任何网址格式,因为列表中的第三个条目需要两个数字的月份。
- /articles/2003/将匹配列表中的第一个模式,而不是第二个模式,因为模式是按顺序测试的,第一个模式是第一个测试通过。随意利用这些命令插入特殊情况。在这里,Django会调用该函数 views.special_case_2003(request)
- /articles/2003 将不匹配任何这些模式,因为每个模式要求URL以斜杠结尾。
- /articles/2003/03/03/将匹配最终模式。Django会调用该函数。views.article_detail(request, '2003', '03', '03')
注意:每个捕获的参数都作为纯Python字符串发送到视图,无论正则表达式的匹配是什么,即使[0-9]{4}只会匹配整数字符串。
通过浏览器访问服务
注意:url路由,由上而下 进行匹配,如果在上面就匹配成功,则不会向下匹配
通过浏览器访问服务
127.0.0.1:8000/abc ==> root url(根路由) ==> 加载子路由(myweb/urls.py)
==> 正则匹配访问的路径(path) =-=> 视图函数(views.index)
==> views.py index() 响应内容
(2). 命名组
- 上述使用为简单实例,属于
正则表达式非命名组(通过括号)捕获URL定位,并将它们作为位置参数传递给视图。 - 在更高级的使用中,我们可以使用
正则表达式命名组来捕获URL定位,并将它们作为关键字 参数传递给视图。 - 在Python正则表达式中,
正则表达式命名组的语法是(?P<name>pattern),其中命名组中的命名就是name,并且 pattern是某些匹配的模式。
实例:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
这完成了与上一个例子完全相同的事情,有一个微妙的区别:捕获的值被传递到视图函数作为关键字参数,而不是位置参数。
例如:
- 请求/articles/2005/03/调用函数 ,。views.month_archive(request, year='2005', month='03') 而不是 views.month_archive(request, '2005', '03')
- 请求/articles/2003/03/03/调用函数 。views.article_detail(request, year='2003', month='03', day='03')
- 在实践中,这意味着您的URLconfs稍微更明确,更不容易出现参数命令错误 - 您可以重新排序视图的函数定义中的参数。当然,这些好处是以简洁为代价的; 一些开发人员发现命名组语法丑陋而且太冗长。
URLconf的搜索
URLconf将根据所请求的URL进行搜索,作为普通的Python字符串。这不包括GET或POST参数或域名。
例如:
- 在请求中https://www.example.com/myapp/,URLconf将寻找myapp/。
- 在请求中https://www.example.com/myapp/?page=3,URLconf将会查找myapp/。
URLconf不查看请求方法。换句话说,所有的请求方法(GET,POST,HEAD等)都将被路由到相同的URL功能。
指定用于视图参数的默认值
一个方便的技巧是为您的视图参数指定默认参数。下面是一个URLconf和view的例子:
在下面的示例中,两个URL模式指向相同的视图views.page- 但是第一个模式不会从URL捕获任何内容。
如果第一个模式匹配,该page()函数将使用它的默认参数num,"1"。
如果第二个模式匹配, page()将使用num正则表达式捕获的任何值。
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# View (in blog/views.py)
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
...
(3). 错误处理
当Django找不到与请求的URL匹配的正则表达式时,或者异常引发时,Django将调用错误处理视图。
用于这些情况的视图由四个变量指定。它们的默认值对于大多数项目都是足够的,但通过覆盖其默认值可以进一步定制。
有关详细信息,请参阅自定义错误视图的文档。
这样的值可以在你的根URLconf中设置。在任何其他URLconf中设置这些变量将不起作用。
值必须是可调用的,或者代表视图的完整的Python导入路径的字符串,应该被调用来处理手头的错误条件。
变量是:
- handler400- 见django.conf.urls.handler400。
- handler403- 见django.conf.urls.handler403。
- handler404- 见django.conf.urls.handler404。
- handler500- 见django.conf.urls.handler500。
关于404错误
- 404的错误页面,在模板目录中创建一个404.html的页面,
- 在配置文件中 settings.py DEBUG=False
- 在出现404的情况时,自动寻找404页面。
- 也可以在视图函数中 手动报出404错误,带提醒信息
在视图函数中也可以指定返回一个404
注意 Http404需要在django.http的模块中引入
# 响应404
raise Http404('纳尼a')
在模板中 404.html
<!DOCTYPE html>
<html>
<head>
<title>404</title>
</head>
<body>
<center>
<h2>404 not found</h2>
<h3>{ { exception } }</h3>
</center>
</body>
</html>
(4). 包括其他的URLconf
在任何时候,您
urlpatterns都可以“包含”其他URLconf模块。这实质上是将一组网址“植根于”其他网址之下
例如,下面是Django网站本身的URLconf的摘录。它包含许多其他URLconf:
from django.conf.urls import include, url
urlpatterns = [
# ... snip ...
url(r'^community/', include('django_website.aggregator.urls')),
url(r'^contact/', include('django_website.contact.urls')),
# ... snip ...
]
请注意,此示例中的正则表达式没有$(字符串尾匹配字符),但包含尾部斜线。
每当Django遇到include()(django.conf.urls.include())时,它会截断与该点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf以供进一步处理。
(5). URL的反向解析
如果在视图、模板中使用硬编码的链接,在urlconf发生改变时,维护是一件非常麻烦的事情
- 解决:在做链接时,通过指向urlconf的名称,动态生成链接地址
- 视图:使用django.core.urlresolvers.reverse()函数
- 模板:使用url模板标签
示例
在URLconf中
from django.conf.urls import url
from . import views
urlpatterns = [
#...
url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
#...
]
您可以使用以下模板代码获取这些:
<a href="{ % url 'news-year-archive' 2012 % }">2012 Archive</a>
{# Or with the year in a template context variable: #}
<ul>
{ % for yearvar in year_list % }
<li><a href="{ % url 'news-year-archive' yearvar % }">{ { yearvar } } Archive</a></li>
{ % endfor % }
</ul>
在Python代码中:
from django.urls import reverse
from django.http import HttpResponseRedirect
def redirect_to_year(request):
# ...
year = 2006
# ...
return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
或简写
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
def index(request):
year = 2006
return redirect(reverse('ews-year-archive',args=(year,)))
2.4 Django的模型层
Model模型
模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。
通常,每个模型对应数据库中唯一的一张表。
- 每个模型都是django.db.models.Model的一个Python 子类。
- 模型的每个属性都表示为数据库中的一个字段。
- Django 提供一套自动生成的用于数据库访问的API;
- 这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳
模型与数据库的关系
模型(Model)负责业务对象和数据库的关系映射(ORM)
ORM是“对象-关系-映射”的简称,主要任务是:
- 根据对象的类型生成表结构
- 将对象、列表的操作,转换为sql语句
- 将sql查询到的结果转换为对象、列表
为什么要用模型?
Model是MVC框架中重要的一部分,主要负责程序中用于处理数据逻辑的部分。通常模型对象负责在数据库中存取数据
它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库
配置Mysql数据库
-
在当前环境中安装mysql
sudo apt-get install mysql-server sudo apt install mysql-client sudo apt install libmysqlclient-dev -
在当前python环境中安装 pymysql
pip3 install pymysql -
在mysql中创建数据库
create databases mytest default charset=utf8 -
在Django项目中配置数据库
修改settings.py文件中的DATABASE配置项DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mytest',#选择数据库的名,请确认你的mysql中有这个库 'USER': 'root', 'PASSWORD': '', 'HOST': 'localhost', 'PORT': '3306', } } -
告诉Django在接下来的mysql操作中使用pymysql
打开
myweb/__init__.py,写入以下代码导入pymysql:import pymysql pymysql.install_as_MySQLdb()
开发流程
-
在models.py中定义模型类,要求继承自models.Model
from django.db import models from datetime import datetime # Create your models here. class Users(models.Model): name = models.CharField(max_length=32) age = models.IntegerField(default=20) phone = models.CharField(max_length=16) addtime=models.DateTimeField(default=datetime.now) #class Meta: # db_table = "myapp_users" # 指定表名 -
把应用加入settings.py文件的installed_app项
编辑myweb/settings.py文件,并将项目应用文件名添加到该INSTALLED_APPS设置。INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'myapp', ] -
生成迁移文件
python3 manage.py makemigrations django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No mo dule named 'MySQLdb'. Did you install mysqlclient or MySQL-python? 安装上面"配置Mysql数据库"的第五步配置,或安装:pip install mysqlclient后就可以了 若执行python manage.py makemigrations命令(也可能人比较皮,把migrations文件夹给删了),会提示"No changes detected." 可能有用的解决方式如下: 先 python manage.py makemigrations --empty yourappname 生成一个空的initial.py 再 python manage.py makemigrations 生成原先的model对应的migration file -
执行迁移
python3 manage.py migrate -
使用模型类进行crud操作(具体详见第二小节)
①. 定义模型
- 在模型中定义属性,会生成表中的字段
- django根据属性的类型确定以下信息:
- 当前选择的数据库支持字段的类型
- 渲染管理表单时使用的默认html控件
- 在管理站点最低限度的验证
- django会为表增加自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列
- 属性命名限制
- 不能是python的保留关键字
- 由于django的查询方式,不允许使用连续的下划线
1. 定义属性
- 定义属性时,需要字段类型
- 字段类型被定义在django.db.models.fields目录下,为了方便使用,被导入到django.db.models中
- 使用方式
- 导入from django.db import models
- 通过models.Field创建字段类型的对象,赋值给属性
- 对于重要数据都做逻辑删除,不做物理删除,实现方法是定义isDelete属性,类型为BooleanField,默认值为False
字段类型
-
AutoField
:一个根据实际ID自动增长的IntegerField,通常不指定
- 如果不指定,一个主键字段将自动添加到模型中
-
BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput
-
NullBooleanField:支持null、true、false三种值
-
CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput
-
TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea
-
IntegerField:整数
-
DecimalField(max_digits=None, decimal_places=None):使用python的Decimal实例表示的十进制浮点数
- DecimalField.max_digits:位数总数
- DecimalField.decimal_places:小数点后的数字位数
-
FloatField:用Python的float实例来表示的浮点数
-
DateField[auto_now=False, auto_now_add=False]):使用Python的datetime.date实例表示的日期
- 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为false
- 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
- 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today"的快捷按钮,包含了一个额外的invalid_date错误消息键
- auto_now_add, auto_now, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
-
TimeField:使用Python的datetime.time实例表示的时间,参数同DateField
-
DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField
-
FileField:一个上传文件的字段
-
ImageField:继承了FileField的所有属性和方法,但对上传的对象进行校验,确保它是个有效的image
字段选项
- 通过字段选项,可以实现对字段的约束
- 在字段对象时通过关键字参数指定
- null:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 False
- blank:如果为True,则该字段允许为空白,默认值是 False
- 对比:null是数据库范畴的概念,blank是表单验证证范畴的
- db_column:字段的名称,如果未指定,则使用属性的名称
- db_index:若值为 True, 则在表中会为此字段创建索引
- default:默认值
- primary_key:若为 True, 则该字段会成为模型的主键字段
- unique:如果为 True, 这个字段在表中必须有唯一值
关系
- 关系的类型包括
- ForeignKey:一对多,将字段定义在多的端中
- ManyToManyField:多对多,将字段定义在两端中
- OneToOneField:一对一,将字段定义在任意一端中
- 可以维护递归的关联关系,使用'self'指定,详见“自关联”
- 用一访问多:对象.模型类小写_set
bookinfo.heroinfo_set
- 用一访问一:对象.模型类小写
heroinfo.bookinfo
- 访问id:对象.属性_id
heroinfo.book_id
2. 元选项
-
在模型类中定义类Meta,用于设置元信息
-
元信息db_table:定义数据表名称,推荐使用小写字母,
-
数据表的默认名称:<app_name>_<model_name>
-
例如myweb_users
-
ordering:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表
class BookInfo(models.Model): ... class Meta(): ordering = ['id'] -
字符串前加-表示倒序,不加-表示正序
class BookInfo(models.Model): ... class Meta(): ordering = ['-id'] -
排序会增加数据库的开销
3. 示例演示
- 创建test2项目,并创建booktest应用,使用mysql数据库
- 定义图书模型
class BookInfo(models.Model):
btitle = models.CharField(max_length=32)
bpub_date = models.DateTimeField()
bread = models.IntegerField(default=0)
bcommet = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False)
- 英雄模型
class HeroInfo(models.Model):
hname = models.CharField(max_length=32)
hgender = models.BooleanField(default=True)
isDelete = models.BooleanField(default=False)
hcontent = models.CharField(max_length=100)
hbook = models.ForeignKey('BookInfo')
- 定义index、detail视图
- index.html、detail.html模板
- 配置url,能够完成图书及英雄的展示
4. 测试数据
- 模型BookInfo的测试数据
insert into booktest_bookinfo(btitle,bpub_date,bread,bcommet,isDelete) values
('射雕英雄传','1980-5-1',12,34,0),
('天龙八部','1986-7-24',36,40,0),
('笑傲江湖','1995-12-24',20,80,0),
('雪山飞狐','1987-11-11',58,24,0)
- 模型HeroInfo的测试数据
insert into booktest_heroinfo(hname,hgender,hbook_id,hcontent,isDelete) values
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('梅超风',0,1,'九阴白骨爪',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('令狐冲',1,3,'独孤九剑',0),
('任盈盈',0,3,'弹琴',0),
('岳不群',1,3,'华山剑法',0),
('东方不败',0,3,'葵花宝典',0),
('胡斐',1,4,'胡家刀法',0),
('苗若兰',0,4,'黄衣',0),
('程灵素',0,4,'医术',0),
('袁紫衣',0,4,'六合拳',0)
②. 模型实例
1. 类的属性
-
objects:是Manager类型的对象,用于与数据库进行交互
-
当定义模型类时没有指定管理器,则Django会为模型类提供一个名为objects的管理器
-
支持明确指定模型类的管理器
class BookInfo(models.Model): ... books = models.Manager() -
当为模型类指定管理器后,django不再为模型类生成名为objects的默认管理器
2. 创建对象
- 当创建对象时,django不会对数据库进行读写操作
- 调用save()方法才与数据库交互,将对象保存到数据库中
- 使用关键字参数构造模型对象很麻烦,推荐使用下面的两种之式
- 说明:init方法已经在基类models.Model中使用,在自定义模型中无法使用,
3. 实例的属性
- DoesNotExist:在进行单个查询时,模型的对象不存在时会引发此异常,结合try/except使用
4. 实例的方法
__str__(self):重写object方法,此方法在将对象转换成字符串时会被调用- save():将模型对象保存到数据表中
- delete():将模型对象从数据表中删除
③. 模型查询
- 查询集表示从数据库中获取的对象集合
- 查询集可以含有零个、一个或多个过滤器
- 过滤器基于所给的参数限制查询的结果
- 从Sql的角度,查询集和select语句等价,过滤器像where和limit子句
- 接下来主要讨论如下知识点
- 查询集
- 字段查询:比较运算符,F对象,Q对象
1. 查询集
-
在管理器上调用过滤器方法会返回查询集
-
查询集经过过滤器筛选后返回新的查询集,因此可以写成链式过滤
-
惰性执行:创建查询集不会带来任何数据库的访问,直到调用数据时,才会访问数据库
-
何时对查询集求值:迭代,序列化,与if合用
-
返回查询集的方法,称为过滤器
- all()
- filter()
- exclude()
- order_by()
- values():一个对象构成一个字典,然后构成一个列表返回
-
写法:
filter(键1=值1,键2=值2) 等价于 filter(键1=值1).filter(键2=值2) -
返回单个值的方法
- get():返回单个满足条件的对象
- 如果未找到会引发"模型类.DoesNotExist"异常
- 如果多条被返回,会引发"模型类.MultipleObjectsReturned"异常
- count():返回当前查询的总条数
- first():返回第一个对象
- last():返回最后一个对象
- exists():判断查询集中是否有数据,如果有则返回True
- get():返回单个满足条件的对象
限制查询集
-
查询集返回列表,可以使用下标的方式进行限制,等同于sql中的limit和offset子句
-
注意:不支持负数索引
-
使用下标后返回一个新的查询集,不会立即执行查询
-
如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()引发DoesNotExist异常
#这会返回前5个对象 LIMIT 5 Entry.objects.all()[:5] #这将返回第六个到第十个对象 OFFSET 5 LIMIT 5 Entry.objects.all()[5:10]
查询集的缓存
-
每个查询集都包含一个缓存来最小化对数据库的访问
-
在新建的查询集中,缓存为空,首次对查询集求值时,会发生数据库查询,django会将查询的结果存在查询集的缓存中,并返回请求的结果,接下来对查询集求值将重用缓存的结果
-
情况一:这构成了两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载
print([e.title for e in Entry.objects.all()]) print([e.title for e in Entry.objects.all()]) -
情况二:两次循环使用同一个查询集,第二次使用缓存中的数据
querylist=Entry.objects.all() print([e.title for e in querylist]) print([e.title for e in querylist]) -
何时查询集不会被缓存:当只对查询集的部分进行求值时会检查缓存,但是如果这部分不在缓存中,那么接下来查询返回的记录将不会被缓存,这意味着使用索引来限制查询集将不会填充缓存,如果这部分数据已经被缓存,则直接使用缓存中的数据
2. 字段查询
- 实现where子名,作为方法filter()、exclude()、get()的参数
- 语法:属性名称__比较运算符=值
- 表示两个下划线,左侧是属性名称,右侧是比较类型
- 对于外键,使用“属性名_id”表示外键的原始值
- 转义:like语句中使用了%与,匹配数据中的%与,在过滤器中直接写,例如:filter(title__contains="%")=>where title like '%%%',表示查找标题中包含%的
比较运算符
-
exact:表示判等,大小写敏感;如果没有写“ 比较运算符”,表示判等
filter(isDelete=False) -
contains:是否包含,大小写敏感
exclude(btitle__contains='传') -
startswith、endswith:以value开头或结尾,大小写敏感
exclude(btitle__endswith='传') -
isnull、isnotnull:是否为null
filter(btitle__isnull=False) -
在前面加个i表示不区分大小写,如iexact、icontains、istarswith、iendswith
-
in:是否包含在范围内
filter(pk__in=[1, 2, 3, 4, 5]) -
gt、gte、lt、lte:大于、大于等于、小于、小于等于
filter(id__gt=3) -
year、month、day、week_day、hour、minute、second:对日期间类型的属性进行运算
filter(bpub_date__year=1980) filter(bpub_date__gt=date(1980, 12, 31))
跨关联关系的查询:处理join查询
-
语法:模型类名 <属性名> <比较>
-
注:可以没有__<比较>部分,表示等于,结果同inner join
-
可返向使用,即在关联的两个模型中都可以使用
filter(heroinfo_ _hcontent_ _contains='八')
聚合函数
-
使用aggregate()函数返回聚合函数的值
-
函数:Avg,Count,Max,Min,Sum
from django.db.models import Max maxDate = list.aggregate(Max('bpub_date')) -
count的一般用法:
count = list.count()
④. 用户信息管理实战
Model模型的实战操作笔记
1. 创建数据库和表(若是执行表迁移,可跳过下面操作)
-
进入MySQL数据库创建数据库:mytest
-
进入数据库创建数据表:myapp_users
CREATE TABLE `myapp_users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `age` tinyint(3) unsigned NOT NULL DEFAULT '20', `phone` varchar(16) DEFAULT NULL, `addtime` datetime(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 -
添加几条测试数据
2. 创建项目myweb和应用myapp
# 创建项目框架myweb
$ django-admin startproject myweb
$ cd myweb
# 在项目中创建一个myapp应用
$ python3.6 manage.py startapp myapp
# 创建模板目录
$ mkdir templates
$ mkdir templates/myapp
$ cd ..
$ tree myweb
myweb
├── myweb
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── myapp
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── templates
└── myapp
3. 执行数据库连接配置,网站配置
3.1 编辑myweb/myweb/__init__.py文件,添加Pymysql的数据库操作支持
import pymysql
pymysql.install_as_MySQLdb()
3.2 编辑myweb/myweb/settings.py文件,配置数据库连接
...
#配置自己的服务器IP地址
ALLOWED_HOSTS = ['localhost','127.0.0.1','172.16.142.129']
...
#添加自己应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
...
# 配置模板路径信息
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
# 数据库连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mytest',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
}
...
4. 定义Model类
4.1 编辑myweb/myapp/models.py
from django.db import models
class Users(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField(default=20)
phone = models.CharField(max_length=16)
addtime=models.DateTimeField(default=datetime.now)
def __str__(self):
return self.name+":"+self.phone
4.2 测试Model类的使用
在项目根目录下执行命令: python3 manage.py shell
[root@localhost myweb]# ls
myweb manage.py myapp
[root@localhost myweb]# python3 manage.py shell
Python 3.6.1 (default, Jul 18 2017, 01:35:19)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-18)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from myapp.models import Users
>>> Users.objects.all()
<QuerySet [<Users: 张三:12345678901>, <Users: aa:13456789023>]>
>>> s = Users.objects.get(id=1)
>>> s.id
1
>>> s.name
'张三'
>>> s.age
20
>>> s.phone
'12345678901'
>>>
5. 实现Web端访问
5.1 编辑myweb/myweb/settings.py文件.做 ALLOWED_HOSTS 主机访问配置(若第三步已做可跳过此步骤)
#此处添加自己的IP地址
ALLOWED_HOSTS = ['localhost','127.0.0.1','172.16.142.129']
5.2 编写项目主路由urls配置,配置对myapp应用路由的访问连接配置
from django.conf.urls import include,url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('myapp.urls')),
]
5.3 配置当前应用myapp的路由配置
在myapp应用目录下创建一个路由文件urls.py文件,注意此文件编码为utf-8(建议复制一个)。
编辑应用中的路由配置文件:myweb/myapp/urls.py, 内容如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name="index"),
]
5.4 编辑视图文件:myweb/myapp/views.py,内容如下
#from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
def index(request):
try:
s = Users.objects.get(id=1)
return HttpResponse(s)
except:
return HttpResponse("没有找到对应的信息!")
5.5 测试
项目根目录下运行 python3 manage.py sunserver 0:8000 命令,开启服务:
[root@localhost myweb]# python3.6 manage.py runserver 0:8000
Performing system checks...
System check identified no issues (0 silenced).
July 28, 2017 - 14:01:52
Django version 1.11, using settings 'myweb.settings'
Starting development server at http://0:8000/
Quit the server with CONTROL-C.
打开浏览器,在浏览其中输入网址测试:http://172.16.142.129:8000
显示结果:张三:12345678901
继续上面操作完成Web版的Users信息增 删 改 查
1. 准备模板文件,创建模板目录
- 在项目目录下创建templates模板目录
- 进入模板目录templates,在模板目录下创建应用名myapp的目录
- 进入myapp目录,在里面创建一个users目录
- 进入users目录,在里面创建文件:index.html,add.html,edit.html,menu.html,info.html
5 设置模板目录信息:编辑myweb/myweb/settings.py文件(若第三步已做可跳过此步骤)
...
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates'),],
'APP_DIRS': True,
...
2. 配置路由信息
打开文件:myweb/myapp/urls.py路由文件,加入六条路由配置信息
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name="index"),
url(r'^users$', views.indexUsers, name="users"), #浏览用户信息
url(r'^users/add$', views.addUsers, name="addusers"), #加载添加用户信息表单
url(r'^users/insert$', views.insertUsers, name="insertusers"), #执行用户信息添加
url(r'^users/(?P<uid>[0-9]+)/del$', views.delUsers, name="delusers"), #执行用户信息删除
url(r'^users/(?P<uid>[0-9]+)/edit$', views.editUsers, name="editusers"), #加载用户信息编辑表单
url(r'^users/update$', views.updateUsers, name="updateusers"), #执行用户信息编辑
]
3. 编写视图文件:myweb/myapp/views.py
# 注意:需导入from django.shortcuts import render
from django.shortcuts import render
from django.http import HttpResponse
from myapp.models import Users
def index(request):
try:
list = Users.objects.filter(id__in=[1,3,5])
s = ','.join([vo.name for vo in list])
#修改(将id值为5的age值改为30)
#ob = Users.objects.get(id=5)
#ob.age = 30
#ob.save()
#删除(删除id为3的信息)
#ob = Users.objects.get(id=3)
#ob.delete()
return HttpResponse(s)
except:
return HttpResponse("没有找到对应的信息!")
# 浏览用户信息
def indexUsers(request):
# 执行数据查询,并放置到模板中
list = Users.objects.all()
context = {"stulist":list}
return render(request,"myapp/users/index.html",context)
# 加载添加信息表单
def addUsers(request):
return render(request,"myapp/users/add.html")
# 执行信息添加操作
def insertUsers(request):
try:
ob = Users()
ob.name = request.POST['name']
ob.age = request.POST['age']
ob.phone = request.POST['phone']
ob.save()
context = {'info':'添加成功!'}
except:
context = {'info':'添加失败!'}
return render(request,"myapp/users/info.html",context)
# 执行信息删除操作
def delUsers(request,uid):
try:
ob = Users.objects.get(id=uid)
ob.delete()
context = {'info':'删除成功!'}
except:
context = {'info':'删除失败!'}
return render(request,"myapp/users/info.html",context)
# 加载信息编辑表单
def editUsers(request,uid):
try:
ob = Users.objects.get(id=uid)
context = {'user':ob}
return render(request,"myapp/users/edit.html",context)
except:
context = {'info':'没有找到要修改的信息!'}
return render(request,"myapp/users/info.html",context)
# 执行信息编辑操作
def updateUsers(request):
try:
ob = Users.objects.get(id= request.POST['id'])
ob.name = request.POST['name']
ob.age = request.POST['age']
ob.phone = request.POST['phone']
#ob.addtime = datetime.now
ob.save()
context = {'info':'修改成功!'}
except:
context = {'info':'修改失败!'}
return render(request,"myapp/users/info.html",context)
4. 编辑浏览信息视图文件
文件位置:myweb/templates/myapp/users/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>用户信息管理</title>
<script>
//自定义执行信息删除提示判断,参数uu是成功的删除url地址
function doDel(uu){
if(confirm("确定要删除吗?")){
//网页跳转
window.location=uu;
}
}
</script>
</head>
<body>
<center>
{% include 'myapp/users/menu.html' %}
<h3>浏览用户信息</h3>
<table width="800" border="1">
<tr>
<th>id号</th>
<th>姓名</th>
<th>年龄</th>
<th>电话</th>
<th>添加时间</th>
<th>操作</th>
</tr>
{% for stu in stulist %}
<tr>
<td>{{ stu.id }}</td>
<td>{{ stu.name }}</td>
<td>{{ stu.age }}</td>
<td>{{ stu.phone }}</td>
<td>{{ stu.addtime|date:'Y-m-d H:i:s' }}</td>
<td>
<a href="{% url 'editusers' stu.id %}">编辑</a>
<a href="javascript:doDel('{% url 'delusers' stu.id %}');">删除</a>
</td>
</tr>
{% endfor %}
</table>
</center>
</body>
</html>
5. 编辑添加表单视图文件
文件位置:myweb/templates/myapp/users/add.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>用户信息管理</title>
</head>
<body>
<center>
{% include "myapp/users/menu.html" %}
<h3>添加用户信息</h3>
<form action="{% url 'insertusers' %}" method="post">
{% csrf_token %}
<table width="280" border="0">
<tr>
<td>姓名:</td>
<td><input type="text" name="name"/></td>
</tr>
<tr>
<td>年龄:</td>
<td><input type="text" name="age"/></td>
</tr>
<tr>
<td>电话:</td>
<td><input type="text" name="phone"/></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="添加"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</center>
</body>
</html>
6. 编辑信息表单视图文件
文件位置:myweb/templates/myapp/users/edit.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>用户信息管理</title>
</head>
<body>
<center>
{% include "myapp/users/menu.html" %}
<h3>修改用户信息</h3>
<form action="{% url 'updateusers' %}" method="post">
<input type="hidden" name="id" value="{{ user.id}}"/>
{% csrf_token %}
<table width="280" border="0">
<tr>
<td>姓名:</td>
<td><input type="text" name="name" value="{{ user.name }}"/></td>
</tr>
<tr>
<td>年龄:</td>
<td><input type="text" name="age" value="{{ user.age }}"/></td>
</tr>
<tr>
<td>电话:</td>
<td><input type="text" name="phone" value="{{ user.phone }}"/></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="编辑"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
</form>
</center>
</body>
</html>
7. 编辑公共导航栏页视图文件
文件位置:myweb/templates/myapp/users/menu.html
<h2>用户信息管理</h2>
<a href="{% url 'users' %}">浏览用户</a> |
<a href="{% url 'addusers' %}">添加用户</a>
<hr/>
文件位置:myweb/templates/myapp/users/info.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>用户信息管理</title>
</head>
<body>
<center>
{% include "myapp/users/menu.html" %}
<h3>{{ info }}</h3>
</center>
</body>
</html>
2.5 Django的视图层(View)
Django框架中的视图(View)是用来负责处理用户请求和返回响应的逻辑程序
视图(View)简而言之就是一个Python的函数或方法,接受处理Web请求。
视图的响应可以是网页的HTML内容,重定向或404错误,XML文档或图像。
视图的代码按惯例是放置一个名为
views.py的文件中,此文件放在项目或应用程序目录中。(其实视图文件名可以自己定义)
1. 一个简单的视图
这是一个返回当前日期和时间的视图,作为HTML文档:
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
2. 返回错误
# 直接返回一个404,没有去加载404的模板页面
# return HttpResponseNotFound('<h1>Page not found</h1>')
# 可以直接返回一个status状态码
# return HttpResponse(status=403)
# 返回一个404的错误页面
raise Http404("Poll does not exist")
3.关于重定向
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
# redirect重定向 reverse反向解析url地址
# return redirect(reverse('userindex'))
# 执行一段js代码,用js进行重定向
# return HttpResponse('<script>alert("添加成功");location.href = "/userindex"; </script>')
# 加载一个提醒信息的跳转页面
context = {'info':'数据添加成功','u':'/userindex'}
return render(request,'info.html',context)
4. 基于类的基本视图:
示例views.py:
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')
示例urls.py:
from django.urls import path
from myapp.views import MyView
urlpatterns = [
path('mine/', MyView.as_view(), name='my-view'),
]
# 其中as_view()是接受请求并返回响应的可调用视图['get', 'post', 'put', 'patch', 'delete, 'options'.]
①. HttpResponse对象
- 在django.http模块中定义了HttpResponse对象的API
- HttpRequest对象由Django自动创建,HttpResponse对象由程序员创建
- 在每一个视图函数中必须返回一个HttpResponse对象,当然也可以是HttpResponse子对象
1. 不用模板,直接返回数据
from django.http import HttpResponse
def index(request):
return HttpResponse('你好')
2. 调用模板返回数据
from django.http import HttpResponse
# 返回模板
return render(request,'user/edit.html',{'info':'你好'})
3. 子类HttpResponseRedirect
重定向,服务器端跳转
构造函数的第一个参数用来指定重定向的地址
可以简写为 redirect
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
def index(request):
return redirect(reverse('myindex')
4. 子类JsonResponse
返回json数据,一般用于异步请求
帮助用户创建JSON编码的响应
JsonResponse的默认Content-Type为application/json
from django.http import JsonResponse
def index2(requeset):
return JsonResponse({'list': 'abc'})
5. set_cookie方法
Cookie 是由 Web 服务器保存在用户浏览器(客户端)上的小文本文件,它可以包含有关用户的信息。
服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。
Cookies最典型的应用是判定注册用户是否已经登录网站
设置cookie
# 获取当前的 响应对象
response = HttpResponse('cookie的设置')
# 使用响应对象进行cookie的设置
response.set_cookie('a','abc')
# 返回响应对象
return response
获取cookie
# 读取
a = request.COOKIES.get('a',None)
if a:
return HttpResponse('cookie的读取:'+a)
else:
return HttpResponse('cookie不存在')
②. HttpReqeust对象
- 服务器接收到http协议的请求后,会根据报文创建HttpRequest对象
- 视图函数的第一个参数是HttpRequest对象
- 在django.http模块中定义了HttpRequest对象的API
1. 属性
下面除非特别说明,属性都是只读的
- path:一个字符串,表示请求的页面的完整路径,不包含域名
- method:一个字符串,表示请求使用的HTTP方法,常用值包括:'GET'、'POST'
- encoding:一个字符串,表示提交的数据的编码方式
- 如果为None则表示使用浏览器的默认设置,一般为utf-8
- 这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值
- GET:一个类似于字典的对象,包含get请求方式的所有参数
- POST:一个类似于字典的对象,包含post请求方式的所有参数
- FILES:一个类似于字典的对象,包含所有的上传文件
- COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串
- session:一个既可读又可写的类似于字典的对象,表示当前的会话,只有当Django 启用会话的支持时才可用,详细内容见“状态保持”
2. 方法
is_ajax():如果请求是通过XMLHttpRequest发起的,则返回True
③. QueryDict对象
-
定义在django.http.QueryDict
-
request对象的属性GET、POST都是QueryDict类型的对象
-
与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况
-
方法get():根据键获取值
-
只能获取键的一个值
-
如果一个键同时拥有多个值,获取最后一个值
dict.get('键',default) 或简写为 dict['键']
-
-
方法getlist():根据键获取值
-
将键的值以列表返回,可以获取一个键的多个值
dict.getlist('键',default)
-
1. GET && POST
一键一值
http://127.0.0.1/get?a=1&b=2&c=3
request.GET['name']
request.GET.get('name',None)
一键多值
http://127.0.0.1/get?a=1&a=2&b=3
request.GET.getlist('name',None)
request.POST.getlist('name',None)
④. 验证码实战 (验证码)
- 在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻一些服务器的压力
- 使用验证码也是一种有效的防止crsf的方法
- 需要安装扩展:
pip install pillow - 验证码效果如下图:
1. 验证码视图
- 新建viewsUtil.py,定义函数verifycode
- 此段代码用到了PIL中的Image、ImageDraw、ImageFont模块,需要先安装Pillow(3.4.1)包,详细文档参考http://pillow.readthedocs.io/en/3.4.x/
- Image表示画布对象
- ImageDraw表示画笔对象
- ImageFont表示字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
*代码如下:
from django.http import HttpResponse
def verifycode(request):
#引入绘图模块
from PIL import Image, ImageDraw, ImageFont
#引入随机函数模块
import random
#定义变量,用于画面的背景色、宽、高
bgcolor = (random.randrange(20, 100), random.randrange(
20, 100), 255)
width = 100
height = 25
#创建画面对象
im = Image.new('RGB', (width, height), bgcolor)
#创建画笔对象
draw = ImageDraw.Draw(im)
#调用画笔的point()函数绘制噪点
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)
#定义验证码的备选值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
#随机选取4个值作为验证码
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
#构造字体对象
font = ImageFont.truetype('static/msyh.ttf', 23)
#font = ImageFont.load_default().font
#构造字体颜色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
#绘制4个字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
#释放画笔
del draw
#存入session,用于做进一步验证
request.session['verifycode'] = rand_str
#内存文件操作
"""
python2的为
# 内存文件操作
import cStringIO
buf = cStringIO.StringIO()
"""
# 内存文件操作-->此方法为python3的
import io
buf = io.BytesIO()
#将图片保存在内存中,文件类型为png
im.save(buf, 'png')
#将内存中的图片数据返回给客户端,MIME类型为图片png
return HttpResponse(buf.getvalue(), 'image/png')
2. 配置url
- 在urls.py中定义请求验证码视图的url
from . import viewsUtil
urlpatterns = [
url(r'^verifycode/$', viewsUtil.verifycode),
]
3. 显示验证码
- 在模板中使用img标签,src指向验证码视图
<img id='verifycode' src="/verifycode/" alt="CheckCode"/>
- 启动服务器,查看显示成功
- 扩展:点击“看不清,换一个”时,可以换一个新的验证码
<script type="text/javascript" src="/static/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(function(){
$('#verifycodeChange').css('cursor','pointer').click(function() {
$('#verifycode').attr('src',$('#verifycode').attr('src')+1)
});
});
</script>
<img id='verifycode' src="/verifycode/?1" alt="CheckCode"/>
<span id='verifycodeChange'>看不清,换一个</span>
- 为了能够实现提交功能,需要增加form和input标签
<form method='post' action='/verifycodeValid/'>
<input type="text" name="vc">
<img id='verifycode' src="/verifycode/?1" alt="CheckCode"/>
<span id='verifycodeChange'>看不清,换一个</span>
<br>
<input type="submit" value="提交">
</form>
4. 验证
- 接收请求的信息,与session中的内容对比
from django.http import HttpResponse
def verifycodeValid(request):
vc = request.POST['vc']
if vc.upper() == request.session['verifycode']:
return HttpResponse('ok')
else:
return HttpResponse('no')
- 配置验证处理的url
urlpatterns = [
url(r'^verifycodeValid/$', views.verifycodeValid),
]
5. 第三方
可以在网上搜索“验证码”,找到一些第三方验证码提供网站,阅读文档,使用到项目中
2.6 Django的模板层
Template模板
作为Web 框架,Django 需要一种很便利的方法以动态地生成HTML。最常见的做法是使用模板。
模板包含所需HTML 输出的静态部分,以及一些特殊的语法,描述如何将动态内容插入。
模板引擎配置
模板引擎使用该TEMPLATES设置进行配置。这是一个配置列表,每个引擎一个。
默认值为空。在 settings.py由所产生的startproject命令定义一个更有用的值:
在做下面模板配置的同时,也要在项目的根目录下创建一个
templates目录
# 项目目录下的settings.py配置文件添加TEMPLATES中的DIRS配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
①. 模板语法
1.变量
-
变量输出语法
{ { var } } -
当模版引擎遇到一个变量,将计算这个变量,然后将结果输出
-
变量名必须由字母、数字、下划线(不能以下划线开头)和点组成
-
当模版引擎遇到点("."),会按照下列顺序查询:
- 字典查询,例如:foo["bar"]
- 属性或方法查询,例如:foo.bar
- 数字索引查询,例如:foo[bar]
-
如果变量不存在, 模版系统将插入'' (空字符串)
-
在模板中调用方法时不能传递参数
2.标签
-
语法
{ % tag % } -
作用
- 在输出中创建文本
- 控制循环或逻辑
- 加载外部信息到模板中
for 标签
{ % for ... in ... % }
循环逻辑
{ % endfor % }
if 标签
{ % if ... % }
逻辑1
{ % elif ... % }
逻辑2
{ % else % }
逻辑3
{ % endif % }
comment 标签
{ % comment % }
多行注释
{ % endcomment % }
include 加载模板并以标签内的参数渲染
{ % include "base/index.html" % }
url 反向解析
{ % url 'name' p1 p2 % }
csrf_token 这个标签用于跨站请求伪造保护
{ % csrf_token % }
3.过滤器
-
语法:
{ { 变量|过滤器 } },例如{ { name|lower } },表示将变量name的值变为小写输出 -
使用管道符号 (|)来应用过滤器
-
通过使用过滤器来改变变量的计算结果
-
关闭HTML自动转义
{ { data|safe } } -
可以在if标签中使用过滤器结合运算符
if list1|length > 1 -
过滤器能够被“串联”,构成过滤器链
name|lower|upper -
过滤器可以传递参数,参数使用引号包起来
list|join:", " -
default:如果一个变量没有被提供,或者值为false或空,则使用默认值,否则使用变量的值
value|default:"什么也没有" -
date:根据给定格式对一个date变量格式化
value|date:'Y-m-d'
4.注释
- 单行注释
{# 注释 #}
- 多行注释
{% comment %}
多行注释
{% endcomment %}
5.模板运算
-
加
{ { value|add:10 } } note:value=5,则结果返回15 -
减
{ { value|add:-10 } } note:value=5,则结果返回-5,加一个负数就是减法了 -
乘
{ % widthratio 5 1 100 % } note:等同于:(5 / 1) * 100 ,结果返回500, withratio需要三个参数,它会使用参数1/参数2*参数3的方式进行运算,进行乘法运算,使「参数2」=1 -
除
{ % widthratio 5 100 1 % } note:等同于:(5 / 100) * 1,则结果返回0.05,和乘法一样,使「参数3」= 1就是除法了。
6.自定义 标签 或 过滤器
- 首先在当前应用目录下创建一个
templatetags模板标签目录,建议内放一个__init__.py的空文件 - 然后在
templatetags目录下创建一个模板标签文件pagetag.py,具体代码如下:
templatetags
├── pagetag.py
----------------pagetag.py-------------------------
from django import template
register = template.Library()
# 自定义过滤器(实现大写转换)
@register.filter
def myupper(val):
# print ('val from template:',val)
return val.upper()
# 自定义标签(实现减法计算)
#from django.utils.html import format_html
@register.simple_tag
def jian(a,b):
res = int(a) - int(b)
return res
- 使用:在模板文件使用
{ % load pagetag % }
<h4>6. 自定义标签 </h4>
{% load pagetag %}
大写:{{name|myupper}} <br/>
相减:{% jian m1 m2 %}
②. 模板继承
- 模板继承可以减少页面内容的重复定义,实现页面内容的重用
- 典型应用:网站的头部、尾部是一样的,这些内容可以定义在父模板中,子模板不需要重复定义
- block标签:在父模板中预留区域,在子模板中填充
- extends继承:继承,写在模板文件的第一行
- 定义父模板base.html
{ % block block_name % }
这里可以定义默认值
如果不定义默认值,则表示空字符串
{ % endblock % }
- 定义子模板index.html
{ % extends "base.html" % }
- 在子模板中使用block填充预留区域
{ % block block_name % }
实际填充内容
{ % endblock % }
说明
- 如果在模版中使用extends标签,它必须是模版中的第一个标签
- 不能在一个模版中定义多个相同名字的block标签
- 子模版不必定义全部父模版中的blocks,如果子模版没有定义block,则使用了父模版中的默认值
- 如果发现在模板中大量的复制内容,那就应该把内容移动到父模板中
- 使用可以获取父模板中block的内容
- 为了更好的可读性,可以给endblock标签一个名字
{ % block block_name % }
区域内容
{ % endblock block_name % }
三层继承结构
- 三层继承结构使代码得到最大程度的复用,并且使得添加内容更加简单
- 如下图为常见的电商页面

1.创建根级模板
- 名称为“base.html”
- 存放整个站点共用的内容
<!DOCTYPE html>
<html>
<head>
<title>{ % block title % }{ % endblock % } 水果超市</title>
</head>
<body>
top--{{logo}}
<hr/>
{ % block left % }
{ % endblock % }
{ % block content % }
{ % endblock % }
<hr/>
bottom
</body>
</html>
2.创建分支模版
- 继承自base.html
- 名为“base_***.html”
- 定义特定分支共用的内容
- 定义base_goods.html
{ % extends 'temtest/base.html' % }
{ % block title % }商品{ % endblock % }
{ % block left % }
<h1>goods left</h1>
{ % endblock % }
- 定义base_user.html
{ % extends 'temtest/base.html' % }
{ % block title % }用户中心{ % endblock % }
{ % block left % }
<font color='blue'>user left</font>
{ % endblock % }
- 定义index.html,继承自base.html,不需要写left块
{ % extends 'temtest/base.html' % }
{ % block content % }
首页内容
{ % endblock content % }
3.为具体页面创建模板,继承自分支模板
- 定义商品列表页goodslist.html
{ % extends 'temtest/base_goods.html' % }
{ % block content % }
商品正文列表
{ % endblock content % }
- 定义用户密码页userpwd.html
{ % extends 'temtest/base_user.html' % }
{ % block content % }
用户密码修改
{ % endblock content % }
4.视图调用具体页面,并传递模板中需要的数据
- 首页视图index
logo='welcome to itcast'
def index(request):
return render(request, 'temtest/index.html', {'logo': logo})
- 商品列表视图goodslist
def goodslist(request):
return render(request, 'temtest/goodslist.html', {'logo': logo})
- 用户密码视图userpwd
def userpwd(request):
return render(request, 'temtest/userpwd.html', {'logo': logo})
5.配置url
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^list/$', views.goodslist, name='list'),
url(r'^pwd/$', views.userpwd, name='pwd'),
]
③. Ajax应用实战
Ajax实战笔记--城市级联操作
1. 项目架构搭建
-
1.1 创建项目tpdemo,创建应用myapp
# 创建项目框架tpdemo $ django-admin startproject tpdemo $ cd tpdemo # 在项目中创建一个myapp应用 $ python3 manage.py startapp myapp # 创建模板目录 $ mkdir templates $ mkdir templates/myapp $ cd .. $ tree tpdemo tpdemo ├── tpdemo │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py ├── myapp │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py └── templates └── mytest -
1.2 编辑tpdemo/tpdemo/settings.py文件,配置数据库连接
...
#配置自己的服务器IP地址
ALLOWED_HOSTS = ['*']
...
#添加自己应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
...
# 配置模板路径信息
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
# 数据库连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mytest',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
}
...
- 1.3 编辑
tpdemo/tpdemo/__init__.py文件,添加Pymysql的数据库操作支持
import pymysql
pymysql.install_as_MySQLdb()
- 1.4 编写项目主路由urls配置,配置对myapp应用路由的访问连接配置: tpdemo/tpdemo/urls.py
from django.conf.urls import include,url
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('myapp.urls')),
]
- 1.5 配置当前应用myapp的路由配置
- 在myapp应用目录下创建一个路由文件urls.py文件,注意此文件编码为utf-8(建议复制一个)。
- 编辑应用中的路由配置文件:tpdemo/myapp/urls.py, 内容如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name="index"),
]
- 1.6 编写视图tpdemo/myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
# 网站首页
def index(request):
return render(request,'myapp/index.html')
- 1.7 定义模板并编写模板 tpdemo/templates/myapp/index.html
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>Django框架案例</title>
</head>
<body>
<h2>Django框架案例</h2>
<h4><a href="#">1. Ajax实战笔记--城市级联操作</a></h4>
</body>
</html>
- 1.8 启动服务,通过浏览器测试效果
[root@localhost tpdemo]# ls
tpdemo manage.py myapp templates
[root@localhost tpdemo]# python3 manage.py runserver 0:8000
- 打开浏览器输入网址:http:localhost:8000
2. 开发《城市级联信息操作》
-
2.1 将提前准备好的district.sql信息导入到mydb数据库中
在mydb数据库中存在一个district(城市区县信息表)
-
2.2 编写model类:打开tpdemo/myapp/models.py文件
from django.db import models
# 自定义城市区县信息model类
class District(models.Model):
name = models.CharField(max_length=255)
upid = models.IntegerField()
class Meta:
db_table = "district" # 指定真实表名
- 2.3 编写子路由文件:tpdemo/myapp/urls.py
...
# 城市级联操作
url(r'^showdistrict$', views.showdistrict, name='showdistrict'), #加载网页
url(r'^district/([0-9]+)$', views.district, name='district'), #Ajax加载城市信息
...
- 2.4 编写视图文件:tpdemo/myapp/views.py
from django.http import HttpResponse,JsonResponse
from myapp.models import District
...
# 加载城市级联信息操作模板
def showdistrict(request):
return render(request,"myapp/district.html")
# 加载对应的城市信息,并json格式ajax方式响应
def district(request,upid):
dlist = District.objects.filter(upid=upid)
list = []
for ob in dlist:
list.append({'id':ob.id,'name':ob.name})
return JsonResponse({'data':list})
...
- 启动服务测试:url:http://localhost:8000/district/0 加载一级城市信息
- 2.5 开发网页前端的准备:首先启用静态资源目录
- 在项目的根目录下创建一个静态资源目录:static 路径:tpdemo/static
- 并在此目录下创建一个js目录。然后将jquery文件:jquery-1.8.2.min.js放到此目录中 具体位置:tpdemo/static/js/jquery-1.8.2.min.js
- 编辑tpdemo/tpdemo/settings.py配置文件,在最后加入代码:(配置静态资源目录)
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
]
- 2.6 配置访问url:编辑tpdemo/templates/myapp/index.html
...
<h4><a href="{% url 'showdistrict' %}">1. Ajax实战笔记--城市级联操作</a></h4>
...
- 2.7 定义并编写模板文件:tpdemo/templates/myapp/district.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax实战--城市级联操作</title>
<script type="text/javascript" src="/static/js/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
//编写js代码
$(function(){
$.ajax({
type:'get',
url:"{% url 'district' 0 %}",
dataType:'json',
async: false,
success:function(res){
list = res.data;
//遍历响应的城市信息
for(var i=0;i<list.length;i++){
$("#cid").append("<option value='"+list[i].id+"'>"+list[i].name+"</option>");
}
},
});
//获取最后一个下拉框并添加选中事件
$("select").live('change',function(){
//获取选中的id号
var id = $(this).val();
$(this).nextAll().remove();
$.ajax({
url: "/district/"+id,
type: 'get',
data: {},
dataType:'json',
success:function(res){
if(res.data.length<1)
return;
var data = res.data;
var select = $("<select></select>")
for(var i=0;i<data.length;i++){
$('<option value="'+data[i].id+'">'+data[i].name+'</option>').appendTo(select)
//$('select:last').append('<option value="'+data[i].id+'">'+data[i].name+'</option>');
}
$("select:last").after(select);
}
});
});
})
</script>
</head>
<body>
<h2>Ajax实战笔记--城市级联操作</h2>
<select id="cid">
<option>-请选择-</option>
</select>
</body>
</html>
2.7 Django常用Web工具
①.文件上传 (缺)
②. 分页操作
- Django提供了一些类实现管理数据分页,这些类位于django/core/paginator.py中
1. Paginator对象
- Paginator(列表,int):返回分页对象,参数为列表数据,每面数据的条数
属性
- count:对象总数
- num_pages:页面总数
- page_range:页码列表,从1开始,例如[1, 2, 3, 4]
方法
- page(num):下标以1开始,如果提供的页码不存在,抛出InvalidPage异常
异常exception
- InvalidPage:当向page()传入一个无效的页码时抛出
- PageNotAnInteger:当向page()传入一个不是整数的值时抛出
- EmptyPage:当向page()提供一个有效值,但是那个页面上没有任何对象时抛出
2. Page对象
创建对象
- Paginator对象的page()方法返回Page对象,不需要手动构造
属性
- object_list:当前页上所有对象的列表
- number:当前页的序号,从1开始
- paginator:当前page对象相关的Paginator对象
方法
- has_next():如果有下一页返回True
- has_previous():如果有上一页返回True
- has_other_pages():如果有上一页或下一页返回True
- next_page_number():返回下一页的页码,如果下一页不存在,抛出InvalidPage异常
- previous_page_number():返回上一页的页码,如果上一页不存在,抛出InvalidPage异常
- len():返回当前页面对象的个数
- 迭代页面对象:访问当前页面中的每个对象
3. 代码示例
1. 创建项目mydemo
创建视图pagTest
from django.core.paginator import Paginator
def pagTest(request, pIndex):
list1 = AreaInfo.objects.filter(aParent__isnull=True)
p = Paginator(list1, 10)
if pIndex == '':
pIndex = '1'
pIndex = int(pIndex)
list2 = p.page(pIndex)
plist = p.page_range
return render(request, 'booktest/pagTest.html', {'list': list2, 'plist': plist, 'pIndex': pIndex})
配置url
url(r'^pag(?P<pIndex>[0-9]*)/$', views.pagTest, name='pagTest'),
定义模板pagTest.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<ul>
{%for area in list%}
<li>{{area.id}}--{{area.atitle}}</li>
{%endfor%}
</ul>
{%for pindex in plist%}
{%if pIndex == pindex%}
{{pindex}}
{%else%}
<a href="/pag{{pindex}}/">{{pindex}}</a>
{%endif%}
{%endfor%}
</body>
</html>
③. 富文本编辑器
Django集成UEditor (封装成应用) 百度富文本编辑器
1. 使用效果

2. 测试环境
- ubuntu 16.04
- python3.5.2
- django1.11.7
目前测试解决了出现的以下两个问题,都是python版本问题
- error1
# name 'file' is not defined
controller.py 68行
# jsonfile = file(config_path)
jsonfile = open(config_path)
- error2
File "/home/yc/py6/myproject-test/ueditor/controller.py", line 45,
in buildFileName
for key, value in texts.iteritems():
AttributeError: 'dict' object has no attribute 'iteritems'
controller.py 45行
# for key, value in texts.iteritems():
for key, value in texts.items():
3. 配置方法
1,下载解压ueditor文件包,放置在项目中.,作为一个应用目录
myproject:
manage.py myproject static ueditor
myadmin myweb templates
uediter文件夹包括以下:
controller.py __init__.py msyhbd.ttf UE/ urls.py
controller.pyc __init__.pyc __pycache__ ueconfig.json urls.pyc
2,打开settings.py,给INSTALLED_APPS加入应用ueditor
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myadmin',
'myweb',
'ueditor',
]
3,检查一下settings.py是否设置好static静态目录,可参考如下设置
STATIC_URL = '/static/'
#静态目录
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
4,打开django项目的urls.py文件,添加ueditor的url路由配置
myproject/myproject/urls.py:
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^ueditor/', include('ueditor.urls')),
url(r'^admin/',include('myadmin.urls')),
url(r'^',include('myweb.urls')),
]
5,上面步骤配置完成之后,基本可以使用了。
ueditor配置可能需要根据你的项目具体情况修改。
ueditor前端配置文件,在ueditor/UE/ueditor.config.js
ueditor后端配置文件,在ueditor/ueconfig.json 具体配置可参考ueditor官网
此时可以在需要使用富文本编辑器的位置设置一下代码:
html:
<link rel="stylesheet" type="text/css" href="/ueditor/UE/third-party/SyntaxHighlighter/shCoreDefault.css">
<script type="text/javascript" src="/ueditor/UE/third-party/SyntaxHighlighter/shCore.js"></script>
<script type="text/javascript" src="/ueditor/UE/ueditor.config.js"></script>
<script type="text/javascript" src="/ueditor/UE/ueditor.all.min.js"></script>
<script type="text/javascript" src="/ueditor/UE/lang/zh-cn/zh-cn.js"></script>
<div class="am-form-group">
<label for="user-intro" class="am-u-sm-3 am-form-label">商品简介</label>
<div class="am-u-sm-9">
<!-- <textarea name="descr" class="" rows="10" id="user-intro" placeholder="请输入商品简介"></textarea> -->
<!-- <script id="editor" type="text/plain" style="width:100%;height:500px;"></script> -->
<script id="editor" name="content" type="text/plain" style="height:500px;"></script>
</div>
</div>
<script type="text/javascript">
var ue = UE.getEditor('editor');
SyntaxHighlighter.all();
</script>
6,其它问题
当前使用 妹子UI 模板 css影响了当前的编辑器的样式
修改,/static/myadmin/assets/css/app.css 379行
.tpl-content-wrapper {
transition: all 0.4s ease-in-out;
/*position: relative;*/
margin-left: 240px;
z-index: 1101;
min-height: 922px;
border-bottom-left-radius: 3px;
}
7,水印功能
-
上传图片自动加水印 该功能默认没开启。
-
上传图片加水印功能需要安装PIL
pip3 install pillow -
水印相关设置在ueconfig.json末尾:
"openWaterMark": false, //是否开启 "waterMarkText": "我的水印\nhttp://xxxxx.com", //水印内容,建议一行文本 "waterMarkFont": "msyhbd.ttf", //字体,中文需要字体支持才不会出错 "waterMarkSize": 15, //字体大小 "waterMarkBottom": 45, //下边距 "waterMarkRight": 155 //右边距
④. Django部署Apache
在前面的章节中我们使用python3 manage.py runserver来运行服务器。这只适用测试环境中使用。
正式发布的服务,我们需要一个可以稳定而持续的服务器,比如Apache, Nginx, IIS等,本文将以 Apache为例。
使用Apache和mod_wsgi部署Django 是一种久经考验的将Django投入生产的方法。
mod_wsgi是一个Apache模块,可以托管任何Python WSGI应用程序,包括Django。
Django将使用任何支持mod_wsgi的Apache版本。
1. 测试环境
说明
- Ubuntu 16.04
- Python 3.5.2
- Django 1.11.7
- Apache 2.4
2. 配置步骤
1,Apache2安装
Apache2安装
sudo apt-get install apache2
查看版本
apachectl -v
Server version: Apache/2.4.18 (Ubuntu)
Server built: 2017-09-18T15:09:02
2,确保有127.0.0.1 localhost,没有就加上。
sudo vim /etc/hosts
127.0.0.1 localhost
127.0.0.1 www.pyweb.cn
3,打开浏览器 输入 127.0.0.1或localhost
出现 Apache2 Ubuntu Default Page
或It works!
则成功
4,安装apache2解析python的包 wsgi程序包
sudo apt-get install libapache2-mod-wsgi-py3
安装完成后 进入 /usr/lib/apache2/modules 目录
cd /usr/lib/apache2/modules
查看是否存在mod_wsgi.so-3.5
5,配置使apache2加载mod-wsgi包
编辑配置文件
sudo vim /etc/apache2/apache2.conf
在文件的最后 添加
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so-3.5
6,创建网站配置文件
编辑网站配置文件
sudo vim /etc/apache2/sites-available/myproject.conf
配置内容:
<VirtualHost *:80>
ServerName www.pyweb.cn
ServerAdmin py@163.cn
#wsgi文件目录
WSGIDaemonProcess python-path=/var/www/myproject
WSGIScriptAlias / /var/www/myproject/myproject/wsgi.py
<Directory /var/www/myproject/myproject>
<Files wsgi.py>
Require all granted
</Files>
</Directory>
#项目文件目录
DocumentRoot /var/www/myproject
<Directory /var/www/myproject>
Require all granted
</Directory>
#静态文件目录
Alias /static/ /var/www/myproject/static/
<Directory /var/www/myproject/static/>
Require all granted
</Directory>
#错误日志
ErrorLog ${APACHE_LOG_DIR}/django-myproject-error.log
CustomLog ${APACHE_LOG_DIR}/myproject-django.log combined
</VirtualHost>
7,将当前的配置文件创建一个软连接到/etc/apache2/sites-enabled
cd /etc/apache2/sites-enabled
sudo ln -s ../sites-available/myproject.conf ./
8,执行命令 生效当前配置
sudo a2ensite myproject.conf
如果需要让这个配置失效,可以执行 sudo a2dissite myproject.conf
9,配置Django项目目录及修改seeting.py文件,
首先把myproject项目目录拷贝至 /var/www/目录下
在将其ALLOWED_HOSTS=[]改为
ALLOWED_HOSTS=['www.pyweb.cn'],多个域名可以通过逗号隔开。
10,修改Django的wsgi.py文件
import os
os.environ["DJANGO_SETTINGS_MODULE"] = "myproject.settings"
#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pyjfive.settings")
from os.path import join,dirname,abspath
PROJECT_DIR = dirname(dirname(abspath(__file__)))
import sys # 4
sys.path.insert(0,PROJECT_DIR)
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
3. 最后:重启apache2
sudo service apache2 restart
4. 浏览器访问
http://www.pyweb.cn/
5. 浏览器错误 500
Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
Please contact the server administrator at [no address given] to inform them of the time this error occurred,
and the actions you performed just before this error.
More information about this error may be available in the server error log.
Apache/2.4.18 (Ubuntu) Server at www.py6web.com Port 80
- 查看apache2的错误日志
cd /var/log/apache2/
File "/var/www/myproject-test/myproject/wsgi.py", line 17, in <module>, referer: http://www.pyweb.com/
from django.core.wsgi import get_wsgi_application, referer: http://www.pyweb.com/
ImportError: No module named 'django', referer: http://www.pyweb.com/
- 问题分析
进入项目目录,使用命令 pip3 show Djando 查看当前是否已经安装django
---
Metadata-Version: 1.1
Name: Django
Version: 1.11.8
切换至root用户 sudo su
进入python3的shell模式
python3
#加载django模块
import django
#错误:No module named 'django'
- 解决方案
在当前root用户下 安装django
sudo su
pip3 install django==1.11
2.8 Django的其他核心功能
①. 静态文件
项目中的CSS、图片、js都是静态文件
1. 配置静态文件
1.在settings 文件中定义静态内容
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
2.在项目根目录下创建static目录,再创建当前应用名称的目录
mysite/static/myapp/
3.在模板中可以使用硬编码
/static/my_app/myexample.jpg
4.在模板中可以使用static编码
{ % load static from staticfiles % }
<img src="{ % static "my_app/myexample.jpg" % }" alt="My image"/>
②. csrf
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本XSS,但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性
CSRF中间件和模板标签为防止跨站点请求伪造提供了易用的保护。
当恶意网站包含链接,表单按钮或某些旨在在您的网站上执行某些操作的JavaScript时,会使用在浏览器中访问恶意网站的登录用户的凭据进行此类攻击。
还介绍了一种相关攻击类型,即“登录CSRF”,攻击网站欺骗用户的浏览器,以便使用其他人的凭证登录到网站。
1. csrf的使用
在django的模板中,提供了防止跨站攻击的方法,使用步骤如下:
-
step1:在settings.py中启用'django.middleware.csrf.CsrfViewMiddleware'中间件,此项在创建项目时,默认被启用
-
step2:在HTML的表单中添加标签
<form> { % csrf_token % } ... </form>
2. 取消保护
如果某些视图不需要保护,可以使用装饰器csrf_exempt,模板中也不需要写标签,修改csrf2的视图如下 from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def csrf2(request):
uname=request.POST['uname']
return render(request,'booktest/csrf2.html',{'uname':uname})
运行上面的两个请求,发现都可以请求
3. 保护原理
加入标签后,可以查看源代码,发现多了如下代码
<input type='hidden' name='csrfmiddlewaretoken' value='nGjAB3Md9ZSb4NmG1sXDolPmh3bR2g59' />
- 在浏览器的调试工具中,通过network标签可以查看cookie信息
- 本站中自动添加了cookie信息,如下图csrf3
- 查看跨站的信息,并没有cookie信息,即使加入上面的隐藏域代码,发现又可以访问了
- 结论:django的csrf不是完全的安全
- 当提交请求时,中间件'django.middleware.csrf.CsrfViewMiddleware'会对提交的cookie及隐藏域的内容进行验证,如果失败则返回403错误
4. Ajax CSRF 认证
GET 请求不需要 CSRF 认证,POST 请求需要正确认证才能得到正确的返回结果。
如果使用Ajax调用的时候,就要麻烦一些。需要注意以下几点:
-
在视图中使用render (而不要使用 render_to_response)
-
使用 jQuery 的 ajax 或者 post 之前 加入这个 js 代码
jQuery(document).ajaxSend(function(event, xhr, settings) { function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } function sameOrigin(url) { // url could be relative or scheme relative or absolute var host = document.location.host; // host + port var protocol = document.location.protocol; var sr_origin = '//' + host; var origin = protocol + sr_origin; // Allow absolute or scheme relative URLs to same origin return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || // or any other URL that isn't scheme relative or absolute i.e relative. !(/^(\/\/|http:|https:).*/.test(url)); } function safeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } if (!safeMethod(settings.type) && sameOrigin(settings.url)) { xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } }); -
或者 更为优雅简洁的代码(不能写在 .js 中,要直接写在模板文件中):
$.ajaxSetup({ data: {csrfmiddlewaretoken: '{ { csrf_token } }' }, });
这样之后,就可以像原来一样的使用 jQuery.ajax() 和 jQuery.post()了
③. 状态保持
- HTTP协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态
- 客户端与服务器端的一次通信,就是一次会话
- 实现状态保持的方式:在客户端或服务器端存储与会话有关的数据
- 存储方式包括cookie、session,会话一般指session对象
- 使用cookie,所有数据存储在客户端,注意不要存储敏感信息
- 推荐使用sesison方式,所有数据存储在服务器端,在客户端cookie中存储session_id
- 状态保持的目的是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据
- 注意:不同的请求者之间不会共享这个数据,与请求者一一对应
1. 开启session
-
使用django-admin startproject创建的项目默认启用
-
禁用会话:删除下面指定的两个值,禁用会话将节省一些性能消耗
-
Django 中session需要依赖数据库,因此需要确认数据库中是否存在 与session相关的 表
-
在settings.py文件中
* 项INSTALLED_APPS列表中添加: * 'django.contrib.sessions', * 项MIDDLEWARE_CLASSES列表中添加: * 'django.contrib.sessions.middleware.SessionMiddleware',
2. 使用session
-
启用会话后,每个HttpRequest对象将具有一个session属性,它是一个类字典对象
-
get(key, default=None):根据键获取会话的值
-
clear():清除所有会话
-
flush():删除当前的会话数据并删除会话的Cookie
-
del request.session['member_id']:删除会话
-
示例:
#session设置 request.session[key] = value #session获取 request.session.get(key,default=Node) #session删除 # 删除单个key 不存在时报错 del request.session['a'] #清除所有会话,但不会删除数据 request.session.clear() #删除当前的会话数据 request.session.flush()
④. 中间件
- 中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出
- 激活:添加到Django配置文件中的MIDDLEWARE_CLASSES元组中
- 使用中间件,可以干扰整个处理过程,每次请求中都会执行中间件的这个方法
1. 验证用户是否登陆示例
在应用中创建AdminLoginMiddleware.py文件
from django.shortcuts import render
from django.http import HttpResponse
import re
class AdminLoginMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# 检测当前的请求是否已经登录,如果已经登录,.则放行,如果未登录,则跳转到登录页
# 获取当前用户的请求路径 /admin/开头 但不是 /admin/login/ /admin/dologin/ /admin/verifycode
urllist = ['/admin/login','/admin/dologin','/admin/vcode']
# 判断是否进入了后台,并且不是进入登录页面
if re.match('/admin/',request.path) and request.path not in urllist:
# 检测session中是否存在 adminlogin的数据记录
if request.session.get('Vuser','') == '':
# 如果在session没有记录,则证明没有登录,跳转到登录页面
return HttpResponse('<script>alert("请先登录");location.href="/admin/login";</script>')
response = self.get_response(request)
return response
2. 配置中间件
在settings.py文件中修改MIDDLEWARE_CLASSES选项
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',
#自定义的中间件
'myadmin.AdminMiddleware.AdminLoginMiddleware'
]
⑤. 密码管理
1. 使用Django中提供的密码方案
该
django.contrib.auth.hashers模块提供了一组函数来创建和验证散列密码。您可以独立于User模型使用它们。
# from django.contrib.auth.hashers import make_password, check_password
# 对密码进行加密操作
# upass = make_password(request.POST['password'], None, 'pbkdf2_sha256')
# upass = check_password('1234567','pbkdf2_sha256$36000$197nqXM6yxYv$Zxh/Vsns8qszkvUmY81BgrjCeLPXhCHLEilP6VO+Rnc=')
2. Python中的MD5加密
#获取密码并md5
import hashlib
m = hashlib.md5()
m.update(bytes(request.POST['password'],encoding="utf8"))
print(m.hexdigest())
五、Django商城项目开发(上)
1. 项目需求分析
①. 项目开发流程
(1). 软件开发过程的划分
-
本规定对一个完整的开发过程按“软件过程改进方法和规范”把
产品生命周期划分为 6 个阶段:
- 产品概念阶段(记为 PH0)
- 产品定义阶段(记为 PH1)
- 产品开发阶段(记为 PH2)
- 产品测试阶段(记为 PH3)
- 用户验收阶段(记为 PH4)
- 产品维护阶段(记为 PH5)
-
软件项目的过程有三大类:
项目管理过程、项目研发过程和机构支持过程。 -
而这三类过程可以细分为
19个主要过程域,分布在PH0到PH5的各个阶段。 -
项目管理过程包
6个过程域,分为:立项管理、结项管理、项目规划、项目监控、风险管理、需求管理。 -
项目研发过程包
8个过程域,分为:需求开发、技术预研、系统设计、实现与测试、系统测试、Beta测试、客户验收、技术评审。 -
机构支撑过程包
5个过程域,分为:配置管理、质量保证、培训管理、外包与采购管理、服务与维护。 -
建议用户(企业)根据自身情况(如发 展战略、研发实力等)适当地修改使用

(2). 软件开发流程


(3). 软件开发过程域遵循的标准文档

(4). 项目需求分析
- 需求调研的主要收集方式有 以下方面:
- 与用户交谈,向用户提问题。
- 参观用户的工作流程,观察用户的操作。
- 向用户群体发调查问卷。
- 与同行、专家交谈,听取他们的意见。
- 分析已经存在的同类产品,提取需求。
- 从行业标准、规则中提取需求。
- 从 Internet 上搜查相关资料。
- 输出:新产品概念书、调研报告、《需求说明书》、《项目建议书》、计划书和计划表:

(5). 项目系统设计
- 输入部分包括:《项目建议书》、《需求说明书》、软件设计过程中的标准与规范、软、硬件开发环境。
- 输出:《系统设计说明》、用户界面原型、《数据库设计说明》、《功能模块设计说明》、《使用说明书初稿》。

(6). 编码测试
- 软件实现是指通过编程、调试、优化、内部测试和代码审查等活动,开发出符合用户需求、质量合格的产品。
- 软件的优化指的是提高软件的运行速度、提高对内存资源的利用率、加强用户界面的 友好化等方面。

(7). 试运行、实施和验收



(8). 服务与维护

②. 商城项目需求分析
(1). 确立项目:商城项目(B2C)单商家模式
(2). 项目功能介绍
本商城项目分为网站前台和网站后台管理两部分:
① 网站前台
- 网站首页商品展示:推荐商品,分类展示部分商品,热卖商品,新商品等展示
- 商品列表页:分页展示某类别或指定条件(搜索)的部分商品列表信息。
- 商品详情页:通过商品ID号来展示指定商品详情信息。
- 购物车管理:添加、查看、删除、清空等自己的购物信息。
- 会员模块:注册,登录、退出以及进入会员中心
- 会员中心:个人信息管理、我的订单信息
- 订单处理
- 其他扩展:商品评论、商品多图;会员收货地址管理,商品收藏;首页轮播图;站内公告、新闻;友情链接;
② 网站后台管理
- 后台操作:登录、退出
- 会员信息管理:查看、修改会员状态、重置密码
- 商品类别信息管理:添加、删除、修改、查看商品类别信息
- 商品信息管理:添加、删除、修改、查看
- 订单信息管理:查看订单、订单详情、处理订单
- 其他扩展:商品评论管理、商品多图管理;会员收货地址管理,商品收藏;首页轮播图管理;站内公告、新闻管理;友情链接管理
(3). 绘制项目的功能模块 和操作流程图
① 商城项目功能模块图(如下图)

② 商城前台用户操作流程图(如下图)

③ 商城后台管理员操作流程图(如下图)

(4). 具体功能描述
- 针对与商场网站的每个功能块进行详细描述,主要包含以下几个方面:
- 功能名称、编号、设计者、时间
- 功能框图及说明
- 操作权限
- 需要哪些输入
- 具体执行过程内容
- 输出结果
- 业务数据流:DFD图
- 功能效果预览
(5). 项目运行环境要求
- 服务器环境要求:服务器数量,类型和用途;以及每台服务器的配置要求
- 软件环境:Python、MySQL、框架Django的版本要求
- 各种接口标准要求(支付、微信、短信等接口)
(6). 项目具体完成时间和报价
- 项目开发进度计划表,时间周期的安排
- 项目总体报价,以及每个模块的报价、付款方式
- 项目违约处理,后期功能附加条款处理等事项说明
(7). 验收标准
- 项目模块功能的完成情况
- 项目的执行性能(如:网站的响应时间值:正常<=3秒)
2. 项目的数据库设计
- 参考《项目建议书》、《需求说明书》、用户界面原型、以及各种标准和规范对数据库设计如下:
(1). 结构设计:
* 找实体:
- 实体是实体-关系模型的基本对象,是现实世界中各种事物的抽象。
- 凡是可以相互区 开并可以被识 的事、物、概念等对象均可认为是实体。
- 基本的实体列表如下:
- 会员
- 类别
- 商品
- 订单
- 订单详情
- 收货地址
- 商品评论
- 商品图片
- 友情链接
- 站内公告
- ...
* 找属性:
- 每个实体都有一组特征或性质,称为实体的属性。
- 实体的属性值是数据库中存储的主要 数据,一个属性实际上相当于表中的一个列。
- 如会员实体:账号、密码、真实姓名、性别、地址、邮编、电话、邮箱、状态、注册时间
- 商品实体:商品名称、厂家、简介、单价、图片、状态、库存量、购买量、点击次数、添加时间
- ... ...
* 找关系:
- 每个商品属于一个类别,每个类别下可以有多个商品,那么类别与商品的关系为:1:n(1对多关系)
- 每个订单属于一个会员,每个会员下可以有多个订单,那么会员与订单的关系为:1:n(1对多关系)
- 每个订单详情属于一个订单,每个订单下可以有多条购买商品,那么关系为:1:n(1对多关系)
* 绘制E-R图:

(2). 逻辑结构设计
- 在上面实体之间的关系基础上,将实体、属性和实体之间的联系转换为关系模式。
* 确定关系模式:
-
根据转换算法,E-R 图中有 5 个实体类型,可以转换成 4 个关系模式:
1).
会员(id号、账号、密码、真实姓名、性别、收货地址、邮政编码、电话、Email、状态、注册时间)2).
商品类别(类别id号,类别名称,父类别id,类别路径)3).
商品(商品id、类别id、商品名称、生产厂家、详情描述、单价、图片名称、库存量、购买数量、点击次数、状态、添加时间)4).
订单(订单id号、会员id号、联系人、收货地址、邮政编码、联系电话、购买时间、总金额、状态)5).
订单详情(id号、订单id号、商品id号,商品名称、单价、购买量)
* 消除冗余:
- 冗余数据和冗余联系容易破坏数据库的完整性,给数据库的维护增加困难,应当予以消除。
- 本系统的冗余数据和冗余关系已经在概念结构设计中处理过了,这里不再进行过多的叙述。
(3). 物理结果设计
- 数据库设计的最后阶段是确定数据库在物理设备上的存储结构和存取方法,即物理数据模型。
- 物理数据模型的设计其实也是在设计表结构。
- 一般地,实体对应于表,实体的属性对应于表的列(字段),实体之间的关系成为表的约束。
* 设计数据表结构:

* 通过数据表结构来创建数据表:
-- 会员信息表(后台管理员信息也在此标准,通过状态区分)
CREATE TABLE `users`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`name` varchar(16) DEFAULT NULL,
`password` char(32) NOT NULL,
`sex` tinyint(1) unsigned NOT NULL DEFAULT '1',
`address` varchar(255) DEFAULT NULL,
`code` char(6) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`state` tinyint(1) unsigned NOT NULL DEFAULT '1',
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 商品类别表
CREATE TABLE `type`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`pid` int(11) unsigned DEFAULT '0',
`path` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 商品信息表
CREATE TABLE `goods`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`typeid` int(11) unsigned NOT NULL,
`goods` varchar(32) NOT NULL,
`company` varchar(50) DEFAULT NULL,
`content` text,
`price` double(6,2) unsigned NOT NULL,
`picname` varchar(255) DEFAULT NULL,
`store` int(11) unsigned NOT NULL DEFAULT '0',
`num` int(11) unsigned NOT NULL DEFAULT '0',
`clicknum` int(11) unsigned NOT NULL DEFAULT '0',
`state` tinyint(1) unsigned NOT NULL DEFAULT '1',
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `typeid` (`typeid`)
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 订单信息表
CREATE TABLE `orders`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) unsigned DEFAULT NULL,
`linkman` varchar(32) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
`code` char(6) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`addtime` datetime DEFAULT NULL,
`total` double(8,2) unsigned DEFAULT NULL,
`state` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 订单信息详情表
CREATE TABLE `detail`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`orderid` int(11) unsigned DEFAULT NULL,
`goodsid` int(11) unsigned DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
`price` double(6,2) DEFAULT NULL,
`num` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 在user上表中添加一条后台管理员账户数据
insert into users values(null,'admin','管理员',md5('admin'),1,'北京市朝阳区大山子007号','100086','13566686868','122794105@qq.com',0,now())
(4). 安全保密设计
- 本数据库系统采用安全的用户名加口令方式登录。用户名的权限限制为只能进行基本的 增删改查数据功能。
- 数据库应用对数据一般都具有一定的限制,这种限制称为完整性。
- 关系数据库系统应该保证输入的值符合其规定的数据类型,并保证值在系统支持的范围内。
- 关系数据库系统都支持 3 种完整性:
域约束、实体完整性约束和关联完整性约束。
3. 项目架构的程序设计
(1). 项目使用技术
- 基于Python语言,版本:>=3.5及以上。
- 使用Django框架,版本:1.11.11的LTS版本。
- MySQL数据库
- 连接数据库:pymysql=0.8.0
- 图像处理: Pillow=5.0.0
- Web前端技术:HTML、CSS、JavaScript和Jquery等
(2). 项目的目录结构
-
本次项目共计四个应用:
myadmin、
web、
common和
ueditor/myobject/ ├── manage.py ├── myobject │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── common 公共应用 │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── shopmiddleware.py 自定义的中间件 │ ├── migrations │ ├── models.py 网站前后台的共用Model类 │ ├── tests.py │ └── views.py ├── myadmin 网站后台应用 │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ ├── views │ │ ├── index.py 后台主视图 │ │ ├── users.py 会员管理视图 │ │ ├── type.py 商品类别管理视图 │ │ ├── goods.py 商品管理视图 │ │ └── orders.py 订单管理视图 │ ├── models.py │ ├── tests.py │ └── urls.py │ ├── web 网站前台应用 │ ├── admin.py │ ├── apps.py │ ├── __init__.py │ ├── migrations │ ├── views │ │ ├── index.py web前台主视图 │ │ ├── users.py 会员操作视图 │ │ ├── cart.py 购物车管理视图 │ │ └── orders.py 订单处理视图 │ ├── models.py │ ├── tests.py │ └── urls.py │ ├── ueditor 百度编辑器(富文本编辑器) │ ├── __init__.py │ ├── UE │ ├── controller.py │ ├── msyhdb.ttf │ ├── ueconfig.json │ └── urls.py │ ├── templates 模板目录 │ ├── myadmin 后台模板总目录 │ │ ├── users/ 后台会员管理 │ │ │ ├── index.html │ │ │ ├── add.html │ │ │ ├── edit.html │ │ │ ├── repass.html │ │ ├── type/ 后台类别管理模板 │ │ │ ├── index.html │ │ │ ├── add.html │ │ │ ├── edit.html │ │ ├── goods/ 商品信息管理模板 │ │ │ ├── index.html │ │ │ ├── add.html │ │ │ ├── edit.html │ │ ├── orders/ 订单信息管理模板 │ │ │ ├── index.html │ │ │ ├── edit.html │ │ ├── index.html │ │ ├── login.html │ │ ├── base.html │ │ ├── info.html │ │ │ ├── web 前台模板目录 │ │ ├── base.html │ │ ├── index.html │ │ ├── list.html │ │ ├── detail.html │ │ ├── login.html │ │ ├── signup.html │ │ └── ...... │ ├── static 静态资源目录 │ ├── myadmin 后台静态资源 │ │ ├──.... │ │ ├──.... │ │ │ │ │ ├── web 网站前台静态资源 │ │ ├──.... │ │ ├──....
(3). 项目模块结构
* 网站后台应用的模块操作说明
- 网站后台模板采用github上提供的一个简洁界面,网址:https://github.com/alecfan/mstp_17_akira
| 模块 | 操作 | 权限 |
|---|---|---|
| 登录&退出管理 | 获取登录界面、处理登录、退出、验证码 | 无 |
| 后台首页页 | 后台首页 | 网站编辑权限 |
| 后台会员信息管理 | 浏览(搜索&分页)、详情、更改状态、重置密码 | 网站编辑权限 |
| 商品类别管理 | 浏览、获取添加界面、执行添加、获取编辑界面、执行修改、删除 | 网站编辑权限 |
| 商品信息管理 | 浏览(搜索&分页)、获取添加界面、执行添加、获取编辑界面、执行修改、删除 | 网站编辑权限 |
| 订单信息管理 | 浏览(搜索&分页)、查看订单详情、处理订单、删除 | 网站编辑权限 |
* 网站前台应用的模块操作说明
| 模块 | 操作 | 权限 |
|---|---|---|
| 商品展示 | 商品首页展示、列表页(搜索分页)、商品详情页 | 无 |
| 购物车管理 | 添加商品、查看购物车,修改、删除、清空 | 无 |
| 前台会员管理 | 注册界面、执行注册,登录界面,执行登录,验证码、退出 | 无 |
| 订单处理 | 订单处理界面,确认订单界面、执行订单处理 | 会员权限 |
| 会员中心管理 | 个人信息界面、执行个人信息修改,查看订单,订单详情、处理订单 | 会员权限 |
(4). 程序结构
-
建议统一URL访问格式:
http://主机名:端口/应用名/视图名/函数名 其中:index省略不写,web前台应用名省略不写。 -
视图中的函数命名格式:
- index() ---- 浏览信息
- add() ---- 加载添加界面
- insert() ---- 执行添加
- delete() ---- 执行删除(路由中使用del)
- edit() ---- 加载编辑界面
- update() ---- 执行信息编辑
(5). 项目中的编码规范
-
遵循良好的编码风格,可以有效的提高代码的可读性,降低出错几率和维护难度。
-
在团队开发中,使用(尽量)统一的编码风格,还可以降低沟通成本。
-
网上有很多版本的编码规范介绍,基本上都是遵循 PEP8 的规范:
-
如下参考格式:
缩进
* 不要使用 tab 缩进
* 使用任何编辑器写 Python,请把一个 tab 展开为 4 个空格
* 绝对不要混用 tab 和空格,否则容易出现 IndentationError
空格
* 在 list, dict, tuple, set, 参数列表的 , 后面加一个空格
* 在 dict 的 : 后面加一个空格
* 在注释符号 # 后面加一个空格,但是 #!/usr/bin/python 的 # 后不能有空格
* 操作符两端加一个空格,如 +, -, *, /, |, &, =
* 接上一条,在参数列表里的 = 两端不需要空格
* 括号((), {}, [])内的两端不需要空格
空行
* function 和 class 顶上两个空行
* class 的 method 之间一个空行
* 函数内逻辑无关的段落之间空一行,不要过度使用空行
* 不要把多个语句写在一行,然后用 ; 隔开
* if/for/while 语句中,即使执行语句只有一句,也要另起一行
换行
* 每一行代码控制在 80 字符以内
* 使用 \ 或 () 控制换行.
命名
* 使用有意义的,英文单词或词组,绝对不要使用汉语拼音
* package/module 名中不要出现 -
import
* 所有 import 尽量放在文件开头,在 docstring 下面,其他变量定义的上面
* 不要使用 from foo imort *
* import 需要分组,每组之间一个空行,每个分组内的顺序尽量采用字典序,分组顺序是:
* 标准库
* 第三方库
* 本项目的 package 和 module
注释
* 文档字符串 docstring, 是 package, module, class, method, function 级别的注释,可以通过 * __doc__ 成员访问到,注释内容在一对 """ 符号之间
* function, method 的文档字符串应当描述其功能、输入参数、返回值,如果有复杂的算法和实现,也需要写清楚
不要写错误的注释,不要无谓的注释
异常
* 不要轻易使用 try/except
* except 后面需要指定捕捉的异常,裸露的 except 会捕捉所有异常,意味着会隐藏潜在的问题
* 可以有多个 except 语句,捕捉多种异常,分别做异常处理
* 使用 finally 子句来处理一些收尾操作
* try/except 里的内容不要太多,只在可能抛出异常的地方使用,
4. 基于Django框架的项目搭建
(1). 创建数据库 shopdb
- 进入MySQL数据库中,创建一个数据库名为:shopdb
- 将上节《项目的数据库设计》中准备好的shopdb.sql脚本导入到
shopdb数据库中
(2). 创建项目 myobject 框架和应用 myamdin、web和common。
# 创建项目框架 `myobject`
$ django-admin startproject myobject
$ cd myobject
# 在项目中创建一个myadmin应用(项目的后台管理)
$ python manage.py startapp myadmin
# 在项目中再创建一个web应用(项目前台)
$ python manage.py startapp web
# 在项目中再创建一个common应用(项目的前台和后台的公告应用)
$ python manage.py startapp common
# 创建模板目录
$ mkdir templates
$ mkdir templates/myadmin
$ mkdir templates/web
# 创建静态资源目录
$ mkdir static
$ mkdir static/myadmin
$ mkdir static/web
# 创建前后台应用模板目录,并在里面各创建一个`__init__.py`和`index.py`的空文件
$ mkdir myadmin/views
$ touch myadmin/views/__init__.py
$ touch myadmin/views/index.py
$ mkdir web/views
$ touch web/views/__init__.py
$ touch web/views/index.py
# 删除前后台应用的默认模板文件
# rm -rf myadmin/views.py
# rm -rf web/views.py
# 拷贝路由文件到应用目录中
$ cp myobject/urls.py myadmin/urls.py
$ cp myobject/urls.py web/urls.py
# 退出项目目录
$ cd ..
#查看项目目录结构
$ tree myobject
myobject/
├── manage.py
├── myobject
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── myadmin
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── views
│ │ ├── __init__.py
│ │ └── index.py
│ ├── models.py
│ ├── tests.py
│ └── urls.py
├── web
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ ├── views
│ │ ├── __init__.py
│ │ └── index.py
│ ├── models.py
│ ├── tests.py
│ └── urls.py
├── common
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── static
│ ├── myadmin/
│ └── web/
└── templates
├── myadmin/
└── web/
(3). 项目框架配置
- 3.1 编辑
myobject/myobject/__init__.py文件,添加Pymysql的数据库操作支持
import pymysql
pymysql.install_as_MySQLdb()
注意:此配置需要安装pymysql软件包, 如:$ pip install pymysql
- 3.2 编辑myobject/myobject/settings.py文件:
# myobject/myobject/settings.py 项目配置文件
# 1. 配置允许访问的主机名信息
ALLOWED_HOSTS = ['*']
或
ALLOWED_HOSTS = ['localhost','127.0.0.1','192.168.2.240']
...
# 2. 将myadmin和web的应用添加到项目框架结构中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myadmin',
'web',
'common',
]
...
# 3. 配置模板目录 os.path.join(BASE_DIR,'templates')
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
# 4. 配置项目的数据库连接信息:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'shopdb',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
}
}
...
# 5. 设置时区和语言
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
...
# 6. 配置网站的静态资源目录
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
(4). 项目urls路由信息配置
- 4.1 打开根路由文件:myobject/myobject/urls.py路由文件,编写路由配置信息
# myobject/myobject/urls.py
from django.conf.urls import url,include
#from django.contrib import admin
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^myadmin/', include('myadmin.urls')), #网站后台路由
url(r'^', include('web.urls')), #网站前台路由
]
- 4.2 打开项目后台管理路由文件:myobject/myadmin/urls.py路由文件,编写路由配置信息
# myobject/myadmin/urls.py
from django.conf.urls import url
from myadmin.views import index
urlpatterns = [
# 后台首页
url(r'^$', index.index, name="myadmin_index"),
]
- 4.3 打开项目前台路由文件:myobject/web/urls.py路由文件,编写路由配置信息
# myobject/web/urls.py
from django.conf.urls import url
from web.views import index
urlpatterns = [
# url(r'^$', index.index, name="index"),
]
(5). 编写后台视图测试
- 5.1 编辑后台视图文件
# myobject/myadmin/views/index.py
from django.shortcuts import render
from django.http import HttpResponse
#后台首页
def index(request):
return HttpResponse('欢迎进入商城网站后台!')
# myobject/web/views/index.py
from django.shortcuts import render
from django.http import HttpResponse
#前台首页
def index(request):
return HttpResponse('欢迎进入商城网站前台首页!')
- 5.2 运行测试
- 在项目根目录下启动服务,并使用浏览器访问测试:http://localhost:8000/myadmin
[root@localhost myobject]# pwd
/python/myobject
[root@localhost myobject]# ls
manage.py myadmin myobject web static templates
[root@localhost myobject]# python3 manage.py runserver 0:8000
Performing system checks...
System check identified no issues (0 silenced).
April 06, 2018 - 14:29:36
Django version 1.11, using settings 'myobject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^C[root@localhost myobject]#
(6). 摆放后台首页面:

-
6.1. 使用事先准备好的后台模板:
-
从github上下载一个后台简洁模板:https://github.com/alecfan/mstp_17_akira
-
将后台模板目录中的资源目录:
css、js、img复制到项目的后台静态资源目录static/myadmin/中 -
在
templates/myadmin/目录中创建一个基类父模板文件base.html -
在
templates/myadmin/目录中创建一个首页模板文件index.html -
在
templates/myadmin/目录中创建一个信息提示模板文件info.html -
修改
myobject/myadmin/views/index.py视图文件中index函数中代码:
def index(request): '''管理后台首页''' return render(request,"myadmin/index.html")
-
-
6.2. 编辑父类模板:/templates/myadmin/base.html
{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<title>网站后台管理</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{% static 'myadmin/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/bootstrap-responsive.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/site.css' %}" rel="stylesheet">
<!--[if lt IE 9]><script src="{% static 'myadmin/js/html5.js' %}"></script><![endif]-->
</head>
<body>
<div class="container">
<!-- 页头开始 -->
<div class="navbar">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <a class="brand" href="#">网站后台管理</a>
<div class="nav-collapse">
<ul class="nav">
<li class="active">
<a href="index.html">首页</a>
</li>
<li>
<a href="settings.htm">在线设置</a>
</li>
<li>
<a href="help.htm">帮助</a>
</li>
<li class="dropdown">
<a href="help.htm" class="dropdown-toggle" data-toggle="dropdown">更多 <b class="caret"></b></a>
<ul class="dropdown-menu">
<li>
<a href="help.htm">Introduction Tour</a>
</li>
<li>
<a href="help.htm">Project Organisation</a>
</li>
<li>
<a href="help.htm">Task Assignment</a>
</li>
<li>
<a href="help.htm">Access Permissions</a>
</li>
<li class="divider">
</li>
<li class="nav-header">
Files
</li>
<li>
<a href="help.htm">How to upload multiple files</a>
</li>
<li>
<a href="help.htm">Using file version</a>
</li>
</ul>
</li>
</ul>
<form class="navbar-search pull-left" action="">
<input type="text" class="search-query span2" placeholder="Search" />
</form>
<ul class="nav pull-right">
<li>
<a href="profile.htm">@管理员</a>
</li>
<li>
<a href="login.htm">退出</a>
</li>
</ul>
</div>
</div>
</div>
</div><!-- 页头结束-->
<div class="row">
<div class="span3">
<!-- 侧边导航开始 -->
<div class="well" style="padding: 8px 0;">
<ul class="nav nav-list">
<li class="nav-header">
导航栏
</li>
<li class="active">
<a href="index.htm"><i class="icon-white icon-home"></i> 首页</a>
</li>
<li class="nav-header">
会员管理
</li>
<li>
<a href="#"><i class="icon-folder-open"></i> 浏览会员</a>
</li>
<li>
<a href="#"><i class="icon-check"></i> 添加会员</a>
</li>
<li class="nav-header">
商品类别管理
</li>
<li>
<a href="messages.htm"><i class="icon-envelope"></i> 浏览商品类别</a>
</li>
<li>
<a href="files.htm"><i class="icon-file"></i> 添加商品类别</a>
</li>
<li class="nav-header">
商品信息管理
</li>
<li>
<a href="activity.htm"><i class="icon-list-alt"></i> 浏览商品信息</a>
</li>
<li>
<a href="activity.htm"><i class="icon-list-alt"></i> 添加商品信息</a>
</li>
<li class="divider">
</li>
<li>
<a href="help.htm"><i class="icon-info-sign"></i> Help</a>
</li>
<li class="nav-header">
Bonus Templates
</li>
<li>
<a href="gallery.htm"><i class="icon-picture"></i> Gallery</a>
</li>
<li>
<a href="blank.htm"><i class="icon-stop"></i> Blank Slate</a>
</li>
</ul>
</div><!-- 侧边导航结束 -->
</div>
<div class="span9">
<!-- 主体开始 -->
{% block mainbody %}
{% endblock %}
<!-- 主体结束 -->
</div>
</div>
</div>
<script src="{% static 'myadmin/js/jquery.min.js' %}"></script>
<script src="{% static 'myadmin/js/bootstrap.min.js' %}"></script>
<script src="{% static 'myadmin/js/site.js' %}"></script>
</body>
</html>
- 6.3. 编辑后台首页模板:/templates/myadmin/index.html
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h2>
商城网站后台管理首页
</h2>
<div class="hero-unit">
<h3>
Welcome!
</h3>
<p>
To get the most out of Akira start with our 3 minute tour.
</p>
<p>
<a href="help.htm" class="btn btn-primary btn-large">Start Tour</a> <a class="btn btn-large">No Thanks</a>
</p>
</div>
<div class="well summary">
<ul>
<li>
<a href="#"><span class="count">3</span> Projects</a>
</li>
<li>
<a href="#"><span class="count">27</span> Tasks</a>
</li>
<li>
<a href="#"><span class="count">7</span> Messages</a>
</li>
<li class="last">
<a href="#"><span class="count">5</span> Files</a>
</li>
</ul>
</div>
{% endblock %}
- 6.4. 后台公共提示信息模板:/templates/myadmin/info.html
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h2>
操作信息提示
</h2>
<h4>
{{ info }}
</h4>
{% endblock %}
5. 项目实战后台之会员管理
- 本页面完成项目后台管理的用户模块操作
(1). 用户信息数据表:users
- 在数据库
shopdb中创建users表,若此表已存在请跳过
CREATE TABLE `users`(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`name` varchar(16) DEFAULT NULL,
`password` char(32) NOT NULL,
`sex` tinyint(1) unsigned NOT NULL DEFAULT '1',
`address` varchar(255) DEFAULT NULL,
`code` char(6) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`state` tinyint(1) unsigned NOT NULL DEFAULT '1',
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
)ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
(2). 定义模型Model类
- 进入
common应用目录中编辑:myobject/common/models.py模型文件
from django.db import models
from datetime import datetime
#用户信息模型
class Users(models.Model):
username = models.CharField(max_length=32)
name = models.CharField(max_length=16)
password = models.CharField(max_length=32)
sex = models.IntegerField(default=1)
address = models.CharField(max_length=255)
code = models.CharField(max_length=6)
phone = models.CharField(max_length=16)
email = models.CharField(max_length=50)
state = models.IntegerField(default=1)
addtime = models.DateTimeField(default=datetime.now)
def toDict(self):
return {'id':self.id,'username':self.username,'name':self.name,'password':self.password,'address':self.address,'phone':self.phone,'email':self.email,'state':self.state,'addtime':self.addtime}
class Meta:
db_table = "users" # 更改表名
(3). 项目urls路由信息配置
- 打开根路由文件:myobject/myadmin/urls.py路由文件,编辑路由配置信息
from django.conf.urls import url
from myadmin.views import index,users
urlpatterns = [
# 后台首页
url(r'^$', index.index, name="myadmin_index"),
# 后台用户管理
url(r'^users$', users.index, name="myadmin_users_index"),
url(r'^users/add$', users.add, name="myadmin_users_add"),
url(r'^users/insert$', users.insert, name="myadmin_users_insert"),
url(r'^users/del/(?P<uid>[0-9]+)$', users.delete, name="myadmin_users_del"),
url(r'^users/edit/(?P<uid>[0-9]+)$', users.edit, name="myadmin_users_edit"),
url(r'^users/update/(?P<uid>[0-9]+)$', users.update, name="myadmin_users_update"),
]
(4). 编辑视图文件
- 创建:myobject/myadmin/views/users.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from common.models import Users
from datetime import datetime
# 浏览会员
def index(request):
# 执行数据查询,并放置到模板中
list = Users.objects.all()
context = {"userslist":list}
#return HttpResponse(list)
return render(request,'myadmin/users/index.html',context)
# 会员信息添加表单
def add(request):
return render(request,'myadmin/users/add.html')
#执行会员信息添加
def insert(request):
try:
ob = Users()
ob.username = request.POST['username']
ob.name = request.POST['name']
#获取密码并md5
import hashlib
m = hashlib.md5()
m.update(bytes(request.POST['password'],encoding="utf8"))
ob.password = m.hexdigest()
ob.sex = request.POST['sex']
ob.address = request.POST['address']
ob.code = request.POST['code']
ob.phone = request.POST['phone']
ob.email = request.POST['email']
ob.state = 1
ob.addtime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
ob.save()
context = {'info':'添加成功!'}
except Exception as err:
print(err)
context = {'info':'添加失败!'}
return render(request,"myadmin/info.html",context)
# 执行会员信息删除
def delete(request,uid):
try:
ob = Users.objects.get(id=uid)
ob.delete()
context = {'info':'删除成功!'}
except:
context = {'info':'删除失败!'}
return render(request,"myadmin/info.html",context)
# 打开会员信息编辑表单
def edit(request,uid):
try:
ob = Users.objects.get(id=uid)
context = {'user':ob}
return render(request,"myadmin/users/edit.html",context)
except Exception as err:
print(err)
context = {'info':'没有找到要修改的信息!'}
return render(request,"myadmin/info.html",context)
# 执行会员信息编辑
def update(request,uid):
try:
ob = Users.objects.get(id=uid)
ob.name = request.POST['name']
ob.sex = request.POST['sex']
ob.address = request.POST['address']
ob.code = request.POST['code']
ob.phone = request.POST['phone']
ob.email = request.POST['email']
ob.state = request.POST['state']
ob.save()
context = {'info':'修改成功!'}
except Exception as err:
print(err)
context = {'info':'修改失败!'}
return render(request,"myadmin/info.html",context)
(5). 编写模板文件

- 5.1. 后台用户信息浏览模板:/templates/myadmin/users/index.html
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h4>
会员信息管理
</h4>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>账号</th>
<th>真实姓名</th>
<th>性别</th>
<th>邮箱</th>
<th>注册时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for vo in userslist %}
<tr>
<td>{{ vo.username }}</td>
<td>{{ vo.name }}</td>
<td>{% if vo.sex == 1 %}男{% else %}女{% endif %}</td>
<td>{{ vo.email }}</td>
<td>{{ vo.addtime|date:'Y-m-d H:i:s' }}</td>
<td>{{ vo.state }}</td>
<td>
<a href="{% url 'myadmin_users_del' vo.id %}" class="view-link">删除</a>
<a href="{% url 'myadmin_users_edit' vo.id %}" class="view-link">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<ul>
<li class="disabled">
<a href="#">«</a>
</li>
<li class="active">
<a href="#">1</a>
</li>
<li>
<a href="#">2</a>
</li>
<li>
<a href="#">3</a>
</li>
<li>
<a href="#">4</a>
</li>
<li>
<a href="#">»</a>
</li>
</ul>
</div>
{% endblock %}
- 5.3. 后台用户信息添加模板:/templates/myadmin/users/add.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
会员信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_users_insert' %}" class="form-horizontal" method="post">
{% csrf_token %}
<fieldset>
<legend>添加会员信息</legend>
<div class="control-group">
<label class="control-label" for="input01">账号:</label>
<div class="controls">
<input type="text" name="username" class="input-xlarge" id="input01" value="" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">密码:</label>
<div class="controls">
<input type="password" name="password" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">重复密码:</label>
<div class="controls">
<input type="password" name="repassword" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">真实姓名:</label>
<div class="controls">
<input type="text" name="name" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">性别:</label>
<div class="controls">
<input type="radio" name="sex" class="input-xlarge" id="input01" value="1" /> 男
<input type="radio" name="sex" class="input-xlarge" id="input01" value="0" /> 女
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">地址:</label>
<div class="controls">
<input type="text" name="address" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">邮编:</label>
<div class="controls">
<input type="text" name="code" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">电话:</label>
<div class="controls">
<input type="text" name="phone" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">email邮箱:</label>
<div class="controls">
<input type="text" name="email" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">添加</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
{% endblock %}
- 5.4. 后台用户信息编辑模板:/templates/myadmin/users/edit.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
会员信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_users_update' user.id %}" class="form-horizontal" method="post">
{% csrf_token %}
<fieldset>
<legend>编辑会员信息</legend>
<div class="control-group">
<label class="control-label" for="input01">账号:</label>
<div class="controls">
{{ user.username }}
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">真实姓名:</label>
<div class="controls">
<input type="text" name="name" class="input-xlarge" id="input01" value="{{ user.name }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">性别:</label>
<div class="controls">
<input type="radio" name="sex" class="input-xlarge" id="input01" value="1"
{% if user.sex == 1 %}checked{% endif %} /> 男
<input type="radio" name="sex" class="input-xlarge" id="input01" value="0"
{% if user.sex == 0 %}checked{% endif %} /> 女
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">地址:</label>
<div class="controls">
<input type="text" name="address" class="input-xlarge" id="input01" value="{{ user.address }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">邮编:</label>
<div class="controls">
<input type="text" name="code" class="input-xlarge" id="input01" value="{{ user.code }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">电话:</label>
<div class="controls">
<input type="text" name="phone" class="input-xlarge" id="input01" value="{{ user.phone }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">email邮箱:</label>
<div class="controls">
<input type="text" name="email" class="input-xlarge" id="input01" value="{{ user.email }}" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">状态:</label>
<div class="controls">
<input type="radio" name="state" class="input-xlarge" id="input01" value="0"
{% if user.state == 0 %}checked{% endif %} /> 管理员
<input type="radio" name="state" class="input-xlarge" id="input01" value="1"
{% if user.state == 1 %}checked{% endif %} /> 启用会员
<input type="radio" name="state" class="input-xlarge" id="input01" value="2"
{% if user.state == 2 %}checked{% endif %} /> 禁用用户
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
{% endblock %}
(6). 运行测试
- 在项目根目录下启动服务,并使用浏览器访问测试:http://localhost:8000/myadmin
[root@localhost myobject]# pwd
/python/myobject
[root@localhost myobject]# ls
manage.py myadmin myobject myweb static templates
[root@localhost myobject]# python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
April 06, 2018 - 14:29:36
Django version 1.11, using settings 'myobject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^C[root@localhost myobject]#
6. 项目实战后台之管理员登陆与退出
注意:本节实战会使用到Django框架中的session,而session信息又存放的数据库中,所以要先使用数据迁移命令在MySQL数据库中先生成一些Django默认自带表。
python manage.py migrate
①. 添加中间件
- 此中间件对后台网址访问做了是否登录的判断
- 关于网站后台要求:只要访问的URL地址是以 "/admin" 开头的都会执行是否登录判断验证。
(1). 在common公共应用中创建中间件
- 创建文件:
myobject/common/shopmiddleware.py, 代码如下:
# 自定义中间件类
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
import re
class ShopMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization(一次性配置和初始化).
#print("ShopMiddleware")
def __call__(self, request):
# 定义网站后台不用登录也可访问的路由url
urllist = ['/myadmin/login','/myadmin/dologin','/myadmin/logout']
# 获取当前请求路径
path = request.path
#print("Hello World!"+path)
# 判断当前请求是否是访问网站后台,并且path不在urllist中
if re.match("/myadmin",path) and (path not in urllist):
# 判断当前用户是否没有登录
if "adminuser" not in request.session:
# 执行登录界面跳转
return redirect(reverse('myadmin_login'))
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
(2). 将自定义的中间件注册到项目中
- 编辑
myobject/settings.py配置文件, 添加如下代码
...
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',
'common.shopmiddleware.ShopMiddleware', #注册中间件
]
...
(3). 配置路由、模板并测试中间件
- 3.1 配置路由
myobject/myadmin/urls.py加入如下代码
....
# 后台管理员路由
url(r'^login$', index.login, name="myadmin_login"),
url(r'^dologin$', index.dologin, name="myadmin_dologin"),
url(r'^logout$', index.logout, name="myadmin_logout"),
....
- 3.2 编写视图
myobject/myadmin/views/index.py文件 并加入如下代码:
...
# ==============后台管理员操作====================
# 会员登录表单
def login(request):
return render(request,'myadmin/login.html')
# 会员执行登录
def dologin(request):
pass
# 会员退出
def logout(request):
pass
...
- 3.3 创建登录模板文件:
templates/myadmin/login.html代码如下:
{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<title>Login - Akira</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{% static 'myadmin/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/bootstrap-responsive.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/site.css' %}" rel="stylesheet">
<!--[if lt IE 9]><script src="{% static 'myadmin/js/html5.js' %}"></script><![endif]-->
</head>
<body>
<div id="login-page" class="container">
<h1>商城后台管理登录</h1>
<form id="login-form" method="post" class="well" action="{% url 'myadmin_dologin' %}">
{% csrf_token %}
账号:<input type="text" name="username" class="span2" placeholder="输入账号" /><br />
密码:<input type="password" name="password" class="span2" placeholder="输入密码" /><br />
<label class="checkbox"> <input type="checkbox" /> Remember me </label>
<button type="submit" class="btn btn-primary">登录</button>
<button type="reset" class="btn">重置</button>
</form>
<br/>
<span style="color:red">{{ info }}</span>
</div>
<script src="{% static 'myadmin/js/jquery.min.js' %}"></script>
<script src="{% static 'myadmin/js/bootstrap.min.js' %}"></script>
<script src="{% static 'myadmin/js/site.js' %}"></script>
</body>
</html>
(4). 启动服务测试,网站后台就进不去了,统一调跳转登录页面中
②. 登录与退出
(1). 配置路由(已配置过可省略)
- 配置路由
myobject/myadmin/urls.py加入如下代码
....
# 后台管理员路由
url(r'^login$', index.login, name="myadmin_login"),
url(r'^dologin$', index.dologin, name="myadmin_dologin"),
url(r'^logout$', index.logout, name="myadmin_logout"),
....
(2). 编写视图文件
- 编写视图
myobject/myadmin/views/index.py文件 并加入如下代码:
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Users
import time,json
...
# ==============后台管理员操作====================
# 会员登录表单
def login(request):
return render(request,'myadmin/login.html')
# 会员执行登录
def dologin(request):
try:
#根据账号获取登录者信息
user = Users.objects.get(username=request.POST['username'])
#判断当前用户是否是后台管理员用户
if user.state == 0:
# 验证密码
import hashlib
m = hashlib.md5()
m.update(bytes(request.POST['password'],encoding="utf8"))
if user.password == m.hexdigest():
# 此处登录成功,将当前登录信息放入到session中,并跳转页面
request.session['adminuser'] = user.name
#print(json.dumps(user))
return redirect(reverse('myadmin_index'))
else:
context = {'info':'登录密码错误!'}
else:
context = {'info':'此用户非后台管理用户!'}
except:
context = {'info':'登录账号错误!'}
return render(request,"myadmin/login.html",context)
# 会员退出
def logout(request):
# 清除登录的session信息
del request.session['adminuser']
# 跳转登录页面(url地址改变)
return redirect(reverse('myadmin_login'))
# 加载登录页面(url地址不变)
#return render(request,"myadmin/login.html")
...
(3). 创建模板

- 创建登录模板文件:
templates/myadmin/login.html代码如下:
{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<title>Login - Akira</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{% static 'myadmin/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/bootstrap-responsive.min.css' %}" rel="stylesheet">
<link href="{% static 'myadmin/css/site.css' %}" rel="stylesheet">
<!--[if lt IE 9]><script src="{% static 'myadmin/js/html5.js' %}"></script><![endif]-->
</head>
<body>
<div id="login-page" class="container">
<h1>商城后台管理登录</h1>
<form id="login-form" method="post" class="well" action="{% url 'myadmin_dologin' %}">
{% csrf_token %}
账号:<input type="text" name="username" class="span2" placeholder="输入账号" /><br />
密码:<input type="password" name="password" class="span2" placeholder="输入密码" /><br />
<label class="checkbox"> <input type="checkbox" /> Remember me </label>
<button type="submit" class="btn btn-primary">登录</button>
<button type="reset" class="btn">重置</button>
</form>
<br/>
<span style="color:red">{{ info }}</span>
</div>
<script src="{% static 'myadmin/js/jquery.min.js' %}"></script>
<script src="{% static 'myadmin/js/bootstrap.min.js' %}"></script>
<script src="{% static 'myadmin/js/site.js' %}"></script>
</body>
</html>
- 修改模板文件:
templates/myadmin/base.html代码如下: - 代码在页头处,62~70行左右
...
<ul class="nav pull-right">
<li>
<a href="profile.htm">@{{ request.session.adminuser }}</a>
</li>
<li>
<a href="{% url 'myadmin_logout' %}">退出</a>
</li>
</ul>
...
(4). 启动服务测试,即可测试网站后台是否可以使用。注意只有管理员才开登录。
③. 添加验证码
(1). 配置路由
- 配置路由
myobject/myadmin/urls.py加入如下代码
....
# 后台管理员路由
url(r'^login$', index.login, name="myadmin_login"),
url(r'^dologin$', index.dologin, name="myadmin_dologin"),
url(r'^logout$', index.logout, name="myadmin_logout"),
url(r'^verify$', index.verify, name="myadmin_verify"), #验证码
....
(2). 编写视图文件

- 编写视图
myobject/myadmin/views/index.py文件 并加入如下代码: - 在
后台管理员操作中添加输出验证码方法verify() - 将字体文件
STXIHEI.TTF复制到 static/目录下
...
# ==============后台管理员操作====================
# 会员登录表单
def verify(request):
#引入随机函数模块
import random
from PIL import Image, ImageDraw, ImageFont
#定义变量,用于画面的背景色、宽、高
#bgcolor = (random.randrange(20, 100), random.randrange(
# 20, 100),100)
bgcolor = (242,164,247)
width = 100
height = 25
#创建画面对象
im = Image.new('RGB', (width, height), bgcolor)
#创建画笔对象
draw = ImageDraw.Draw(im)
#调用画笔的point()函数绘制噪点
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)
#定义验证码的备选值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
#随机选取4个值作为验证码
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
#构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
font = ImageFont.truetype('static/STXIHEI.TTF', 21)
#font = ImageFont.load_default().font
#构造字体颜色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
#绘制4个字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
#释放画笔
del draw
#存入session,用于做进一步验证
request.session['verifycode'] = rand_str
"""
python2的为
# 内存文件操作
import cStringIO
buf = cStringIO.StringIO()
"""
# 内存文件操作-->此方法为python3的
import io
buf = io.BytesIO()
#将图片保存在内存中,文件类型为png
im.save(buf, 'png')
#将内存中的图片数据返回给客户端,MIME类型为图片png
return HttpResponse(buf.getvalue(), 'image/png')
...
(3). 在中间件设置放行
- 编写:myobject/common/shopmiddleware.py文件
- 在
__call__()方法的urllist变量中加入:/myadmin/verify
# 定义网站后台不用登录也可访问的路由url
urllist = ['/myadmin/login','/myadmin/dologin','/myadmin/logout','/myadmin/verify']
测试:http://localhost:8000/myadmin/verify
(4). 在登录模板中使用验证码
- 登录模板文件:
templates/myadmin/login.html中加入代码如下:
....
<form id="login-form" method="post" class="well" action="{% url 'myadmin_dologin' %}">
{% csrf_token %}
账 号:<input type="text" name="username" class="span2" placeholder="输入账号" /><br />
密 码:<input type="password" name="password" class="span2" placeholder="输入密码" /><br />
验证码:<input type="text" name="code" class="span2" style="width:30px;" />
<img src="{% url 'myadmin_verify'%}" onclick="this.src='{% url 'myadmin_verify' %}?sn='+Math.random()"/>
<br />
<label class="checkbox"> <input type="checkbox" /> Remember me </label>
<button type="submit" class="btn btn-primary">登录</button>
<button type="reset" class="btn">重置</button>
</form>
...
(5). 验证码校验
- 编辑视图文件:
myobject/myadmin/views/index.py文件 - 在视图文件中的会员执行登录方法
dologin()中加入验证代码
...
# 会员执行登录
def dologin(request):
# 校验验证码
verifycode = request.session['verifycode']
code = request.POST['code']
if verifycode != code:
context = {'info':'验证码错误!'}
return render(request,"myadmin/login.html",context)
try:
#根据账号获取登录者信息
user = Users.objects.get(username=request.POST['username'])
#判断当前用户是否是后台管理员用户
if user.state == 0:
# 验证密码
import hashlib
m = hashlib.md5()
m.update(bytes(request.POST['password'],encoding="utf8"))
if user.password == m.hexdigest():
# 此处登录成功,将当前登录信息放入到session中,并跳转页面
request.session['adminuser'] = user.name
#print(json.dumps(user))
return redirect(reverse('myadmin_index'))
else:
context = {'info':'登录密码错误!'}
else:
context = {'info':'此用户非后台管理用户!'}
except:
context = {'info':'登录账号错误!'}
return render(request,"myadmin/login.html",context)
....
(6). 启动服务测试,即可测试网站后台是否可以使用。注意只有管理员才开登录。
7. 项目实战后台之商品类别信息管理
后台商品类别信息管理
本页面完成项目后台管理的商品类别信息模块操作
(1). 商品类别信息数据表:type
- 在数据库
shopdb中创建type表,若此表已存在请跳过
CREATE TABLE `type` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`pid` int(11) unsigned DEFAULT '0',
`path` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
(2). 定义模型Model类
- 进入
common应用目录中编辑:myobject/common/models.py模型文件
from django.db import models
#商品类别信息模型
class Types(models.Model):
name = models.CharField(max_length=32)
pid = models.IntegerField(default=0)
path = models.CharField(max_length=255)
class Meta:
db_table = "type" # 更改表名
(3). 项目urls路由信息配置
- 打开根路由文件:myobject/myadmin/urls.py路由文件,编辑路由配置信息
from django.conf.urls import url
from myadmin.views import index,users,type
urlpatterns = [
...
# 后台商品类别信息管理
url(r'^type$', type.index, name="myadmin_type_index"),
url(r'^type/add/(?P<tid>[0-9]+)$', type.add, name="myadmin_type_add"),
url(r'^type/insert$', type.insert, name="myadmin_type_insert"),
url(r'^type/del/(?P<tid>[0-9]+)$', type.delete, name="myadmin_type_del"),
url(r'^type/edit/(?P<tid>[0-9]+)$', type.edit, name="myadmin_type_edit"),
url(r'^type/update/(?P<tid>[0-9]+)$', type.update, name="myadmin_type_update"),
]
(4). 编辑视图文件
- 新建视图文件:myobject/myadmin/views/type.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Types
# 浏览商品类别信息
def index(request):
# 执行数据查询,并放置到模板中
list = Types.objects.extra(select = {'_has':'concat(path,id)'}).order_by('_has')
# 遍历查询结果,为每个结果对象追加一个pname属性,目的用于缩进标题
for ob in list:
ob.pname ='. . . '*(ob.path.count(',')-1)
# print(list[0].__dict__)
context = {"typeslist":list}
return render(request,'myadmin/type/index.html',context)
# 商品类别信息添加表单
def add(request,tid):
# 获取父类别信息,若没有则默认为根类别信息
if tid == '0':
context = {'pid':0,'path':'0,','name':'根类别'}
else:
ob = Types.objects.get(id=tid)
context = {'pid':ob.id,'path':ob.path+str(ob.id)+',','name':ob.name}
return render(request,'myadmin/type/add.html',context)
#执行商品类别信息添加
def insert(request):
try:
ob = Types()
ob.name = request.POST['name']
ob.pid = request.POST['pid']
ob.path = request.POST['path']
ob.save()
context = {'info':'添加成功!'}
except Exception as err:
print(err)
context = {'info':'添加失败!'}
return render(request,"myadmin/info.html",context)
# 执行商品类别信息删除
def delete(request,tid):
try:
# 获取被删除商品的子类别信息量,若有数据,就禁止删除当前类别
row = Types.objects.filter(pid=tid).count()
if row > 0:
context = {'info':'删除失败:此类别下还有子类别!'}
return render(request,"myadmin/info.html",context)
ob = Types.objects.get(id=tid)
ob.delete()
context = {'info':'删除成功!'}
except Exception as err:
print(err)
context = {'info':'删除失败!'}
return render(request,"myadmin/info.html",context)
# 打开商品类别信息编辑表单
def edit(request,tid):
try:
ob = Types.objects.get(id=tid)
context = {'type':ob}
return render(request,"myadmin/type/edit.html",context)
except Exception as err:
print(err)
context = {'info':'没有找到要修改的信息!'}
return render(request,"myadmin/info.html",context)
# 执行商品类别信息编辑
def update(request,tid):
try:
ob = Types.objects.get(id=tid)
ob.name = request.POST['name']
ob.save()
context = {'info':'修改成功!'}
except Exception as err:
print(err)
context = {'info':'修改失败!'}
return render(request,"myadmin/info.html",context)
(5). 编写模板文件
- 5.1. 打开父类模板:/templates/myadmin/base.html ,编辑导航栏代码
...
<li class="nav-header">
商品类别管理
</li>
<li>
<a href="{% url 'myadmin_type_index' %}"><i class="icon-envelope"></i> 浏览商品类别</a>
</li>
<li>
<a href="{% url 'myadmin_type_add' 0 %}"><i class="icon-file"></i> 添加商品类别</a>
</li>
...
- 5.2. 后台商品类别信息浏览页模板:/templates/myadmin/type/index.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h4>
商品类别信息管理
</h4>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>ID号</th>
<th>类别名称</th>
<th>父类别id</th>
<th>路径</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for vo in typeslist %}
<tr>
<td>{{ vo.id }}</td>
<td>{{ vo.pname}}|-- {{ vo.name }}</td>
<td>{{ vo.pid }}</td>
<td>{{ vo.path }}</td>
<td width="30%">
<a href="{% url 'myadmin_type_add' vo.id %}" class="view-link">添加子类别</a>
<a href="{% url 'myadmin_type_del' vo.id %}" class="view-link">删除</a>
<a href="{% url 'myadmin_type_edit' vo.id %}" class="view-link">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<ul>
<li class="disabled">
<a href="#">«</a>
</li>
<li class="active">
<a href="#">1</a>
</li>
<li>
<a href="#">2</a>
</li>
<li>
<a href="#">3</a>
</li>
<li>
<a href="#">4</a>
</li>
<li>
<a href="#">»</a>
</li>
</ul>
</div>
{% endblock %}
- 5.3. 后台商品类别信息添加表单页模板:/templates/myadmin/type/add.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
商品类别信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_type_insert' %}" class="form-horizontal" method="post">
<input type="hidden" name="pid" value="{{ pid }}"/>
<input type="hidden" name="path" value="{{ path }}"/>
{% csrf_token %}
<fieldset>
<legend>添加商品类别信息</legend>
<div class="control-group">
<label class="control-label" for="input01">父类别名称:</label>
<div class="controls">
<input type="text" name="pname" class="input-xlarge" id="input01" value="" placeholder="{{name}}"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">类别名称:</label>
<div class="controls">
<input type="text" name="name" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">添加</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
{% endblock %}
- 5.4. 后台商品信息编辑模板:/templates/myadmin/type/edit.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
商品类别信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_type_update' type.id %}" class="form-horizontal" method="post">
{% csrf_token %}
<fieldset>
<legend>编辑商品类别信息</legend>
<div class="control-group">
<label class="control-label" for="input01">类别名称:</label>
<div class="controls">
<input type="text" name="name" class="input-xlarge" id="input01" value="{{ type.name }}" />
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
{% endblock %}
(6). 运行测试
- 在项目根目录下启动服务,并使用浏览器访问测试:http://localhost:8000/myadmin
[root@localhost myobject]# pwd
/python/myobject
[root@localhost myobject]# ls
manage.py myadmin myobject myweb static templates
[root@localhost myobject]# python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
April 07, 2018 - 16:29:36
Django version 1.11, using settings 'myobject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
^C[root@localhost myobject]#
8. 项目实战后台之商品信息管理
- 本页面完成项目后台管理的商品信息模块操作
(1). 商品信息数据表:goods
- 在数据库
shopdb中创建goods表,若此表已存在请跳过
CREATE TABLE `goods` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`typeid` int(11) unsigned NOT NULL,
`goods` varchar(32) NOT NULL,
`company` varchar(50) DEFAULT NULL,
`content` text,
`price` double(6,2) unsigned NOT NULL,
`picname` varchar(255) DEFAULT NULL,
`store` int(11) unsigned NOT NULL DEFAULT '0',
`num` int(11) unsigned NOT NULL DEFAULT '0',
`clicknum` int(11) unsigned NOT NULL DEFAULT '0',
`state` tinyint(1) unsigned NOT NULL DEFAULT '1',
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `typeid` (`typeid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
(2). 定义模型Model类
- 进入common应用目录中,编辑:
myobject/common/models.py模型文件,添加如下代码
from django.db import models
from datetime import datetime
#商品信息模型
class Goods(models.Model):
typeid = models.IntegerField()
goods = models.CharField(max_length=32)
company = models.CharField(max_length=50)
content = models.TextField()
price = models.FloatField()
picname = models.CharField(max_length=255)
store = models.IntegerField(default=0)
num = models.IntegerField(default=0)
clicknum = models.IntegerField(default=0)
state = models.IntegerField(default=1)
addtime = models.DateTimeField(default=datetime.now)
def toDict(self):
return {'id':self.id,'typeid':self.typeid,'goods':self.goods,'company':self.company,'price':self.price,'picname':self.picname,'store':self.store,'num':self.num,'clicknum':self.clicknum,'state':self.state}
class Meta:
db_table = "goods" # 更改表名
(3). 项目urls路由信息配置
- 打开根路由文件:myobject/myadmin/urls.py路由文件,编辑路由配置信息
from django.conf.urls import url
from myadmin.views import index,users,type,goods
urlpatterns = [
...
# 后台商品信息管理
url(r'^goods$', goods.index, name="myadmin_goods_index"),
url(r'^goods/add$', goods.add, name="myadmin_goods_add"),
url(r'^goods/insert$', goods.insert, name="myadmin_goods_insert"),
url(r'^goods/del/(?P<gid>[0-9]+)$', goods.delete, name="myadmin_goods_del"),
url(r'^goods/edit/(?P<gid>[0-9]+)$', goods.edit, name="myadmin_goods_edit"),
url(r'^goods/update/(?P<gid>[0-9]+)$', goods.update, name="myadmin_goods_update"),
]
(4). 编辑视图文件
- 新建视图文件:myobject/myadmin/views/goods.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Types,Goods
from PIL import Image
from datetime import datetime
import time,json,os
# ==============后台商品信息管理======================
# 浏览商品信息
def index(request):
# 执行数据查询,并放置到模板中
list = Goods.objects.all()
for ob in list:
ty = Types.objects.get(id=ob.typeid)
ob.typename = ty.name
context = {"goodslist":list}
return render(request,'myadmin/goods/index.html',context)
# 商品信息添加表单
def add(request):
# 获取商品的类别信息
list = Types.objects.extra(select = {'_has':'concat(path,id)'}).order_by('_has')
context = {"typelist":list}
return render(request,'myadmin/goods/add.html',context)
#执行商品类别信息添加
def insert(request):
try:
# 判断并执行图片上传,缩放等处理
myfile = request.FILES.get("pic", None)
if not myfile:
return HttpResponse("没有上传文件信息!")
# 以时间戳命名一个新图片名称
filename= str(time.time())+"."+myfile.name.split('.').pop()
destination = open(os.path.join("./static/goods/",filename),'wb+')
for chunk in myfile.chunks(): # 分块写入文件
destination.write(chunk)
destination.close()
# 执行图片缩放
im = Image.open("./static/goods/"+filename)
# 缩放到375*375:
im.thumbnail((375, 375))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/"+filename, 'jpeg')
# 缩放到220*220:
im.thumbnail((220, 220))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/m_"+filename, 'jpeg')
# 缩放到75*75:
im.thumbnail((75, 75))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/s_"+filename, 'jpeg')
# 获取商品信息并执行添加
ob = Goods()
ob.goods = request.POST['goods']
ob.typeid = request.POST['typeid']
ob.company = request.POST['company']
ob.price = request.POST['price']
ob.store = request.POST['store']
ob.content = request.POST['content']
ob.picname = filename
ob.state = 1
ob.addtime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
ob.save()
context = {'info':'添加成功!'}
except Exception as err:
print(err)
context = {'info':'添加失败!'}
return render(request,"myadmin/info.html",context)
# 执行商品信息删除
def delete(request,gid):
try:
# 获取被删除商品信的息量,先删除对应的图片
ob = Goods.objects.get(id=gid)
#执行图片删除
os.remove("./static/goods/"+ob.picname)
os.remove("./static/goods/m_"+ob.picname)
os.remove("./static/goods/s_"+ob.picname)
#执行商品信息的删除
ob.delete()
context = {'info':'删除成功!'}
except Exception as err:
print(err)
context = {'info':'删除失败!'}
return render(request,"myadmin/info.html",context)
# 打开商品类别信息编辑表单
def edit(request,gid):
try:
# 获取要编辑的信息
ob = Goods.objects.get(id=gid)
# 获取商品的类别信息
list = Types.objects.extra(select = {'_has':'concat(path,id)'}).order_by('_has')
# 放置信息加载模板
context = {"typelist":list,'goods':ob}
return render(request,"myadmin/goods/edit.html",context)
except Exception as err:
print(err)
context = {'info':'没有找到要修改的信息!'}
return render(request,"myadmin/info.html",context)
# 执行商品类别信息编辑
def update(request,gid):
try:
b = False
oldpicname = request.POST['oldpicname']
if None != request.FILES.get("pic"):
myfile = request.FILES.get("pic", None)
if not myfile:
return HttpResponse("没有上传文件信息!")
# 以时间戳命名一个新图片名称
filename = str(time.time())+"."+myfile.name.split('.').pop()
destination = open(os.path.join("./static/goods/",filename),'wb+')
for chunk in myfile.chunks(): # 分块写入文件
destination.write(chunk)
destination.close()
# 执行图片缩放
im = Image.open("./static/goods/"+filename)
# 缩放到375*375:
im.thumbnail((375, 375))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/"+filename, 'jpeg')
# 缩放到220*220:
im.thumbnail((220, 220))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/m_"+filename, 'jpeg')
# 缩放到75*75:
im.thumbnail((75, 75))
# 把缩放后的图像用jpeg格式保存:
im.save("./static/goods/s_"+filename, 'jpeg')
b = True
picname = filename
else:
picname = oldpicname
ob = Goods.objects.get(id=gid)
ob.goods = request.POST['goods']
ob.typeid = request.POST['typeid']
ob.company = request.POST['company']
ob.price = request.POST['price']
ob.store = request.POST['store']
ob.content = request.POST['content']
ob.picname = picname
ob.state = request.POST['state']
ob.save()
context = {'info':'修改成功!'}
if b:
os.remove("./static/goods/m_"+oldpicname) #执行老图片删除
os.remove("./static/goods/s_"+oldpicname) #执行老图片删除
os.remove("./static/goods/"+oldpicname) #执行老图片删除
except Exception as err:
print(err)
context = {'info':'修改失败!'}
if b:
os.remove("./static/goods/m_"+picname) #执行新图片删除
os.remove("./static/goods/s_"+picname) #执行新图片删除
os.remove("./static/goods/"+picname) #执行新图片删除
return render(request,"myadmin/info.html",context)
(5). 编写模板文件
- 5.1. 打开父类模板:/templates/myadmin/base.html ,编辑导航栏代码
...
<li class="nav-header">
商品信息管理
</li>
<li>
<a href="{% url 'myadmin_goods_index' %}"><i class="icon-list-alt"></i> 浏览商品信息</a>
</li>
<li>
<a href="{% url 'myadmin_goods_add' %}"><i class="icon-list-alt"></i> 添加商品信息</a>
</li>
...
- 5.2. 后台商品信息浏览页模板:/templates/myadmin/goods/index.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h4>
商品信息管理
</h4>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>id号</th>
<th>商品名称</th>
<th>商品类别</th>
<th>图片</th>
<th>单价</th>
<th>点击量</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for vo in goodslist %}
<tr>
<td>{{ vo.id }}</td>
<td>{{ vo.goods }}</td>
<td>{{ vo.typename }}</td>
<td><img src="/static/goods/s_{{ vo.picname }}" width="60"/></td>
<td>{{ vo.price }}</td>
<td>{{ vo.clicknum }}</td>
<td>
{% if vo.state == 1 %}
新添加
{% elif vo.state == 2 %}
在售
{% else %}
下架
{% endif %}
</td>
<td>
<a href="{% url 'myadmin_goods_del' vo.id %}" class="view-link">删除</a>
<a href="{% url 'myadmin_goods_edit' vo.id %}" class="view-link">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<ul>
<li class="disabled">
<a href="#">«</a>
</li>
<li class="active">
<a href="#">1</a>
</li>
<li>
<a href="#">2</a>
</li>
<li>
<a href="#">3</a>
</li>
<li>
<a href="#">4</a>
</li>
<li>
<a href="#">»</a>
</li>
</ul>
</div>
{% endblock %}
- 5.3. 后台商品信息添加表单页模板:/templates/myadmin/goods/add.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
商品信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_goods_insert' %}" class="form-horizontal" method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset>
<legend>添加商品信息</legend>
<div class="control-group">
<label class="control-label" for="input01">商品类别:</label>
<div class="controls">
<select name="typeid">
{% for vo in typelist %}
<option
{% if vo.pid == 0 %}
disabled
{% endif %}
value="{{ vo.id }}">{{ vo.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品名称:</label>
<div class="controls">
<input type="text" name="goods" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">生产厂家:</label>
<div class="controls">
<input type="text" name="company" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">单价:</label>
<div class="controls">
<input type="text" name="price" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">库存量:</label>
<div class="controls">
<input type="text" name="store" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品图片:</label>
<div class="controls">
<input type="file" name="pic" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品简介:</label>
<div class="controls">
<textarea cols="40" style="width:450px" rows="10" name="content"></textarea>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">添加</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
{% endblock %}
- 5.4. 后台商品信息编辑模板:/templates/myadmin/goods/edit.html

{% extends "myadmin/base.html" %}
{% block mainbody %}
<h3>
商品信息管理
</h3>
<form id="edit-profile" action="{% url 'myadmin_goods_update' goods.id %}" class="form-horizontal" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="hidden" name="oldpicname" value="{{ goods.picname }}"/>
<fieldset>
<legend>编辑商品信息</legend>
<div class="control-group">
<label class="control-label" for="input01">商品类别:</label>
<div class="controls">
<select name="typeid">
{% for vo in typelist %}
<option
{% if vo.pid == 0 %}
disabled
{% endif %}
{% if vo.id == goods.typeid %}
selected
{% endif %}
value="{{ vo.id }}">{{ vo.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品名称:</label>
<div class="controls">
<input type="text" name="goods" value="{{ goods.goods }}" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">生产厂家:</label>
<div class="controls">
<input type="text" name="company" value="{{ goods.company }}" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">单价:</label>
<div class="controls">
<input type="text" name="price" value="{{ goods.price }}" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">库存量:</label>
<div class="controls">
<input type="text" name="store" value="{{ goods.store }}" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品图片:</label>
<div class="controls">
<input type="file" name="pic" class="input-xlarge" id="input01"/>
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">状态:</label>
<div class="controls">
<input type="radio" name="state" class="input-xlarge" id="input01"
{% if goods.state == 1 %}
checked
{% endif %}
value="1" /> 新商品
<input type="radio" name="state" class="input-xlarge" id="input01"
{% if goods.state == 2 %}
checked
{% endif %}
value="2" /> 在售
<input type="radio" name="state" class="input-xlarge" id="input01"
{% if goods.state == 3 %}
checked
{% endif %}
value="3" /> 已下架
</div>
</div>
<div class="control-group">
<label class="control-label" for="input01">商品简介:</label>
<div class="controls">
<textarea cols="40" style="width:450px" rows="10" name="content">{{ goods.content }}</textarea>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">保存</button> <button type="reset" class="btn">重置</button>
</div>
</fieldset>
</form>
<br/>
<img src="/static/goods/m_{{ goods.picname }}"/>
{% endblock %}
(6). 运行测试
- 在项目根目录下启动服务,并使用浏览器访问测试:http://localhost:8000/myadmin
[root@localhost myobject]# pwd
/python/myobject
[root@localhost myobject]# ls
manage.py myadmin myobject myweb static templates
[root@localhost myobject]# python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
April 08, 2018 - 12:20:30
Django version 1.11, using settings 'myobject.settings'
Starting development server at http://0:8000/
Quit the server with CONTROL-C.
^C[root@localhost myobject]#
①. 搜索分页浏览商品信息(附加)
8.1 搜索&分页浏览商品信息(附加)
- 本节案例是建立在第八节《商品信息管理》之上继续开发的,具体效果如下:

(1). 编辑项目urls路由信息配置
- 打开根路由文件:myobject/myadmin/urls.py路由文件,编辑路由配置信息:
- 为商品信息浏览路由添加页码参数:
url(r'^goods/(?P<pIndex>[0-9]+)$', ... ... ),,具体如下:
from django.conf.urls import url
from myadmin.views import index,users,type,goods
urlpatterns = [
...
# 后台商品信息管理
url(r'^goods/(?P<pIndex>[0-9]+)$', goods.index, name="myadmin_goods_index"),
url(r'^goods/add$', goods.add, name="myadmin_goods_add"),
url(r'^goods/insert$', goods.insert, name="myadmin_goods_insert"),
url(r'^goods/del/(?P<gid>[0-9]+)$', goods.delete, name="myadmin_goods_del"),
url(r'^goods/edit/(?P<gid>[0-9]+)$', goods.edit, name="myadmin_goods_edit"),
url(r'^goods/update/(?P<gid>[0-9]+)$', goods.update, name="myadmin_goods_update"),
]
(2). 编辑视图文件
- 新建视图文件:myobject/myadmin/views/goods.py 视图文件,并进行编辑index方法中代码
- 导入查询
Q和分页Paginator:
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.core.paginator import Paginator
from common.models import Types,Goods
from PIL import Image
from datetime import datetime
import time,json,os
# ==============后台商品信息管理======================
# 浏览商品信息
def index(request,pIndex):
'''浏览信息'''
#获取商品类别信息
tlist = Types.objects.extra(select={'_has':'concat(path,id)'}).order_by('_has')
for ob in tlist:
ob.pname = '. . .'*(ob.path.count(',')-1)
#获取商品信息查询对象
mod = Goods.objects
mywhere=[] #定义一个用于存放搜索条件列表
# 获取、判断并封装关keyword键搜索
kw = request.GET.get("keyword",None)
if kw:
# 查询商品名中只要含有关键字的都可以
list = mod.filter(goods__contains=kw)
mywhere.append("keyword="+kw)
else:
list = mod.filter()
# 获取、判断并封装商品类别typeid搜索条件
typeid = request.GET.get('typeid','0')
if typeid != '0':
tids = Types.objects.filter(Q(id=typeid) | Q(pid=typeid)).values_list('id',flat=True)
list = list.filter(typeid__in=tids)
mywhere.append("typeid="+typeid)
# 获取、判断并封装商品状态state搜索条件
state = request.GET.get('state','')
if state != '':
list = list.filter(state=state)
mywhere.append("state="+state)
#执行分页处理
pIndex = int(pIndex)
page = Paginator(list,5) #以5条每页创建分页对象
maxpages = page.num_pages #最大页数
#判断页数是否越界
if pIndex > maxpages:
pIndex = maxpages
if pIndex < 1:
pIndex = 1
list2 = page.page(pIndex) #当前页数据
plist = page.page_range #页码数列表
#遍历商品信息,并获取对应的商品类别名称,以typename名封装
for vo in list2:
ty = Types.objects.get(id=vo.typeid)
vo.typename = ty.name
#封装信息加载模板输出
context = {'typelist':tlist,"goodslist":list2,'plist':plist,'pIndex':pIndex,'maxpages':maxpages,'mywhere':mywhere,'typeid':int(typeid)}
return render(request,"myadmin/goods/index.html",context)
# 商品信息添加表单
....
....
(3). 编写模板文件
- 3.1. 打开父类模板:/templates/myadmin/base.html ,编辑导航栏代码
- 将为路由
myadmin_goods_index添加一个默认页号参数1:
...
<li class="nav-header">
商品信息管理
</li>
<li>
<a href="{% url 'myadmin_goods_index' 1 %}"><i class="icon-list-alt"></i> 浏览商品信息</a>
</li>
<li>
<a href="{% url 'myadmin_goods_add' %}"><i class="icon-list-alt"></i> 添加商品信息</a>
</li>
...
- 3.2. 后台商品信息浏览页模板:/templates/myadmin/goods/index.html
- 在里面添加
搜索表单和页码信息, 注意:{ { mywhere|join:'&' } }是为了下一页维持搜索条件用的:
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h2>
商品信息浏览
</h2>
<form class="form-inline" action="{% url 'myadmin_goods_index' 1 %}" method="get">
<label>关键字:</label>
<input type="text" name="keyword" value="{{request.GET.keyword}}" class="input-small" placeholder="商品名称">
<label> 类别:</label>
<select name="typeid" class="span2">
<option value="0">全部</option>
{% for vo in typelist %}
<option value="{{ vo.id }}" {% if typeid == vo.id %}selected{% endif %}>{{vo.pname}}|--{{ vo.name }}</option>
{% endfor %}
</select>
<label> 状态:</label>
<select name="state" class="span1">
<option value="">全部</option>
<option value="1" {% if request.GET.state == '1' %}selected{% endif %}>新商品</option>
<option value="2" {% if request.GET.state == '2' %}selected{% endif %}>在售</option>
<option value="3" {% if request.GET.state == '3' %}selected{% endif %}>已下架</option>
</select>
<button type="submit" class="btn">搜索</button>
<a href="{% url 'myadmin_goods_index' 1 %}" class="btn">全部</a>
</form>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>id号</th>
<th>商品名称</th>
<th>商品类别</th>
<th>图片</th>
<th>价格</th>
<th>点击量</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for vo in goodslist %}
<tr>
<td>{{ vo.id }}</td>
<td>{{ vo.goods }}</td>
<td>{{ vo.typename }}</td>
<td><img src="/static/goods/s_{{ vo.picname }}" width="40"/></td>
<td>{{ vo.price }}</td>
<td>{{ vo.clicknum }}</td>
<td>
{% if vo.state == 1 %}
<span style="color:green">新商品</span>
{% elif vo.state == 2 %}
在售
{% elif vo.state == 3 %}
已下架
{% else %}
<span style="color:red">无效状态</span>
{% endif %}
</td>
<td>
<a href="{% url 'myadmin_goods_edit' vo.id %}" class="btn btn-mini btn-primary">编辑</a>
<a href="{% url 'myadmin_goods_del' vo.id %}" class="btn btn-mini btn-danger">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<ul>
<li>
<a href="{% url 'myadmin_goods_index' pIndex|add:-1 %}?{{ mywhere|join:'&' }}">«</a>
</li>
{% for p in plist %}
<li {% if pIndex == p %}class="active"{% endif %}>
<a href="{% url 'myadmin_goods_index' p %}?{{ mywhere|join:'&' }}">{{p}}</a>
</li>
{% endfor %}
<li>
<a href="{% url 'myadmin_goods_index' pIndex|add:1 %}?{{ mywhere|join:'&' }}">»</a>
</li>
</ul>
</div>
{% endblock %}
(4). 运行测试
- 在项目根目录下启动服务,并使用浏览器访问测试:http://localhost:8000/myadmin
六、Django商城项目开发(下)
商城项目前台结构:
- 本次项目共计四个应用:中web应用为项目前台:
/myobject/
├── manage.py
├── myobject/ 项目总目录
│ ├── ... 略
│
├── common/ 公共应用目录
│ ├── ... 略
│
├── myadmin/ 网站后台应用目录
│ ├── ... 略
│
├── web 网站前台应用
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ ├── views
│ │ ├── index.py web前台主视图
│ │ ├── vip.py 会员操作视图
│ │ ├── cart.py 购物车管理视图
│ │ └── orders.py 订单处理视图
│ ├── models.py
│ ├── tests.py
│ └── urls.py 网站前台的URL路由配置
│
├── ueditor 百度编辑器(富文本编辑器)目录
│ ├── ... 略
│
├── templates 模板目录
│ ├── myadmin 后台模板总目录
│ │ ├── ... 略
│ │
│ ├── web 前台模板目录
│ │ ├── base.html
│ │ ├── index.html
│ │ ├── list.html
│ │ ├── detail.html
│ │ ├── login.html
│ │ ├── reg.html
│ │ └── ......
│
├── static 静态资源目录
│ ├── myadmin 后台静态资源
│ │ ├──....
│ │
│ ├── web 网站前台静态资源
│ │ ├──....
│ │ ├──....
项目前台的URL路由配置:
from django.conf.urls import url
from web.views import index,cart,orders,vip
urlpatterns = [
# 前台首页
url(r'^$', index.index, name="index"), #商城首页
url(r'^list$', index.lists, name="list"),# 商品列表
url(r'^list/(?P<pIndex>[0-9]+)$', index.lists, name="list"),# 商品列表
url(r'^detail/(?P<gid>[0-9]+)$', index.detail, name="detail"),#商品详情
# 会员登录和退出路由配置
url(r'^login$', index.login, name="login"),
url(r'^dologin$', index.dologin, name="dologin"),
url(r'^logout$', index.logout, name="logout"),
# 购物车信息管理路由配置
url(r'^cart$', cart.index, name="cart_index"),
url(r'^cart/add/(?P<gid>[0-9]+)$', cart.add, name="cart_add"),
url(r'^cart/del/(?P<gid>[0-9]+)$', cart.delete, name="cart_del"),
url(r'^cart/clear$', cart.clear, name="cart_clear"),
url(r'^cart/change$', cart.change, name="cart_change"),
# 订单处理
url(r'^orders/add$', orders.add,name='orders_add'), #订单的表单页
url(r'^orders/confirm$', orders.confirm,name='orders_confirm'), #订单确认页
url(r'^orders/insert$', orders.insert,name='orders_insert'), #执行订单添加操作
# 会员中心
url(r'^vip/orders$', vip.viporders,name='vip_orders'), #会员中心我的订单
url(r'^vip/odstate$', vip.odstate,name='vip_odstate'), #修改订单状态(确认收货)
#url(r'^vip/info$', vip.info,name='vip_info'), #会员中心的个人信息
#url(r'^vip/update$', vip.update,name='vip_update'), #执行修改会员信息
#url(r'^vip/resetps$', vip.resetps,name='vip_resetps'), #重置密码表单
#url(r'^vip/doresetps$', vip.doresetps,name='vip_doresetps'), #执行重置密码
]
9. 项目实战前台搭建
- 本节是完成商城网站前台结构搭建,并且将网站首页,商品列表和详情页的界面摆放到web项目中
- 由于三个网页界面都有公共的页头和页脚,故采用模板继承来实现网页布局:


(1). 开发前的准备工作:
- 安装项目设计创建对象的文件和目录
- 将素材下的提前准备好模板目录中的静态资源目录:
css、fonts、img、js复制到项目的static/web/目录下。
(2). 目urls路由信息配置:
from django.conf.urls import url
from web.views import index
urlpatterns = [
# 前台首页
url(r'^$', index.index, name="index"), #商城首页
url(r'^list$', index.lists, name="list"),# 商品列表
url(r'^detail/(?P<gid>[0-9]+)$', index.detail, name="detail"),#商品详情
]
(3). 编辑视图文件
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
'''项目前台首页'''
return render(request,"web/index.html")
def lists(request,pIndex=1):
'''商品列表页'''
return render(request,"web/list.html")
def detail(request,gid):
'''商品详情页'''
return render(request,"web/detail.html")
(4). 编写模板文件
- 使用模板继承套用模板文件:
base.html、index.html、list.html、detail.html具体参考老师的授课。 - 静态资源中的正则替换技巧:
"\./public/(.*?)"` 换成 `"{% static 'web/\1' %}"
10. 项目实战前台之会员注册与登陆
- 本节完成前台会员登录操作,关于会员注册留为练习由学员自行完成(可参考后台的会员信息添加)。



(1). 项目urls路由信息配置
- 在数据库
shopdb中已存在数据表users,并且内有测试数据。 - 在common应用目录中的
myobject/common/models.py模型文件中,已存在Users模型类的定义。 - 打开根路由文件:myobject/web/urls.py路由文件,编辑路由配置信息
from django.conf.urls import url
from web.views import index
urlpatterns = [
#网站前台
url(r'^$',index.index,name="index"), #首页
url(r'^list$',index.lists,name="list"), #商品列表展示
#url(r'^list/(?P<pIndex>[0-9]+)$',index.lists,name="list"), #分页商品列表展示
url(r'^detail/(?P<gid>[0-9]+)$',index.detail,name="detail"), #商品详情
# 会员及个人中心等路由配置
url(r'^login$', index.login, name="login"),
url(r'^dologin$', index.dologin, name="dologin"),
url(r'^logout$', index.logout, name="logout"),
]
(2). 编辑视图文件
- 新建视图文件:myobject/web/views/index.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Users
# =============商品展示========================
def index(request):
'''项目前台首页'''
return render(request,"web/index.html")
def lists(request,pIndex=1):
'''商品列表页'''
return render(request,"web/list.html")
def detail(request,gid):
'''商品详情页'''
return render(request,"web/detail.html")
# ==============前台会员登录====================
def login(request):
'''会员登录表单'''
return render(request,'web/login.html')
def dologin(request):
'''会员执行登录'''
# 校验验证码
verifycode = request.session['verifycode']
code = request.POST['code']
if verifycode != code:
context = {'info':'验证码错误!'}
return render(request,"web/login.html",context)
try:
#根据账号获取登录者信息
user = Users.objects.get(username=request.POST['username'])
#判断当前用户是否是后台管理员用户
if user.state == 0 or user.state == 1:
# 验证密码
import hashlib
m = hashlib.md5()
m.update(bytes(request.POST['password'],encoding="utf8"))
if user.password == m.hexdigest():
# 此处登录成功,将当前登录信息放入到session中,并跳转页面
request.session['vipuser'] = user.toDict()
return redirect(reverse('index'))
else:
context = {'info':'登录密码错误!'}
else:
context = {'info':'此用户为非法用户!'}
except:
context = {'info':'登录账号错误!'}
return render(request,"web/login.html",context)
def logout(request):
'''会员退出'''
# 清除登录的session信息
del request.session['vipuser']
# 跳转登录页面(url地址改变)
return redirect(reverse('login'))
(3). 编写模板文件
- 3.1. 打开父类模板:/templates/web/base.html ,编辑导航栏代码
...
<li class="layout-header-service-item" id="layoutHeaderUser">
<a class="layout-header-service-link g-user" style="background: #fff;width:auto;" href="#">
<i class="glyphicon glyphicon-user"></i>
<span>{{request.session.vipuser.name}}</span>
</a>
<div class="layout-user-downmenu">
<ul class="layout-user-downmenu-list">
{% if request.session.vipuser %}
<li class="layout-user-downmenu-item">
<a href="#" class="layout-user-downmenu-link" data-mtype="wmz_public_grzx_myorder">个人中心</a>
</li>
<li class="layout-user-downmenu-item">
<a href="#" class="layout-user-downmenu-link" data-mtype="wmz_public_grzx_myorder">我的订单</a>
</li>
<li class="layout-user-downmenu-item">
<a href="{% url 'logout' %}" class="layout-user-downmenu-link" data-mtype="wmz_public_grzx_mformy">退出</a>
</li>
{% else %}
<li class="layout-user-downmenu-item">
<a href="{% url 'login'%}" class="layout-user-downmenu-link">立即登录</a>
</li>
<li class="layout-user-downmenu-item">
<a href="#" target="_blank" class="layout-user-downmenu-link" data-mtype="wmz_public_grzx_register">立即注册</a>
</li>
{% endif %}
</ul>
</div>
</li>
...
- 3.2. 项目前台登录页模板:/templates/web/login.html
{% load static from staticfiles %}
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title> Flyme 账号 - 登录</title>
<meta name="description" content="欢迎登录和注册 Flyme 账号,您可以体验手机云服务功能,包括:在线下载应用,同步手机数据和查找手机等,让您的手机管理更加智能。" />
<meta name="keywords" content="魅族 meizu 登录flyme 云服务 查找手机 充值账号 MX M9 MX2" /> <link href="{% static 'web/img/favicon.ico' %}" rel="shortcut icon" type="image/x-icon"/>
<link href="{% static 'web/img/favicon.ico' %}" rel="icon" type="image/x-icon">
<!-- Bootstrap -->
<link href="{% static 'web/css/bootstrap.min.css' %}" rel="stylesheet">
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="{% static 'web/js/jquery-1.12.4.min.js' %}"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="{% static 'web/js/bootstrap.min.js' %}"></script>
<!-- 兼任ie9以下 -->
<!--[if lt IE 9]>
<script src="http://cdn.bootcss.com/html5shiv/3.7.0/html5shiv.min.js"></script>
<script src="http://cdn.bootcss.com/respond.js/1.3.0/respond.min.js"></script>
<![endif]-->
<!-- 自定义 -->
<link rel="stylesheet" type="text/css" href="{% static 'web/css/global.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/register.css' %}">
<script type="text/javascript" src="{% static 'web/js/rem.js' %}"></script>
<script type="text/javascript" src="{% static 'web/js/topNav.js' %}"></script>
</head>
<body>
<!-- 导航 -->
<div class="layout-header hidden-xs hidden-sm" id="scroll-wrap">
<nav class="navbar navbar-default header ">
<div class="container clearfix">
<div class="layout-header-logo navbar-left">
<a href="{% url 'index' %}" class="layout-header-logo-link" alt="魅族科技">
<img src="{% static 'web/img/logo.png' %}">
</a>
</div>
</div>
</nav>
</div><!-- 导航 E-->
<!-- 主内容区域 -->
<div id="content" class="content">
<div class="container-fluid">
<div class="banner-box hidden-xs hidden-sm">
<div class="container">
<form id="mainForm" name="mainForm" action="{% url 'dologin' %}" method="post" class="main-form">
{% csrf_token %}
<div class="tab-title">
<a class="linkAGray" id="toAccountLogin" href="javascript:void(0);">账号登录</a>
</div><br/>
{% if info %}
<div class="tip-box">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<i class="glyphicon glyphicon-exclamation-sign"></i>
<span class="tip-font">{{info}}</span>
<span aria-hidden="true" class="cha">×</span>
</button>
</div>
{% endif %}
<div class="normalInput cycode-box fieldInput" id="cycode-box">
<div class="cycode-selectbox">
<input class="ipt-account inp-focus" name="username" id="account" maxlength="50" placeholder="登录账号" autocomplete="off">
</div>
</div>
<div class="normalInput fieldInput passwd-box">
<input class="inp-focus" name="password" id="password" maxlength="16" autocomplete="off" placeholder="登录密码" type="password"/>
</div>
<div class="normalInput fieldInput passwd-box">
<input class="inp-focus" name="code" style="width:100px;" maxlength="16" placeholder="验证码" autocomplete="off" type="text" />
<img src="{% url 'myadmin_verify' %}?id=1" onclick="this.src='{% url 'myadmin_verify' %}?sn='+Math.random()"/>
</div>
<a id="register" href="javascript:document.mainForm.submit();" class="fullBtnBlue">登录</a>
<div class="transferField">
<a class="go2forgetpwd linkABlue rememberFieldForA" href="#">忘记密码?</a>
<a class="go2register linkABlue" href="./register.html" id="toRegister">注册</a>
<span>测试号:zhangsan 密码:123</span>
</div>
</form>
</div>
</div>
<!-- 移动端结构 -->
<form id="mainForm" class="main-form app-main-form">
<div class="tab-title">
<a class="linkAGray" id="toAccountLogin" href="javascript:void(0);">账号登录</a>
</div>
<div class="tip-box visiblility-hidden">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<i class="glyphicon glyphicon-exclamation-sign"></i>
<span class="tip-font"></span>
<span aria-hidden="true" class="cha">×</span>
</button>
</div>
<div class="normalInput cycode-box show-cycode" id="cycode-box">
<div class="cycode-selectbox">
<input id="phone" name="phone" class="ipt-phone inp-focus" maxlength="11" placeholder="手机号码" autocomplete="off">
</div>
</div>
<div class="normalInput box-input">
<input class="pswInput inp-focus" name="kapkey" id="kapkey" maxlength="6" placeholder="密码" autocomplete="off" tabindex="3" type="text">
</div>
<a id="register" class="fullBtnBlue">登录</a>
<div class="transferField">
<a class="go2forgetpwd linkABlue rememberFieldForA" href="#">忘记密码?</a>
<a class="go2register linkABlue" href="./register.html" id="toRegister">注册</a>
</div>
</form>
<!-- 移动端结构 E-->
</div>
</div>
<!-- 主内容区域 E-->
<!-- 底部区域 -->
<div id="flymeFooter " class="footerWrap hidden-xs hidden-sm">
<div class="container">
<div class="footer-layer1">
<div class="footer-innerLink">
<a href="#" target="_blank" title="关于魅族">关于魅族</a>
<img class="foot-line" src="{% static 'web/img/space.gif' %}">
<a href="#" target="_blank" title="工作机会">工作机会</a>
<img class="foot-line" src="{% static 'web/img/space.gif' %}">
<a href="#" target="_blank" title="联系我们">联系我们</a>
<img class="foot-line" src="{% static 'web/img/space.gif' %}">
<a href="#" target="_blank" title="法律声明">法律声明</a>
<img class="foot-line" src="{% static 'web/img/space.gif' %}">
<div href="javascript:void(0);" id="globalName" class="footer-language" title="简体中文">
简体中文
<div id="globalContainer" class="footer-language_menu">
<a href="#" id="i18n-link" title="English" class="ClobalItem">English</a>
</div>
</div>
</div>
<div class="footer-service">
<span class="service-label">客服热线</span>
<span class="service-num">400-888-6666</span>
<a id="service-online" class="service-online" href="javascript:void(0);" title="在线客服">在线客服</a>
</div>
<div class="footer-outerLink">
<a class="footer-sinaMblog" href="#" target="_blank"><i class="i_icon"></i></a>
<a id="footer-weChat" class="footer-weChat" href="javascript:void(0);" target="_blank"><i class="i_icon"></i></a>
<a class="footer-qzone" href="#" target="_blank"><i class="i_icon"></i></a>
</div>
</div>
<div class="clear"></div>
<div id="flymeCopyright" class="copyrightWrap">
<div class="copyrightInner">
<span>©2018 Meizu Telecom Equipment Co., Ltd. All rights reserved.</span>
<a href="#" class="linkAGray" target="_blank">备案号: 京ICP备123456789号-4</a>
<a href="#" class="linkAGray" target="_blank">经营许可证编号: 京A1-20280198</a>
<a target="_blank" href="#" class="linkAGray">营业执照</a>
</div>
</div>
</div>
</div>
<!-- 底部区域 E -->
<script type="text/javascript">
//
nLogin();
</script>
</body>
</html>
11. 项目实战前台之商品展示
- 本节将实现商城项目前台:分类导航、商品列表和商品详情的功能实现,关于商城首页的商品信息展示将会在项目的后期完成。
- 分类导航:将商品的一级类别信息作为页面的导航链接信息显示

- 商品列表:在
/list或/list/页号请求中分类分页展示商品信息

- 商品详情:在
/detail/gid号请求中展示指定商品id号的商品信息

- 商城首页:输出部分最新商品、热卖商品、推荐商品或点击量最高的信息,由于目前数据信息不完善,故后期完成。

(1). 项目urls路由信息配置
- 在数据库
shopdb中已存在数据表goods和type,并且内有测试数据。 - 在common应用目录中的
myobject/common/models.py模型文件中,已存在Goods和Types模型类的定义。 - 打开根路由文件:myobject/web/urls.py路由文件,编辑路由配置信息
from django.conf.urls import url
from web.views import index
urlpatterns = [
#网站前台
url(r'^$',index.index,name="index"), #首页
url(r'^list$',index.lists,name="list"), #商品列表展示
#url(r'^list/(?P<pIndex>[0-9]+)$',index.lists,name="list"), #分页商品列表展示
url(r'^detail/(?P<gid>[0-9]+)$',index.detail,name="detail"), #商品详情
# 会员及个人中心等路由配置
url(r'^login$', index.login, name="login"),
url(r'^dologin$', index.dologin, name="dologin"),
url(r'^logout$', index.logout, name="logout"),
]
(2). 编辑视图文件
- 新建视图文件:myobject/web/views/index.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator
from common.models import Users,Types,Goods
# 公共信息加载
def loadinfo(request):
'''公共信息加载'''
context = {}
lists = Types.objects.filter(pid=0)
context['typelist'] = lists
return context
# =============商品展示========================
def index(request):
'''项目前台首页'''
context = loadinfo(request)
return render(request,"web/index.html",context)
def lists(request,pIndex=1):
'''商品列表页(搜索&分页)'''
context = loadinfo(request)
#获取商品信息查询对象
mod = Goods.objects
mywhere=[] #定义一个用于存放搜索条件列表
#判断添加搜索条件
tid = int(request.GET.get('tid',0))
if tid > 0:
list = mod.filter(typeid__in=Types.objects.only('id').filter(pid=tid))
mywhere.append("tid="+str(tid))
else:
list = mod.filter()
#获取、判断并封装关keyword键搜索
kw = request.GET.get("keyword",None)
if kw:
# 查询商品名中只要含有关键字的都可以
list = list.filter(goods__contains=kw)
mywhere.append("keyword="+kw)
#执行分页处理
pIndex = int(pIndex)
page = Paginator(list,5) #以5条每页创建分页对象
maxpages = page.num_pages #最大页数
#判断页数是否越界
if pIndex > maxpages:
pIndex = maxpages
if pIndex < 1:
pIndex = 1
list2 = page.page(pIndex) #当前页数据
plist = page.page_range #页码数列表
#封装信息加载模板输出
context['goodslist'] = list2
context['plist'] = plist
context['pIndex'] = pIndex
context['maxpages'] = maxpages
context['mywhere'] = mywhere
context['tid'] = int(tid)
return render(request,"web/list.html",context)
def detail(request,gid):
'''商品详情页'''
context = loadinfo(request)
#加载商品详情信息
ob = Goods.objects.get(id=gid)
ob.clicknum += 1 # 点击量加1
ob.save()
context['goods'] = ob
return render(request,"web/detail.html",context)
# ==============前台会员登录====================
# 略(上节中已编写)... ...
(3). 编写模板文件
- 3.1. 打开父类模板:/templates/web/base.html ,编辑导航栏代码
...
<ul class="nav navbar-nav navbar-right layout-header-nav clearfix">
<li class="layout-header-nav-item">
<a href="{% url 'index' %}" class="layout-header-nav-link">网站首页</a><p class="line-top hidden-xs"></p>
</li>
<li class="layout-header-nav-item"><a href="{% url 'list' %}" class="layout-header-nav-link">全部商品</a></li>
{% for type in typelist %}
<li class="layout-header-nav-item">
<a href="{% url 'list' %}?tid={{ type.id }}" class="layout-header-nav-link">{{ type.name }}</a>
</li>
{% endfor %}
<li class="layout-header-nav-item"><a href="#" class="layout-header-nav-link">社区</a></li>
</ul>
...
- 3.2. 商品列表信息显示模板:/templates/web/list.html
...
<!-- 商品列表 -->
<div class="goods-list">
<div class="row">
{% for goods in goodslist %}
<div class="col-md-3 col-sm-6 col-xs-6">
<div class="gl-item">
<div class="compare-btn-list" >
<i class="iconfont icon-duibi compare-duibi"></i>
<span class="hidden-xs hidden-sm">对比</span>
</div>
<div class="gl-item-wrap">
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active mod-pic" id="list-p1">
<a href="{% url 'detail' goods.id %}">
<img class="lazy j-modProduct" src="/static/goods/m_{{ goods.picname }}" width="220" height="220">
</a>
</div>
</div>
<!-- Nav tabs -->
<div class="item-slide j-pro-wrap hidden-xs hidden-sm">
<ul class="nav nav-tabs " role="tablist">
<li role="presentation" class="active">
<a href="#list-p1" aria-controls="list-p1" role="tab" data-toggle="tab">
<img class="lazy" src="/static/goods/s_{{ goods.picname }}" style="display: inline;" width="40" height="40">
</a>
</li>
</ul>
</div>
<div class="slide-btn j-modBtns" style="display: none;">
<span class="prev iconfont disabled"></span>
<span class="next iconfont"></span>
</div>
<h2>{{ goods.goods}}</h2>
<h3 class="red" title="双11促销:满500,减100!">
双11促销:满500,减100!
</h3>
<dd class="mod-price">
<span>¥</span>
<span class="vm-price">{{ goods.price }}</span>
<span class="vm-start">起</span>
</dd>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- 商品列表 -->
<nav aria-label="Page navigation" class="text-center">
<ul class="pagination">
<li>
<a href="{% url 'list' pIndex|add:-1 %}?{{ mywhere|join:'&' }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% for p in plist %}
<li {% if pIndex == p %}class="active"{% endif %}>
<a href="{% url 'list' p %}?{{ mywhere|join:'&' }}">{{p}}</a>
</li>
{% endfor %}
<li>
<a href="{% url 'list' pIndex|add:1 %}?{{ mywhere|join:'&' }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
...
- 3.3. 商品详情信息显示模板:/templates/web/detail.html
在详情模板中输出部分商品信息即可
12. 项目实战前台之购物车实战
- 本节将实现商城项目前台的购物车管理模块:添加、浏览、删除、修改和清空等购物车操作。


(1). 开发前的准备工作:
- 在数据库
shopdb中已存在数据表goods,并且内有测试数据。 - 默认开启了session,并且执行了数据迁移,数据库中已存在
django_session表。 - 在common应用目录中的
myobject/common/models.py模型文件中,已存在Goods模型类的定义。 - 并且在
Goods模型类中已经定义了toDict(self)方法:
# 注意:返回此商品的字典格式
def toDict(self):
return {'id':self.id,'typeid':self.typeid,'goods':self.goods,'company':self.company,'price':self.price,'picname':self.picname,'store':self.store,'num':self.num,'clicknum':self.clicknum,'state':self.state}
- 在商品详情页中处理
立即购买按钮,完成向添加购物车操作的传值和跳转:
<form action="{% url 'cart_add' goods.id %}" method="post">
{% csrf_token %}
<div class="property-buy">
<p class="vm-message" id="J_message"></p>
<dl class="property-buy-quantity">
<dt class="vm-metatit">数<span class="s-space"></span><span class="s-space"></span>量:</dt>
<dd class="clearfix">
<div class="mod-control">
<a title="减少" href="javascript:;" class="vm-minus disabled">-</a>
<input value="1" name="m" id="J_quantity" data-max="5" type="text">
<a title="增加" href="javascript:;" class="vm-plus">+</a>
</div>
</dd>
</dl>
<div class="property-buy-action">
<button data-mtype="store_de_buy" type="submit" id="J_btnBuy" class="btn btn-danger btn-lg mr20">立即购买</button>
<a data-mtype="store_de_cart" href="javascript:void(0);" id="J_btnAddCart" class="btn btn-primary btn-lg hide" style="display:inline-block;"><i></i>加入购物车</a>
<span class="vm-service" id="J_panicBuyingWrap"></span>
</div>
</div>
</form>
(2). 项目urls路由信息配置
- 打开根路由文件:myobject/web/urls.py路由文件,编辑路由配置信息:
from django.conf.urls import url
from web.views import index,cart
urlpatterns = [
#网站前台
# ... ...
# 会员及个人中心等路由配置
# ... ...
# 购物车路由
url(r'^cart$', cart.index,name='cart_index'), #浏览购物车
url(r'^cart/add/(?P<gid>[0-9]+)$', cart.add,name='cart_add'), #添加购物车
url(r'^cart/del/(?P<gid>[0-9]+)$', cart.delete,name='cart_del'), #从购物车中删除一个商品
url(r'^cart/clear$', cart.clear,name='cart_clear'), #清空购物车
url(r'^cart/change$', cart.change,name='cart_change'), #更改购物车中商品数量
]
(3). 编辑视图文件
- 新建视图文件:myobject/web/views/cart.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Goods,Types
# 公共信息加载
def loadinfo(request):
'''公共信息加载'''
context = {}
lists = Types.objects.filter(pid=0)
context['typelist'] = lists
return context
def index(request):
'''浏览购物车'''
context = loadinfo(request)
if 'shoplist' not in request.session:
request.session['shoplist']={}
return render(request,"web/cart.html",context)
def add(request,gid):
'''在购物车中放入商品信息'''
#获取要放入购物车中的商品信息
goods = Goods.objects.get(id=gid)
shop = goods.toDict();
shop['m'] = int(request.POST.get('m',1)) #添加一个购买量属性m
#从session获取购物车信息,没有默认空字典
shoplist = request.session.get('shoplist',{})
#判断此商品是否在购物车中
if gid in shoplist:
#商品数量加
shoplist[gid]['m']+=shop['m']
else:
#新商品添加
shoplist[gid]=shop
#将购物车信息放回到session
request.session['shoplist'] = shoplist
#重定向到浏览购物车页
return redirect(reverse('cart_index'))
#return render(request,"web/cart.html")
def delete(request,gid):
'''删除一个商品'''
shoplist = request.session['shoplist']
del shoplist[gid]
request.session['shoplist'] = shoplist
return redirect(reverse('cart_index'))
def clear(request):
'''清空购物车'''
context = loadinfo(request)
request.session['shoplist'] = {}
return render(request,"web/cart.html",context)
def change(request):
'''更改购物车中的商品信息'''
#context = loadinfo(request)
shoplist = request.session['shoplist']
#获取信息
shopid = request.GET.get('gid','0')
num = int(request.GET['num'])
if num<1:
num = 1
shoplist[shopid]['m'] = num #更改商品数量
request.session['shoplist'] = shoplist
return redirect(reverse('cart_index'))
#return render(request,"web/cart.html",context)
(4). 编写模板文件
- 4.1. 打开父类模板:/templates/web/base.html ,编辑导航栏代码
...
<li class="layout-header-service-item layout-header-service-cart" id="layoutHeaderCart">
<a class="layout-header-service-link" href="{% url 'cart_index' %}" data-mtype="wmz_public_yt_cart">
<i class="glyphicon glyphicon-shopping-cart"></i>
<span class="layout-header-service-cart-num">{{ request.session.shoplist|length}}</span>
</a>
</li>
...
- 4.2. 购物车页面信息显示模板:/templates/web/cart.html
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart-app.css' %}">
{% endblock %}
{% block mainbody %}
<div class="mainbody cart" style="margin-top: 80px;">
<div class="container">
<!-- 购物车详情头 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-select col-md-3 col-xs-3 col-sm-3">
<div class="cart-select-all JSelectAll">
<div class="mz-checkbox"></div>
<span class="cart-select-title">全选</span>
</div>
</td>
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">商品</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">单价(元)</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">数量</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">小计(元)</td>
<td class="cart-col-ctrl col-md-1 hidden-xs hidden-sm">操作</td>
</tr>
</tbody>
</table><!-- 购物车详情头 E-->
<!-- 购物清单信息列表 -->
<div class="cart-merchant-list">
<div class="cart-merchant">
<table class="cart-merchant-body">
<tbody>
{% for shop in request.session.shoplist.values %}
<tr class="cart-product">
<td class="cart-col-select col-md-3 col-xs-4 col-sm-4">
<div class="mz-checkbox" gid="{{shop.id}}" price="{% widthratio shop.price 1 shop.m %}"></div>
<a href="{% url 'detail' shop.id %}" class="cart-product-link" target="_blank">
<img src="/static/goods/s_{{ shop.picname }}" class="cart-product-img" alt="{{ shop.goods }}" width="50">
</a>
</td>
<td class="cart-col-name col-md-3 col-xs-8 col-sm-8">
<a href="{% url 'detail' shop.id %}" class="cart-product-link" target="_blank">
<p>{{ shop.goods }}</p>
</a>
<p class="">
<span class="cart-product-price">{{ shop.price }}</span>
</p>
<div class="cart-col-number">
<div class="cart-product-number-adder">
<p class="cart-product-number-max show"></p>
<div class="mz-adder">
<button class="mz-adder-subtract disabled"></button>
<div class="mz-adder-num"><input class="mz-adder-input" value="1" type="text"></div>
<button class="mz-adder-add"></button>
</div>
</div>
</div>
</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">
<p>
<span class="cart-product-price">{{ shop.price}}</span>
</p>
</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">
<div class="cart-product-number-adder">
<p class="cart-product-number-max show"></p>
<div class="mz-adder">
<button onclick="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num={{shop.m|add:-1}}'" class="mz-adder-subtract"></button>
<div class="mz-adder-num"><input class="mz-adder-input" value="{{ shop.m }}" onblur="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num='+this.value" type="text"></div>
<button onclick="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num={{shop.m|add:1}}'" class="mz-adder-add"></button>
</div>
</div>
</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">
<span class="cart-product-price total">{% widthratio shop.price 1 shop.m %}</span>
</td>
<td class="cart-col-ctrl col-md-1 hidden-xs hidden-sm">
<a href="{% url 'cart_del' shop.id %}" title="删除">
<div class="cart-product-remove">
<span class="glyphicon glyphicon-remove"></span>
</div>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div><!-- 购物清单信息列表 E-->
</div>
<!-- 结算详情 -->
<div class="cart-footer" id="cartFooter">
<div class="container">
<div class="cart-footer-left col-md-6 col-xs-4 col-sm-4">
<div class="cart-select-all JSelectAll" data-mdesc="全选按钮" data-mtype="store_cart_all">
<div class="mz-checkbox"></div>
<span class="cart-select-title">全选</span>
</div>
<!-- <span class="cart-remove-selected" id="removeSelected">删除选中的商品</span> -->
<span class="cart-footer-count">
共
<span class="cart-footer-num" id="totalCount"></span>
件商品
</span>
<div class="mz-btn btn-danger" onclick="window.location='{% url 'cart_clear' %}'" id="cartSubmit">清空购物车</div>
</div>
<div class="cart-footer-right col-md-5 col-md-offset-1 col-sm-offset-2 col-xs-8 col-sm-6">
<span class="cart-footer-sum">
<span class="cart-footer-text">已优惠</span>
<span class="cart-footer-num red" id="totalDiscount">0.00</span>
<span class="cart-footer-text">元, 合计(不含运费):</span>
<span class="cart-footer-total" id="totalPrice">0.0</span>
</span>
<div onclick="window.location='/orders/add?ids='+loadTotal().join(',')" class="mz-btn btn-success" id="cartSubmit">去结算</div>
</div>
</div>
</div><!-- 结算详情 E-->
</div>
{% endblock %}
{% block myjs %}
//全选
allSelect();
//登录图片鼠标经过
//topLogin();
//商品数量加减
//cartAddMin()
//loadTotal();
var gidlist = [];
{% endblock %}
13. 项目实战前台之下单操作
- 本节将实现商城项目中会员下单操作模块:填写收货地址、确认订单信息和执行下单等操作。



(1). 开发前的准备工作:
- 在数据库 shopdb 中已存在订单表orders和订单详情表detail。
- 在执行下单操作前要确认会员必须在登录后状态(使用中间件验证)。
#在myobject/common/shopmiddleware.py中的__call__()方法中条件如下代码
# ... ...
# 网站前台登录用户判断(订单操作和会员中心操作需登录)
if re.match("^/orders",path) or re.match("^/vip",path):
# 判断当前用户是否没有登录
if "vipuser" not in request.session:
# 执行登录界面跳转
return redirect(reverse('login'))
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
- 在common应用目录中的myobject/common/models.py 模型文件中,已存在Orders和Detail模型类的定义。
# ... ...
# 订单模型
class Orders(models.Model):
uid = models.IntegerField()
linkman = models.CharField(max_length=32)
address = models.CharField(max_length=255)
code = models.CharField(max_length=6)
phone = models.CharField(max_length=16)
addtime = models.DateTimeField(default=datetime.now)
total = models.FloatField()
state = models.IntegerField()
class Meta:
db_table = "orders" # 更改表名
#订单详情模型
class Detail(models.Model):
orderid = models.IntegerField()
goodsid = models.IntegerField()
name = models.CharField(max_length=32)
price = models.FloatField()
num = models.IntegerField()
class Meta:
db_table = "detail" # 更改表名
-
在商品购物车页中处理下单按钮,完成向下单操作界面的传值和跳转:
-
首先将实现准备的
topNav.js替换掉项目中的web/js/topNav.js<div onclick="window.location='/orders/add?ids='+loadTotal().join(',')" class="mz-btn btn-success" id="cartSubmit">去结算</div>
(2). 项目urls路由信息配置
- 打开根路由文件:myobject/web/urls.py路由文件,编辑路由配置信息:
from django.conf.urls import url
from web.views import index,cart,orders
urlpatterns = [
#网站前台
# ... ...
# 会员及个人中心等路由配置
# ... ...
# 购物车路由
# ... ...
# 订单处理
url(r'^orders/add$', orders.add,name='orders_add'), #订单的表单页
url(r'^orders/confirm$', orders.confirm,name='orders_confirm'), #订单确认页
url(r'^orders/insert$', orders.insert,name='orders_insert'), #执行订单添加操作
]
(3). 编辑视图文件
- 新建视图文件:myobject/web/views/orders.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Goods,Types,Orders,Detail
from datetime import datetime
# 公共信息加载
def loadinfo(request):
'''公共信息加载'''
context = {}
lists = Types.objects.filter(pid=0)
context['typelist'] = lists
return context
def add(request):
'''下订单第一步:订单表单'''
context = loadinfo(request)
# 获取要结算商品的id号
ids = request.GET.get("ids",'')
if len(ids) == 0:
context = {"info":"请选择要结算的商品!"}
return render(request,"web/ordersinfo.html",context)
gidlist = ids.split(',')
# 从购物车获取要结算所有商品,并放入到orderslist中,并且累计总金额
shoplist = request.session['shoplist']
orderslist = {}
total = 0.0
for gid in gidlist:
orderslist[gid] = shoplist[gid]
total += shoplist[gid]['price']*shoplist[gid]['m']
# 将这些信息放入到session中
request.session['orderslist'] = orderslist
request.session['total'] = total
return render(request,"web/ordersadd.html",context)
def confirm(request):
context = loadinfo(request)
return render(request,"web/ordersconfirm.html",context)
def insert(request):
context = loadinfo(request)
try:
# 执行订单信息添加操作
od = Orders()
od.uid = request.session['vipuser']['id'] #当前登录者的id号
od.linkman = request.POST.get('linkman')
od.address = request.POST.get('address')
od.code = request.POST.get('code')
od.phone = request.POST.get('phone')
od.addtime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
od.total = request.session['total']
od.state = 0
od.save()
# 执行订单详情添加
orderslist = request.session['orderslist']
shoplist = request.session['shoplist']
for shop in orderslist.values():
del shoplist[str(shop['id'])]
ov = Detail()
ov.orderid = od.id
ov.goodsid = shop['id']
ov.name = shop['goods']
ov.price = shop['price']
ov.num = shop['m']
ov.save()
del request.session['orderslist']
del request.session['total']
request.session['shoplist'] = shoplist
context = {"info":"订单添加成功!订单号:"+str(od.id)}
return render(request,"web/ordersinfo.html",context)
except Exception as err:
print(err)
context = {"info":"订单添加失败,请稍后再试!"}
return render(request,"web/ordersinfo.html",context)
(4). 编写模板文件
- 4.1. 打开购物车模板:/templates/web/cart.html ,编辑js代码,替换topNav.js文件
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart-app.css' %}">
{% endblock %}
{% block mainbody %}
<div class="mainbody cart" style="margin-top: 80px;">
<div class="container">
<!-- 购物车详情头 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-select col-md-3 col-xs-3 col-sm-3">
<div class="cart-select-all JSelectAll">
<div class="mz-checkbox"></div>
<span class="cart-select-title">全选</span>
</div>
</td>
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">商品</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">单价(元)</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">数量</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">小计(元)</td>
<td class="cart-col-ctrl col-md-1 hidden-xs hidden-sm">操作</td>
</tr>
</tbody>
</table><!-- 购物车详情头 E-->
<!-- 购物清单信息列表 -->
<div class="cart-merchant-list">
<div class="cart-merchant">
<table class="cart-merchant-body">
<tbody>
{% for shop in request.session.shoplist.values %}
<tr class="cart-product">
<td class="cart-col-select col-md-3 col-xs-4 col-sm-4">
<div class="mz-checkbox" gid="{{shop.id}}" price="{% widthratio shop.price 1 shop.m %}"></div>
<a href="{% url 'detail' shop.id %}" class="cart-product-link" target="_blank">
<img src="/static/goods/s_{{ shop.picname }}" class="cart-product-img" alt="{{ shop.goods }}" width="50">
</a>
</td>
<td class="cart-col-name col-md-3 col-xs-8 col-sm-8">
<a href="{% url 'detail' shop.id %}" class="cart-product-link" target="_blank">
<p>{{ shop.goods }}</p>
</a>
<p class="">
<span class="cart-product-price">{{ shop.price }}</span>
</p>
<div class="cart-col-number">
<div class="cart-product-number-adder">
<p class="cart-product-number-max show"></p>
<div class="mz-adder">
<button class="mz-adder-subtract disabled"></button>
<div class="mz-adder-num"><input class="mz-adder-input" value="1" type="text"></div>
<button class="mz-adder-add"></button>
</div>
</div>
</div>
</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">
<p>
<span class="cart-product-price">{{ shop.price}}</span>
</p>
</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">
<div class="cart-product-number-adder">
<p class="cart-product-number-max show"></p>
<div class="mz-adder">
<button onclick="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num={{shop.m|add:-1}}'" class="mz-adder-subtract"></button>
<div class="mz-adder-num"><input class="mz-adder-input" value="{{ shop.m }}" onblur="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num='+this.value" type="text"></div>
<button onclick="window.location='{% url 'cart_change' %}?gid={{shop.id}}&num={{shop.m|add:1}}'" class="mz-adder-add"></button>
</div>
</div>
</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">
<span class="cart-product-price total">{% widthratio shop.price 1 shop.m %}</span>
</td>
<td class="cart-col-ctrl col-md-1 hidden-xs hidden-sm">
<a href="{% url 'cart_del' shop.id %}" title="删除">
<div class="cart-product-remove">
<span class="glyphicon glyphicon-remove"></span>
</div>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div><!-- 购物清单信息列表 E-->
</div>
<!-- 结算详情 -->
<div class="cart-footer" id="cartFooter">
<div class="container">
<div class="cart-footer-left col-md-6 col-xs-4 col-sm-4">
<div class="cart-select-all JSelectAll" data-mdesc="全选按钮" data-mtype="store_cart_all">
<div class="mz-checkbox"></div>
<span class="cart-select-title">全选</span>
</div>
<!-- <span class="cart-remove-selected" id="removeSelected">删除选中的商品</span> -->
<span class="cart-footer-count">
共
<span class="cart-footer-num" id="totalCount"></span>
件商品
</span>
<div class="mz-btn btn-danger" onclick="window.location='{% url 'cart_clear' %}'" id="cartSubmit">清空购物车</div>
</div>
<div class="cart-footer-right col-md-5 col-md-offset-1 col-sm-offset-2 col-xs-8 col-sm-6">
<span class="cart-footer-sum">
<span class="cart-footer-text">已优惠</span>
<span class="cart-footer-num red" id="totalDiscount">0.00</span>
<span class="cart-footer-text">元, 合计(不含运费):</span>
<span class="cart-footer-total" id="totalPrice">0.0</span>
</span>
<div onclick="window.location='/orders/add?ids='+loadTotal().join(',')" class="mz-btn btn-success" id="cartSubmit">去结算</div>
</div>
</div>
</div><!-- 结算详情 E-->
</div>
{% endblock %}
{% block myjs %}
//全选
allSelect();
//登录图片鼠标经过
//topLogin();
//商品数量加减
//cartAddMin()
loadTotal();
var gidlist = [];
{% endblock %}
- 4.2. 订单地址填写模板:/templates/web/ordersadd.html
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart-app.css' %}">
{% endblock %}
{% block mainbody %}
<form action="{% url 'orders_confirm' %}" method="post">
{% csrf_token %}
<div class="mainbody cart" style="margin-top: 80px;">
<div class="container">
<!-- 下订单的1/3步骤 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-select col-md-12">
当前位置: 订单处理 > 1/3 填写收货地址:
</td>
</tr>
</tbody>
</table><!-- 下订单的1/3步骤 E-->
<!-- 订单物流信息 -->
<div class="cart-merchant-list">
<div class="cart-merchant">
<table class="cart-merchant-body">
<tbody>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
联系人:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" name="linkman" value="{{ request.session.vipuser.name}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
收货地址:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" name="address" value="{{ request.session.vipuser.address}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
联系电话:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" name="phone" value="{{ request.session.vipuser.phone}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
邮编:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" name="code" value="{{ request.session.vipuser.code}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
总金额:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" disabled value="{{ request.session.total }} 元" name="linkman" size="40"/>
</td>
</tr>
</tbody>
</table>
</div>
</div><!-- 订单物流信息 E-->
</div>
<!-- 操作按钮 -->
<div class="cart-footer" id="cartFooter">
<div class="container">
<div class="cart-footer-right col-md-12" style="text-align:center">
<div onclick="window.history.go(-1)" class="mz-btn btn-success" id="cartSubmit">返回</div>
<button type="submit" class="mz-btn btn-success" id="cartSubmit">下一步</button>
</div>
</div>
</div><!-- 操作按钮 E-->
</div></form>
{% endblock %}
- 4.3. 订单确认模板:/templates/web/ordersconfirm.html
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart-app.css' %}">
{% endblock %}
{% block mainbody %}
<form action="{% url 'orders_insert' %}" method="post">
{% csrf_token %}
<div class="mainbody cart" style="margin-top: 80px;padding-bottom:5px;">
<div class="container">
<!-- 下订单的2/3步骤 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-select col-md-12">
当前位置: 订单处理 > 2/3 确认订单信息:
</td>
</tr>
</tbody>
</table><!-- 下订单的2/3步骤 E-->
<!-- 订单通信信息 -->
<div class="cart-merchant-list">
<div class="cart-merchant">
<table class="cart-merchant-body">
<tbody>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
联系人:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input readonly type="text" name="linkman" value="{{ request.POST.linkman}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
收货地址:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input readonly type="text" name="address" value="{{ request.POST.address}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
联系电话:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input readonly type="text" name="phone" value="{{ request.POST.phone}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
邮编:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input readonly type="text" name="code" value="{{ request.POST.code}}" size="40"/>
</td>
</tr>
<tr class="cart-product" style="height:60px;border:none;">
<td class="cart-col-select col-md-2 col-xs-3 col-sm-3" style="text-align: right;">
总金额:
</td>
<td class="cart-col-name col-md-3 col-xs-4 col-sm-4">
<input type="text" disabled value="{{ request.session.total }} 元" name="linkman" size="40"/>
</td>
</tr>
</tbody>
</table>
</div>
</div><!-- 订单通信信息 E-->
</div>
<!-- 操作按钮 -->
<div class="cart-footer" id="cartFooter">
<div class="container">
<div class="cart-footer-right col-md-12" style="text-align:center">
<div onclick="window.history.go(-1)" class="mz-btn btn-success" id="cartSubmit">返回修改</div>
<button type="submit" class="mz-btn btn-success" id="cartSubmit">确认下单</button>
</div>
</div>
</div><!-- 操作按钮 E-->
</div></form>
<div class="mainbody cart" style="padding-bottom:15px;">
<div class="container">
<!-- 订单信息头 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">商品</td>
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">图片</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">单价(元)</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">数量</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">小计(元)</td>
</tr>
</tbody>
</table><!-- 订单信息头 E-->
<!-- 订单确认信息 -->
<div class="cart-merchant-list">
<div class="cart-merchant">
<table class="cart-merchant-body">
<tbody>
{% for shop in request.session.orderslist.values %}
<tr class="cart-product" style="height:100px;">
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">
<p>
<div class="mz-adder-num">{{ shop.goods }}</div>
</p>
</td>
<td class="cart-col-name col-md-3 hidden-xs hidden-sm">
<p>
<div class="mz-adder-num"><img src="/static/goods/s_{{ shop.picname }}" alt="{{ shop.goods }}" width="50"></div>
</p>
</td>
<td class="cart-col-price col-md-2 hidden-xs hidden-sm">
<p>
<span class="cart-product-price">{{ shop.price}}</span>
</p>
</td>
<td class="cart-col-number col-md-2 hidden-xs hidden-sm">
<p>
<div class="mz-adder-num">{{ shop.m }}</div>
</p>
</td>
<td class="cart-col-total col-md-1 hidden-xs hidden-sm">
<span class="cart-product-price total">{% widthratio shop.price 1 shop.m %}</span>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div><!-- 订单确认信息 E-->
</div>
</div>
{% endblock %}
- 4.4. 订单信息提示模板:/templates/web/ordersinfo.html
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/cart-app.css' %}">
{% endblock %}
{% block mainbody %}
<div class="mainbody cart" style="margin-top: 80px;">
<div class="container">
<!-- 下订单的3/3步骤 -->
<table class="cart-header">
<tbody>
<tr>
<td class="cart-col-select col-md-12">
当前位置: 订单处理 > 3/3 订单完成:
</td>
</tr>
</tbody>
</table><!-- 下订单的3/3步骤 E-->
<!-- 下订单提示信息 -->
<div class="cart-merchant-list">
<div class="cart-merchant text-center" style="font-size:30px;color:#fc0;line-height:100px;">
{{ info }}
</div>
</div><!-- 下订单提示信息 E-->
</div>
<!-- 操作按钮 -->
<div class="cart-footer" id="cartFooter">
<div class="container">
<div class="cart-footer-right col-md-12" style="text-align:center">
<div onclick="window.history.go(-1)" class="mz-btn btn-success" id="cartSubmit">返回</div>
<a href="{% url 'index' %}" class="mz-btn btn-success">首页</a>
</div>
</div>
</div><!-- 操作按钮 E-->
</div>
{% endblock %}
14. 项目实战前台之个人中心
(1). 个人中心简介:
- 会员个人中心模块:浏览个人信息、修改个人信息、密码重置、查看个人订单和处理订单等处理操作。
- 由于个人中心的信息操作大部分都是在网站后台锻炼过,所以本节重点讲解个人中心的订单管理。

(2). 项目urls路由信息配置
- 打开根路由文件:myobject/web/urls.py路由文件,编辑路由配置信息:
from django.conf.urls import url
from web.views import index,cart,orders,vip
urlpatterns = [
#网站前台
# ... ...
# 会员及个人中心等路由配置
# ... ...
# 购物车路由
# ... ...
# 订单处理
# ... ...
# 会员中心
url(r'^vip/orders$', vip.viporders,name='vip_orders'), #会员中心我的订单
url(r'^vip/odstate$', vip.odstate,name='vip_odstate'), #修改订单状态(确认收货)
#url(r'^vip/info$', vip.info,name='vip_info'), #会员中心的个人信息
#url(r'^vip/update$', vip.update,name='vip_update'), #执行修改会员信息
#url(r'^vip/resetps$', vip.resetps,name='vip_resetps'), #重置密码表单
#url(r'^vip/doresetps$', vip.doresetps,name='vip_doresetps'), #执行重置密码
]
(3). 编辑视图文件
- 新建视图文件:myobject/web/views/vip.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from common.models import Users,Goods,Types,Orders,Detail
# 公共信息加载
def loadinfo(request):
'''公共信息加载'''
context = {}
lists = Types.objects.filter(pid=0)
context['typelist'] = lists
return context
# 我的订单
def viporders(request):
'''当前用户订单'''
context = loadinfo(request)
# 获取当前用户的所有订单信息
odlist = Orders.objects.filter(uid=request.session['vipuser']['id'])
# 遍历当前用户的所有订单,添加他的订单详情
for od in odlist:
delist = Detail.objects.filter(orderid=od.id)
# 遍历每个商品详情,从Goods中获取对应的图片
for og in delist:
og.picname = Goods.objects.only('picname').get(id=og.goodsid).picname
od.detaillist = delist
# 将整理好的订单信息放置到模板遍历中
context['orderslist'] = odlist
return render(request,"web/viporders.html",context)
def odstate(request):
''' 修改订单状态 '''
try:
oid = request.GET.get("oid",'0')
ob = Orders.objects.get(id=oid)
ob.state = request.GET['state']
ob.save()
return redirect(reverse('vip_orders'))
except Exception as err:
print(err)
return HttpResponse("订单处理失败!")
(4). 编写模板文件
- 4.1. 新建订单信息显示模板:/templates/web/viporders.html。
{% extends "web/base.html" %}
{% load static from staticfiles %}
{% block mylink %}
<link rel="stylesheet" type="text/css" href="{% static 'web/css/order.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'web/css/order-app.css' %}">
{% endblock %}
{% block mainbody %}
<div class="mainbody order">
<div class="container">
<!-- 面包屑导航 -->
<div class="crumbs col-xs-12 col-sm-12">
<ol class="breadcrumb">
<li class="hidden-xs hidden-sm"><a href="index.html">首页</a></li>
<li class="hidden-xs hidden-sm"><a href="member.html">我的商城</a></li>
<li class="active">我的订单</li>
</ol>
</div><!-- 面包屑导航 E-->
<div class="main clearfix">
<!-- 左侧导航 -->
<div class="left-nav f-fl col-md-4 hidden-xs hidden-sm">
<div class="nav-main">
<a href="javascript:;" class="type-title"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span>订单中心</a>
<a href="order.html" class="ml active" >我的订单</a>
<a href="#" class="ml " >我的回购单</a>
<a href="#" class="ml " >我的意外保</a>
<a href="javascript:;" class="type-title"><span class="glyphicon glyphicon-user" aria-hidden="true"></span>个人中心</a>
<a href="/#" class="ml " >地址管理</a>
<a href="#" class="ml " >我的收藏</a>
<a href="#" class="ml " >消息提醒</a>
<a href="#" class="ml " >建议反馈</a>
</div>
</div><!-- 左侧导航 E-->
<!-- 右侧内容展示 -->
<div class="right-content f-fr col-md-8 col-xs-12 col-sm-12">
<div class="order-main">
<div class="type-tab-btn">
<a href="javascript:;" class="allOrder active col-20" data-type="-1">全部订单</a><i class="line hidden-xs hidden-sm">|</i>
<a class="waitPay col-20" href="javascript:;" data-type="0">待付款<span class="amount _actAmount"></span></a><i class="line hidden-xs hidden-sm">|</i>
<a class="waitDeliver col-20" href="javascript:;" data-type="1">待发货</a><i class="line hidden-xs hidden-sm">|</i>
<a class="hasDeliver col-20" href="javascript:;" data-type="2">已发货</a><i class="line hidden-xs hidden-sm">|</i>
<a class="other col-20" href="javascript:;" data-type="99">其他</a>
</div>
<div class="list-head hidden-xs hidden-sm">
<ul class="clearfix">
<li class="w50">
<select id="checkType" class="check-type">
<option value="0">近三个月的订单</option>
<option value="1">全部订单</option>
</select>
订单明细
</li>
<li class="w125">售后</li>
<li class="w125">金额</li>
<li class="w125">状态</li>
<li class="w125">操作</li>
</ul>
</div>
<div id="tableList" class="type-contain ui-load-container">
<!-- 每个订单信息 -->
{% for orders in orderslist %}
<div class="ui-load-content" style="margin-top: 20px">
<table class="orderItem">
<tbody>
<tr class="trHead hidden-xs hidden-sm">
<td colspan="4" class="title clearfix">
<div class="f-fl">
订单号:<span class="time">201700{{ orders.id }}</span>
收货人:<span class="orderNumber">{{ orders.linkman }}</span>
收货地址:<span class="orderNumber">{{ orders.address }}</span>
联系电话:<span class="orderNumber">{{ orders.phone }}</span>
</div>
</td>
</tr>
<tr class="list-box b-l b-r b-b">
<td class="list b-r j-iamCart">
<div class="cart-wrap j-CartWrap">
<div class="shop j-shop j-amLight">
{% for detail in orders.detaillist %}
<div class="item b-t clearfix j-item j-iamMain" style="height:110px;">
<a class="productDetail nameWidth col-xs-4 col-sm-4" href="{% url 'detail' detail.goodsid %}" target="_blank">
<img src="/static/goods/s_{{detail.picname}}" style="width:75px;height:75px" class="f-fl"/>
</a>
<div class="describe f-fl col-xs-8 col-sm-8">
<div class="vertic clearfix">
<span class="clearfix">
<a class="productDetail nameWidth" href="#" target="_blank">
<i>{{ detail.name }}</i></a>
<p>
¥{{ detail.price}}×{{ detail.num}}
</p>
</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</td>
<td class="b-r w125 center price b-t hidden-xs hidden-sm">
<div class="priceDiv">
¥ {{ orders.total }} 元
</div>
</td>
<td class="b-r w125 center state b-t hidden-xs hidden-sm">
<div class="stateDiv">
<div>
{% if orders.state == 0 %}
新订单 <br/><br/><br/>
【<a href="{% url 'vip_odstate' %}?oid={{orders.id}}&state=3">撤销订单</a>】
{% elif orders.state == 1 %}
已发货 <br/><br/>
【<a href="{% url 'vip_odstate' %}?oid={{orders.id}}&state=2">确认收货</a>】
{% elif orders.state == 2 %}
已完成
{% elif orders.state == 3 %}
无效订单
{% else %}
未知
{% endif %}
</div>
</div>
</td>
<td class="w125 center opreat b-t hidden-xs hidden-sm">
<ul>
<li class="more"><a href="#" target="_blank">查看详情</a></li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
{% endfor %}
<!-- 每个订单信息 end-->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 主内容区域 E-->
{% endblock %}
15. 项目实战后台之订单处理
(1). 后台订单处理说明:
- 后台订单处理模块:浏览订单(搜索&分页)、查看订单详情、订单处理(确认发货,撤销订单)等处理操作。


(2). 项目urls路由信息配置
- 打开根路由文件:myobject/myadmin/urls.py路由文件,编辑路由配置信息:
from django.conf.urls import url
from myadmin.views import index,users,type,goods,orders
urlpatterns = [
...
# 订单信息管理路由
url(r'^orders$', orders.index, name="myadmin_orders_index"),
url(r'^orders/(?P<pIndex>[0-9]+)$', orders.index, name="myadmin_orders_index"),
url(r'^orders/detail/(?P<oid>[0-9]+)$', orders.detail, name="myadmin_orders_detail"),
url(r'^orders/state$',orders.state, name="myadmin_orders_state"),
]
(3). 编辑视图文件
- 新建视图文件:myobject/myadmin/views/orders.py 视图文件,并进行编辑
from django.shortcuts import render
from django.http import HttpResponse
from django.db.models import Q
from django.core.paginator import Paginator
from common.models import Goods,Users,Orders,Detail
# Create your views here.
def index(request,pIndex=1):
'''浏览信息'''
#获取订单信息
mod = Orders.objects
mywhere=[]
# 获取、判断并封装关keyword键搜索
kw = request.GET.get("keyword",None)
if kw:
# 查询收件人和地址中只要含有关键字的都可以
list = mod.filter(Q(linkman_contains=kw) | Q(address__contains=kw))
mywhere.append("keyword="+kw)
else:
list = mod.filter()
# 获取、判断并封装订单状态state搜索条件
state = request.GET.get('state','')
if state != '':
list = list.filter(state=state)
mywhere.append("state="+state)
#执行分页处理
pIndex = int(pIndex)
page = Paginator(list,5) #以5条每页创建分页对象
maxpages = page.num_pages #最大页数
#判断页数是否越界
if pIndex > maxpages:
pIndex = maxpages
if pIndex < 1:
pIndex = 1
list2 = page.page(pIndex) #当前页数据
plist = page.page_range #页码数列表
# 遍历订单信息并追加 下订单人姓名信息
for od in list2:
user = Users.objects.only('name').get(id=od.uid)
od.name = user.name
#封装信息加载模板输出
context = {"orderslist":list2,'plist':plist,'pIndex':pIndex,'maxpages':maxpages,'mywhere':mywhere}
return render(request,"myadmin/orders/index.html",context)
def detail(request,oid):
''' 订单详情信息 '''
try:
# 加载订单信息
orders = Orders.objects.get(id=oid)
if orders != None:
user = Users.objects.only('name').get(id=orders.uid)
orders.name = user.name
# 加载订单详情
dlist = Detail.objects.filter(orderid=oid)
# 遍历每个商品详情,从Goods中获取对应的图片
for og in dlist:
og.picname = Goods.objects.only('picname').get(id=og.goodsid).picname
# 放置模板变量,加载模板并输出
context = {'orders':orders,'detaillist':dlist}
return render(request,"myadmin/orders/detail.html",context)
except Exception as err:
print(err)
context = {'info':'没有找到要修改的信息!'}
return render(request,"myadmin/info.html",context)
def state(request):
''' 修改订单状态 '''
try:
oid = request.GET.get("oid",'0')
ob = Orders.objects.get(id=oid)
ob.state = request.GET['state']
ob.save()
context = {'info':'修改成功!'}
except Exception as err:
print(err)
context = {'info':'修改失败!'}
return render(request,"myadmin/info.html",context)
(4). 编写模板文件
- 4.1. 订单信息浏览显示模板:/templates/myadmin/orders/index.html。
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h4>
订单信息管理
</h4>
<form class="form-inline" action="{% url 'myadmin_orders_index'%}" method="get">
<label>关键字:</label>
<input type="text" name="keyword" value="{{request.GET.keyword}}" class="input-small" placeholder="收货人或地址"/>
<label> 状态:</label>
<select name="state" class="span1" style="width:100px;">
<option value="">全部</option>
<option value="1" {% if request.GET.state == '0' %}selected{% endif %}>未发货</option>
<option value="1" {% if request.GET.state == '1' %}selected{% endif %}>已发货</option>
<option value="2" {% if request.GET.state == '2' %}selected{% endif %}>已完成</option>
<option value="3" {% if request.GET.state == '3' %}selected{% endif %}>已作废</option>
</select>
<button type="submit" class="btn">搜索</button>
<a href="{% url 'myadmin_goods_index' 1 %}" class="btn">全部</a>
</form>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>订单号</th>
<th>下单人</th>
<th>收货人</th>
<th>收货地址</th>
<th>联系电话</th>
<th>时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for vo in orderslist %}
<tr>
<td>{{ vo.id }}</td>
<td>{{ vo.name }}</td>
<td>{{ vo.linkman }}</td>
<td>{{ vo.address }}</td>
<td>{{ vo.phone }}</td>
<td>{{ vo.addtime|date:'Y-m-d H:i:s' }}</td>
<td>
{% if vo.state == 0 %}
新订单
{% elif vo.state == 1 %}
已发货
{% elif vo.state == 2 %}
已完成
{% elif vo.state == 3 %}
无效订单
{% else %}
未知
{% endif %}</td>
<td>
<a href="{% url 'myadmin_orders_detail' vo.id %}" class="view-link">查看详情</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<ul>
<li>
<a href="{% url 'myadmin_orders_index' pIndex|add:-1 %}?{{ mywhere|join:'&' }}">«</a>
</li>
{% for p in plist %}
<li {% if pIndex == p %}class="active"{% endif %}>
<a href="{% url 'myadmin_orders_index' p %}?{{ mywhere|join:'&' }}">{{p}}</a>
</li>
{% endfor %}
<li>
<a href="{% url 'myadmin_orders_index' pIndex|add:1 %}?{{ mywhere|join:'&' }}">»</a>
</li>
</ul>
</div>
{% endblock %}
- 4.2. 订单详情信息显示模板:/templates/myadmin/orders/detail.html。
{% extends "myadmin/base.html" %}
{% block mainbody %}
<h4>
订单信息:
</h4>
<table class="table table-bordered">
<tr>
<th width="12%" style="text-align:right;background-color:#eee;">订单号:</th>
<td width="25%">201700{{ orders.id }}</td>
<th width="12%" style="text-align:right;background-color:#eee;">下单人:</th>
<td width="51%">{{ orders.name }}</td>
</tr>
<tr>
<th style="text-align:right;background-color:#eee;">收货人:</th>
<td>{{ orders.linkman }}</td>
<th style="text-align:right;background-color:#eee;">订单地址:</th>
<td>{{ orders.address }}</td>
</tr>
<tr>
<th style="text-align:right;background-color:#eee;">邮政编码:</th>
<td>{{ orders.code }}</td>
<th style="text-align:right;background-color:#eee;">下单时间:</th>
<td>{{ orders.addtime }}</td>
</tr>
<tr>
<th style="text-align:right;background-color:#eee;">总计金额:</th>
<td>{{ orders.total }}</td>
<th style="text-align:right;background-color:#eee;">订单状态:</th>
<td>
{% if orders.state == 0 %}
新订单 【<a href="{% url 'myadmin_orders_state' %}?oid={{orders.id}}&state=1">确认发货</a>】
【<a href="{% url 'myadmin_orders_state' %}?oid={{orders.id}}&state=3">订单作废</a>】
{% elif orders.state == 1 %}
已发货
{% elif orders.state == 2 %}
已完成
{% elif orders.state == 3 %}
无效订单
{% else %}
未知
{% endif %}
</td>
</tr>
</table>
<br/>
<h4>
订单详情信息:
</h4>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>id号</th>
<th>图片</th>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
</tr>
</thead>
<tbody>
{% for vo in detaillist %}
<tr>
<td>{{ vo.id }}</td>
<td><img src="/static/goods/s_{{ vo.picname }}" width="35" /></td>
<td>{{ vo.name }}</td>
<td>{{ vo.price }}</td>
<td>{{ vo.num }}</td>
<td>{{ vo.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div style="text-align:center">
<button onclick="window.history.back()">返回</button>
<button >打印订单</button>
</div>
{% endblock %}
16. 项目实战总结
1 本次项目完成的模块介绍:

(1). 项目中还有那些需要开发
- 网站首页:各种商品展示、轮播图片展示
- 个人中心:注册、个人信息,修改,密码重置和收藏等
- 商品详情: 商品参数、商品清单、售后服务和详情图片
- 扩展模块:会员多地址、商品多图片,商品评论、商城支付、友情链接、导航标签 ... ...
(2). 项目中的优化:
- 项目前台的表单验证
- 使用缓存
- ... ...
七、Python网络爬虫基础(上)
1. Python中的正则表达式
-
一些表达式进行提取,正则表达式就是其中一种进行数据筛选的表达式。
-
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。 -
正则表达式通常被用来
匹配、检索、替换和分割那些符合某个模式(规则)的文本。 -
Python自1.5版本起增加了re模块,它提供Perl风格的正则表达式模式。 -
re模块使Python语言拥有全部的正则表达式功能,使用前需要使用import re导入此模块-
compile函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。import re str = 'This year is 2018' pat = re.compile("[0-9]{4}") print(pat.findall(str)) #结果:['2018'] -
re模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。import re str = 'This year is 2018' res = re.findall("[0-9]{4}",str) print(res) #结果:['2018']
-
2. 正则表达式基础语法介绍
2.1 原子
- 原子是正则表达式中最基本的组成单位,每个正则表达式中至少包含一个原子。
- 常见的原子类型有:
- 普通字符作为原子 如:a b c 字母
- 非打印字符作为原子 如:\n \t
- 通用字符作为原子 如:\d \D \w \W \s \S
- 原子表 如:多个原子拼接在一起
非打印字符:
| 字符 | 描述 |
|---|---|
| \cx | 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。 x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 |
| \f | 匹配一个换页符。等价于 \x0c 和 \cL。 |
| \n | 匹配一个换行符。等价于 \x0a 和 \cJ。 |
| \r | 匹配一个回车符。等价于 \x0d 和 \cM。 |
| \s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
| \S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
| \t | 匹配一个制表符。等价于 \x09 和 \cI。 |
| \v | 匹配一个垂直制表符。等价于 \x0b 和 \cK。 |
通用字符:
| 字符 | 描述 |
|---|---|
| \d | 匹配一个数字字符。等价于[0-9]。 |
| \D | 匹配一个非数字字符。等价于[^0-9]。 |
| \s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [\f\n\r\t\v]。 |
| \S | 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 |
| \w | 匹配字母、数字、下划线。等价于'[A-Za-z0-9_]'。 |
| \W | 匹配非字母、数字、下划线。等价于 '[^A-Za-z0-9_]'。 |
2.2 元字符
- 所谓的元字符,就是正则表达式中具有一些特殊含义的字符,比如重复N次前面的字符等。
元字符:
| 字符 | 描述 |
|---|---|
| . | 匹配除 "\n" 之外的任何单个字符。 要匹配包括 '\n' 在内的任何字符,请使用像"(.¦\n)"的模式。 |
| [xyz] | 字符集合。匹配所包含的任意一个字符。例如, '[abc]' 可以匹配 "plain" 中的 'a'。 |
[^xyz] |
负值字符集合。匹配未包含的任意字符。例如, '[^abc]' 可以匹配 "plain" 中的'p'、'l'、'i'、'n'。 |
| [a-z] | 字符范围。匹配指定范围内的任意字符。 例如,'[a-z]' 可以匹配 'a' 到 'z' 范围内的任意小写字母字符。 |
[^a-z] |
负值字符范围。匹配任何不在指定范围内的任意字符。 例如,'[^a-z]' 可以匹配任何不在 'a' 到 'z'范围内的任意字符。 |
| * | 匹配前面的子表达式零次或多次。例如,zo 能匹配 "z" 以及 "zoo"。 等价于{0,}。 |
| + | 匹配前面的子表达式一次或多次。 例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
| ? | 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 或 "does" 。? 等价于 {0,1}。 |
| n 是一个非负整数。匹配确定的 n 次。 例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 | |
| n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 | |
| m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 | |
\ |
将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,'n' 匹配字符 "n"。'\n' 匹配一个换行符。序列 '' 匹配 "" 而 "(" 则匹配 "("。 |
^ |
匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 '\n' 或 '\r' 之后的位置。 |
| $ | 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 '\n' 或 '\r' 之前的位置。 |
| ? | 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个 "o",而 'o+' 将匹配所有 'o'。 |
| (pattern) | 匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 '(' 或 ')'。 |
| (?:pattern) | 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (¦) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y¦ies) 就是一个比'industry¦industries'更简略的表达式。 |
| x¦y | 匹配 x 或 y。例如,'z¦food' 能匹配 "z" 或 "food"。'(z¦f)ood' 则匹配 "zood" 或 "food"。 |
import re
rst = re.rearch('',str)
2.3 模式修正符
- 所谓模式修正符,即可以在不改变正则表达式的情况下,通过模式修正符改变正则表达式的含义,从而实现一些匹配结果的调整等功能。
| 修饰符 | 描述 |
|---|---|
| re.I | 使匹配对大小写不敏感 |
| re.L | 做本地化识别(locale-aware)匹配 |
| re.M | 多行匹配,影响 ^ 和 $ |
| re.S | 使 . 匹配包括换行在内的所有字符 |
| re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. |
| re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
- 实例:
string = "Python"
pat = "pyt"
rst = re.search(pat,string,re.I) # 第三个参数
print(rst)
2.4 贪婪模式与懒惰模式
- 贪婪模式的核心点就是尽可能多的匹配,而懒惰模式的核心点就是尽可能少的匹配。
.*? 的使用
3. 正则表达式中的常用函数
- 正则表达式本身是一种小型的、高度专业化的编程语言,而在python中,通过内嵌集成
re模块来实现正则匹配。 - re模块中常用功能函数:
3.1 compile():
-
编译正则表达式模式,返回一个
正则对象的模式。(可以把那些常用的正则表达式编译成正则表达式对象,这样可以提高一点效率。) -
格式:
re.compile(pattern[,flags=0])pattern: 编译时用的表达式字符串。flags: 编译标志位,用于修改正则表达式的匹配方式,如:re.I(不区分大小写)、re.S等
import re
tt = "Tina is a good girl, she is cool, clever, and so on..."
rr = re.compile(r'\w*oo\w*')
print(rr.findall(tt)) #查找所有包含'oo'的单词
# 执行结果如下:
# ['good', 'cool']
3.2 match()
- 决定RE是否在字符串刚开始的位置匹配。
- //注:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。
- 格式:
re.match(pattern, string[, flags=0])
print(re.match('com','comwww.csdn').group())
print(re.match('com','Comwww.csdn',re.I).group())
#执行结果如下:
#com
#com
3.3 search()
- 格式:
re.search(pattern, string[, flags=0]) - re.search函数会在字符串内查找模式匹配,只要找到第一个匹配然后返回,如果字符串没有匹配,则返回None。
print(re.search('\dcom','www.4comcsdn.5com').group())
执行结果如下:
# 4com
- 注:match和search一旦匹配成功,就是一个match object对象,而match object对象有以下方法:
- group() 返回被 RE 匹配的字符串
- start() 返回匹配开始的位置
- end() 返回匹配结束的位置
- span() 返回一个元组包含匹配 (开始,结束) 的位置
import re
a = "123abc456"
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(0)) #123abc456,返回整体
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(1)) #123
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(2)) #abc
print(re.search("([0-9]*)([a-z]*)([0-9]*)",a).group(3)) #456
###group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。###
3.4 findall()
- re.findall遍历匹配,可以获取字符串中所有匹配的字符串,返回一个列表。
- 格式:
re.findall(pattern, string[, flags=0])
p = re.compile(r'\d+')
print(p.findall('o1n2m3k4'))
执行结果如下:
['1', '2', '3', '4']
import re
tt = "Tina is a good girl, she is cool, clever, and so on..."
rr = re.compile(r'\w*oo\w*')
print(rr.findall(tt))
print(re.findall(r'(\w)*oo(\w)',tt))#()表示子表达式
执行结果如下:
['good', 'cool']
[('g', 'd'), ('c', 'l')]
3.5 finditer()
- 搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。
- 找到 RE 匹配的所有子串,并把它们作为一个迭代器返回。
- 格式:
re.finditer(pattern, string[, flags=0])
iter = re.finditer(r'\d+','12 drumm44ers drumming, 11 ... 10 ...')
for i in iter:
print(i)
print(i.group())
print(i.span())
'''
# 执行结果如下:
<_sre.SRE_Match object; span=(0, 2), match='12'>
12
(0, 2)
<_sre.SRE_Match object; span=(8, 10), match='44'>
44
(8, 10)
<_sre.SRE_Match object; span=(24, 26), match='11'>
11
(24, 26)
<_sre.SRE_Match object; span=(31, 33), match='10'>
10
(31, 33)
'''
3.6 split()
-
按照能够匹配的子串将string分割后返回列表。
-
可以使用re.split来分割字符串,如:re.split(r'\s+', text);将字符串按空格分割成一个单词列表。
-
格式:
re.split(pattern, string[, maxsplit])maxsplit: 用于指定最大分割次数,不指定将全部分割。
print(re.split('\d+','one1two2three3four4five5'))
# 执行结果如下:
# ['one', 'two', 'three', 'four', 'five', '']
3.7 sub()
- 使用re替换string中每一个匹配的子串后返回替换后的字符串。
- 格式:
re.sub(pattern, repl, string, count)
import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
print(re.sub(r'\s+', '-', text))
执行结果如下:
JGood-is-a-handsome-boy,-he-is-cool,-clever,-and-so-on...
其中第二个函数是替换后的字符串;本例中为'-'
第四个参数指替换个数。默认为0,表示每个匹配项都替换。
- re.sub还允许使用函数对匹配项的替换进行复杂的处理。
- 如:re.sub(r'\s', lambda m: '[' + m.group(0) + ']', text, 0);将字符串中的空格' '替换为'[ ]'。
import re
text = "JGood is a handsome boy, he is cool, clever, and so on..."
print(re.sub(r'\s+', lambda m:'['+m.group(0)+']', text,0))
执行结果如下:
JGood[ ]is[ ]a[ ]handsome[ ]boy,[ ]he[ ]is[ ]cool,[ ]clever,[ ]and[ ]so[ ]on...
3.8 subn()
- 返回替换次数
- 格式:
subn(pattern, repl, string, count=0, flags=0)
print(re.subn('[1-2]','A','123456abcdef'))
print(re.sub("g.t","have",'I get A, I got B ,I gut C'))
print(re.subn("g.t","have",'I get A, I got B ,I gut C'))
执行结果如下:
('AA3456abcdef', 2)
I have A, I have B ,I have C
('I have A, I have B ,I have C', 3)
3.9 re.match与re.search与re.findall的区别:
- re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;
- 而re.search匹配整个字符串,直到找到一个匹配。
a=re.search('[\d]',"abc33").group()
print(a)
p=re.match('[\d]',"abc33")
print(p)
b=re.findall('[\d]',"abc33")
print(b)
执行结果:
3
None
['3', '3']
4. 正则表达式的案例实战
4.1 正则表达式在Python中的使用--解析数据
- 案例要求:如下图所示有一个网页文件,请使用Python的正则将网页中的超级链接信息(名称和url地址)解析处理
- 实现步骤:使用open()、read()读取文件内容,导入re模块,使用正则匹配后遍历输出。

<!DOCTYPE html>
<html>
<head>
<title>Python 正则表达式实例</title>
</head>
<body>
<h2>常用网站链接</h2>
<ul>
<li><a href="https://www.python.org">Python官方网站</a></li>
<li><a href="https://www.djangoproject.com">Django官方网站</a></li>
<li><a href="https://www.baidu.com">百度搜索引擎</a></li>
<li><a href="https://blog.csdn.net">CSDN官方网站</a></li>
<li><a href="https://edu.csdn.net/">CSDN学院</a></li>
</ul>
</body>
</html>
- 实现代码:
import re
f = open("./index.html","r")
content = f.read()
f.close()
#print(content)
title = re.search("<title>(.*?)</title>",content)
if title:
print(title)
print("标题:"+title.group())
alist = re.findall('<a href="(.*?)">(.*?)</a>',content)
for ov in alist:
print(ov[1]+":"+ov[0])
`
4.2 正则表达式在JavaScript中的使用--表单验证
- 案例要求:参考下图,使用JavaScript语言实现页面中表单的验证功能。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>JavaScript--实例</title>
</head>
<body>
<h3 id="hid">JavaScript实例--表单事件</h3>
<form action="my.html" name="myform" method="post" onsubmit="return doSubmit()">
账号:<input type="text" name="uname"/> 8-16位的有效字符<br/><br/>
密码:<input type="password" name="upass"/> 6-18位<br/><br/>
邮箱:<input type="text" name="email"/> <br/><br/>
<input type="submit" value="提交"/>
</form>
<script type="text/javascript">
//表单提交事件处理
function doSubmit(){
//验证账号
var name = document.myform.uname.value;
if(name.match(/^\w{8,16}$/)==null){
alert("账号必须为8-16的有效字符!");
return false;
}
//验证密码
var pass = document.myform.upass.value;
if(pass.match(/^.{6,18}$/)==null){
alert("密码必须为6-18位!");
return false;
}
//验证邮箱
var email = document.myform.email.value;
if(email.match(/^\w+@\w+(\.\w+){1,2}$/)==null){
alert("请输入正确的Email地址!");
return false;
}
return true;
}
</script>
</body>
</html>
4.3 正则表达式在MySQL数据库中的使用--数据查询
MariaDB [mydb]> select * from stu;
+----+----------+-----+-----+----------+
| id | name | age | sex | classid |
+----+----------+-----+-----+----------+
| 1 | zhangsan | 22 | m | python03 |
| 2 | lisi | 25 | w | python04 |
| 3 | wangwu | 20 | m | python03 |
| 4 | zhaoliu | 19 | w | python04 |
| 5 | qq01 | 20 | m | python03 |
| 6 | qqmn | 21 | w | python04 |
| 7 | qq03 | 20 | m | python05 |
| 8 | uu01 | 21 | w | python04 |
| 9 | uu02 | 20 | m | python05 |
| 10 | aa | 29 | w | python03 |
| 11 | bb | 20 | m | python04 |
| 16 | abc | 25 | m | python05 |
+----+----------+-----+-----+----------+
12 rows in set (0.00 sec)
-- 使用正则查询姓名是使用任意两位小写字母构成的数据信息
MariaDB [mydb]> select * from stu where name regexp '^[a-z]{2}$';
+----+------+-----+-----+----------+
| id | name | age | sex | classid |
+----+------+-----+-----+----------+
| 10 | aa | 29 | w | python03 |
| 11 | bb | 20 | m | python04 |
+----+------+-----+-----+----------+
2 rows in set (0.00 sec)
--查询name的值为2~4位的小写字母
MariaDB [mydb]> select * from stu where name regexp '^[a-z]{2,4}$';
+----+------+-----+-----+----------+
| id | name | age | sex | classid |
+----+------+-----+-----+----------+
| 2 | lisi | 25 | w | python04 |
| 6 | qqmn | 21 | w | python04 |
| 10 | aa | 29 | w | python03 |
| 11 | bb | 20 | m | python04 |
| 16 | abc | 25 | m | python05 |
+----+------+-----+-----+----------+
5 rows in set (0.00 sec)
-- 查询name字段值是由两位字母加两位数字构成的数据信息
MariaDB [mydb]> select * from stu where name regexp '^[a-z]{2}[0-9]{2}$';
+----+------+-----+-----+----------+
| id | name | age | sex | classid |
+----+------+-----+-----+----------+
| 5 | qq01 | 20 | m | python03 |
| 7 | qq03 | 20 | m | python05 |
| 8 | uu01 | 21 | w | python04 |
| 9 | uu02 | 20 | m | python05 |
+----+------+-----+-----+----------+
4 rows in set (0.00 sec)
MariaDB [mydb]>
5. 网络爬虫概述
5.1 网络爬虫概述:
- 网络爬虫(Web Spider)又称网络蜘蛛、网络机器人,是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。
- 网络爬虫按照系统结构和实现技术,大致可分为一下集中类型:
- 通用网络爬虫:就是尽可能大的网络覆盖率,如 搜索引擎(百度、雅虎和谷歌等…)。
- 聚焦网络爬虫:有目标性,选择性地访问万维网来爬取信息。
- 增量式网络爬虫:只爬取新产生的或者已经更新的页面信息。特点:耗费少,难度大
- 深层网络爬虫:通过提交一些关键字才能获取的Web页面,如登录或注册后访问的页面。
- 注:实际工作中通常是几种爬虫技术结合实现。
5.2 应用场景:
- 爬虫技术在
科学研究、Web安全、产品研发、舆情监控等领域可以做很多事情。 - 在数据挖掘、机器学习、图像处理等科学研究领域,如果没有数据,则可以通过爬虫从网上抓取;
- 在Web安全方面,使用爬虫可以对网站是否存在某一漏洞进行批量验证、利用;
- 在产品研发方面,可以采集各个商城物品价格,为用户提供市场最低价;
- 在舆情监控方面,可以抓取、分析新浪微博的数据,从而识别出某用户是否为水军
5.3. 学习爬虫前的技术准备:
- (1). Python基础语言: 基础语法、运算符、数据类型、流程控制、函数、对象 模块、文件操作、多线程、网络编程 … 等
- (2). W3C标准: HTML、CSS、JavaScript、Xpath、JSON
- (3). HTTP标准: HTTP的请求过程、请求方式、状态码含义,头部信息以及Cookie状态管理
- (4). 数据库: SQLite、MySQL、MongoDB、Redis …
5.4 关于爬虫的合法性:
几乎每个网站都有一个名为robots.txt的文档,当然也有有些网站没有设定。对于没有设定robots.txt的网站可以通过网络爬虫获取没有口令加密的数据,也就是该网站所有页面的数据都可以爬取。如果网站有文件robots.txt文档,就要判断是否有禁止访客获取数据 如:https://www.taobao.com/robots.txt
6. 网络爬虫工作原理
6.1 网络爬虫使用的技术--数据抓取:
- 在爬虫实现上,除了scrapy框架之外,python有许多与此相关的库可供使用。其中,在数据抓取方面包括: urllib2(urllib3)、requests、mechanize、selenium、splinter;
- 其中,urllib2(urllib3)、requests、mechanize用来获取URL对应的原始响应内容;而selenium、splinter通过加载浏览器驱动,获取浏览器渲染之后的响应内容,模拟程度更高。
- 考虑效率、当然能使用urllib2(urllib3)、requests、mechanize等解决的尽量不用selenium、splinter,因为后者因需要加载浏览器而导致效率较低。
- 对于数据抓取,涉及的过程主要是模拟浏览器向服务器发送构造好的http请求,常见类型有:get/post。
6.2 网络爬虫使用的技术--数据解析:
- 在数据解析方面,相应的库包括:lxml、beautifulsoup4、re、pyquery。
- 对于数据解析,主要是从响应页面里提取所需的数据,常用方法有:xpath路径表达式、CSS选择器、正则表达式等。
- 其中,xpath路径表达式、CSS选择器主要用于提取结构化的数据。而正则表达式主要用于提取非结构化的数据。
7. 网络爬虫基础使用
1. urllib介绍:
-
在Python2版本中,有urllib和urlib2两个库可以用来实现request的发送。
-
而在Python3中,已经不存在urllib2这个库了,统一为urllib。
-
Python3 urllib库官方链接:
https://docs.python.org/3/library/urllib.html
- urllib中包括了四个模块,包括:
- urllib.request:可以用来发送request和获取request的结果
- urllib.error:包含了urllib.request产生的异常
- urllib.parse:用来解析和处理URL
- urllib.robotparse:用来解析页面的robots.txt文件
2. urllib.request:
- urllib.request 模块提供了最基本的构造 HTTP 请求方法,可以模拟浏览器的一个请求发起过程。同时它还带有处理authenticaton(授权验证),redirections(重定向),cookies(浏览器Cookies)以及其它内容。
- urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
- 参数:url地址
- data:可选附加参数,字节流编码格式(bytes() 可转换),请求方式会变为POST
- timeout (超时时间)单位为秒,若请求超出了设置时间还没有响应,则抛异常
- 返回HTTPResposne类型的对象:
- response.read() 就可以得到返回的网页内容,可使用decode("utf-8")解码字符串
- response.status 就可以得到返回结果的状态码,如200代表请求成功,404代表网页未找到
3. urllib.request.Request:
- 利用urlopen()方法可以实现最基本的请求发起,但这几个简单的参数并不足以构建一个完整的请求。
- 使用强大的Request类可以在请求中加入需要的headers等信息。
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None,
unverifiable=False, method=None)
- 第一个参数是请求链接,这个是必传参数,其他的都是可选参数
- data 参数如果要传必须传 bytes (字节流)类型的,如果是一个字典,可以先用 urllib.parse.urlencode() 编码。
- headers 参数是一个字典,你可以在构造 Request 时通过 headers 参数传递,也可以通过调用 Request 对象的 add_header() 方法来添加请求头。
- origin_req_host 指的是请求方的 host 名称或者 IP 地址。
- unverifiable 指的是这个请求是否是无法验证的,默认是 False 。意思就是说用户没有足够权限来选择接收这个请求的结果
- method 是一个字符串,它用来指示请求使用的方法,比如 GET , POST , PUT 等等。
8. 网络爬虫实战
-
案例:爬取百度新闻首页的新闻标题信息
-
url地址:http://news.baidu.com/
-
具体实现步骤:
- 导入urlib库和re正则
- 使用urllib.request.Request()创建request请求对象
- 使用urllib.request.urlopen执行信息爬取,并返回Response对象
- 使用read()读取信息,使用decode()执行解码
- 使用re正则解析结果
- 遍历输出结果信息
-
具体代码如下:
import urllib.request
import re
url = "http://news.baidu.com/"
#伪装浏览器用户
headers = {'User-Agent':'User-Agent:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'}
req = urllib.request.Request(url,headers=headers)
#执行请求获取响应信息
res = urllib.request.urlopen(req)
# 从响应对象中读取信息并解码
html = res.read().decode("utf-8")
#print(len(html))
#使用正则解析出新闻标题信息
pat = '<a href="(.*?)" .*? target="_blank">(.*?)</a>'
dlist = re.findall(pat,html)
# 遍历输出结果
for v in dlist:
print(v[1]+":"+v[0])
9. 网络爬虫中的异常处理
- 在网络爬虫运行时出现异常,若不处理则会因报错而终止运行,导致爬取数据中断,所以异常处理还是十分重要的。
urllib.error可以接收有urllib.request产生的异常。urllib.error有两个类,URLError和HTTPError。URLError内有一个属性:reason返回错误的原因
# 测试URLError的异常处理
from urllib import request
from urllib import error
url = "http://www.wer3214e13wer3.com/"
req = request.Request(url)
try:
response = request.urlopen(req)
html = response.read().decode('utf-8')
print(len(html))
except error.URLError as e:
print(e.reason) #输出错误信息
print("ok")
- 报错信息,但程序继续执行:
[Errno 8] nodename nor servname provided, or not known
ok
HTTPError内有三个属性:code返回HTTP状态码,如404 ;reason返回错误原因;headers返回请求头
# 测试HTTPError的异常处理
from urllib import request
from urllib import error
url = "https://img-ads.csdn.net/2018/20180420184005werqwefsd9410.png"
req = request.Request(url)
try:
response = request.urlopen(req)
html = response.read().decode('utf-8')
print(len(html))
except error.HTTPError as e:
print(e.reason) #输出错误信息
print(e.code) #输出HTTP状态码
print("ok")
- 报的错误
Not Found
404
ok
URLError是OSError的一个子类,HTTPError是URLError的一个子类:- 注意:父类一定要在后面:
from urllib import request
from urllib import error
#url = "https://img-ads.csdn.net/2018/20180420184005werqwefsd9410.png"
url = "http://www.wer3214e13wer3.com/"
req = request.Request(url)
try:
response = request.urlopen(req)
html = response.read().decode('utf-8')
print(len(html))
except error.HTTPError as e:
print("HTTPError")
print(e.reason)
print(e.code)
except error.URLError as e:
print("URLError")
print(e.reason) # 输出错误信息
print("ok")
- 不什么错误都去处理:
from urllib import request
from urllib import error
url = "https://img-ads.csdn.net/2018/20180420184005werqwefsd9410.png"
#url = "http://www.wer3214e13wer3.com/"
req = request.Request(url)
try:
response = request.urlopen(req)
html = response.read().decode('utf-8')
print(len(html))
except Exception as e:
if hasattr(e,'reason'):
print(e.reason)
if hasattr(e,'code'):
print(e.code)
print("ok")
Not Found
404
ok
zhang
10. Urllib3和requests的使用
- Python3 默认提供了urllib库,可以爬取网页信息,但其中确实有不方便的地方,如:处理网页验证和Cookies,以及Hander头信息处理。
- 为了更加方便处理,有了更为强大的库
urllib3和requests, 本节会分别介绍一下,以后我们着重使用requests。 - urllib3网址:https://pypi.org/project/urllib3/
- requests网址:http://www.python-requests.org/en/master/
1. urllib3库的使用:
- 安装:通过使用pip命令来安装urllib3
pip install urllib3
- 简单使用:
import urllib3
import re
# 实例化产生请求对象
http = urllib3.PoolManager()
# get请求指定网址
url = "http://www.baidu.com"
res = http.request("GET",url)
# 获取HTTP状态码
print("status:%d" % res.status)
# 获取响应内容
data = res.data.decode("utf-8")
# 正则解析并输出
print(re.findall("<title>(.*?)</title>",data))
- 其他设置: 增加了超时时间,请求参数等设置
import urllib3
import re
url = "http://www.baidu.com"
http = urllib3.PoolManager(timeout = 4.0) #设置超时时间
res = http.request(
"GET",
url,
#headers={
# 'User-Agent':'Mozilla/5.0(WindowsNT6.1;rv:2.0.1)Gecko/20100101Firefox/4.0.1',
#},
fields={'id':100,'name':'lisi'}, #请求参数信息
)
print("status:%d" % res.status)
data = res.data.decode("utf-8")
print(re.findall("<title>(.*?)</title>",data))
2. requests库的使用:
- 安装:通过使用pip命令来安装requests
pip install requests
- 简单使用:
import requests
import re
url = "http://www.baidu.com"
# 抓取信息
res = requests.get(url)
#获取HTTP状态码
print("status:%d" % res.status_code)
# 获取响应内容
data = res.content.decode("utf-8")
#解析出结果
print(re.findall("<title>(.*?)</title>",data))
- 关于请求中的参数下节课再讲
11. GET请求爬取数据实战
- 使用urllib的GET获取58同城中关于python的招聘信息
from urllib import request
from urllib import error
import re
url = "http://bj.58.com/job/?key=python&final=1&jump=1"
req = request.Request(url)
try:
response = request.urlopen(req)
html = response.read().decode('utf-8')
pat = '<span class="address" >(.*?)</span> \| <span class="name">(.*?)</span>'
dlist = re.findall(pat,html)
#print(len(dlist))
for v in dlist:
print(v[0]+" | "+v[1])
except error.URLError as e:
print(e.reason) #输出错误信息
print("ok")
- 使用requests的GET获取58同城中关于python的招聘信息
import requests
import re
data = {
'key':'python',
'final':1,
'jump':1,
}
url = "http://bj.58.com/job/"
res = requests.get(url,params=data)
html = res.content.decode('utf-8')
pat = '<span class="address" >(.*?)</span> \| <span class="name">(.*?)</span>'
dlist = re.findall(pat,html)
print(len(dlist))
for v in dlist:
print(v[0]+" | "+v[1])
12. POST请求爬取数据实战
import json
json.loads(json_str) # json字符串转换成字典
json.dumps(dict) # 字典转换成json字符串
- 使用urllib发送POST数据,并抓取百度翻译信息
from urllib import request,parse
import json
url = 'http://fanyi.baidu.com/sug'
# 定义请求参数
data = {
'kw' : 'python'
}
data = parse.urlencode(data) #编码转换
# 封装headers头信息
headers = {
'Content-Length' : len(data)
}
# 发送请求,抓取信息
req = request.Request(url=url,data=bytes(data,encoding='utf-8'),headers=headers)
res = request.urlopen(req)
# 解析结果并输出
str_json = res.read().decode('utf-8') # json
myjson = json.loads(str_json) # 把json转字典
info = myjson['data'][0]['v']
print(info)
- 使用requests发送POST数据,并抓取百度翻译信息
import requests
import json
url = 'http://fanyi.baidu.com/sug'
# 定义请求参数
data = {
'kw' : 'python'
}
# 发送请求,抓取信息
res = requests.post(url,data=data)
# 解析结果并输出
str_json = res.content.decode('utf-8') # 获取响应的json字串
myjson = json.loads(str_json) # 把json转字典
info = myjson['data'][0]['v']
print(info)
- 使用requests 实现翻译信息抓取
import requests
import json
def fanyi(keyword):
url = 'http://fanyi.baidu.com/sug'
# 定义请求参数
data = {
'kw' : keyword
}
# 发送请求,抓取信息
res = requests.post(url,data=data)
# 解析结果并输出
str_json = res.content.decode('utf-8') # 获取响应的json字串
myjson = json.loads(str_json) # 把json转字典
info = myjson['data'][0]['v']
print(info)
if __name__ == '__main__':
while True:
keyword = input('输入翻译的单词:')
if keyword == 'q':
break
fanyi(keyword)
- urllib模式:
from urllib import request,parse
import json
def fanyi(keyword):
base_url = 'http://fanyi.baidu.com/sug'
# 构建请求对象
data = {
'kw' : keyword
}
data = parse.urlencode(data)
headers = {
'Content-Length':len(data)
}
req = request.Request(url=base_url,data=bytes(data,encoding='utf-8'),headers=headers)
res = request.urlopen(req)
str_json = res.read().decode('utf-8') # 获取响应的json字串
myjson = json.loads(str_json) # 把json转字典
info = myjson['data'][0]['v']
print(info)
if __name__ == '__main__':
while True:
keyword = input('输入翻译的单词:')
if keyword == 'q':
break
fanyi(keyword)
13. 网络爬虫案例实战1
- 本次案例是通过登录人人网,抓取登录后用户中心的信息
1. 模拟人人登录请求,执行登录验证操作
from urllib import request,parse
login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2018321648829'
data = {
'email':'1352*****6',
'icode':'',
'origURL':'http://www.renren.com/home',
'domain':'renren.com',
'key_id':'1',
'captcha_type':'web_login',
'password':'478b7c2dca554eeabed3b7374703bff4a6a22e78b8a9fcfb090e3a7fb792992b',
'rkey':'e954ec64a7ecf4e33bdf81bb1abad158',
'f':'http%3A%2F%2Fwww.renren.com%2F965541786',
}
data = parse.urlencode(data)
headers = {
'Content-Length' : len(data)
}
req = request.Request(url=login_url,data=bytes(data,encoding='utf-8'),headers=headers,)
response = request.urlopen(req)
print(response.read().decode('utf-8'))
2. 抓取登录成功后的用户home页信息
from urllib import request
import re,gzip
base_url = 'http://www.renren.com/965541786'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Cookie': 'anonymid=jgdcqcjgqy4yxt; depovince=BJ; _r01_=1; JSESSIONID=abc7HUv9M_HsB7WkgK2lw; ick_login=b954cf62-bbe5-480d-b679-e1e3ce584896; SL_GWPT_Show_Hide_tmp=1; SL_wptGlobTipTmp=1; ick=b4337770-b7ce-4a70-b9d0-cd63c7fc7bb5; XNESSESSIONID=738f4bde312f; jebe_key=f950add1-40e8-4009-a157-bfc3d89f7350%7C24a48cb369f8637c5ee2c4a23eb5b93f%7C1524555370510%7C1%7C1524555375485; first_login_flag=1; ln_uact=13520319616; ln_hurl=http://head.xiaonei.com/photos/0/0/men_main.gif; wp_fold=0; jebecookies=ef7f7372-0e70-45db-aaae-c415d4611918|||||; _de=8C2F648D7158ED727318288C8F3F21C5; p=f1ea4b6984cefb7d88164a67816c91fe6; t=401516286d37bde6735180d25f68f2fe6; societyguester=401516286d37bde6735180d25f68f2fe6; id=965541786; xnsid=928c27b; ver=7.0; loginfrom=null',
'Host': 'www.renren.com',
'Referer': 'http://www.renren.com/SysHome.do',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
}
req = request.Request(url=base_url,headers=headers)
res = request.urlopen(req)
#html = res.read().decode("utf-8") # 网页响应时开启了gzip压缩,需要解压
#报错:UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte
# 获取将请求头中的Accept-Encoding的gzip删除也可以
# 对gzip压缩的响应,我们解压后转码
html = gzip.decompress(res.read()).decode("utf-8")
#print(html)
print(re.findall("<title>(.*?)</title>",html))
3. 使用cookiejar将上面两个合并到一起执行
from urllib import request,parse
import re,gzip,time
# cookie管理模块,
from http import cookiejar
# 返回存储cookie对象
cookie = cookiejar.CookieJar()
# 返回一个cookie管理器
cookie_handler = request.HTTPCookieProcessor(cookie)
# 请求管理器
opener = request.build_opener(cookie_handler)
def doLogin():
login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2018321648829'
data = {
'email':'1352*****16',
'icode':'',
'origURL':'http://www.renren.com/home',
'domain':'renren.com',
'key_id':'1',
'captcha_type':'web_login',
'password':'478b7c2dca554eeabed3b7374703bff4a6a22e78b8a9fcfb090e3a7fb792992b',
'rkey':'e954ec64a7ecf4e33bdf81bb1abad158',
'f':'http%3A%2F%2Fwww.renren.com%2F965541786',
}
data = parse.urlencode(data)
headers = {
'Content-Length' : len(data)
}
req = request.Request(url=login_url,data=bytes(data,encoding='utf-8'),headers=headers,)
response = opener.open(req)
def myHome():
home_url = 'http://www.renren.com/965541786'
res = opener.open(home_url)
html = res.read().decode("utf-8")
#print(html)
print(re.findall("<title>(.*?)</title>",html))
if __name__ == '__main__':
# 登陆
print("正在登录中...")
doLogin()
time.sleep(3)
# 访问个人首页
myHome()
4. 使用requests重写第三步的代码,实现人人网登录并抓取登录后信息
import requests
import re,time
s = requests.Session()
def doLogin():
login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2018321648829'
data = {
'email':'1352*****6',
'icode':'',
'origURL':'http://www.renren.com/home',
'domain':'renren.com',
'key_id':'1',
'captcha_type':'web_login',
'password':'478b7c2dca554eeabed3b7374703bff4a6a22e78b8a9fcfb090e3a7fb792992b',
'rkey':'e954ec64a7ecf4e33bdf81bb1abad158',
'f':'http%3A%2F%2Fwww.renren.com%2F965541786',
}
s.post(login_url,data=data)
def myHome():
home_url = 'http://www.renren.com/965541786'
res = s.get(home_url)
html = res.content.decode("utf-8")
#print(html)
print(re.findall("<title>(.*?)</title>",html))
if __name__ == '__main__':
# 登陆
print("正在登录中...")
doLogin()
time.sleep(3)
# 访问个人首页
myHome()
13. 网络爬虫案例实战2
1. 爬取猫眼电影中榜单栏目中TOP100榜的所有电影信息,并将信息写入文件
- 目标:使用urllib分页爬取猫眼电影中榜单栏目中TOP100榜的所有电影信息,并将信息写入文件
- URL地址:http://maoyan.com/board/4 其中参数offset表示其实条数
- 获取信息:
from urllib import request,error
import re,time,json
def getPage(url):
'''爬取指定url页面信息'''
try:
#定义请求头信息
headers = {
'User-Agent':'User-Agent:Mozilla/5.0(WindowsNT6.1;rv:2.0.1)Gecko/20100101Firefox/4.0.1'
}
# 封装请求对象
req = request.Request(url,headers=headers)
# 执行爬取
res = request.urlopen(req)
#判断响应状态,并响应爬取内容
if res.code == 200:
return res.read().decode("utf-8")
else:
return None
except error.URLError:
return None
def parsePage(html):
'''解析爬取网页中的内容,并返回字段结果'''
#定义解析正则表达式
pat = '<i class="board-index board-index-[0-9]+">([0-9]+)</i>.*?<img data-src="(.*?)" alt="(.*?)" class="board-img" />.*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>.*?<i class="integer">([0-9\.]+)</i><i class="fraction">([0-9]+)</i>'
#执行解析
items = re.findall(pat,html,re.S)
#遍历封装数据并返回
for item in items:
yield {
'index':item[0],
'image':item[1],
'title':item[2],
'actor':item[3].strip()[3:],
'time':item[4].strip()[5:],
'score':item[5]+item[6],
}
def writeFile(content):
'''执行文件追加写操作'''
#print(content)
with open("./result.txt",'a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False) + "\n")
#json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False
def main(offset):
''' 主程序函数,负责调度执行爬虫处理 '''
url = 'http://maoyan.com/board/4?offset=' + str(offset)
#print(url)
html = getPage(url)
#判断是否爬取到数据,并调用解析函数
if html:
for item in parsePage(html):
writeFile(item)
# 判断当前执行是否为主程序运行,并遍历调用主函数爬取数据
if __name__ == '__main__':
for i in range(10):
main(offset=i*10)
time.sleep(1)
- 目标:使用requests分页爬取猫眼电影中榜单栏目中TOP100榜的所有电影信息,并将信息写入文件
- URL地址:http://maoyan.com/board/4 其中参数offset表示其实条数
- 获取信息:
from requests.exceptions import RequestException
import requests
import re,time,json
def getPage(url):
'''爬取指定url页面信息'''
try:
#定义请求头信息
headers = {
'User-Agent':'User-Agent:Mozilla/5.0(WindowsNT6.1;rv:2.0.1)Gecko/20100101Firefox/4.0.1'
}
# 执行爬取
res = requests.get(url,headers=headers)
#判断响应状态,并响应爬取内容
if res.status_code == 200:
return res.text
else:
return None
except RequestException:
return None
def parsePage(html):
'''解析爬取网页中的内容,并返回字段结果'''
#定义解析正则表达式
pat = '<i class="board-index board-index-[0-9]+">([0-9]+)</i>.*?<img data-src="(.*?)" alt="(.*?)" class="board-img" />.*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>.*?<i class="integer">([0-9\.]+)</i><i class="fraction">([0-9]+)</i>'
#执行解析
items = re.findall(pat,html,re.S)
#遍历封装数据并返回
for item in items:
yield {
'index':item[0],
'image':item[1],
'title':item[2],
'actor':item[3].strip()[3:],
'time':item[4].strip()[5:],
'score':item[5]+item[6],
}
def writeFile(content):
'''执行文件追加写操作'''
#print(content)
with open("./result.txt",'a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False) + "\n")
#json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False
def main(offset):
''' 主程序函数,负责调度执行爬虫处理 '''
url = 'http://maoyan.com/board/4?offset=' + str(offset)
#print(url)
html = getPage(url)
#判断是否爬取到数据,并调用解析函数
if html:
for item in parsePage(html):
writeFile(item)
# 判断当前执行是否为主程序运行,并遍历调用主函数爬取数据
if __name__ == '__main__':
for i in range(10):
main(offset=i*10)
time.sleep(1)
八、Python网络爬虫基础(下)
- 在上一周我们实现了一个基本的网络爬虫,但是提取页面信息时使用的是正则表达式,这还是比较烦琐,出错率比较高。
- 我们爬取的信息大多都是网页信息,网页HTML节点(标签)中定义了大量的id和class属性,而且节点之间还有层级关系。
- 针对于上述这种格式的解析,给大家介绍几种解析库:
14. 解析库的使用
14-1. 解析库的使用--XPath
- XPath(XML Path Language)是一门在XML文档中查找信息的语言。
- XPath 可用来在XML文档中对元素和属性进行遍历。
- XPath 是
W3C XSLT标准的主要元素,并且XQuery和XPointer都构建于 XPath 表达之上。 - 官方网址:
http://lxml.de官方文档:http://lxml.de/api/index.html- 注:XQuery 是用于 XML 数据查询的语言(类似SQL查询数据库中的数据)
- 注:XPointer 由统一资源定位地址(URL)中#号之后的描述组成,类似于HTML中的锚点链接
- python中如何安装使用XPath:
- ①: 安装 lxml 库。
- ②: from lxml import etree
- ③: Selector = etree.HTML(网页源代码)
- ④: Selector.xpath(一段神奇的符号)
1. 准备工作:
- 要使用XPath首先要先安装lxml库:
pip install lxml
2. XPath选取节点规则
| 表达式 | 描述 |
|---|---|
| nodename | 选取此节点的所有子节点。 |
| / | 从当前节点选取直接子节点 |
| // | 从匹配选择的当前节点选择所有子孙节点,而不考虑它们的位置 |
| . | 选取当前节点。 |
| .. | 选取当前节点的父节点。 |
| @ | 选取属性。 |
- XPath 运算符
| 运算符 | 描述 | 实例 | 返回值 |
|---|---|---|---|
| ¦ | 计算两个节点集 | //book ¦ //cd | 返回所有拥有 book 和 cd 元素的节点集 |
| + | 加法 | 6 + 4 | 10 |
| - | 减法 | 6 - 4 | 2 |
| * | 乘法 | 6 * 4 | 24 |
| div | 除法 | 8 div 4 | 2 |
| = | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。、\n 如果 price 是 9.90,则返回 false。 |
| != | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。\n 如果 price 是 9.80,则返回 false。 |
| < | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。\n 如果 price 是 9.90,则返回 false。 |
| <= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。\n 如果 price 是 9.90,则返回 false。 |
| > | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。\n如果 price 是 9.80,则返回 false。 |
| >= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。\n如果 price 是 9.70,则返回 false。 |
| or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。\n如果 price 是 9.50,则返回 false。 |
| and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。\n如果 price 是 8.50,则返回 false。 |
| mod | 计算除法的余数 | 5 mod 2 | 1 |
3. 解析案例:
- 首先创建一个html文件:my.html 用于测试XPath的解析效果
<!DOCTYPE html>
<html>
<head>
<title>我的网页</title>
</head>
<body>
<h3 id="hid">我的常用链接</h3>
<ul>
<li class="item-0"><a href="http://www.baidu.com">百度</a></li>
<li class="item-1 shop"><a href="http://www.jd.com">京东</a></li>
<li class="item-2"><a href="http://www.sohu.com">搜狐</a></li>
<li class="item-3"><a href="http://www.sina.com">新浪</a></li>
<li class="item-4 shop"><a href="http://www.taobao.com">淘宝</a></li>
</ul>
</body>
</html>
- 使用XPath解析说明
# 导入模块
from lxml import etree
# 读取html文件信息(在真实代码中是爬取的网页信息)
f = open("./my.html",'r',encoding="utf-8")
content = f.read()
f.close()
# 解析HTML文档,返回根节点对象
html = etree.HTML(content)
#print(html) # <Element html at 0x103534c88>
# 获取网页中所有标签并遍历输出标签名
result = html.xpath("//*")
for t in result:
print(t.tag,end=" ")
#[html head title body h3 ul li a li a ... ... td]
print()
# 获取节点
result = html.xpath("//li") # 获取所有li节点
result = html.xpath("//li/a") # 获取所有li节点下的所有直接a子节点
result = html.xpath("//ul//a") # 效果同上(ul下所有子孙节点)
result = html.xpath("//a/..") #获取所有a节点的父节点
print(result)
# 获取属性和文本内容
result = html.xpath("//li/a/@href") #获取所有li下所有直接子a节点的href属性值
result = html.xpath("//li/a/text()") #获取所有li下所有直接子a节点内的文本内容
print(result) #['百度', '京东', '搜狐', '新浪', '淘宝']
result = html.xpath("//li/a[@class]/text()") #获取所有li下所有直接含有class属性子a节点内的文本内容
print(result) #['百度', '搜狐', '新浪']
#获取所有li下所有直接含有class属性值为aa的子a节点内的文本内容
result = html.xpath("//li/a[@class='aa']/text()")
print(result) #['搜狐', '新浪']
#获取class属性值中含有shop的li节点下所有直接a子节点内的文本内容
result = html.xpath("//li[contains(@class,'shop')]/a/text()")
print(result) #['搜狐', '新浪']
# 按序选择
result = html.xpath("//li[1]/a/text()") # 获取每组li中的第一个li节点里面的a的文本
result = html.xpath("//li[last()]/a/text()") # 获取每组li中最后一个li节点里面的a的文本
result = html.xpath("//li[position()<3]/a/text()") # 获取每组li中前两个li节点里面的a的文本
result = html.xpath("//li[last()-2]/a/text()") # 获取每组li中倒数第三个li节点里面的a的文本
print(result)
print("--"*30)
# 节点轴选择
result = html.xpath("//li[1]/ancestor::*") # 获取li的所有祖先节点
result = html.xpath("//li[1]/ancestor::ul") # 获取li的所有祖先中的ul节点
result = html.xpath("//li[1]/a/attribute::*") # 获取li中a节点的所有属性值
result = html.xpath("//li/child::a[@href='http://www.sohu.com']") #获取li子节点中属性href值的a节点
result = html.xpath("//body/descendant::a") # 获取body中的所有子孙节点a
print(result)
result = html.xpath("//li[3]") #获取li中的第三个节点
result = html.xpath("//li[3]/following::li") #获取第三个li节点之后所有li节点
result = html.xpath("//li[3]/following-sibling::*") #获取第三个li节点之后所有同级li节点
for v in result:
print(v.find("a").text)
- 解析案例
# 导入模块
from lxml import etree
# 读取html文件信息(在真实代码中是爬取的网页信息)
f = open("./my.html",'r')
content = f.read()
f.close()
# 解析HTML文档,返回根节点对象
html = etree.HTML(content)
# 1. 获取id属性为hid的h3节点中的文本内容
print(html.xpath("//h3[@id='hid']/text()")) #['我的常用链接']
# 2. 获取li中所有超级链接a的信息
result = html.xpath("//li/a")
for t in result:
# 通过xapth()二次解析结果
#print(t.xpath("text()")[0], ':', t.xpath("@href")[0])
# 效果同上,使用节点对象属性方法解析
print(t.text, ':', t.get("href"))
'''
#结果:
百度 : http://www.baidu.com
京东 : http://www.jd.com
搜狐 : http://www.sohu.com
新浪 : http://www.sina.com
淘宝 : http://www.taobao.com
'''
'''
HTML元素的属性:
tag:元素标签名
text:标签中间的文本
HTML元素的方法:
find() 查找一个匹配的元素
findall() 查找所有匹配的元素
get(key, default=None) 获取指定属性值
items()获取元素属性,作为序列返回
keys()获取属性名称列表
value是()将元素属性值作为字符串序列
'''
14-2. 解析库的使用--Beautiful Soup
- BeautifulSoup是Python的一个HTML或XML解析库,最主要的功能就是从网页爬取我们需要的数据。
- BeautifulSoup将html解析为对象进行处理,全部页面转变为字典或者数组,相对于正则表达式的方式,可以大大简化处理过程。
- Beautiful Soup3 目前已经停止开发,我们推荐在现在的项目中使用Beautiful Soup4,
1. BeautifulSoup 安装与使用:
- Beautiful Soup是一个依赖于lxml的解析库,所以在安装之前要先确保lxml库已安装:
pip install lxml - 安装 BeautifulSoup 解析器:
pip install beautifulsoup4
-
中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/
-
主要的解析器,以及它们的优缺点:
| 解析器 | 使用方法 | 优势 | 劣势 |
|---|---|---|---|
| Python标准库 | BeautifulSoup(markup, "html.parser") | Python的内置标准库,执行速度适中,文档容错能力强 | Python 2.7.3 or 3.2.2前的版本中文档容错能力差 |
| lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快 文档容错能力强 | 需要安装C语言库 |
| lxml XML 解析器 | BeautifulSoup(markup, ["lxml-xml"]) BeautifulSoup(markup, "xml") | 速度快 唯一支持XML的解析器 | 需要安装C语言库 |
| html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 速度慢、不依赖外部扩展 |
- lxml解析器有解析html和xml的功能,而且速度快,容错能力强,故推荐使用。
- 快速使用案例:
# 导入模块
from bs4 import BeautifulSoup
# 读取html文件信息(在真实代码中是爬取的网页信息)
f = open("./my.html",'r',encoding="utf-8")
content = f.read()
f.close()
# 创建解析器
soup = BeautifulSoup(content,"lxml")
# 输出网页内容:注:此内容已被缩进格式化(自动更正格式),其实这个是在上一步实例化时就已完成
print(soup.prettify())
#输出网页中title标签中的内容
print(soup.title.string)
2. 节点选择器:
- 直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本了,这种方式速度非常快。
- ① 选择元素:
...
print(soup.title) #<title>我的网页</title>
print(type(soup.title)) #<class 'bs4.element.Tag'>
print(soup.head) #获取整个head元素,及内部元素
print(soup.li) #获取第一个li元素(后面其他li不会获取)
# <li class="item-0"><a class="bb" href="http://www.baidu.com">百度</a></li>
- ② 提取信息:
...
print(soup.a) #获取第一个a元素标签: <a class="bb" href="http://www.baidu.com">百度</a>
print(soup.a.name) #获取标签名: a
print(soup.a.attrs) #获取所有属性:{'class': ['bb'], 'href': 'http://www.baidu.com'}
print(soup.a.attrs['href']) #获取其中一个属性:http://www.baidu.com
print(soup.a.string) # 获取元素标签中间的文本内容:百度
- ③ 嵌套选择:
print(soup.li.a) #获取网页中第一个li中的第一个a元素节点
#输出 <a class="bb" href="http://www.baidu.com">百度</a>
print(type(soup.body.h3)) ##获取body中的第一个h3元素的类型:<class 'bs4.element.Tag'>
print(soup.body.h3.string) #获取body中的第一个h3中的文本内容: 我的常用链接
- ④ 关联选择:
- 我们在做选择的时候,难免有时候不能够一步就选到想要的节点元素。
- 需要先选中某一个节点元素,然后再基于这些继续向下选择(子,父,兄弟)。
#....
# 子或子孙节点
# 以下获取的节点列表都可以使用for...in遍历
print(soup.ul.contents) #获取ul下面的所有直接子节点,返回列表
print(soup.ul.children) #获取ul下面的所有直接子节点,返回一个:<list_iterator object at 0x110346a20>
print(soup.ul.descendants) # 获取ul下的所有子孙节点。
for v in soup.ul.descendants:
print("a:",v)
# 父祖节点
print(soup.a.parent.name) #通过parent属性获取a的父节点 li
# print(list(soup.a.parents)) # 获取所有祖先节点
#兄弟节点
print(soup.li.next_siblings) #获取指定li节点的所有后面的兄弟节点
print(soup.li.previous_siblings)#获取指定li节点的所有前面的兄弟节点
#for v in soup.li.next_siblings:
# print(v)
# 获取信息
print(soup.a.string) #获取a节点中的文本
print(soup.a.attrs['href']) # 或a节点的href属性值
3. 方法选择器:
- ① find_all() -- 传入属性或文本,返回所有符合条件的元素 格式:
find_all(name,attrs,recursive,text, **kwargs )
# 导入模块
from bs4 import BeautifulSoup
import re
# 读取html文件信息(在真实代码中是爬取的网页信息)
f = open("./my.html",'r')
content = f.read()
f.close()
# 创建解析器
soup = BeautifulSoup(content,"lxml")
# 通过name指定li值,获取所有li元素节点,返回列表
lilist = soup.find_all(name="li")
# 通过attrs指定属性来获取所有元素节点
lilist = soup.find_all(attrs={"class":"aa"})
lilist = soup.find_all(class_="aa") #同上(class属性中包含就可以了)
lilist = soup.find_all(class_="shop") #class属性值中包含shop的所有节点
lilist = soup.find_all(id="hid") #<h3 id="hid">我的常用链接</h3>
# 通过文本内容获取
lilist = soup.find_all(text='百度') # 百度
lilist = soup.find_all(text=re.compile('张')) # 张翠山 张无忌
for i in lilist:
print(i)
- ② find() -- 传入属性或文本,返回所有符合条件的第一个元素
# 获取一个li元素节点
lilist = soup.find(name="li")
# 通过attrs指定属性来获取一个元素节点
lilist = soup.find(attrs={"class":"aa"})
4. CSS选择器:
# 导入模块
from bs4 import BeautifulSoup
import re
# 读取html文件信息(在真实代码中是爬取的网页信息)
f = open("./my.html",'r')
content = f.read()
f.close()
# 创建解析器
soup = BeautifulSoup(content,"lxml")
print(soup.select("ul li a")) #获取ul里面li下面的a元素节点
print(soup.select("#hid")) #获取id属性值为hid的元素节点
print(soup.select("li.shop a")) #获取class属性为shop的li元素里面所有的a元素节点
# 套用选择解析器
blist = soup.select("ul li")
for li in blist:
a = li.select("a")[0] #获取每个li里面的a元素节点
print(a)
print(a['href']) #获取属性href的值
# print(a.attrs['href']) #等价 同上 获取属性值
print(a.get_text()) #等价 print(a.string) 获取元素节点的文本内容
14-3. 解析库的使用--pyquery
① PyQuery介绍与安装
-
PyQuery库也是一个非常强大又灵活的网页解析库,如果你有前端开发经验的,都应该接触过jQuery,那么PyQuery就是你非常绝佳的选择。
-
PyQuery 是 Python 仿照 jQuery 的严格实现。
-
语法与 jQuery 几乎完全相同,所以不用再去费心去记一些奇怪的方法了。
-
jQuery参考文档: http://jquery.cuishifeng.cn/
-
PyQuery的安装
pip install pyquery
- URL初始化:
# URL初始化
from pyquery import PyQuery as pq
doc = pq(url="http://www.baidu.com",encoding="utf-8")
print(doc('title'))
# 文件初始化
from pyquery import PyQuery as pq
doc = pq(filename='my.html',encoding="utf-8")
print(doc('title'))
'''
# 推荐使用requests爬取信息
from pyquery import PyQuery as pq
import requests
res = requests.get("http://www.baidu.com")
res.encoding = "utf-8" #因为原编码为ISO-8859-1
#print(res.text)
doc = pq(res.text)
print(doc("title"))
② PyQuery的使用
# 读取my.html的文件内容,并使用pyquery来查找节点
from pyquery import PyQuery as pq
doc = pq(filename='my.html',encoding="utf-8")
print(doc('title')) #通过html标签名获取元素节点
print(doc('#hid')) #获取id属性值为hid的元素节点
print(doc('.bb')) #获取class属性值为bb的元素节点
print(doc('title,h3')) #选择符组的使用
print(doc("ul li.shop a")) #关联选择符的使用
print(doc("a")) #获取所有a
print(doc("a:first")) #获取第一个a
print(doc("a:last")) #获取最后一个a
print(doc("a:lt(2)")) #获取前连个a
print(doc("a:eq(2)")) #获取索引位置2的a(第三个)
print(doc('a[href="http://www.sina.com"]')) #获取指定属性值的节点
print("="*60)
# 节点的二次筛选:
lilist = doc("ul li") #获取ul中所有的li
print(type(lilist)) #<class 'pyquery.pyquery.PyQuery'>
print(lilist.find("a.bb")) #在结果的基础上再次查找
print(lilist.children("a.bb")) #在结果的基础上再次查找
print(doc("a.bb").parent()) #获取指定元素的直接父节点
#print(doc("a.bb").parents()) #获取指定元素的所有父节点
print(doc("a.bb").parent().siblings()) #获取兄弟节点
print("="*60)
# 遍历:
alist = doc("a")
for a in alist.items():
print(a.attr.href)
#print(a.attr('href')) #同上
print(a.text()) #获取内容
print(a.html())
#节点操作具体参考手册
15. 豆瓣电影Top250信息爬取实战
- 通过本案例[豆瓣电影Top250信息爬取]锻炼除正则表达式之外三种信息解析方式:Xpath、BeautifulSoup和PyQuery。
- 爬取url地址:https://movie.douban.com/top250
1. 分析:
-
- 分析url地址:https://movie.douban.com/top250 每页25条数据,共计10页
第一页:https://movie.douban.com/top250?start=0
第二页:https://movie.douban.com/top250?start=25
第三页:https://movie.douban.com/top250?start=50
...
结果:
for i in range(10):
url = "https://movie.douban.com/top250?start="+str(i*25)
-
- 分析网页源代码内容:每部电影信息都是放在
<div class="item">...</div>中
- 分析网页源代码内容:每部电影信息都是放在
2. 具体实现代码如下:
from requests.exceptions import RequestException
from lxml import etree
from bs4 import BeautifulSoup
from pyquery import PyQuery as pq
import requests
import re,time,json
def getPage(url):
'''爬取指定url页面信息'''
try:
#定义请求头信息
headers = {
'User-Agent':'User-Agent:Mozilla/5.0(WindowsNT6.1;rv:2.0.1)Gecko/20100101Firefox/4.0.1'
}
# 执行爬取
res = requests.get(url,headers=headers)
#判断响应状态,并响应爬取内容
if res.status_code == 200:
return res.text
else:
return None
except RequestException:
return None
def parsePage(content):
'''解析爬取网页中的内容,并返回字段结果'''
print(content)
# =========使用pyquery解析==================
# 解析HTML文档
doc = pq(content)
#获取网页中所有标签并遍历输出标签名
items = doc("div.item")
#遍历封装数据并返回
for item in items.items():
yield {
'index':item.find("div.pic em").text(),
'image':item.find("div.pic img").attr('src'),
'title':item.find("div.hd span.title").text(),
'actor':item.find("div.bd p:eq(0)").text(),
'score':item.find("div.bd div.star span.rating_num").text(),
}
'''
# =======使用Beautiful Soup解析====================
# 解析HTML文档
soup = BeautifulSoup(content,"lxml")
#获取网页中所有标签并遍历输出标签名
items = soup.find_all(name="div",attrs={"class":"item"})
print(items)
#遍历封装数据并返回
for item in items:
yield {
'index':item.em.string,
'image':item.find(name="img",attrs={'width':'100'}).attrs['src'],
'title':item.find(name="span",attrs={'class':'title'}).string,
'actor':item.select("div.bd p")[0].get_text(), #内有标签使用string获取不到
'score':item.select("div.star span")[1].string,
}
'''
'''
# =======使用xpath解析====================
# 解析HTML文档,返回根节点对象
html = etree.HTML(content)
#获取网页中所有标签并遍历输出标签名
items = html.xpath('//div[@class="item"]')
#遍历封装数据并返回
for item in items:
yield {
'index':item.xpath('.//div/em[@class=""]/text()')[0],
'image':item.xpath('.//img[@width="100"]/@src')[0],
'title':item.xpath('.//span[@class="title"]/text()')[0],
'actor':item.xpath('.//p[@class=""]/text()')[0],
'score':item.xpath('.//span[@class="rating_num"]/text()'),
#'time':item[4].strip()[5:],
}
'''
def writeFile(content):
'''执行文件追加写操作'''
#print(content)
with open("./result.txt",'a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False) + "\n")
#json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False
def main(offset):
''' 主程序函数,负责调度执行爬虫处理 '''
url = 'https://movie.douban.com/top250?start=' + str(offset)
#print(url)
html = getPage(url)
#判断是否爬取到数据,并调用解析函数
if html:
for item in parsePage(html):
writeFile(item)
# 判断当前执行是否为主程序运行,并遍历调用主函数爬取数据
if __name__ == '__main__':
#main(0)
for i in range(10):
main(offset=i*25)
time.sleep(1)
16. 图片信息爬取实战
1. 案例分析
- 任务:爬取京东指定商品图片信息,并存储在当期目录下。
- url地址:https://list.jd.com/list.html?cat=9987,653,655
- 分析Web的响应内容,并作出对应处理准备:
2. 具体实现代码:
import requests
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
# 定义请求url地址
url = "https://list.jd.com/list.html?cat=9987,653,655"
# 使用requests爬取指定url信息
res = requests.get(url)
#print(res.text)
# 使用BeautifulSoup创建解析器
soup = BeautifulSoup(res.text,"lxml")
# 解析里面的所有商品图片
imlist = soup.find_all(name="img",attrs={"width":"220","height":"220"})
#print(len(imlist))
# 遍历并解析里面的图片url地址信息
m=1
for im in imlist:
#首先判断有无src属性来决定如何获取
if 'src' in im.attrs:
imurl = "https:"+im.attrs['src']
else:
imurl = "https:"+im.attrs['data-lazy-img']
#储存图片(两种方式)
# 使用urllib中urlretrieve直接存储图片
urlretrieve(imurl,'./mypic/p'+str(m)+'.jpg')
'''
# 默认情况下,当您发出请求时,响应正文会立即下载,而设置stream参数为true,则只有响应头已经下载并且连接保持打开状态。
with requests.get(imurl, stream=True) as ir: # 使用with的好处不用考虑close关闭问题。
with open('./mypic/p'+str(m)+'.jpg', 'wb') as f:
for chunk in ir:
f.write(chunk)
'''
print('p'+str(m)+'.jpg')
m += 1
17. Fiddler抓包工具
1. Fiddler抓包工具:
- Fiddler(中文名称:小提琴)是一个HTTP的调试代理,以代理服务器的方式,监听系统的Http网络数据流动
- Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件,这些都可以让你胡乱修改的意思)。
- Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。
- 官方网址:https://www.telerik.com/fiddler
- 文档地址:http://docs.telerik.com/fiddler/configure-fiddler/tasks/configurefiddler
- 运行过程:

- 操作界面:

2. Fiddler配置过程:
- 安装好后打开fiddler→选择 Tools >Fildder Options > Https

- https 勾选下列选项

- Connection勾选允许远程连接

- 设置完成后点击下面的【OK】按钮保存

- 我们在刚开始进入Fildder的界面上,使用手机或者电脑网上冲浪就可以看到我们的http请求了。

3. 使用fiddler抓取HTTPS协议数据与疑难杂症终极解决方案
18. 浏览器伪装技术实战
18.1 网站常见的反爬虫和应对方法
- 一般网站从三个方面反爬虫:
用户请求的Headers,用户行为,网站目录和数据加载方式。 - 前两种比较容易遇到,大多数网站都从这些角度来反爬虫。第三种一些应用ajax的网站会采用,这样增大了爬取的难度。
① 通过Headers反爬虫
- 从用户请求的Headers反爬虫是最常见的反爬虫策略。
- 很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。
- 对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。
- 将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。
② 基于用户行为反爬虫
- 还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。
- 大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。
- 对于第二种情况,可以在每次请求后随机间隔几秒再进行下一次请求。
- 有些有逻辑漏洞的网站,可以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限制。
③ 动态页面的反爬虫
- 大多网站界面都是静态页面(即在浏览器中查看源代码可见),但是还有一部分网站中的数据是后期通过ajax请求(或其他方式如推送技术)得到。
- 解决办法:首先用Firebug或者Fiddler对网络请求进行分析。找到ajax的请求url,通过Python模拟请求得到需要的数据。
- 但是还有些网站把ajax请求的所有参数全部加密了,针对于这方式我们后面会给大家讲解
动态渲染页面信息爬取。
18.2 请求头Headers介绍
1)请求(客户端->服务端[request])
GET(请求的方式) /newcoder/hello.html(请求的目标资源) HTTP/1.1(请求采用的协议和版本号)
Accept: */*(客户端能接收的资源类型)
Accept-Language: en-us(客户端接收的语言类型)
Connection: Keep-Alive(维护客户端和服务端的连接关系)
Host: localhost:8080(连接的目标主机和端口号)
Referer: http://localhost/links.asp(告诉服务器我来自于哪里)
User-Agent: Mozilla/4.0(客户端版本号的名字)
Accept-Encoding: gzip, deflate(客户端能接收的压缩数据的类型)
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(缓存时间)
Cookie(客户端暂存服务端的信息)
Date: Tue, 11 Jul 2000 18:23:51 GMT(客户端请求服务端的时间)
2)响应(服务端->客户端[response])
HTTP/1.1(响应采用的协议和版本号) 200(状态码) OK(描述信息)
Location: http://www.baidu.com(服务端需要客户端访问的页面路径)
Server:apache tomcat(服务端的Web服务端名)
Content-Encoding: gzip(服务端能够发送压缩编码类型)
Content-Length: 80(服务端发送的压缩数据的长度)
Content-Language: zh-cn(服务端发送的语言类型)
Content-Type: text/html; charset=GB2312(服务端发送的类型及采用的编码方式)
Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务端对该资源最后修改的时间)
Refresh: 1;url=http://www.it315.org(服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径)
Content-Disposition: attachment; filename=aaa.zip(服务端要求客户端以下载文件的方式打开该文件)
Transfer-Encoding: chunked(分块传递数据到客户端)
Set-Cookie:SS=Q0=5Lb_nQ; path=/search(服务端发送到客户端的暂存数据)
Expires: -1//3种(服务端禁止客户端缓存页面数据)
Cache-Control: no-cache(服务端禁止客户端缓存页面数据)
Pragma: no-cache(服务端禁止客户端缓存页面数据)
Connection: close(1.0)/(1.1)Keep-Alive(维护客户端和服务端的连接关系)
Date: Tue, 11 Jul 2000 18:23:51 GMT(服务端响应客户端的时间)
18.3 在requests中设置请求头Headers
import requests
# 代理IP地址
proxy = {'HTTP':'117.85.105.170:808','HTTPS':'117.85.105.170:808'}
# header头信息
headers = {
'Host': 'blog.csdn.net',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip, deflate',
'Referer': 'http://www.baidu.com',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
}
# 请求url地址
url = "http://blog.csdn.net"
# 提交请求爬取信息
response = requests.get(url,headers=headers,proxies=proxy)
# 获取响应码
print(response.status_code)
- 浏览器用户代理池信息
user_agents = [
'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
'Opera/9.25 (Windows NT 5.1; U; en)',
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7',
'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0',
]
import random
agent = random.choice(user_agents) # 随机获取一个浏览器用户信息
19. Ajax信息爬取实战
- Ajax = 异步 JavaScript 和 XML(标准通用标记语言的子集)。
- Ajax 是一种用于创建快速动态网页的技术。
- Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
- 案例:爬取京东指定商品信息的评论信息
#爬取指定京东商品的评论信息
import requests
import re
# header头信息
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept-Encoding': 'gzip, deflate',
'Referer': 'http://www.baidu.com',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
}
#请求url地址
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv14&productId=6045059&score=0&sortType=5&page=1&pageSize=10&isShadowSku=4687958&rid=0&fold=1"
# 提交请求爬取信息
response = requests.get(url,headers=headers)
#获取响应码
html = response.text
pat = '"content":"(.*?)"'
items = re.findall(pat,html,re.S)
#print(items)
for v in items:
print(v)
print("="*80)
20. 验证码信息识别
- OCR 即Optical Character Recognition, 光学字符识别,是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程。
- tesserocr 是Python的一个OCR识别库。GitHub:https://github.com/tesseract-ocr/tesseract
20.1 软件安装:
- 注意:在安装tesserocr前都需要先安装tesseract,具体说明如下:
pip install tesserocr #安装tesserocr
pip install pillow # 图片处理目录
注意:
在mac下安装tesserocr ,需要先安装下面的软件
brew install tesseract --all-languages
brew install imagemagick
在:Ubuntu、Debian 系统下先安装:
sudo apt-get intall -y tesseract-ocr libtesseract-dev libleptonica-dev
在:CentOS、Red Hat 系统:
yum install -y tesseract
在windows安装tesserocr前要先安装:tesseract 他是为tesserocr提供支持的
下载目录:https://digi.bib.uni-mannheim.de/tesseract/ 会有很多下载文件,下载一个3.0版本的exe文件即可
在安装中要勾选上Additional language data(download)选项来安装OCR识别支持的语言包
20.2 具体使用:
- 简单识别验证码
#验证识别测试
import tesserocr
from PIL import Image
#打开图片
image = Image.open("./code/5.png")
#识别验证码
result = tesserocr.image_to_text(image)
print(result)
- 验证码的深度处理,如转灰度、二值化等操作
#验证识别测试
import tesserocr
from PIL import Image
image = Image.open("./code/5.png")
#将图片转化为灰度图像
image = image.convert("L")
threshold = 127
table=[]
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
image = image.point(table,'1')
#识别验证码
result = tesserocr.image_to_text(image)
print(result)
21. 网络爬虫阶段案例实战
- 任务:Ajax爬取今日头条的街拍美图
- 爬取url地址:https://www.toutiao.com/search_content/
1. 分析:
- 分析url地址:https://www.toutiao.com/search_content/? 每页20条数据,Ajax加载数据
- 需要提交参数:
params = {
'offset': offset, #页码数据
'format': 'json',
'keyword': '街拍', #搜索关键字
'autoload': 'true',
'count': '20',
'cur_tab': '3',
'from': 'gallery',
}
2. 具体实现代码如下:
import os,time
import requests
from urllib.parse import urlencode
from urllib.request import urlretrieve
def getPage(offset):
'''爬取指定url页面信息'''
params = {
'offset': offset,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': '20',
'cur_tab': '3',
'from': 'gallery',
}
url = 'https://www.toutiao.com/search_content/?' + urlencode(params)
try:
response = requests.get(url)
if response.status_code == 200:
return response.json()
except requests.ConnectionError:
return None
def getImages(json):
'''解析获取图片信息'''
data = json.get('data')
if data:
for item in data:
# print(item)
image_list = item.get('image_list')
title = item.get('title')
# print(image_list)
for image in image_list:
yield {
'image': image.get('url'),
'title': title
}
def saveImage(item):
'''储存图片'''
# 处理每组图片的存储路径
path = os.path.join("./mypic/",item.get('title'))
if not os.path.exists(path):
os.mkdir(path)
# 拼装原图和目标图片的路径即名称
local_image_url = item.get('image')
image_url = "http:"+local_image_url.replace('list','large')
save_pic = path+"/"+local_image_url.split("/").pop()+".jpg"
# 使用urllib中urlretrieve直接存储图片
urlretrieve(image_url,save_pic)
def main(offset):
''' 主程序函数,负责调度执行爬虫处理 '''
json = getPage(offset)
for item in getImages(json):
print(item)
saveImage(item)
# 判断当前执行是否为主程序运行,并遍历调用主函数爬取数据
if __name__ == '__main__':
#main(0)
for i in range(5):
main(offset=i*20)
time.sleep(1)
九、Python网络爬虫进阶实战(上)
1. Scrapy框架介绍与安装
2. Scrapy框架的使用
3. Selector选择器
4. Spider的使用
5. Downloader Middleware的使用
6. Spider Middleware的使用
7. ItemPipeline的使用
8. Scrapy实战案例
1. Scrapy框架介绍与安装
1.1.认识Scrapy框架
Scrapy框架介绍:
Scrapy是: 由Python语言开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。- Scrapy用途广泛,可以用于
数据挖掘、监测和自动化测试。

- Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。
- Scrap,是碎片的意思,这个Python的爬虫框架叫Scrapy。
Scrapy框架的运行原理:

Scrapy主要包括了以下组件:
- 引擎(Scrapy Engine)
- 用来处理整个系统的数据流处理, 触发事务(框架核心)
- Item 项目,它定义了爬取结果的数据结构,爬取的数据会赋值成改Item对象
- 调度器(Scheduler)
- 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回.
- 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader)
- 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
- 爬虫(Spiders)
- 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(Pipeline)
- 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
- 下载器中间件(Downloader Middlewares)
- 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
- 爬虫中间件(Spider Middlewares)
- 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
- 调度中间件(Scheduler Middewares)
- 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程大概如下:
数据处理流程
- Scrapy的整个数据处理流程有Scrapy引擎进行控制,其主要的运行方式为:
- 引擎打开一个域名,时蜘蛛处理这个域名,并让蜘蛛获取第一个爬取的URL。
- 引擎从蜘蛛那获取第一个需要爬取的URL,然后作为请求在调度中进行调度。
- 引擎从调度那获取接下来进行爬取的页面。
- 调度将下一个爬取的URL返回给引擎,引擎将它们通过下载中间件发送到下载器。
- 当网页被下载器下载完成以后,响应内容通过下载中间件被发送到引擎。
- 引擎收到下载器的响应并将它通过蜘蛛中间件发送到蜘蛛进行处理。
- 蜘蛛处理响应并返回爬取到的项目,然后给引擎发送新的请求。
- 引擎将抓取到的项目项目管道,并向调度发送请求。
- 系统重复第二部后面的操作,直到调度中没有请求,然后断开引擎与域之间的联系。
1.2 Scrapy的安装:
- 第一种:在命令行模式下使用pip命令即可安装:
$ pip install scrapy
F:\Python02\demo>scrapy version
Scrapy 1.5.0
- 第二种:首先下载,然后再安装:
$ pip download scrapy -d ./
# 通过指定国内镜像源下载
$pip download -i https://pypi.tuna.tsinghua.edu.cn/simple scrapy -d ./
- 具体文件如下:

- 进入下载目录后执行下面命令安装:
$ pip install Scrapy-1.5.0-py2.py3-none-any.whl
- 注意安装过程中由于缺少
Microsoft Visual C++ 14.0那么请先安装此软件后再执行上面的命令安装就可以了。

- 下载软件如下:visualcppbuildtools_full.exe

1.3 Scrapy爬虫框架的具体使用步骤如下:

2. Scrapy框架的使用
2.1 Scrapy框架的命令介绍
Scrapy 命令 分为两种:全局命令 和 项目命令。
全局命令:在哪里都能使用。项目命令:必须在爬虫项目里面才能使用。
全局命令
C:\Users\AOBO>scrapy -h
Scrapy 1.2.1 - no active project
使用格式:
scrapy <command> [options] [args]
可用的命令:
bench 测试本地硬件性能(工作原理:):scrapy bench
commands
fetch 取URL使用Scrapy下载
genspider 产生新的蜘蛛使用预先定义的模板
runspider 运用单独一个爬虫文件:scrapy runspider abc.py
settings 获取设置值
shell 进入交互终端,用于爬虫的调试(如果你不调试,那么就不常用):scrapy shell http://www.baidu.com --nolog(--nolog 不显示日志信息)
startproject 创建一个爬虫项目,如:scrapy startproject demo(demo 创建的爬虫项目的名字)
version 查看版本:(scrapy version)
view 下载一个网页的源代码,并在默认的文本编辑器中打开这个源代码:scrapy view http://www.aobossir.com/
[ more ] 从项目目录运行时可获得更多命令
使用 "scrapy <command> -h" 要查看有关命令的更多信息
项目命令:
D:\BaiduYunDownload\first>scrapy -h
Scrapy 1.2.1 - project: first
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
check Check spider contracts
commands
crawl 运行一个爬虫文件。:scrapy crawl f1 或者 scrapy crawl f1 --nolog
edit 使用编辑器打开爬虫文件 (Windows上似乎有问题,Linux上没有问题):scrapy edit f1
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
list 列出当前爬虫项目下所有的爬虫文件: scrapy list
parse Parse URL (using its spider) and print the results
runspider Run a self-contained spider (without creating a project)
settings 获取设置值
shell 进入交互终端,用于爬虫的调试(如果你不调试,那么就不常用)
startproject 创建一个爬虫项目,如:scrapy startproject demo(demo 创建的爬虫项目的名字)
version 查看版本:(scrapy version)
view 下载一个网页的源代码,并在默认的文本编辑器中打开这个源代码
Use "scrapy <command> -h" to see more info about a command
注意:Scrapy运行ImportError: No module named win32api错误。请安装:pip install pypiwin32
Scrapy框架的命令使用:
-
查看所有命令
scrapy -h -
查看帮助信息:
scapy --help -
查看版本信息:
(venv)ql@ql:~$ scrapy version Scrapy 1.1.2 (venv)ql@ql:~$ (venv)ql@ql:~$ scrapy version -v Scrapy : 1.1.2 lxml : 3.6.4.0 libxml2 : 2.9.4 Twisted : 16.4.0 Python : 2.7.12 (default, Jul 1 2016, 15:12:24) - [GCC 5.4.0 20160609] pyOpenSSL : 16.1.0 (OpenSSL 1.0.2g-fips 1 Mar 2016) Platform : Linux-4.4.0-36-generic-x86_64-with-Ubuntu-16.04-xenial (venv)ql@ql:~$ -
新建一个工程
scrapy startproject spider_name
-
构建爬虫genspider(generator spider)
-
一个工程中可以存在多个spider, 但是名字必须唯一
scrapy genspider name domain #如: #scrapy genspider sohu sohu.org -
查看当前项目内有多少爬虫
scrapy list -
view使用浏览器打开网页
scrapy view http://www.baidu.com
shell命令, 进入scrpay交互环境
# 进入该url的交互环境
scrapy shell http://www.dmoz.org/Computers/Programming/Languages/Python/Books/
-
之后便进入交互环境,我们主要使用这里面的response命令, 例如可以使用
response.xpath() #括号里直接加xpath路径 -
runspider命令用于直接运行创建的爬虫, 并不会运行整个项目
scrapy runspider 爬虫名称
2.2 Scrapy框架的使用:
- 接下来通过一个简单的项目,完成一遍Scrapy抓取流程。
- 具体流程如下:
- 创建一个scrapy项目:
- 创键一个Spider来抓取站点和处理数据。
- 到过命令行将抓取的抓取内容导出
① 创建项目
- 爬取我爱我家的楼盘信息:
- 网址: https://fang.5i5j.com/bj/loupan/
- 在命令行编写下面命令,创建项目demo
scrapy startproject demo
- 项目目录结构:
demo
├── demo
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
② 进入demo项目目录,创建爬虫spider类文件
- 执行genspider命令,第一个参数是Spider的名称,第二个参数是网站域名。
scrapy genspider fang fang.5i5j.com
$ tree
├── demo
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-36.pyc
│ └── fang.py #在spiders目录下有了一个爬虫类文件fang.py
└── scrapy.cfg
# fang.py的文件代码如下:
# -*- coding: utf-8 -*-
import scrapy
class FangSpider(scrapy.Spider):
name = 'fang'
allowed_domains = ['fang.5i5j.com']
start_urls = ['http://fang.5i5j.com/']
def parse(self, response):
pass
- Spider是自己定义的类,Scrapy用它来从网页中抓取内容,并解析抓取结果。
- 此类继承Scrapy提供的Spider类scrapy.Spider,类中有三个属性:name、allowed_domains、start_urls和方法parse。
- name:是每个项目唯一名字,用于区分不同Spider。
- allowed_domains: 它是允许爬取的域名,如果初始或后续的请求链接不是这个域名,则请求链接会被过滤掉
- start_urls: 它包含了Spider在启动时爬取的URL列表,初始请求是由它来定义的。
- parse方法: 调用start_urls链接请求下载执行后则调用parse方法,并将结果传入此方法。
③ 创建Item
- Item是保存爬取数据的容器,它的使用方法和字典类型,但相比字典多了些保护机制。
- 创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段:(标题、地址、开盘时间、浏览次数、单价)
- 具体代码如下:
import scrapy
class FangItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
address = scrapy.Field()
time = scrapy.Field()
clicks = scrapy.Field()
price = scrapy.Field()
#pass
④ 解析Response
- 在fang.py文件中,parse()方法的参数response是start_urls里面的链接爬取后的结果。
- 提取的方式可以是CSS选择器、XPath选择器或者是re正则表达式。
# -*- coding: utf-8 -*-
import scrapy
from demo.items import FangItem
class FangSpider(scrapy.Spider):
name = 'fang'
allowed_domains = ['fang.5i5j.com']
#start_urls = ['http://fang.5i5j.com/']
start_urls = ['https://fang.5i5j.com/bj/loupan/']
def parse(self, response):
hlist = response.css("div.houseList_list")
for vo in hlist:
item = FangItem()
item['title'] = vo.css("h3.fontS20 a::text").extract_first()
item['address'] = vo.css("span.addressName::text").extract_first()
item['time'] = vo.re("<span>(.*?)开盘</span>")[0]
item['clicks'] = vo.re("<span><i>([0-9]+)</i>浏览</span>")[0]
item['price'] = vo.css("i.fontS24::text").extract_first()
#print(item)
yield item
⑤ 使用Item Pipeline
- Item Pipeline为项目管道,当Item生产后,他会自动被送到Item Pipeline进行处理:
- 我们常用Item Pipeline来做如下操作:
- 清理HTML数据
- 验证抓取数据,检查抓取字段
- 查重并丢弃重复内容
- 将爬取结果保存到数据库里。
class DemoPipeline(object):
def process_item(self, item, spider):
print(item)
return item
- 进入配置settings中开启Item Pipelines的使用
⑥ 运行:
-
执行如下命令来启用数据爬取
scrapy crawl fang -
将结果保存到文件中: 格式:json、csv、xml、pickle、marshal等
scrapy crawl fang -o fangs.json
scrapy crawl fang -o fangs.csv
scrapy crawl fang -o fangs.xml
scrapy crawl fang -o fangs.pickle
scrapy crawl fang -o fangs.marshal
2.3 Scrapy框架中的POST提交:
- 在Scrapy框架中默认都是GET的提交方式,但是我们可以使用
FormRequest来完成POST提交,并可以携带参数。 - 如下案例为有道词典的翻译信息爬取案例,网址:
http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule - 首先创建一个
youdao有道的爬虫文件:
scrapy genspider youdao fanyi.youdao.com
- 编写爬虫文件,注意返回的是json格式,具体代码如下:
# -*- coding: utf-8 -*-
import scrapy,json
class YoudaoSpider(scrapy.Spider):
name = 'youdao'
allowed_domains = ['fanyi.youdao.com']
#start_urls = ['http://fanyi.youdao.com']
def start_requests(self):
url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
keyword = input("请输入要翻译的单词:")
data = {'i':keyword,'doctype': 'json',}
# FormRequest 是Scrapy发送POST请求的方法
yield scrapy.FormRequest(
url = url,
formdata = data,
callback = self.parse
)
def parse(self, response):
res = json.loads(response.body)
print(res['translateResult'][0][0]['tgt'])
input("按任意键继续")
3. Selector选择器
- 对用爬取信息的解析,我们在之前已经介绍了正则re、Xpath、Beautiful Soup和PyQuery。
- 而Scrapy还给我们提供自己的数据解析方法,即Selector(选择器)。
- Selector(选择器)是基于lxml来构建的,支持XPath、CSS选择器以及正则表达式,功能全面,解析速度和准确度非常高。
3.1 直接使用:
- Selector(选择器)是一个可以独立使用模块。 直接导入模块,就可以实例化使用,如下所示:
from scrapy import Selector
content="<html><head><title>My html</title><body><h3>Hello Word!</h3></body></head></html>"
selector = Selector(text=content)
print(selector.xpath('/html/head/title/text()').extract_first())
print(selector.css('h3::text').extract_first())
3.2 Scrapy shell
- 我们借助于Scrapy shell来模拟请求的过程,然后把一些可操作的变量传递给我们,如request、response等。
zhangtaodeMacBook-Pro:scrapydemo zhangtao$ scrapy shell http://www.baidu.com
2018-05-08 14:46:29 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: scrapybot)
2018-05-08 14:46:29 [scrapy.utils.log] INFO: Versions: lxml 4.2.1.0, libxml2 2.9.8, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 18.4.0, Python 3.6.4 (default, Jan 6 2018, 11:49:38) - [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)], pyOpenSSL 17.5.0 (OpenSSL 1.1.0h 27 Mar 2018), cryptography 2.2.2, Platform Darwin-15.6.0-x86_64-i386-64bit
2018-05-08 14:46:29 [scrapy.crawler] INFO: Overridden settings: {'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter', 'LOGSTATS_INTERVAL': 0}
2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.memusage.MemoryUsage']
2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2018-05-08 14:46:29 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2018-05-08 14:46:29 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-05-08 14:46:29 [scrapy.core.engine] INFO: Spider opened
2018-05-08 14:46:29 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.baidu.com> (referer: None)
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x108ea8ac8>
[s] item {}
[s] request <GET http://www.baidu.com>
[s] response <200 http://www.baidu.com>
[s] settings <scrapy.settings.Settings object at 0x109cbb8d0>
[s] spider <DefaultSpider 'default' at 0x109f56e10>
[s] Useful shortcuts:
[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s] fetch(req) Fetch a scrapy.Request and update local objects
[s] shelp() Shell help (print this help)
[s] view(response) View response in a browser
>>> response.url
'http://www.baidu.com'
>>> response.status
200
>>> response.xpath('/html/head/title/text()').extract_first()
'百度一下,你就知道'
>>> response.xpath('//a/text()').extract_first()
'新闻'
>>> response.xpath('//a/text()').extract()
['新闻', 'hao123', '地图', '视频', '贴吧', '登录', '更多产品', '关于百度', 'About Baidu', '使用百度前必读', '意见反馈']
>>> response.xpath('//a/@href').extract()
['http://news.baidu.com', 'http://www.hao123.com', 'http://map.baidu.com', 'http://v.baidu.com', 'http://tieba.baidu.com', 'http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1', '//www.baidu.com/more/', 'http://home.baidu.com', 'http://ir.baidu.com', 'http://www.baidu.com/duty/', 'http://jianyi.baidu.com/']
3.3 Xpath选择器:
- response.selector属性返回内容相当于response的body构造了一个Selector对象。
- Selector对象可以调用xpath()方法实现信息的解析提取。
- 在xpath()后使用extract()可以返回所有的元素结果。
- 若xpath()有问题,那么extract()会返回一个空列表。
- 在xpath()后使用extract_first()可以返回第一个元素结果。
- 使用scrapy shell 爬取"淘宝网"->"商品分类"->"特色市场"的信息。
$ scrapy shell https://www.taobao.com/tbhome/page/special-markets
... ...
>>> response.url
'https://www.taobao.com/tbhome/page/special-markets'
>>> response.status
200
>>> response.selector.xpath("//dt/text()")
[<Selector xpath='//dt/text()' data='时尚爆料王'>, <Selector xpath='//dt/text()' data='品质生活家'>,
<Selector xpath='//dt/text()' data='特色玩味控'>, <Selector xpath='//dt/text()' data='实惠专业户'>]
>>> response.selector.xpath("//dt/text()").extract()
['时尚爆料王', '品质生活家', '特色玩味控', '实惠专业户']
>>> dllist = response.selector.xpath("//dl[@class='market-list']")
>>> for v in dllist:
... print(v.xpath("./dt/text()").extract_first())
...
时尚爆料王
品质生活家
特色玩味控
实惠专业户
>>> for v in dllist:
... print(v.xpath("./dt/text()").extract_first())
... print("="*50)
... alist = v.xpath(".//a")
... for a in alist:
... print(a.xpath("./@href").extract_first(),end=":")
... print(a.xpath("./span/img/@alt").extract_first())
...
时尚爆料王
==================================================
https://if.taobao.com/:潮流从这里开始
https://guang.taobao.com/:外貌协会の逛街指南
https://mei.taobao.com/:妆 出你的腔调
https://g.taobao.com/:探索全球美好生活
//star.taobao.com/:全球明星在这里
https://mm.taobao.com/:美女红人集中地
https://www.taobao.com/markets/designer/stylish:全球创意设计师平台
品质生活家
==================================================
https://chi.taobao.com/chi/:食尚全球 地道中国
//q.taobao.com:懂得好生活
https://www.jiyoujia.com/:过我想要的生活
https://www.taobao.com/markets/sph/sph/sy:尖货奢品品味选择
https://www.taobao.com/markets/qbb/index:享受育儿生活新方式
//car.taobao.com/:买车省钱,用车省心
//sport.taobao.com/:爱上运动每一天
//zj.taobao.com:匠心所在 物有所值
//wt.taobao.com/:畅享优质通信生活
https://www.alitrip.com/:比梦想走更远
特色玩味控
==================================================
https://china.taobao.com:地道才够味!
https://www.taobao.com/markets/3c/tbdc:为你开启潮流新生活
https://acg.taobao.com/:ACGN 好玩好看
https://izhongchou.taobao.com/index.htm:认真对待每一个梦想。
//enjoy.taobao.com/:园艺宠物爱好者集中营
https://sf.taobao.com/:法院处置资产,0佣金捡漏
https://zc-paimai.taobao.com/:超值资产,投资首选
https://paimai.taobao.com/:想淘宝上拍卖
//xue.taobao.com/:给你未来的学习体验
//2.taobao.com:让你的闲置游起来
https://ny.taobao.com/:价格实惠品类齐全
实惠专业户
==================================================
//tejia.taobao.com/:优质好货 特价专区
https://qing.taobao.com/:品牌尾货365天最低价
https://ju.taobao.com/jusp/other/mingpin/tp.htm:奢侈品团购第一站
https://ju.taobao.com/jusp/other/juliangfan/tp.htm?spm=608.5847457.102202.5.jO4uZI:重新定义家庭生活方式
https://qiang.taobao.com/:抢到就是赚到!
https://ju.taobao.com/jusp/nv/fcdppc/tp.htm:大牌正品 底价特惠
https://ju.taobao.com/jusp/shh/life/tp.htm?:惠聚身边精选好货
https://ju.taobao.com/jusp/sp/global/tp.htm?spm=0.0.0.0.biIDGB:10点上新 全球底价
https://try.taobao.com/index.htm:总有新奇等你发现
3.4 CSS选择器:
- 同xpath()一样。
- 使用scrapy shell 爬取"淘宝网"->"商品分类"->"主题市场"的信息。
>>> response.url
'https://www.taobao.com/tbhome/page/market-list'
>>> response.status
200
>>> response.css("a.category-name-level1::text").extract()
['女装男装', '鞋类箱包', '母婴用品', '护肤彩妆', '汇吃美食', '珠宝配饰', '家装建材', '家居家纺', '百货市场', '汽车·用品', '手机数码', '家电办公', '更多服务', '生活服务', '运动户外', '花鸟文娱', '农资采购']
#获取淘宝页面中所有分类信息
>>> dlist = response.css("div.home-category-list")
>>> for dd in dlist:
print(dd.css("a.category-name-level1::text").extract_first())
print("="*50)
alist = dd.css("li.category-list-item")
for v in alist:
print(v.xpath("./a/text()").extract_first())
print("-"*50)
talist = v.css("div.category-items a")
for a in talist:
print(a.css("::text").extract_first(),end=" ")
print()
>>>女装男装
==================================================
潮流女装
--------------------------------------------------
羽绒服 毛呢大衣 毛衣 冬季外套 新品 裤子 连衣裙 腔调
时尚男装
--------------------------------------------------
秋冬新品 淘特莱斯 淘先生 拾货 秋冬外套 时尚套装 潮牌 爸爸装
性感内衣
--------------------------------------------------
春新品 性感诱惑 甜美清新 简约优雅 奢华高贵 运动风 塑身 基础内衣
...
注:css中获取属性:a.css("::attr(href)").extract_first()
3.5 正则匹配:
>>> response.xpath("//head").re("<title>(.*?)</title>")
['淘宝首页行业市场']
>>> response.selector.re("<a .*?>(.*?)</a>")
4. Spider的使用
- 在Scrapy中,要抓取网站的链接配置、抓取逻辑、解析逻辑里其实都是在Spider中配置的。
- Spider要做的事就是有两件:
定义抓取网站的动作和分析爬取下来的网页。
4.1 Spider运行流程:
- 整个抓取循环过程如下所述:
- 以初始的URL初始化Request,并设置回调函数。请求成功时Response生成并作为参数传给该回调函数。
- 在回调函数内分析返回的网页内容。返回结果两种形式,一种为字典或Item数据对象;另一种是解析到下一个链接。
- 如果返回的是字典或Item对象,我们可以将结果存入文件,也可以使用Pipeline处理并保存。
- 如果返回Request,Response会被传递给Request中定义的回调函数参数,即再次使用选择器来分析生成数据Item。
4.2 Spider类分析:
- Spider类源代码:打开文件
Python36/Lib/site-packages/scrapy/spiders/__init__.py
import logging
import warnings
from scrapy import signals
from scrapy.http import Request
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider
from scrapy.utils.deprecate import create_deprecated_class
from scrapy.exceptions import ScrapyDeprecationWarning
from scrapy.utils.deprecate import method_is_overridden
#所有爬虫的基类,自定义的爬虫必须从继承此类
class Spider(object_ref):
#定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。
#name是spider最重要的属性,而且是必须的。
#一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 douban.com ,该spider通常会被命名为 douban
name = None
custom_settings = None
#初始化,提取爬虫名字,start_ruls
def __init__(self, name=None, **kwargs):
if name is not None:
self.name = name
# 如果爬虫没有名字,中断后续操作则报错
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
# python 对象或类型通过内置成员__dict__来存储成员信息
self.__dict__.update(kwargs)
#URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。
if not hasattr(self, 'start_urls'):
self.start_urls = []
@property
def logger(self):
logger = logging.getLogger(self.name)
return logging.LoggerAdapter(logger, {'spider': self})
# 打印Scrapy执行后的log信息
def log(self, message, level=log.DEBUG, **kw):
log.msg(message, spider=self, level=level, **kw)
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = cls(*args, **kwargs)
spider._set_crawler(crawler)
return spider
#判断对象object的属性是否存在,不存在做断言处理
def set_crawler(self, crawler):
assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler
self._set_crawler(crawler)
def _set_crawler(self, crawler):
self.crawler = crawler
self.settings = crawler.settings
crawler.signals.connect(self.close, signals.spider_closed)
#@property
#def crawler(self):
# assert hasattr(self, '_crawler'), "Spider not bounded to any crawler"
# return self._crawler
#@property
#def settings(self):
# return self.crawler.settings
#该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response
#该方法仅调用一次
def start_requests(self):
for url in self.start_urls:
yield self.make_requests_from_url(url)
#start_requests()中调用,实际生成Request的函数。
#Request对象默认的回调函数为parse(),提交的方式为get
def make_requests_from_url(self, url):
return Request(url, dont_filter=True)
#默认的Request对象回调函数,处理返回的response。
#生成Item或者Request对象。用户必须实现这个类
def parse(self, response):
raise NotImplementedError
@classmethod
def handles_request(cls, request):
return url_is_from_spider(request.url, cls)
@staticmethod
def close(spider, reason):
closed = getattr(spider, 'closed', None)
if callable(closed):
return closed(reason)
def __str__(self):
return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))
__repr__ = __str__
-
Spider类继承自scrapy.spiders.Spider. -
Spider类这个提供了start_requests()方法的默认实现,读取并请求start_urls属性,并调用parse()方法解析结果。 -
Spider类的属性和方法:
name:爬虫名称,必须唯一的,可以生成多个相同的Spider实例,数量没有限制。allowed_domains: 允许爬取的域名,是可选配置,不在此范围的链接不会被跟进爬取。start_urls: 它是起始URL列表,当我们没有实现start_requests()方法时,默认会从这个列表开始抓取。custom_settings: 它是一个字典,专属于Spider的配置,此设置会覆盖项目全局的设置,必须定义成类变量。crawler:它是由from_crawler()方法设置的,Crawler对象包含了很多项目组件,可以获取settings等配置信息。settings: 利用它我们可以直接获取项目的全局设置变量。start_requests(): 使用start_urls里面的URL来构造Request,而且Request是GET请求方法。parse(): 当Response没有指定回调函数时,该方法会默认被调用。closed(): 当Spider关闭时,该方法会调用。
4.3 实战案例:
- 任务:使用scrapy爬取关键字为
python信息的百度文库搜索信息(每页10条信息) - url地址分析:
https://wenku.baidu.com/search?word=python&pn=0第一页 - url地址分析:
https://wenku.baidu.com/search?word=python&pn=10第二页 - 具体实现:
- ① 使用scrapy命令创建爬虫项目dbwenku
$ scrapy startproject bdwenku
- ② 创建spider爬虫文件wenku:
$ cd bdwenku
$ scrapy genspider wenku wenku.baidu.com
- ③ 编辑
wenku.py爬虫文件,代码如下:
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
def parse(self, response):
print("Hello Scrapy")
print(response)
- ④ 运行测试:
$ scrapy crawl wenku
- 爬取数据出现错误:
DEBUG: Forbidden by robots.txt:请求被拒绝了 - 也就是百度文库的robots.txt设置了禁止外部爬取信息。
- 解决办法:在settings.py配置文件中,将ROBOTSTXT_OBEY的值设为False,忽略robot协议,继续爬取。
...
2018-05-11 11:00:54 [scrapy.core.engine] INFO: Spider opened
2018-05-11 11:00:54 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-05-11 11:00:54 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6027
2018-05-11 11:00:56 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://wenku.baidu.com/robots.txt> (referer: None)
2018-05-11 11:00:56 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET https://wenku.baidu.com/search?word=python&pn=0>
2018-05-11 11:00:56 [scrapy.core.engine] INFO: Closing spider (finished)
2018-05-11 11:00:56 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
...
- ⑤ Spider爬虫文件:
wenku.py的几种写法:
# 单请求的信息爬取
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
# 多个请求的信息爬取
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0','https://wenku.baidu.com/search?word=python&pn=10','https://wenku.baidu.com/search?word=python&pn=20']
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
print("="*70) #输出一条每个请求后分割线
# 实现一个爬取循环,获取10页信息
# -*- coding: utf-8 -*-
import scrapy
class WenkuSpider(scrapy.Spider):
name = 'wenku'
allowed_domains = ['wenku.baidu.com']
start_urls = ['https://wenku.baidu.com/search?word=python&pn=0']
p=0
def parse(self, response):
dllist = response.selector.xpath("//dl")
#print(len(dllist))
for dd in dllist:
print(dd.xpath("./dt/p/a/@title").extract_first())
print("="*70)
self.p += 1
if self.p < 10:
next_url = 'https://wenku.baidu.com/search?word=python&pn='+str(self.p*10)
url = response.urljoin(next_url) #构建绝对url地址(这里可省略)
yield scrapy.Request(url=url,callback=self.parse)
python
python教程
PYTHON测试题
简明_Python_教程
如何自学 Python(干货合集)
python
python
Python介绍(Introduction to Python)
Python编程入门(适合于零基础朋友)
Python教程
======================================================================
python
十分钟学会Python
Python 基础语法(一)
python基础分享
Python入门
python新手教程
python资源中文大全
python_笔记
Python与中文处理
python
======================================================================
python
python教程
PYTHON测试题
简明_Python_教程
如何自学 Python(干货合集)
python
python
Python介绍(Introduction to Python)
Python编程入门(适合于零基础朋友)
Python教程
======================================================================
5. Downloader Middleware的使用
- 在Downloader Middleware的功能十分强大:可以修改User-Agent、处理重定向、设置代理、失败重试、设置Cookies等。
- Downloader Middleware在整个架构中起作用的位置是以下两个。
- 在Scheduler调度出队列的Request发送给Doanloader下载之前,也就是我们可以在Request执行下载前对其进行修改。
- 在下载后生成的Response发送给Spider之前,也就是我们可以生成Resposne被Spider解析之前对其进行修改。
5.1 使用说明:
- 在Scrapy中已经提供了许多Downloader Middleware,如:负责失败重试、自动重定向等中间件:
- 它们都被定义到DOWNLOADER_MIDDLEWARES_BASE变量中。
# 在python3.6/site-packages/scrapy/settings/default_settings.py默认配置中
DOWNLOADER_MIDDLEWARES_BASE = {
# Engine side
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
# Downloader side
}
- 字典格式,其中数字为优先级,越小的优先调用。
5.2 自定义Downloader Middleware中间件
- 我们可以通过项目的DOWNLOADER_MIDDLEWARES变量设置来添加自己定义的Downloader Middleware。
- 其中Downloader Middleware有三个核心方法:
- process_request(request,spider)
- process_response(request,response,spider)
- process_exception(request,exception,spider)
① process_request(request,spider)
- 当每个request通过下载中间件时,该方法被调用,这里有一个要求,该方法必须返回以下三种中的任意一种:
None,返回一个Response对象、返回一个Request对象或raise IgnoreRequest。三种返回值的作用是不同的。 None:Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用,该request被执行(其response被下载)。Response对象:Scrapy将不会调用任何其他的process_request()或process_exception() 方法,或相应地下载函数;其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。Request对象:Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。raise一个IgnoreRequest异常:则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常,则该异常被忽略且不记录。
② process_response(request, response, spider)
- process_response的返回值也是有三种:
response对象,request对象,或者raise一个IgnoreRequest异常 - 如果其返回一个Response(可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
- 如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
- 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。
- 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
- 这里我们写一个简单的例子还是上面的项目,我们在中间件中继续添加如下代码:
...
def process_response(self, request, response, spider):
response.status = 201
return response
...
③ process_exception(request, exception, spider)
- 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常(包括 IgnoreRequest 异常)时,Scrapy调用 process_exception()。
- process_exception() 也是返回三者中的一个: 返回 None 、 一个 Response 对象、或者一个 Request 对象。
- 如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。
- 如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。
5.3 实战案例:
- 任务测试:使用scrapy爬取豆瓣图书Top250信息
- 网址:https://book.douban.com/top250?start=0
- 使用shell命令直接爬取报403错误
# 在命令行下直接运行scrapy shell命令爬取信息,报403错误
$ scrapy shell https://book.douban.com/top250
>>> response.status
>>> 403
- ① 新建一个项目douban,命令如下所示:
scrapy startproject douban
- ② 新建一个Spider类,名字为dbbook,命令如下所示:
cd douban
scrapy genspider dbbook book.douban.com
- 编写爬虫代码。如下:
# -*- coding: utf-8 -*-
import scrapy
class DbbookSpider(scrapy.Spider):
name = 'dbbook'
allowed_domains = ['book.douban.com']
start_urls = ['https://book.douban.com/top250?start=0']
def parse(self, response):
#print("状态:")
pass
- ③ 执行爬虫命令,排除错误。
$ scrapy crawl dbbook #结果返回403错误(服务器端拒绝访问)。
原因分析:默认scrapy框架请求信息中的User-Agent的值为:Scrapy/1.5.0(http://scrapy.org).
解决方案:我们可以在settings.py配置文件中:设置 USER_AGENT 或者DEFAULT_REQUEST_HEADERS信息:
USER_AGENT = 'Opera/9.80(WindowsNT6.1;U;en)Presto/2.8.131Version/11.11'
或者
...
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
}
...
- ④ 开启Downloader Middleware中间件
- 在项目的settings.py配置文件中:开启设置
DOWNLOADER_MIDDLEWARES信息:
DOWNLOADER_MIDDLEWARES = {
'douban.middlewares.DoubanDownloaderMiddleware': 543,
}
def process_request(self, request, spider):
#输出header头信息
print(request.headers)
#伪装浏览器用户
request.headers['user-agent']='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
return None
6. Spider Middleware的使用
- Spider中间件是介入到Scrapy的spider处理机制的钩子框架,您可以添加代码来处理发送给 Spiders 的response及spider产生的item和request。
6.1 激活spider中间件
- 要启用spider中间件,您可以将其加入到 SPIDER_MIDDLEWARES 设置中。 该设置是一个字典,键位中间件的路径,值为中间件的顺序(order)。
- 样例:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
}
- SPIDER_MIDDLEWARES 设置会与Scrapy定义的 SPIDER_MIDDLEWARES_BASE 设置合并(但不是覆盖), 而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近spider的。
- 关于如何分配中间件的顺序请查看 SPIDER_MIDDLEWARES_BASE 设置,而后根据您想要放置中间件的位置选择一个值。 由于每个中间件执行不同的动作,您的中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。
- 如果您想禁止内置的(在 SPIDER_MIDDLEWARES_BASE 中设置并默认启用的)中间件, 您必须在项目的 SPIDER_MIDDLEWARES 设置中定义该中间件,并将其值赋为 None 。 例如,如果您想要关闭off-site中间件:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None,
}
- 最后,请注意,有些中间件需要通过特定的设置来启用。更多内容请查看相关中间件文档。
6.2 编写您自己的spider中间件
- 编写spider中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:
- 来自类:class scrapy.contrib.spidermiddleware.SpiderMiddleware
process_spider_input(response, spider)
当response通过spider中间件时,该方法被调用,处理该response。
`process_spider_input()` 应该返回 None 或者抛出一个异常。
如果其返回 None ,Scrapy将会继续处理该response,调用所有其他的中间件直到spider处理该response。
如果其跑出一个异常(exception),Scrapy将不会调用任何其他中间件的 process_spider_input() 方法,并调用request的errback。 errback的输出将会以另一个方向被重新输入到中间件链中,使用 process_spider_output() 方法来处理,当其抛出异常时则带调用 process_spider_exception() 。
参数:
response (Response 对象) – 被处理的response
spider (Spider 对象) – 该response对应的spider
process_spider_output(response, result, spider)
当Spider处理response返回result时,该方法被调用。
`process_spider_output()` 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)。
参数:
response (Response 对象) – 生成该输出的response
result (包含 Request 或 Item 对象的可迭代对象(iterable)) – spider返回的result
spider (Spider 对象) – 其结果被处理的spider
process_spider_exception(response, exception, spider)
当spider或(其他spider中间件的) process_spider_input() 跑出异常时, 该方法被调用。
`process_spider_exception()` 必须要么返回 None , 要么返回一个包含 Response 或 Item 对象的可迭代对象(iterable)。
如果其返回 None ,Scrapy将继续处理该异常,调用中间件链中的其他中间件的 process_spider_exception() 方法,直到所有中间件都被调用,该异常到达引擎(异常将被记录并被忽略)。
如果其返回一个可迭代对象,则中间件链的 process_spider_output() 方法被调用, 其他的 process_spider_exception() 将不会被调用。
参数:
response (Response 对象) – 异常被抛出时被处理的response
exception (Exception 对象) – 被跑出的异常
spider (Spider 对象) – 抛出该异常的spider
process_start_requests(start_requests, spider)
0.15 新版功能.
该方法以spider 启动的request为参数被调用,执行的过程类似于 process_spider_output() ,只不过其没有相关联的response并且必须返回request(不是item)。
其接受一个可迭代的对象(start_requests 参数)且必须返回另一个包含 Request 对象的可迭代对象。
注解
当在您的spider中间件实现该方法时, 您必须返回一个可迭代对象(类似于参数start_requests)且不要遍历所有的 start_requests。 该迭代器会很大(甚至是无限),进而导致内存溢出。 Scrapy引擎在其具有能力处理start request时将会拉起request, 因此start request迭代器会变得无限,而由其他参数来停止spider( 例如时间限制或者item/page记数)。
参数:
start_requests (包含 Request 的可迭代对象) – start requests
spider (Spider 对象) – start requests所属的spider
Scrapy框架的配置Settings
- Scrapy设置(settings)提供了定制Scrapy组件的方法。可以控制包括核心(core),插件(extension),pipeline及spider组件。
- 参考文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/settings.html#topics-settings-ref
内置设置参考手册
-
BOT_NAME- 默认: 'scrapybot'
- 当您使用 startproject 命令创建项目时其也被自动赋值。
-
CONCURRENT_ITEMS- 默认: 100
- Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。
-
CONCURRENT_REQUESTS- 默认: 16
- Scrapy downloader 并发请求(concurrent requests)的最大值。
-
DEFAULT_REQUEST_HEADERS-
默认: 如下
{ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', } -
Scrapy HTTP Request使用的默认header。
-
-
DEPTH_LIMIT- 默认: 0
- 爬取网站最大允许的深度(depth)值。如果为0,则没有限制。
-
DOWNLOAD_DELAY- 默认: 0
- 下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:
-
DOWNLOAD_DELAY = 0.25 # 250 ms of delay- 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。
-
DOWNLOAD_TIMEOUT- 默认: 180
- 下载器超时时间(单位: 秒)。
-
ITEM_PIPELINES-
默认: {}
-
保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意,不过值(value)习惯设置在0-1000范围内,值越小优先级越高。
ITEM_PIPELINES = { 'mySpider.pipelines.SomethingPipeline': 300, 'mySpider.pipelines.ItcastJsonPipeline': 800, }
-
-
LOG_ENABLED- 默认: True
- 是否启用logging。
-
LOG_ENCODING- 默认: 'utf-8'
- logging使用的编码。
-
LOG_LEVEL- 默认: 'DEBUG'
- log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。
-
USER_AGENT- 默认: "Scrapy/VERSION (+http://scrapy.org)"
- 爬取的默认User-Agent,除非被覆盖。
-
PROXIES:代理设置-
示例:
PROXIES = [ {'ip_port': '111.11.228.75:80', 'password': ''}, {'ip_port': '120.198.243.22:80', 'password': ''}, {'ip_port': '111.8.60.9:8123', 'password': ''}, {'ip_port': '101.71.27.120:80', 'password': ''}, {'ip_port': '122.96.59.104:80', 'password': ''}, {'ip_port': '122.224.249.122:8088', 'password':''}, ]
-
-
COOKIES_ENABLED = False- 禁用Cookies
7. ItemPipeline的使用
- 当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。
- 每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
- 以下是item pipeline的一些典型应用:
- 清理HTML数据
- 验证爬取的数据(检查item包含某些字段)
- 查重(并丢弃)
- 将爬取结果保存到数据库中
7.1 如何编写你自己的item pipeline
- 编写你自己的item pipeline很简单,每个item pipiline组件是一个独立的Python类,同时必须实现以下方法:
① process_item(item, spider)
- 每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象, 或是抛出 DropItem 异常,被丢弃的item将不会被之后的pipeline组件所处理。
- 参数:
- item (Item 对象) – 被爬取的item
- spider (Spider 对象) – 爬取该item的spider
- 此外,他们也可以实现以下方法:
② open_spider(spider)
- 当spider被开启时,这个方法被调用。
- 参数: spider (Spider 对象) – 被开启的spider
③ close_spider(spider)
- 当spider被关闭时,这个方法被调用
- 参数: spider (Spider 对象) – 被关闭的spider
7.2 样例:
验证价格,同时丢弃没有价格的item
- 让我们来看一下以下这个假设的pipeline,它为那些不含税(price_excludes_vat 属性)的item调整了 price 属性,同时丢弃了那些没有价格的item:
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
将item写入JSON文件:
- 以下pipeline将所有(从所有spider中)爬取到的item,存储到一个独立地 items.jl 文件,每行包含一个序列化为JSON格式的item:
import json
class JsonWriterPipeline(object):
def __init__(self):
self.file = open('items.jl', 'wb')
def process_item(self, item, spider):
line = json.dumps(dict(item)) + "\n"
self.file.write(line)
return item
- 注解:JsonWriterPipeline的目的只是为了介绍怎样编写item pipeline,如果你想要将所有爬取的item都保存到同一个JSON文件, 你需要使用 Feed exports 。
去重
- 一个用于去重的过滤器,丢弃那些已经被处理过的item。让我们假设我们的item有一个唯一的id,但是我们spider返回的多个item中包含有相同的id:
from scrapy.exceptions import DropItem
class DuplicatesPipeline(object):
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
return item
启用一个Item Pipeline组件:
- 为了启用一个Item Pipeline组件,你必须将它的类添加到 ITEM_PIPELINES 配置,就像下面这个例子:
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
- 分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内。
7.3 Scrapy框架案例实战:
- 任务:爬取csdn学院中的课程信息(编程语言的)
- 网址:
https://edu.csdn.net/courses/o280/p1(第一页) https://edu.csdn.net/courses/o280/p2(第二页)
① 创建项目
- 在命令行编写下面命令,创建项目demo
scrapy startproject educsdn
- 项目目录结构:
educsdn
├── educsdn
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
② 进入educsdn项目目录,创建爬虫spider类文件(courses课程)
- 执行genspider命令,第一个参数是Spider的名称,第二个参数是网站域名。
scrapy genspider courses edu.csdn.net
$ tree
├── demo
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-36.pyc
│ └── courses.py #在spiders目录下有了一个爬虫类文件courses.py
└── scrapy.cfg
# courses.py的文件代码如下:
# -*- coding: utf-8 -*-
import scrapy
class CoursesSpider(scrapy.Spider):
name = 'courses'
allowed_domains = ['edu.csdn.net']
start_urls = ['http://edu.csdn.net/']
def parse(self, response):
pass
③ 创建Item
- Item是保存爬取数据的容器,它的使用方法和字典类型,但相比字典多了些保护机制。
- 创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段:(课程标题、课程地址、图片、授课老师,视频时长、价格)
- 具体代码如下:(修改类名为CoursesItem)
import scrapy
class CoursesItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
url = scrapy.Field()
pic = scrapy.Field()
teacher = scrapy.Field()
time = scrapy.Field()
price = scrapy.Field()
#pass
④ 解析Response
- 在fang.py文件中,parse()方法的参数response是start_urls里面的链接爬取后的结果。
- 提取的方式可以是CSS选择器、XPath选择器或者是re正则表达式。
# -*- coding: utf-8 -*-
import scrapy
from educsdn.items import CoursesItem
class CoursesSpider(scrapy.Spider):
name = 'courses'
allowed_domains = ['edu.csdn.net']
start_urls = ['https://edu.csdn.net/courses/o280/p1']
p=1
def parse(self, response):
#解析并输出课程标题
#print(response.selector.css("div.course_dl_list span.title::text").extract())
#获取所有课程
dlist = response.selector.css("div.course_dl_list")
#遍历课程,并解析信息后封装到item容器中
for dd in dlist:
item = CoursesItem()
item['title'] = dd.css("span.title::text").extract_first()
item['url'] = dd.css("a::attr(href)").extract_first()
item['pic'] = dd.css("img::attr(src)").extract_first()
item['teacher'] = dd.re_first("<p>讲师:(.*?)</p>")
item['time'] = dd.re_first("<em>([0-9]+)</em>课时")
item['price'] = dd.re_first("¥([0-9\.]+)")
#print(item)
#print("="*70)
yield item
#获取前10页的课程信息
self.p += 1
if self.p <= 10:
next_url = 'https://edu.csdn.net/courses/o280/p'+str(self.p)
url = response.urljoin(next_url) #构建绝对url地址(这里可省略)
yield scrapy.Request(url=url,callback=self.parse)
⑤、创建数据库和表:
- 在mysql中创建数据库
csdndb和数据表courses
CREATE TABLE `courses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`pic` varchar(255) DEFAULT NULL,
`teacher` varchar(32) DEFAULT NULL,
`time` varchar(16) DEFAULT NULL,
`price` varchar(16) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
⑥、使用Item Pipeline
- Item Pipeline为项目管道,当Item生产后,他会自动被送到Item Pipeline进行处理:
- 我们常用Item Pipeline来做如下操作:
- 清理HTML数据
- 验证抓取数据,检查抓取字段
- 查重并丢弃重复内容
- 将爬取结果保存到数据库里。
import pymysql
from scrapy.exceptions import DropItem
class EducsdnPipeline(object):
def process_item(self, item, spider):
if item['price'] == None:
raise DropItem("Drop item found: %s" % item)
else:
return item
class MysqlPipeline(object):
def __init__(self,host,database,user,password,port):
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
self.db=None
self.cursor=None
@classmethod
def from_crawler(cls,crawler):
return cls(
host = crawler.settings.get("MYSQL_HOST"),
database = crawler.settings.get("MYSQL_DATABASE"),
user = crawler.settings.get("MYSQL_USER"),
password = crawler.settings.get("MYSQL_PASS"),
port = crawler.settings.get("MYSQL_PORT")
)
def open_spider(self,spider):
self.db = pymysql.connect(self.host,self.user,self.password,self.database,charset='utf8',port=self.port)
self.cursor = self.db.cursor()
def process_item(self, item, spider):
sql = "insert into courses(title,url,pic,teacher,time,price) values('%s','%s','%s','%s','%s','%s')"%(item['title'],item['url'],item['pic'],item['teacher'],str(item['time']),str(item['price']))
#print(item)
self.cursor.execute(sql)
self.db.commit()
return item
def close_spider(self,spider):
self.db.close()
⑦ 修改配置文件
- 打开配置文件:settings.py 开启并配置ITEM_PIPELINES信息,配置数据库连接信息
ITEM_PIPELINES = {
'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.MysqlPipeline': 301,
}
MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'csdndb'
MYSQL_USER = 'root'
MYSQL_PASS = ''
MYSQL_PORT = 3306
⑧、运行爬取:
-
执行如下命令来启用数据爬取
scrapy crawl courses
7.4 下载和处理文件和图像:
① ImagesPipeline介绍
- Scrapy提供了专门处理下载的Pipeline,包含文件下载和图片下载,其原理与抓取页面的原理一样。
- 下载过程支持异步和多线程,下载十分高效。
- 网址:https://docs.scrapy.org/en/latest/topics/media-pipeline.html
- 实现方式:定义一个
ItemPipeline类继承scrapy.pipelines.images.ImagesPipeline。
② 具体使用:
- 首先定义存储文件的路径,在settings.py配置文件中添加代码:
IMAGES_STORE = './images' - 并在项目目录下创建
images文件夹 (与scrapy.cfg文件同级)。 - 要在pipelines.py文件中定义一个‘ImagePipeline’类,其代码如下:
from scrapy import Request
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline
class ImagePipeline(ImagesPipeline):
'''自定义图片存储类'''
def get_media_requests(self, item, info):
'''通过抓取的item对象获取图片信息,并创建Request请求对象添加调度队列,等待调度执行下载'''
yield Request(item['pic'])
def file_path(self,request,response=None,info=None):
'''返回图片下载后保存的名称,没有此方法Scrapy则自动给一个唯一值作为图片名称'''
url = request.url
file_name = url.split("/")[-1]
return file_name
def item_completed(self, results, item, info):
''' 下载完成后的处理方法,其中results内容结构如下说明'''
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
#item['image_paths'] = image_paths
return item
-
在item_completed()方法中,results参数内容结构如下:
print(results) [(True, {'url': 'https://img-bss.csdn.net/201803191642534078.png', 'path': '201803191642534078.png', 'checksum': 'cc1368dbc122b6762f3e26ccef0dd105'} )] -
在settings.py文件中配置如下(启用下载):
...
ITEM_PIPELINES = {
'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.ImagePipeline': 301,
'educsdn.pipelines.MysqlPipeline': 302,
}
MYSQL_HOST = "localhost"
MYSQL_DATABASE = "csdndb"
MYSQL_USER = "root"
MYSQL_PASS = ""
MYSQL_PORT = 3306
IMAGES_STORE = "./images"
...
8. Scrapy爬虫案例实战
- 任务:爬取腾讯网中关于指定条件的所有
社会招聘信息,搜索条件为北京地区,Python关键字的就业岗位,并将信息存储到MySql数据库中。 - 网址:https://hr.tencent.com/position.php?keywords=python&lid=2156
- 实现思路:首先爬取每页的招聘信息列表,再爬取对应的招聘详情信息
① 创建项目
- 在命令行编写下面命令,创建项目tencent
scrapy startproject tencent
- 项目目录结构:
tencent
├── tencent
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
② 进入tencent项目目录,创建爬虫spider类文件(hr招聘信息)
- 执行genspider命令,第一个参数是Spider的名称,第二个参数是网站域名。
scrapy genspider hr hr.tencent.com
$ tree
├── tencent
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-36.pyc
│ └── hr.py #在spiders目录下有了一个爬虫类文件hr.py
└── scrapy.cfg
# hr.py的文件代码如下:
# -*- coding: utf-8 -*-
import scrapy
class HrSpider(scrapy.Spider):
name = 'hr'
allowed_domains = ['hr.tencent.com']
start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156']
def parse(self, response):
#解析当前招聘列表信息的url地址:
detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract()
#遍历url地址
for url in detail_urls:
#fullurl = 'http://hr.tencent.com/' + url
#构建绝对的url地址,效果同上(域名加相对地址)
fullurl = response.urljoin(url)
print(fullurl)
- 测试一下获取第一页的招聘详情url地址信息
③ 创建Item
- Item是保存爬取数据的容器,它的使用方法和字典类型,但相比字典多了些保护机制。
- 创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段:
- 职位id号,名称、位置、类别、要求、人数、工作职责、工作要求
- 具体代码如下:(创建一个类名为HrItem)
import scrapy
class TencentItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class HrItem(scrapy.Item):
'''
人事招聘信息封装类
(职位id号,名称、位置、类别、要求、人数、职责和要求)
'''
table = "hr" #表名
id = scrapy.Field()
title = scrapy.Field()
location = scrapy.Field()
type = scrapy.Field()
number = scrapy.Field()
duty = scrapy.Field()
requirement = scrapy.Field()
④ 解析Response
- 在hr.py文件中,parse()方法的参数response是start_urls里面的链接爬取后的结果。
- 提取的方式可以是CSS选择器、XPath选择器或者是re正则表达式。
# -*- coding: utf-8 -*-
import scrapy
from tencent.items import HrItem
class HrSpider(scrapy.Spider):
name = 'hr'
allowed_domains = ['hr.tencent.com']
start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156']
def parse(self, response):
#解析当前招聘列表信息的url地址:
detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract()
#遍历url地址
for url in detail_urls:
#fullurl = 'http://hr.tencent.com/' + url
#构建绝对的url地址,效果同上(域名加相对地址)
fullurl = response.urljoin(url)
#print(fullurl)
# 构造请求准备爬取招聘详情信息,并指定由parse_page()方法解析回调函数
yield scrapy.Request(url=fullurl,callback=self.parse_page)
#获取下一页的url地址
next_url = response.css("#next::attr(href)").extract_first()
#判断若不是最后一页
if next_url != "javascript:;":
url = response.urljoin(next_url)
#构造下一页招聘列表信息的爬取
yield scrapy.Request(url=url,callback=self.parse)
# 解析详情页
def parse_page(self,response):
#构造招聘信息的Item容器对象
item = HrItem()
# 解析id号信息,并封装到Item中
item["id"] = response.selector.re_first('onclick="applyPosition\(([0-9]+)\);"')
#标题
item["title"] = response.css('#sharetitle::text').extract_first()
#位置
item["location"] = response.selector.re_first('<span class="lightblue l2">工作地点:</span>(.*?)</td>')
#类别
item["type"] = response.selector.re_first('<span class="lightblue">职位类别:</span>(.*?)</td>')
#人数
item["number"] = response.selector.re_first('<span class="lightblue">招聘人数:</span>([0-9]+)人</td>')
#工作职责
duty = response.xpath('//table//tr[3]//li/text()').extract()
item["duty"] = ''.join(duty)
#工作要求
requirement = response.xpath('//table//tr[4]//li/text()').extract()
item["requirement"] = ''.join(requirement)
#print(item)
#交给管道文件
yield item
⑤、创建数据库和表:
- 在mysql中创建数据库
mydb和数据表hr
CREATE TABLE `hr` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`location` varchar(32) DEFAULT NULL,
`type` varchar(32) DEFAULT NULL,
`number` varchar(32) DEFAULT NULL,
`duty` text DEFAULT NULL,
`requirement` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
⑥、使用Item Pipeline
- 在Item管道文件中,定义一个MysqlPipeline,负责连接数据库并执行信息写入操作
import pymysql
class TencentPipeline(object):
def process_item(self, item, spider):
return item
class MysqlPipeline(object):
def __init__(self,host,user,password,database,port):
self.host = host
self.user = user
self.password = password
self.database = database
self.port = port
@classmethod
def from_crawler(cls,crawler):
return cls(
host = crawler.settings.get("MYSQL_HOST"),
user = crawler.settings.get("MYSQL_USER"),
password = crawler.settings.get("MYSQL_PASS"),
database = crawler.settings.get("MYSQL_DATABASE"),
port = crawler.settings.get("MYSQL_PORT"),
)
def open_spider(self, spider):
'''负责连接数据库'''
self.db = pymysql.connect(self.host,self.user,self.password,self.database,charset="utf8",port=self.port)
self.cursor = self.db.cursor()
def process_item(self, item, spider):
'''执行数据表的写入操作'''
#组装sql语句
data = dict(item)
keys = ','.join(data.keys())
values=','.join(['%s']*len(data))
sql = "insert into %s(%s) values(%s)"%(item.table,keys,values)
#指定参数,并执行sql添加
self.cursor.execute(sql,tuple(data.values()))
#事务提交
self.db.commit()
return item
def close_spider(self, spider):
'''关闭连接数据库'''
self.db.close()
⑦ 修改配置文件
- 打开配置文件:settings.py 开启并配置ITEM_PIPELINES信息,配置数据库连接信息
- 当有CONCURRENT_REQUESTS,没有DOWNLOAD_DELAY 时,服务器会在同一时间收到大量的请求。
- 当有CONCURRENT_REQUESTS,有DOWNLOAD_DELAY 时,服务器不会在同一时间收到大量的请求。
# 忽略爬虫协议
ROBOTSTXT_OBEY = False
# 并发量
CONCURRENT_REQUESTS = 1
#下载延迟
DOWNLOAD_DELAY = 0
ITEM_PIPELINES = {
#'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.MysqlPipeline': 301,
}
MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'mydb'
MYSQL_USER = 'root'
MYSQL_PASS = ''
MYSQL_PORT = 3306
⑧、运行爬取:
-
执行如下命令来启用数据爬取
scrapy crawl hr
9. Scrapy扩展
1. 如何使scrapy爬取信息不打印在命令窗口中
- 通常,我们使用这条命令运行自己的scrapy爬虫:
scrapy crawl spider_name
- 但是,由这条命令启动的爬虫,会将所有爬虫运行中的debug信息及抓取到的信息打印在运行窗口中。
- 很乱,也不方便查询。所以,可使用该命令代替:
scrpay crawl spider_name -s LOG_FILE=all.log
2. Scrapy中的日志处理
- Scrapy提供了log功能,可以通过 logging 模块使用
- 可以修改配置文件settings.py,任意位置添加下面两行
LOG_FILE = "mySpider.log"
LOG_LEVEL = "INFO"
- Scrapy提供5层logging级别:
CRITICAL - 严重错误(critical)
ERROR - 一般错误(regular errors)
WARNING - 警告信息(warning messages)
INFO - 一般信息(informational messages)
DEBUG - 调试信息(debugging messages)
- logging设置
- 通过在setting.py中进行以下设置可以被用来配置logging:
LOG_ENABLED 默认: True,启用logging
LOG_ENCODING 默认: 'utf-8',logging使用的编码
LOG_FILE 默认: None,在当前目录里创建logging输出文件的文件名
LOG_LEVEL 默认: 'DEBUG',log的最低级别
LOG_STDOUT 默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示
- 记录信息
- 下面给出如何使用WARING级别来记录信息
from scrapy import log
log.msg("This is a warning", level=log.WARNING)
十、Python网络爬虫进阶实战(中)
09. Selenium的使用
9.1 动态渲染页面爬取
- 对于访问Web时直接响应的数据(就是response内容可见),我们使用urllib、requests或Scrapy框架爬取。
- 对应一般的JavaScript动态渲染的页面信息(Ajax加载),我们可以通过分析Ajax请求来抓取信息。
- 即使通过Ajax获取数据,但还有会部分加密参数,后期经过JavaScript计算生成内容,导致我们难以直接找到规律,如淘宝页面。
- 为了解决这些问题,我们可以直接使用模拟浏览器运行的方式来实现信息获取。
- 在Python中有许多模拟浏览器运行库,如:Selenium、Splash、PyV8、Ghost等。
9.2 Selenium的介绍
- Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击,下拉,等操作。
- Selenium可以获取浏览器当前呈现的页面源代码,做到可见既可爬,对应JavaScript动态渲染的信息爬取非常有效。
- 官方网址:http://www.seleniumhq.org
- 官方文档:http://selenium-python.readthedocs.io
- 中文文档:http://selenium-python-zh.readthedocs.io
- 安装:
pip install selenium - Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还支持无界面浏览器PhantomJS。
- ChromeDriver浏览器驱动的安装:(注意浏览器版本:)
- 首先查看当前谷歌Chrome浏览器的版本V61V67(对应2.352.38),再到下面网址下载
- 网址:
https://chromedriver.storage.googleapis.com/index.html - Windows安装:将解压的文件:
chromedriver.exe放置到Python的Scripts目录下。 - Mac/Linux安装:将解压的文件:
chromedriver放置到/usr/local/bin/目录下
- PhantomJS驱动的下载地址:http://phantomjs.org/download.html
9.3 Selenium的使用
① 初次体验:模拟谷歌浏览器访问百度首页,并输入python关键字搜索
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
#初始化一个浏览器(如:谷歌,使用Chrome需安装chromedriver)
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS() #无界面浏览器
try:
#请求网页
driver.get("https://www.baidu.com")
#查找id值为kw的节点对象(搜索输入框)
input = driver.find_element_by_id("kw")
#模拟键盘输入字串内容
input.send_keys("python")
#模拟键盘点击回车键
input.send_keys(Keys.ENTER)
#显式等待,最长10秒
wait = WebDriverWait(driver,10)
#等待条件:10秒内必须有个id属性值为content_left的节点加载出来,否则抛异常。
wait.until(EC.presence_of_element_located((By.ID,'content_left')))
# 输出响应信息
print(driver.current_url)
print(driver.get_cookies())
print(driver.page_source)
finally:
#关闭浏览器
#driver.close()
pass
② 声明浏览器对象
from selenium import webdriver
driver = webdriver.Chrome() #谷歌 需:ChromeDriver驱动
driver = webdriver.FireFox() #火狐 需:GeckoDriver驱动
driver = webdriver.Edge()
driver = webdriver.Safari()
driver = webdriver.PhantomJS() #无界面浏览器
③ 访问页面
from selenium import webdriver
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
print(driver.page_source)
#driver.close()
④ 查找节点:
- 获取单个节点的方法:
- find_element_by_id()
- find_element_by_name()
- find_element_by_xpath()
- find_element_by_link_text()
- find_element_by_partial_link_text()
- find_element_by_tag_name()
- find_element_by_class_name()
- find_element_by_css_seletor()
from selenium import webdriver
from selenium.webdriver.common.by import By
#创建浏览器对象
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
#下面都是获取id属性值为q的节点对象
input = driver.find_element_by_id("q")
print(input)
input = driver.find_element_by_css_selector("#q")
print(input)
input = driver.find_element_by_xpath("//*[@id='q']")
print(input)
#效果同上
input = driver.find_element(By.ID,"q")
print(input)
#driver.close()
- 获取多个节点的方法:
- find_elements_by_id()
- find_elements_by_name()
- find_elements_by_xpath()
- find_elements_by_link_text()
- find_elements_by_partial_link_text()
- find_elements_by_tag_name()
- find_elements_by_class_name()
- find_elements_by_css_seletor()
⑤ 节点交互:
from selenium import webdriver
import time
#创建浏览器对象
driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()
driver.get("http://www.taobao.com")
#下面都是获取id属性值为q的节点对象
input = driver.find_element_by_id("q")
#模拟键盘输入iphone
input.send_keys('iphone')
time.sleep(3)
#清空输入框
input.clear()
#模拟键盘输入iPad
input.send_keys('iPad')
#获取搜索按钮节点
botton = driver.find_element_by_class_name("btn-search")
#触发点击动作
botton.click()
#driver.close()
⑥ 动态链:
- ActionChains是一种自动化低级别交互的方法,如鼠标移动,鼠标按钮操作,按键操作和上下文菜单交互。
- 这对于执行更复杂的操作(如悬停和拖放)很有用.
- move_to_element(to_element )-- 将鼠标移到元素的中间
- move_by_offset(xoffset,yoffset )-- 将鼠标移至当前鼠标位置的偏移量
- drag_and_drop(源,目标)-- 然后移动到目标元素并释放鼠标按钮。
- pause(秒)-- 以秒为单位暂停指定持续时间的所有输入
- perform()-- 执行所有存储的操作。
- release(on_element = None )释放元素上的一个持有鼠标按钮。
- reset_actions()-- 清除已存储在远程端的操作。
- send_keys(* keys_to_send )-- 将键发送到当前的焦点元素。
- send_keys_to_element(element,* keys_to_send )-- 将键发送到一个元素。
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
#创建浏览器对象
driver = webdriver.Chrome()
#加载指定url地址
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
driver.get(url)
# 切换Frame窗口
driver.switch_to.frame('iframeResult')
#获取两个div节点对象
source = driver.find_element_by_css_selector("#draggable")
target = driver.find_element_by_css_selector("#droppable")
#创建一个动作链对象
actions = ActionChains(driver)
#将一个拖拽操作添加到动作链队列中
actions.drag_and_drop(source,target)
time.sleep(3)
#执行所有存储的操作(顺序被触发)
actions.perform()
#driver.close()
⑦ 执行JavaScript:
from selenium import webdriver
#创建浏览器对象
driver = webdriver.Chrome()
#加载指定url地址
driver.get("https://www.zhihu.com/explore")
#执行javascript程序将页面滚动移至底部
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
#执行javascript实现一个弹框操作
driver.execute_script('window.alert("Hello Selenium!")')
#driver.close()
⑧ 获取节点信息:
from selenium import webdriver
from selenium.webdriver import ActionChains
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#获取id属性值为zh-top-link-logo的节点(logo)
logo = driver.find_element_by_id("zh-top-link-logo")
print(logo) #输出节点对象
print(logo.get_attribute('class')) #节点的class属性值
#获取id属性值为zu-top-add-question节点(提问按钮)
input = driver.find_element_by_id("zu-top-add-question")
print(input.text) #获取节点间内容
print(input.id) #获取id属性值
print(input.location) #节点在页面中的相对位置
print(input.tag_name) #节点标签名称
print(input.size) #获取节点的大小
#driver.close()
⑨ 切换Frame:
- 网页中有一种节点叫做
iframe,也就是子Frame,他可以将一个页面分成多个子父界面。 - 我们可以使用switch_to.frame()来切换Frame界面,实例详见
第⑥的动态链案例
⑩ 延迟等待:
- 浏览器加载网页是需要时间的,Selenium也不例外,若要获取完整网页内容,就要延时等待。
- 在Selenium中延迟等待方式有两种:一种是隐式等待,一种是显式等待(推荐)。
from selenium import webdriver
#创建浏览器对象
driver = webdriver.Chrome()
#使用隐式等待(固定时间)
driver.implicitly_wait(2)
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#获取节点
input = driver.find_element_by_id("zu-top-add-question")
print(input.text) #获取节点间内容
#driver.close()
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
#显式等待,最长10秒
wait = WebDriverWait(driver,10)
#等待条件:10秒内必须有个id属性值为zu-top-add-question的节点加载出来,否则抛异常。
input = wait.until(EC.presence_of_element_located((By.ID,'zu-top-add-question')))
print(input.text) #获取节点间内容
#driver.close()
11 前进和后退:
from selenium import webdriver
import time
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.baidu.com")
driver.get("https://www.taobao.com")
driver.get("https://www.jd.com")
time.sleep(2)
driver.back() #后退
time.sleep(2) #前进
driver.forward()
#driver.close()
12 Cookies:
from selenium import webdriver
from selenium.webdriver import ActionChains
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.zhihu.com/explore")
print(driver.get_cookies())
driver.add_cookie({'name':'namne','domain':'www.zhihu.com','value':'zhangsan'})
print(driver.get_cookies())
driver.delete_all_cookies()
print(driver.get_cookies())
#driver.close()
13 选项卡管理:
from selenium import webdriver
import time
#创建浏览器对象
driver = webdriver.Chrome()
#加载请求指定url地址
driver.get("https://www.baidu.com")
#使用JavaScript开启一个新的选型卡
driver.execute_script('window.open()')
print(driver.window_handles)
#切换到第二个选项卡,并打开url地址
driver.switch_to_window(driver.window_handles[1])
driver.get("https://www.taobao.com")
time.sleep(2)
#切换到第一个选项卡,并打开url地址
driver.switch_to_window(driver.window_handles[0])
driver.get("https://www.jd.com")
#driver.close()
14 异常处理:
from selenium import webdriver
from selenium.common.exceptions import TimeoutException,NoSuchElementException
#创建浏览器对象
driver = webdriver.Chrome()
try:
#加载请求指定url地址
driver.get("https://www.baidu.com")
except TimeoutException:
print('Time Out')
try:
#加载请求指定url地址
driver.find_element_by_id("demo")
except NoSuchElementException:
print('No Element')
finally:
#driver.close()
pass
10. Selenium爬取淘宝商品
① 案例要求
- 使用Selenium爬取淘宝商品,指定关键字和指定页码信息来进行爬取
② 案例分析:
③ 具体代码实现
'''通过关键字爬取淘宝网站的信息数据'''
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from pyquery import PyQuery as pq
from urllib.parse import quote
KEYWORD = "ipad"
MAX_PAGE = 10
# browser = webdriver.Chrome()
# browser = webdriver.PhantomJS()
#创建谷歌浏览器对象,启用Chrome的Headless无界面模式
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)
#显式等待:
wait = WebDriverWait(browser, 10)
def index_page(page):
'''抓取索引页 :param page: 页码'''
print('正在爬取第', page, '页')
try:
url = 'https://s.taobao.com/search?q=' + quote(KEYWORD)
browser.get(url)
if page > 1:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
input.clear()
input.send_keys(page)
submit.click()
#等待条件:显示当前页号,显式商品
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
get_products()
except TimeoutException:
index_page(page)
def get_products():
'''提取商品数据'''
html = browser.page_source
doc = pq(html)
items = doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
'image': item.find('.pic .img').attr('data-src'),
'price': item.find('.price').text(),
'deal': item.find('.deal-cnt').text(),
'title': item.find('.title').text(),
'shop': item.find('.shop').text(),
'location': item.find('.location').text()
}
print(product)
save_data(product)
def save_data(result):
'''保存数据'''
pass
def main():
'''遍历每一页'''
for i in range(1, MAX_PAGE + 1):
index_page(i)
browser.close()
# 主程序入口
if __name__ == '__main__':
main()
11. MongoDB数据库
- MongoDB 是一个
基于分布式文件存储的数据库。由C++语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。 - MongoDB 是一个
介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。 - 参考地址:
- MongoDB 官网地址:https://www.mongodb.com/
- MongoDB 官方英文文档:https://docs.mongodb.com/manual/
- MongoDB 各平台下载地址:https://www.mongodb.com/download-center#community
1.1 RDBMS与NoSQL区别:
- 关系数据库管理系统(RDBMS)
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
- 非关系型数据库(NoSQL)
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID属性
- 非结构化和不可预知的数据
- CAP定理
- 高性能,高可用性和可伸缩性
- RDBMS 与 MongoDB 对应的术语区别:
| RDBMS | MongoDB |
|---|---|
| 数据库 | 数据库 |
| 表格 | 集合 |
| 行 | 文档 |
| 列 | 字段 |
| 表联合 | 嵌入文档 |
| 主键 | 主键 (MongoDB 提供了 key 为 _id ) |
1.2 Windows下安装MongoDB:
- 下载地址:https://www.mongodb.org/dl/win32/x86_64-2008plus-ssl
- 最新版的在安装过程中出现卡死现象,建议选择版本3.4版本(测试过)。
- 安装图形界面,一步一步的安装即可:
- 创建数据库目录:
c:\>cd c:\
c:\>mkdir data
c:\>cd data
c:\data>mkdir db
c:\data>cd db
c:\data\db>
- 启动MongoDB服务:
C:\Program Files\MongoDB\Server\3.4\bin>mongod --dbpath c:\data\db
- 连接MongoDB
C:\Program Files\MongoDB\Server\3.4\bin>mongo
1.3 数据库的操作
① MongoDB的数据库操作
- 查看当前数据库名称
db
- 查看所有数据库名称
- 列出所有在物理上存在的数据库
show dbs
·
- 切换数据库
- 如果数据库不存在,则指向数据库,但不创建,直到插入数据或创建集合时数据库才被创建
use 数据库名称
默认的数据库为测试,如果你没有创建新的数据库,集合将存放在测试数据库中
- 数据库删除
- 删除当前指向的数据库
- 如果数据库不存在,则什么也不做
db.dropDatabase()
② MongoDB的集合操作:
- 创建集合:
db.createCollection(name, options)
- name是要创建的集合的名称
- options是一个文档,用于指定集合的配置
- 选项参数是可选的,所以只需要到指定的集合名称。以下是可以使用的选项列表:
- 例1:不限制集合大小
- db.createCollection("stu")
- 例2:限制集合大小,后面学会插入语句后可以查看效果
- 参数capped:默认值为false表示不设置上限,值为true表示设置上限
- 参数size:当capped值为true时,需要指定此参数,表示上限大小,当文档达到上限时,会将之前的数据覆盖,单位为字节
- db.createCollection("sub", { capped : true, size : 10 } )
- 查看当前数据库的集合
show collections
删除集合:
db.集合名称.drop()
③ 数据类型:
- 下表为MongoDB中常用的几种数据类型:
- Object ID:文档ID
- String:字符串,最常用,必须是有效的UTF-8
- Boolean:存储一个布尔值,true或false
- Integer:整数可以是32位或64位,这取决于服务器
- Double:存储浮点值
- Arrays:数组或列表,多个值存储到一个键
- Object:用于嵌入式的文档,即一个值为一个文档
- Null:存储Null值
- Timestamp:时间戳
- Date:存储当前日期或时间的UNIX时间格式
- object id
- 每个文档都有一个属性,为_id,保证每个文档的唯一性
- 可以自己去设置_id插入文档
- 如果没有提供,那么MongoDB为每个文档提供了一个独特的_id,类型为objectID
- objectID是一个12字节的十六进制数
- 前4个字节为当前时间戳
- 接下来3个字节的机器ID
- 接下来的2个字节中MongoDB的服务进程id
- 最后3个字节是简单的增量值
④ 数据的操作
- 插入语法
db.集合名称.insert(document)
-
插入文档时,如果不指定_id参数,MongoDB的会为文档分配一个唯一的的ObjectId
-
例1:
db.stu.insert({name:'gj',gender:1}) -
例2:
s1={_id:'20160101',name:'hr'} s1.gender=0 db.stu.insert(s1) -
简单查询
db.集合名称.find()
- 数据的更新
db.集合名称.update(
<query>,
<update>,
{multi: <boolean>}
)
- 参数查询:查询条件,类似SQL语句更新中,其中部分
- 参数更新:更新操作符,类似SQL语句更新中集部分
- 参数多:可选,默认是假的,表示只更新找到的第一条记录,值为真表示把满足条件的文档全部更新
例3:全文档更新
db.stu.update({name:'hr'},{name:'mnc'})
例4:指定属性更新,通过操作符$集
db.stu.insert({name:'hr',gender:0})
db.stu.update({name:'hr'},{$set:{name:'hys'}})
例5:修改多条匹配到的数据
db.stu.update({},{$set:{gender:0}},{multi:true})
- 数据的保存语法
db.集合名称.save(document)
- 如果文档的_id已经存在则修改,如果文档的_id不存在则添加
db.stu.save({_id:'20160102','name':'yk',gender:1})
db.stu.save({_id:'20160102','name':'wyk'})
- 删除 语法
db.集合名称.remove(
<query>,
{
justOne: <boolean>
}
)
- 参数查询:可选,删除的文档的条件
- 参数来说只是个:可选,如果设为真或1,则只删除一条,默认为false,表示删除多条
例:只删除匹配到的第一条
db.stu.remove({gender:0},{justOne:true})
例:全部删除
db.stu.remove({})
- 关于大小的示例
创建集合
db.createCollection('sub',{capped:true,size:10})
插入第一条数据库查询
db.sub.insert({title:'linux',count:10})
db.sub.find()
插入第二条数据库查询
db.sub.insert({title:'web',count:15})
db.sub.find()
插入第三条数据库查询
db.sub.insert({title:'sql',count:8})
db.sub.find()
插入第四条数据库查询
db.sub.insert({title:'django',count:12})
db.sub.find()
插入第五条数据库查询
db.sub.insert({title:'python',count:14})
db.sub.find()
- limit限制
方法限制():用于读取指定数量的文档
db.集合名称.find().limit(NUMBER)
参数号表示要获取文档的条数
如果没有指定参数则显示集合中的所有文档
例1:查询2条学生信息
db.stu.find().limit(2)
- 投影
在查询到的返回结果中,只选择必要的字段,而不是选择一个文档的整个字段
如:一个文档有5个字段,需要显示只有3个,投影其中3个字段即可
参数为字段与值,值为1表示显示,值为0不显示
db.集合名称.find({},{字段名称:1,...})
特殊:对于_id列默认是显示的,如果不显示需要明确设置为0
例1
db.stu.find({},{name:1,gender:1})
例2
db.stu.find({},{_id:0,name:1,gender:1})
- 排序
方法sort(),用于对结果集进行排序
db.集合名称.find().sort({字段:1,...})
参数1为升序排列
参数-1为降序排列
例1:根据性别降序,再根据年龄升序
db.stu.find().sort({gender:-1,age:1})
- 统计个数
方法count()用于统计结果集中文档条数
db.集合名称.find({条件}).count()
也可以与为
db.集合名称.count({条件})
例1:统计男生人数
db.stu.find({gender:1}).count()
例2:统计年龄大于20的男生人数
b.stu.count({age:{$gt:20},gender:1})
- 消除重复
方法distinct()对数据进行去重
db.集合名称.distinct('去重字段',{条件})
例1:查找年龄大于18的性别(去重)
db.stu.distinct('gender',{age:{$gt:18}})
11.4 备份与恢复
语法
mongodump -h dbhost -d dbname -o dbdirectory
-h:服务器地址,也可以指定端口号
-d:需要备份的数据库名称
-o:备份的数据存放位置,此目录中存放着备份出来的数据
例1
sudo mkdir test1bak
sudo mongodump -h 192.168.196.128:27017 -d test1 -o ~/Desktop/test1bak
恢复
语法
mongorestore -h dbhost -d dbname --dir dbdirectory
-h:服务器地址
-d:需要恢复的数据库实例
--dir:备份数据所在位置
例2
mongorestore -h 192.168.196.128:27017 -d test2 --dir ~/Desktop/test1bak/test1
11.5 与python交互
-
安装python包
pip install pymongo -
使用:
-
引入包pymongo
import pymongo -
连接,创建客户端
client=pymongo.MongoClient("localhost", 27017) -
获得数据库test1
db=client.test1 -
获得集合stu
stu = db.stu -
添加文档
s1={name:'gj',age:18} s1_id = stu.insert_one(s1).inserted_id -
查找一个文档
s2=stu.find_one() -
查找多个文档1
for cur in stu.find(): print cur -
查找多个文档2
cur=stu.find() cur.next() cur.next() cur.next() -
获取文档个数
print stu.count()
12. Scrapy框架使用Selenium
- 案例目标:
- 本节案例主要是通过Scrapy框架使用Selenium,以PhantomJS进行演示,爬取淘宝商品信息案例,并将信息存入数据库MongoDB中。
- 准备工作:
- 请确保PhantomJS和MongoDB都已安装号,并确保可以正常运行,安装好Scrapy、Selenium和PyMongod库。
① 创建项目
- 首先新建项目,名为scrapyseleniumtest:
scrapy startproject scrapyseleniumtest
- 进入项目目录下,创建一个Spider(爬虫类):
cd srapytseleniumtest
scrapy genspider taobao www.baobao.com
- 进入settings.py的配置文件:将ROBOTSTXT_OBEY改为false
ROBOTSTXT_OBEY = False
② 定义Item类
# 定义信息封装类(图片、价格、购买人数、标题、店铺、发货源)
from scrapy import Item, Field
class ProductItem(Item):
collection = 'products'
image = Field()
price = Field()
deal = Field()
title = Field()
shop = Field()
location = Field()
③ 解析页面
- 在配置文件settings.py最后面定义搜索关键字和最大页码数信息:
KEYWORDS = ['iPad']
MAX_PAGE = 100
- 进入spider/taobao.py文件中编写,代码如下:
# -*- coding: utf-8 -*-
from scrapy import Request, Spider
from urllib.parse import quote
from scrapyseleniumtest.items import ProductItem
class TaobaoSpider(Spider):
name = 'taobao'
allowed_domains = ['www.taobao.com']
base_url = 'https://s.taobao.com/search?q='
def start_requests(self):
for keyword in self.settings.get('KEYWORDS'):
for page in range(1, self.settings.get('MAX_PAGE') + 1):
url = self.base_url + quote(keyword)
yield Request(url=url, callback=self.parse, meta={'page': page}, dont_filter=True)
def parse(self, response):
pass
④ 对接Selenium
- 通过定义DownloaderMiddleware中间件来实现对Selenium的使用。
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from scrapy.http import HtmlResponse
from logging import getLogger
class SeleniumMiddleware():
def __init__(self, timeout=None, service_args=[]):
self.logger = getLogger(__name__)
self.timeout = timeout
self.browser = webdriver.PhantomJS(service_args=service_args)
self.browser.set_window_size(1400, 700)
self.browser.set_page_load_timeout(self.timeout)
self.wait = WebDriverWait(self.browser, self.timeout)
def __del__(self):
self.browser.close()
def process_request(self, request, spider):
"""
用PhantomJS抓取页面
:param request: Request对象
:param spider: Spider对象
:return: HtmlResponse
"""
self.logger.debug('PhantomJS is Starting')
page = request.meta.get('page', 1)
try:
self.browser.get(request.url)
if page > 1:
input = self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
submit = self.wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
input.clear()
input.send_keys(page)
submit.click()
self.wait.until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8',
status=200)
except TimeoutException:
return HtmlResponse(url=request.url, status=500, request=request)
@classmethod
def from_crawler(cls, crawler):
return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'),
service_args=crawler.settings.get('PHANTOMJS_SERVICE_ARGS'))
- 在settings.py配置文件中.设置我们自定义的中间件设置:
DOWNLOADER_MIDDLEWARES = {
'scrapyseleniumtest.middlewares.SeleniumMiddleware': 543,
}
⑤ 解析页面信息
# -*- coding: utf-8 -*-
from scrapy import Request, Spider
from urllib.parse import quote
from scrapyseleniumtest.items import ProductItem
class TaobaoSpider(Spider):
name = 'taobao'
allowed_domains = ['www.taobao.com']
base_url = 'https://s.taobao.com/search?q='
def start_requests(self):
for keyword in self.settings.get('KEYWORDS'):
for page in range(1, self.settings.get('MAX_PAGE') + 1):
url = self.base_url + quote(keyword)
yield Request(url=url, callback=self.parse, meta={'page': page}, dont_filter=True)
def parse(self, response):
products = response.xpath(
'//div[@id="mainsrp-itemlist"]//div[@class="items"][1]//div[contains(@class, "item")]')
for product in products:
item = ProductItem()
item['price'] = ''.join(product.xpath('.//div[contains(@class, "price")]//text()').extract()).strip()
item['title'] = ''.join(product.xpath('.//div[contains(@class, "title")]//text()').extract()).strip()
item['shop'] = ''.join(product.xpath('.//div[contains(@class, "shop")]//text()').extract()).strip()
item['image'] = ''.join(product.xpath('.//div[@class="pic"]//img[contains(@class, "img")]/@data-src').extract()).strip()
item['deal'] = product.xpath('.//div[contains(@class, "deal-cnt")]//text()').extract_first()
item['location'] = product.xpath('.//div[contains(@class, "location")]//text()').extract_first()
yield item
⑥ 存储结果
import pymongo
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DB'))
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
self.db[item.collection].insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
- 配置文件信息:
ITEM_PIPELINES = {
'scrapyseleniumtest.pipelines.MongoPipeline': 300,
}
KEYWORDS = ['iPad']
MAX_PAGE = 100
SELENIUM_TIMEOUT = 20
PHANTOMJS_SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
MONGO_URI = 'localhost'
MONGO_DB = 'taobao'
13. 代理的使用
13.1 代理服务的介绍:
- 我们在做爬虫的过程中经常最初爬虫都正常运行,正常爬取数据,一切看起来都是美好,然而一杯茶的功夫就出现了错误。
- 如:403 Forbidden错误,“您的IP访问频率太高”错误,或者跳出一个验证码让我们输入,之后解封,但过一会又出现类似情况。
- 出现这个现象的原因是因为网站采取了一些反爬中措施,如:服务器检测IP在单位时间内请求次数超过某个阀值导致,称为封IP。
- 为了解决此类问题,代理就派上了用场,如:代理软件、付费代理、ADSL拨号代理,以帮助爬虫脱离封IP的苦海。
- 测试HTTP请求及响应的网站:http://httpbin.org/
- httpbin这个网站能测试 HTTP 请求和响应的各种信息,比如 cookie、ip、headers 和登录验证等.
- 且支持 GET、POST 等多种方法,对 web 开发和测试很有帮助。
- GET地址 :http://httpbin.org/get
- POST地址:http://httpbin.org/post
- 它用 Python + Flask 编写,是一个开源项目。开源地址:https://github.com/Runscope/httpbin
- 返回信息中origin的字段就是客户端的IP地址,即可判断是否成功伪装IP:
13.2 代理的设置:
① urllib的代理设置
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy = '127.0.0.1:8888'
#需要认证的代理
#proxy = 'username:password@127.0.0.1:8888'
#使用ProxyHandler设置代理
proxy_handler = ProxyHandler({
'http': 'http://' + proxy,
'https': 'https://' + proxy
})
#传入参数创建Opener对象
opener = build_opener(proxy_handler)
try:
response = opener.open('http://httpbin.org/get')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)
② requests的代理设置
import requests
proxy = '127.0.0.1:8888'
#需要认证的代理
#proxy = 'username:password@127.0.0.1:8888'
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy,
}
try:
response = requests.get('http://httpbin.org/get', proxies=proxies)
print(response.text)
except requests.exceptions.ConnectionError as e:
print('Error', e.args)
③ Selenium的代理使用
- 使用的是PhantomJS
from selenium import webdriver
service_args = [
'--proxy=127.0.0.1:9743',
'--proxy-type=http',
#'--proxy-auth=username:password' #带认证代理
]
browser = webdriver.PhantomJS(service_args=service_args)
browser.get('http://httpbin.org/get')
print(browser.page_source)
- 使用的是Chrome
from selenium import webdriver
proxy = '127.0.0.1:9743'
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--proxy-server=http://' + proxy)
chrome = webdriver.Chrome(chrome_options=chrome_options)
chrome.get('http://httpbin.org/get')
④ 在Scrapy使用代理
#在Scrapy的Downloader Middleware中间件里
...
def process_request(self, request, spider):
request.meta['proxy'] = 'http://127.0.0.1:9743'
...
13.3 免费代理IP的使用
- 我们可以从互联网中获取免费的代理IP:如:西刺 http://www.xicidaili.com
import requests,random
#定义代理池
proxy_list = [
'182.39.6.245:38634',
'115.210.181.31:34301',
'123.161.152.38:23201',
'222.85.5.187:26675',
'123.161.152.31:23127',
]
# 随机选择一个代理
proxy = random.choice(proxy_list)
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy,
}
try:
response = requests.get('http://httpbin.org/get', proxies=proxies)
print(response.text)
except requests.exceptions.ConnectionError as e:
print('Error', e.args)
13.4 收费代理IP的使用
- 收费代理还是很多的如:
- 在requests中使用收费代理
import requests
# 从代理服务中获取一个代理IP
proxy = requests.get("http://tvp.daxiangdaili.com/ip/?tid=559775358931681&num=1").text
proxies = {
'http': 'http://' + proxy,
'https': 'https://' + proxy,
}
try:
response = requests.get('http://httpbin.org/get', proxies=proxies)
print(response.text)
except requests.exceptions.ConnectionError as e:
print('Error', e.args)
- 在scrapy中使用收费代理
- 创建scrapy项目:scrapy startproject httpbin
- 创建爬虫文件: ``` cd httpbin
scrapy genspider hb httpbin.org
```python
#编写爬虫文件hb.py
import scrapy
class HbSpider(scrapy.Spider):
name = 'hb'
allowed_domains = ['httpbin.org']
start_urls = ['http://httpbin.org/get']
def parse(self, response):
print(response.body)
#编写中间件文件:middlewares.py
class HttpbinProxyMiddleware(object):
def process_request(self, request, spider):
pro_addr = requests.get('http://127.0.0.1:5000/get').text
request.meta['proxy'] = 'http://' + pro_addr
#修改配置文件settings.py
ROBOTSTXT_OBEY = False #关闭爬虫协议
DOWNLOADER_MIDDLEWARES = {
'httpbin.middlewares.HttpbinProxyMiddleware': 543,
}
#关闭终端输出,改输出到指定日志文件中
LOG_LEVEL= 'DEBUG'
LOG_FILE ='log.txt'
- 执行信息爬取测试: scrapy crawl hb
14. 使用代理爬取信息实战
14.1 实战目标:
- 本节目标是利用代理爬取微信公众号的文章信息,从中提取标题、摘要、发布日期、公众号以及url地址等内容。
- 本节爬取的是搜索关键字为
python的,类别为微信的所有文章信息,并将信息存储到MongoDB中。 - URL地址:http://weixin.sogou.com/weixin?type=2&query=python&ie=utf8&s_from=input
14.2 准备工作:
- 首先对要爬取的微信公众号的文章信息进行分析,确定url地址。
- 分析要爬取的信息加载方式,确定属性普通加载(在响应里使用xpath或css解析)。
- 分析如何获取更多页信息的爬取。就是如何跳转下一页。(没有登录的用户只能看到10页,登陆后才可看到其他页)
- 本次案例需要使用的Python库:Scrapy、requests、pymongo。
- 在MongoDB中创建一个数据库
wenxin,让后在此库中创建一个集合wx,最后开启MongoDB数据库
14.3 具体实现:
① 创建项目
- 首先新建项目,名为weixin:
scrapy startproject weixin
- 进入项目weixin目录下,创建一个Spider(爬虫类wx):
cd weixin
scrapy genspider wx weixin.sogou.com
- 进入settings.py的配置文件:将ROBOTSTXT_OBEY改为false,忽略爬虫协议
ROBOTSTXT_OBEY = False
② 定义Item类
# 定义信息封装类(标题、摘要、公众号、时间、URL地址)
import scrapy
class WxItem(scrapy.Item):
# define the fields for your item here like:
collection = ‘wx’
title = scrapy.Field()
content = scrapy.Field()
nickname = scrapy.Field()
date = scrapy.Field()
url = scrapy.Field()
③ 解析页面
- 进入spider/wx.py文件中编写,代码如下:
# -*- coding: utf-8 -*-
import scrapy
from weixin.items import WxItem
class WxSpider(scrapy.Spider):
name = 'wx'
allowed_domains = ['weixin.sogou.com']
start_urls = ['http://weixin.sogou.com/weixin?query=python&type=2&page=1&ie=utf8']
def parse(self, response):
#解析出当前页面中的所有文章信息
ullist = response.selector.css("ul.news-list li")
#遍历文章信息
for ul in ullist:
#解析具体信息并封装到item中
item = WxItem()
item['title'] = ul.css("h3 a").re_first("<a.*?>(.*?)</a>")
item['content'] = ul.css("p.txt-info::text").extract_first()
item['nickname'] = ul.css("a.account::text").extract_first()
item['date'] = ul.re_first("document.write\(timeConvert\('([0-9]+)'\)\)")
item['url'] = ul.css("h3 a::attr(href)").extract_first()
print(item)
# 交给pipelines(item管道)处理
yield item
#解析出下一頁的url地址
next_url = response.selector.css("#sogou_next::attr(href)").extract_first()
#判断是否存在
if next_url:
url = response.urljoin(next_url) #构建绝对url地址
yield scrapy.Request(url=url,callback=self.parse) #交给调度去继续爬取下一页信息
④ 存储结果
import pymongo
class MongoPipeline(object):
''' 完成MongoDB数据库对Item信息的存储'''
def __init__(self, mongo_uri, mongo_db):
'''对象初始化'''
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
'''通过依赖注入方式实例化当前类,并返回,参数是从配置文件获取MongoDB信息'''
return cls(mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DB'))
def open_spider(self, spider):
'''Spider开启自动调用此方法,负责连接MongoDB,并选择数据库'''
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
'''选择对应集合并写入Item信息'''
self.db[item.collection].insert(dict(item))
return item
def close_spider(self, spider):
'''Spider关闭时自动调用,负责关闭MongoDB的连接'''
self.client.close()
- 修改配置文件settings.py信息:(开启MongoPipeline管道类,设置MongoDB的连接信息)
ITEM_PIPELINES = {
'scrapyseleniumtest.pipelines.MongoPipeline': 300,
}
MONGO_URI = 'localhost'
MONGO_DB = 'taobao'
⑤ 执行爬虫文件开始信息爬取
scrapy crawl wx
- 注意:当前爬取信息过多时会报如下302错误:
2018-05-30 22:40:10 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (
302) to <GET http://weixin.sogou.com/antispider/?from=%2fweixin%3Fquery%3dpython
%26type%3d2%26page%3d1%26ie%3dutf8> from <GET http://weixin.sogou.com/weixin?que
ry=python&type=2&page=1&ie=utf8>

⑥ 在中间件中使用付费代理服务来解决上面错误:
# 在middlewares.py文件中定义一个Downloader中间件
import requests
class HttpbinProxyMiddleware(object):
def process_request(self, request, spider):
pro_addr = requests.get('http://tvp.daxiangdaili.com/ip/?tid=559775358931681&num=1').text
request.meta['proxy'] = 'http://' + pro_addr
# 设置启动上面我们写的这个代理
#在settings.py配置文件中.设置我们自定义的Downloader MiddleWares中间件设置:
DOWNLOADER_MIDDLEWARES = {
'httpbin.middlewares.HttpbinProxyMiddleware': 543,
}
练习:没有登录的用户只能看到10页,登陆后才可看到其他页,那么如何实现爬取更多页信息呢?
15. Redis数据库
15.1 Redis简介
- Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
- Redis与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启后可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型(hash)的数据,同时还提供包括string(字符串)、list(链表)、set(集合)和sorted set(有序集合)。
- Redis支持数据的备份,即master-slave模式的数据备份。
- Redis是一个高性能的key-value数据库。
- Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。
- 它提供了Python,Ruby,Erlang,PHP客户端,使用很方便。
- Redis优势:
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的Strings,Lists,Hashes,Sets及Ordered Sets数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
15.2 Redis的安装:
- 官方网站:https://redis.io
- 官方文档:https://redis.io/documentation
- 中文官网:http://www.redis.cn
- GitHub:https://github.com/antirez/redis
- 可视化管理工具:https://redisdesktop.com/download
- windows下安装地址:https://github.com/MSOpenTech/redis/releases
- 可下载:Redis-x64-3.2.100.msi 直接next按钮安装即可
- 配置文件:redis.windows-service.conf
- Liunx下的安装:(如 ubuntu)
安装命令: sudo apt-get -y install redis-server
进入命令行模式:
$ redis-cli
127.0.0.1:6379> set 'name' 'zhangsan'
ok
127.0.0.1:6379> get 'name'
"zhangsan"
启停Redis服务:
sudo /etc/init.d/redis-server start
sudo /etc/init.d/redis-server stop
sudo /etc/init.d/redis-server restart
- Mac下的安装:
安装命令:brew install redis
启停服务:
brew services start redis
brew services stop redis
brew services restart redis
配置文件:
/usr/local/etc/redis.conf
- redis-py的安装:(python操作redis)
pip install redis
15.3 Redis的操作:
- Redis的数据类型:
- 共计5种类型:string(字符串)、hash(哈希表) list(双向链表)、set(集合)和sorted set(有序集合)
① String(子串类型)
set命令:设置一个键和值,键存在则只覆盖,返回ok
> set 键 值 例如: >set name zhangsan
get命令:获取一个键的值,返回值
> get 键 例如:>get name
setnx命令:设置一个不存在的键和值(防止覆盖),
> setnx 键 值 若键已存在则返回0表示失败
setex命令:设置一个指定有效期的键和值(单位秒)
> setex 键 [有效时间] 值 例如: >setex color 10 red
不写有效时间则表示永久有效,等价于set
setrange命令:替换子字符串 (替换长度由子子串长度决定)
> setrange 键 位置 子字串
> setrange name 4 aa 将name键对应值的第4个位置开始替换
mset命令:批量设置键和值,成功则返回ok
> mset 键1 值1 键2 值2 键3 值3 ....
msetnx命令:批量设置不存在的键和值,成功则返回ok
> msetnx 键1 值1 键2 值2 键3 值3 ....
getset命令:获取原值,并设置新值
getrange命令:获取指定范围的值
>getrange 键 0,4 //获取指定0到4位置上的值
mget命令: 批量获取值
>mget 键1 键2 键3....
incr命令: 指定键的值做加加操作,返回加后的结果。
> 键 例如: >incr kid
incrby命令: 设置某个键加上指定值
> incrby 键 m //其中m可以是正整数或负整数
decr命令: 指定键的值做减减操作,返回减后的结果。
> decr 键 例如: >decr kid
decrby命令: 设置某个键减上指定值
> decrby 键 m //其中m可以是正整数或负整数
append命令:给指定key的字符串追加value,返回新字符串值的长度
>append 键 追加字串
strlen求长度 >strlen 键名 //返回对应的值。
② hash类型:
hset命令:设置一个哈希表的键和值
>hset hash名 键 值
如:>hset user:001 name zhangsan
hsetnx命令:设置一个哈希表中不存在的键和值
>hsetnx hash名 键 值 //成功返回1,失败返回0
如:>hsetnx user:001 name zhangsan
hmset命令: 批量设置
hget命令: 获取执行哈希名中的键对应值
hexists user:001 name //是否存在, 若存在返回1
hlen user:001 //获取某哈希user001名中键的数量
hdel user:001 name //删除哈希user:001 中name键
hkeys user:002 //返回哈希名为user:002中的所有键。
hvals user:002 //返回哈希名为user:002中的所有值。
hgetall user:002 //返回哈希名为user:002中的所有键和值。
③ list类型(双向链表结构)
- list即可以作为“栈”也可以作为"队列"。
>lpush list1 "world" //在list1头部压入一个字串
>lpush list1 "hello" // 在list1头部压入一个字串
>lrange list1 0 -1 //获取list1中内容
0:表示开头 -1表示结尾。
>rpush list2 "world" //在list2尾部压入一个字串
>rpush list2 "hello" // 在list2尾部压入一个字串
>lrange list2 0 -1 //获取list2中内容
0:表示开头 -1表示结尾。
>linsert list2 before "hello" "there"
在key对应list的特定位置前或后添加字符串
>lset list2 1 "four"
修改指定索引位置上的值
>lrem list2 2 "hello" //删除前两个hello值
>lrem list2 -2 "hello" //删除后两个hello值
>lrem list2 0 "hello" //删除所有hello值
>ltrim mylist8 1 -1 //删除此范围外的值
>lpop list2 //从list2的头部删除元素,并返回删除元素
>rpop list2 //从list2的尾部删除元素,并返回删除元素
>rpoplpush list1 list2 //将list1的尾部一个元素移出到list2头部。并返回
>lindex list2 1 //返回list2中索引位置上的元素
>llen list2 //返回list2上长度
④ sets类型和操作:
- Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
- 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
>sadd myset "hello" //向myset中添加一个元素
成功返回1,失败(重复)返回0
>smembers myset //获取myset中的所有元素
>srem myset "one" //从myset中删除一个one
成功返回1,失败(不存在)返回0
>spop myset //随机返回并删除myset中的一个元素
>sdiff myset1 myset2 //返回两个集合的差集
以myset1为标准,获取myset2中不存在的。
> sinter myset2 myset3 交集
> sunion myset2 myset3 并集
> scard myset2 返回元素个数
> sismember myset2 two 判断myset2中是否包含two
⑤ 有序集合(sorted set):
- Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
- 不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
向名称为 key 的 zset 中添加元素 member,score 用于排序。如果该元素已经存在,则根据 score 更新该元素的顺序
redis 127.0.0.1:6379> zadd myzset 1 "one" 添加
(integer) 1
redis 127.0.0.1:6379> zadd myzset 2 "two"
(integer) 1
redis 127.0.0.1:6379> zadd myzset 3 "two"
(integer) 0
redis 127.0.0.1:6379> zrange myzset 0 -1 withscores 查看
1) "one"
2) "1"
3) "two"
4) "3"
redis 127.0.0.1:6379> zrem myzset two 删除
(integer) 1
redis 127.0.0.1:6379> zrange myzset 0 -1 withscores 查看
1) "one"
2) "1"
redis 127.0.0.1:6379>
⑥ Redis常用命令:
1. 键值相关命令
>keys * //返回键(key)
>keys list* //返回名以list开头的所有键(key)
>exists list1 //判断键名为list1的是否存在
存在返回1, 不存在返回0
>del list1 //删除一个键(名为list1)
>expire list1 10 //设置键名为list1的过期时间为10秒后
>ttl list1 //查看键名为list1的过期时间,若为-1表示以过期
>move age 1 //将键名age的转移到1数据库中。
>select 1 //表示进入到1数据库中,默认在0数据库
>persist age //移除age的过期时间(设置为过期)
15.4 Redis高级实用特性
1. 安全性:为Redis添加密码
-------------------------------
1.进入配置文件:
vi /usr/local/redis/etc/redis.conf
设置:requirepass redis的密码
2. 重启服务:
# ./redis-cli shutdown 执行关闭
# ./redis-server /usr/local/redis/etc/redis.conf 启动
3. 登录(两种)
# ./redis-cli 客户端命令链接服务器
>auth 密码值 //授权后方可使用
# ./redis-cli -a 密码 //连接时指定密码来进行授权
2. 主从复制
------------------------------------------
操作步骤:
1.先将linux虚拟机关闭,之后克隆一个。
2.启动两个虚拟机:master(主)和slave(从)
3. 在slave(从)中配置一下ip地址
# ifconfig eth0 192.168.128.229
# ping 一下看看通不通。
4. 配置从机
进入:配置文件
slaveof 192.168.128.228 6379 //配置连接主机的Redis的ip和端口
masterauth 密码 //配置连接密码
最后启动slave(从)机的Redis服务。
其他:可以通过info命令中的role属性查看自己角色是master、slave
3. 事务处理
--------------------------------------------
>multi //开启一个事务
>set age 10 //暂存指令队列
>set age 20
>exec //开始执行(提交事务)
或>discard //清空指令队列(事务回滚)
4. 乐观锁
-----------------------------------
在事务前对被操作的属性做一个:
> watch age
>multi //开启一个事务(在此期间有其他修改,则此处会失败)
>set age 10 //暂存指令队列
>set age 20
>exec //开始执行(提交事务)
或>discard //清空指令队列(事务回滚)
5. 持久化机制(通过修改配置文件做设置)
-----------------------------------
1. snapshotting(快照)默认方式
配置 save
save 900 1 #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000
2. Append-only file(aof方式)
配置 appendonly on 改为yes
会在bin目录下产生一个.aof的文件
关于aof的配置
appendonly yes //启用aof 持久化方式
# appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
# appendfsync no //完全依赖os,性能最好,持久化没保证
6. 发布及订阅消息
----------------------
需要开启多个会话端口
会话1:>subscribe tv1 //监听tv1频道
会话2:>subscribe tv1 tv2 //监听tv1和tv2频道
会话3: >publish tv1 消息 //向tv1频道发送一个消息
7. 使用虚拟内存
-------------------------------
在redis配置文件中设置
vm-enabled yes #开启vm功能
vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径
vm-max-memory 1000000 #redis使用的最大内存上限
vm-page-size 32 #每个页面的大小32字节
vm-pages 134217728 #最多使用多少页面
vm-max-threads 4 #用于执行value对象换入患处的工作线程数量
15.5 Python使用Redis
import redis
# host是redis主机,需要redis服务端和客户端都启动 redis默认端口是6379
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# 字串操作
r.set('name', 'junxi') # key是"foo" value是"bar" 将键值对存入redis缓存
print(r['name'])
print(r.get('name')) # 取出键name对应的值
print(type(r.get('name')))
# 如果键fruit不存在,那么输出是True;如果键fruit已经存在,输出是None
print(r.set('fruit', 'watermelon', nx=True)) # True--不存在
print(r.setnx('fruit1', 'banana')) # fruit1不存在,输出为True
#设置过期时间
r.setex("fruit2", "orange", 5)
time.sleep(5)
print(r.get('fruit2')) # 5秒后,取值就从orange变成None
print(r.mget("fruit", "fruit1", "fruit2", "k1", "k2")) # 将目前redis缓存中的键对应的值批量取出来
redis操作hash哈希
r.hset("hash1", "k1", "v1")
r.hset("hash1", "k2", "v2")
print(r.hkeys("hash1")) # 取hash中所有的key
print(r.hget("hash1", "k1")) # 单个取hash的key对应的值
print(r.hmget("hash1", "k1", "k2")) # 多个取hash的key对应的值
r.hsetnx("hash1", "k2", "v3") # 只能新建
print(r.hget("hash1", "k2"))
#hash的批量操作
r.hmset("hash2", {"k2": "v2", "k3": "v3"})
print(r.hget("hash2", "k2")) # 单个取出"hash2"的key-k2对应的value
print(r.hmget("hash2", "k2", "k3")) # 批量取出"hash2"的key-k2 k3对应的value --方式1
print(r.hmget("hash2", ["k2", "k3"])) # 批量取出"hash2"的key-k2 k3对应的value --方式2
print(r.hgetall("hash1")) #取出所有的键值对
redis操作list链表
r.lpush("list1", 11, 22, 33)
print(r.lrange('list1', 0, -1))
r.rpush("list2", 11, 22, 33) # 表示从右向左操作
print(r.llen("list2")) # 列表长度
print(r.lrange("list2", 0, 3)) # 切片取出值,范围是索引号0-3
r.rpush("list2", 44, 55, 66) # 在列表的右边,依次添加44,55,66
print(r.llen("list2")) # 列表长度
print(r.lrange("list2", 0, -1)) # 切片取出值,范围是索引号0到-1(最后一个元素)
r.lset("list2", 0, -11) # 把索引号是0的元素修改成-11
print(r.lrange("list2", 0, -1))
r.lrem("list2", "11", 1) # 将列表中左边第一次出现的"11"删除
print(r.lrange("list2", 0, -1))
r.lrem("list2", "99", -1) # 将列表中右边第一次出现的"99"删除
print(r.lrange("list2", 0, -1))
r.lrem("list2", "22", 0) # 将列表中所有的"22"删除
print(r.lrange("list2", 0, -1))
r.lpop("list2") # 删除列表最左边的元素,并且返回删除的元素
print(r.lrange("list2", 0, -1))
r.rpop("list2") # 删除列表最右边的元素,并且返回删除的元素
print(r.lrange("list2", 0, -1))
print(r.lindex("list2", 0)) # 取出索引号是0的值
redis操作set集合
#新增
r.sadd("set1", 33, 44, 55, 66) # 往集合中添加元素
print(r.scard("set1")) # 集合的长度是4
print(r.smembers("set1")) # 获取集合中所有的成员
print(r.sscan("set1")) #获取集合中所有的成员--元组形式
for i in r.sscan_iter("set1"):
print(i)
#差集
r.sadd("set2", 11, 22, 33)
print(r.smembers("set1")) # 获取集合中所有的成员
print(r.smembers("set2"))
print(r.sdiff("set1", "set2")) # 在集合set1但是不在集合set2中
print(r.sdiff("set2", "set1")) # 在集合set2但是不在集合set1中
16. 分布式爬虫原理
- 在前面我们已经掌握了Scrapy框架爬虫,虽然爬虫是异步多线程的,但是我们只能在一台主机上运行,爬取效率还是有限。
- 分布式爬虫则是将多台主机组合起来,共同完成一个爬取任务,将大大提高爬取的效率。
16.1 分布式爬虫架构
- 回顾Scrapy的架构:
- Scrapy单机爬虫中有一个本地爬取队列Queue,这个队列是利用deque模块实现的。
- 如果有新的Request产生,就会放到队列里面,随后Request被Scheduler调度。
- 之后Request交给Downloader执行爬取,这就是简单的调度架构。
- 我们需要做的就是在多台主机上同时运行爬虫任务

16.2 维护爬取队列
- 关于爬取队列我们自然想到的是基于内存存储的Redis。它支持多种数据结构,如:列表、集合、有序集合等,存取的操作也非常简单。
- Redis支持的这几种数据结构,在存储中都有各自优点:
- 列表(list)有lpush()、lpop()、rpush()、rpop()方法,可以实现先进先出的队列和先进后出的栈式爬虫队列。
- 集合(set)的元素是无序且不重复的,这样我们可以非常方便的实现随机且不重复的爬取队列。
- 有序集合有分数表示,而Scrapy的Request也有优先级的控制,我们可以用它来实现带优先级调度的队列。
16.3 如何去重
- Scrapy有自动去重,它的去重使用了Python中的集合实现。用它记录了Scrapy中每个Request的指纹(Request的散列值)。
- 对于分布式爬虫来说,我们肯定不能再用每个爬虫各自的集合来去重了,因为不能共享,各主机之间就无法做到去重了。
- 可以使用Redis的集合来存储指纹集合,那么这样去重集合也是利用Redis共享的。
- 每台主机只要将新生成Request的指纹与集合比对,判断是否重复并选择添加入到其中。即实例了分布式Request的去重。
16.4 防止中断
-
在Scrapy中,爬虫运行时的Request队列放在内存中。爬虫运行中断后,这个队列的空间就会被释放,导致爬取不能继续。
-
要做到中断后继续爬取,我们可以将队列中的Request保存起来,下次爬取直接读取保存的数据既可继续上一次爬取的队列。
-
在Scrapy中制定一个爬取队列的存储路径即可,这个路径使用
JOB_DIR变量来标识,命令如下:
scrapy crawl spider -s JOB_DIR=crawls/spider -
更多详细使用请详见官方文档:http://doc.scrapy.org/en/latest/topics/jobs.html
-
在Scrapy中,我们实际是把爬取队列保存到本地,第二次爬取直接读取并恢复队列既可。
-
在分布式框架中就不用担心这个问题了,因为爬取队列本身就是用数据库存储的,中断后再启动就会接着上次中断的地方继续爬取。
-
当Redis的队列为空时,爬虫会重新爬取;当队列不为空时,爬虫便会接着上次中断支处继续爬取。
16.5 架构实现
- 首先实现一个共享的爬取队列,还要实现去重的功能。
- 重写一个Scheduer的实现,使之可以从共享的爬取队列存取Request
- 幸运的是,我们可以下载一个现成
Scrapy-Redis分布式爬虫的开源包,直接使用就可以很方便实现分布式爬虫。
17. Scrapy分布式实战
- Scrapy-Redis则是一个基于Redis的Scrapy分布式组件。它利用Redis对用于爬取的请求(Requests)进行存储和调度(Schedule),并对爬取产生的项目(items)存储以供后续处理使用。
- scrapy-redi重写了scrapy一些比较关键的代码,将scrapy变成一个可以在多个主机上同时运行的分布式爬虫。

17.1 准备
-
既然这么好能实现分布式爬取,那都需要准备什么呢?
-
需要准备的东西比较多,都有:
- scrapy
- scrapy-redis
- redis
- mysql
- python的mysqldb模块
- python的redis模块
-
为什么要有mysql呢?是因为我们打算把收集来的数据存放到mysql中
-
安装:
$ pip install scrapy-redis $ pip install redis -
Scrapy-Redis的官方网址:https://github.com/rmax/scrapy-redis
17.2 Scrapy-redis各个组件介绍
① connection.py
- 负责根据setting中配置实例化redis连接。被dupefilter和scheduler调用,总之涉及到redis存取的都要使用到这个模块。
② dupefilter.py
- 负责执行requst的去重,实现的很有技巧性,使用redis的set数据结构。
- 但是注意scheduler并不使用其中用于在这个模块中实现的dupefilter键做request的调度,而是使用queue.py模块中实现的queue。
- 当request不重复时,将其存入到queue中,调度时将其弹出。
③ queue.py
- 其作用如II所述,但是这里实现了三种方式的queue:
- FIFO的SpiderQueue,SpiderPriorityQueue,以及LIFI的SpiderStack。默认使用的是第二中,这也就是出现之前文章中所分析情况的原因(链接)。
④ pipelines.py
- 这是是用来实现分布式处理的作用。它将Item存储在redis中以实现分布式处理。
- 另外可以发现,同样是编写pipelines,在这里的编码实现不同于文章(链接:)中所分析的情况,由于在这里需要读取配置,所以就用到了from_crawler()函数。
⑤ scheduler.py
- 此扩展是对scrapy中自带的scheduler的替代(在settings的SCHEDULER变量中指出),正是利用此扩展实现crawler的分布式调度。其利用的数据结构来自于queue中实现的数据结构。
- scrapy-redis所实现的两种分布式:爬虫分布式以及item处理分布式就是由模块scheduler和模块pipelines实现。上述其它模块作为为二者辅助的功能模块。
⑥ spider.py
- 设计的这个spider从redis中读取要爬的url,然后执行爬取,若爬取过程中返回更多的url,那么继续进行直至所有的request完成。之后继续从redis中读取url,循环这个过程。
17.3 具体使用(对Scrapy改造):
1.首先在settings.py中配置redis(在scrapy-redis 自带的例子中已经配置好)
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter'
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
REDIS_URL = None # 一般情况可以省去
REDIS_HOST = '127.0.0.1' # 也可以根据情况改成 localhost
REDIS_PORT = 6379
2.item.py的改造
from scrapy.item import Item, Field
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, TakeFirst, Join
class ExampleItem(Item):
name = Field()
description = Field()
link = Field()
crawled = Field()
spider = Field()
url = Field()
class ExampleLoader(ItemLoader):
default_item_class = ExampleItem
default_input_processor = MapCompose(lambda s: s.strip())
default_output_processor = TakeFirst()
description_out = Join()
3.spider的改造。star_turls变成了redis_key从redis中获得request,继承的scrapy.spider变成RedisSpider。
from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
"""Spider that reads urls from redis queue (myspider:start_urls)."""
name = 'myspider_redis'
redis_key = 'myspider:start_urls'
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
super(MySpider, self).__init__(*args, **kwargs)
def parse(self, response):
return {
'name': response.css('title::text').extract_first(),
'url': response.url,
}
启动爬虫:
$ scrapy runspider my.py
可以输入多个来观察多进程的效果。。打开了爬虫之后你会发现爬虫处于等待爬取的状态,是因为list此时为空。所以需要在redis控制台中添加启动地址,这样就可以愉快的看到所有的爬虫都动起来啦。
lpush mycrawler:start_urls http://www.***.com
更多关于配置Scrapy框架中配置:settings.py
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilters.RFPDupeFilter'
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# 可选的 按先进先出排序(FIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderQueue'
# 可选的 按后进先出排序(LIFO)
# SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderStack'
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 只在使用SpiderQueue或者SpiderStack是有效的参数,指定爬虫关闭的最大间隔时间
# SCHEDULER_IDLE_BEFORE_CLOSE = 10
# 通过配置RedisPipeline将item写入key为 spider.name : items 的redis的list中,供后面的分布式处理item
# 这个已经由 scrapy-redis 实现,不需要我们写代码
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400
}
# 指定redis数据库的连接参数
# REDIS_PASS是我自己加上的redis连接密码(默认不做)
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
#REDIS_PASS = 'redisP@ssw0rd'
# LOG等级
LOG_LEVEL = 'DEBUG'
#默认情况下,RFPDupeFilter只记录第一个重复请求。将DUPEFILTER_DEBUG设置为True会记录所有重复的请求。
DUPEFILTER_DEBUG =True
# 覆盖默认请求头,可以自己编写Downloader Middlewares设置代理和UserAgent
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8',
'Connection': 'keep-alive',
'Accept-Encoding': 'gzip, deflate, sdch'
}
17.4 实战案例:
- 案例:实现主从分布式爬虫,爬取5i5j的楼盘信息
- URL地址:https://fang.5i5j.com/bj/loupan/
- 准备工作:
- 开启redis数据库服务
- 将第二节
Scrapy框架的使用中的案例demo复制过来两份:master(主)、slave(从)

① 编写slave(从)项目代码:
- 查看items.py 保持不变:
import scrapy
class FangItem(scrapy.Item):
# define the fields for your item here like:
title = scrapy.Field()
address = scrapy.Field()
time = scrapy.Field()
clicks = scrapy.Field()
price = scrapy.Field()
- 编辑爬虫文件:fang.py
# -*- coding: utf-8 -*-
import scrapy
from demo.items import FangItem
from scrapy_redis.spiders import RedisSpider
class FangSpider(RedisSpider):
name = 'fang'
#allowed_domains = ['fang.5i5j.com']
#start_urls = ['https://fang.5i5j.com/bj/loupan/']
redis_key = 'fangspider:start_urls'
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
super(FangSpider, self).__init__(*args, **kwargs)
def parse(self, response):
#print(response.status)
hlist = response.css("div.houseList_list")
for vo in hlist:
item = FangItem()
item['title'] = vo.css("h3.fontS20 a::text").extract_first()
item['address'] = vo.css("span.addressName::text").extract_first()
item['time'] = vo.re("<span>(.*?)开盘</span>")[0]
item['clicks'] = vo.re("<span><i>([0-9]+)</i>浏览</span>")[0]
item['price'] = vo.css("i.fontS24::text").extract_first()
print(item)
yield item
#pass
- 查看pipelines.py保持不变
class DemoPipeline(object):
def process_item(self, item, spider):
print("="*70)
return item
- 编辑配置文件:settings.py配置文件:
...
ITEM_PIPELINES = {
#'demo.pipelines.DemoPipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
...
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去
REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost
REDIS_PORT = 6379
- 测试:爬取具体房屋信息
# 进入爬虫文件目录找到爬虫文件:
$ scrapy runspider fang.py
另启一个终端,并连接redis数据库
$ redis_cli -p 6379
6379 >lpush fangspider:start_urls https://fang.5i5j.com/bj/loupan/
② 编写master(主)项目代码:
- 编辑items.py 储存url地址:
import scrapy
class MasterItem(scrapy.Item):
# define the fields for your item here like:
url = scrapy.Field()
#pass
- 编辑爬虫文件:fang.py
# -*- coding: utf-8 -*-
from scrapy.spider import CrawlSpider,Rule
from scrapy.linkextractors import LinkExtractor
from demo.items import MasterItem
class FangSpider(CrawlSpider):
name = 'master'
allowed_domains = ['fang.5i5j.com']
start_urls = ['https://fang.5i5j.com/bj/loupan/']
item = MasterItem()
#Rule是在定义抽取链接的规则
rules = (
Rule(LinkExtractor(allow=('https://fang.5i5j.com/bj/loupan/n[0-9]+/',)), callback='parse_item',
follow=True),
)
def parse_item(self,response):
item = self.item
item['url'] = response.url
return item
- 编辑pipelines.py负责存储爬取的url地址到redis中:
import redis,re
class MasterPipeline(object):
def __init__(self,host,port):
#连接redis数据库
self.r = redis.Redis(host=host, port=port, decode_responses=True)
#self.redis_url = 'redis://password:@localhost:6379/'
#self.r = redis.Redis.from_url(self.redis_url,decode_responses=True)
@classmethod
def from_crawler(cls,crawler):
'''注入实例化对象(传入参数)'''
return cls(
host = crawler.settings.get("REDIS_HOST"),
port = crawler.settings.get("REDIS_PORT"),
)
def process_item(self, item, spider):
#使用正则判断url地址是否有效,并写入redis。
if re.search('/bj/loupan/',item['url']):
self.r.lpush('fangspider:start_urls', item['url'])
else:
self.r.lpush('fangspider:no_urls', item['url'])
- 编辑配置文件:settings.py配置文件:
ITEM_PIPELINES = {
'demo.pipelines.MasterPipeline': 300,
#'scrapy_redis.pipelines.RedisPipeline': 400,
}
...
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# REDIS_URL = 'redis:password//127.0.0.1:6379' # 一般情况可以省去
REDIS_HOST = '127.0.0.1' # 也可以根据情况改成 localhost
REDIS_PORT = 6379
- 测试:爬取更多的url地址:
# 进入爬虫文件目录找到爬虫文件:
$ scrapy runspider fang.py
17.5 处理的Redis里的数据:
- 网站的数据爬回来了,但是放在Redis里没有处理。之前我们配置文件里面没有定制自己的ITEM_PIPELINES,而是使用了RedisPipeline,所以现在这些数据都被保存在redis的demo:items键中,所以我们需要另外做处理。
- 在scrapy-youyuan目录下可以看到一个process_items.py文件,这个文件就是scrapy-redis的example提供的从redis读取item进行处理的模版。
- 假设我们要把demo:items中保存的数据读出来写进MongoDB或者MySQL,那么我们可以自己写一个process_demo_profile.py文件,然后保持后台运行就可以不停地将爬回来的数据入库了。
存入的MongoDB
- 启动的MongoDB数据库:sudo mongod
- 执行下面程序:python process_demo_mongodb.py
# process_demo_mongodb.py
import json
import redis
import pymongo
def main():
# 指定Redis数据库信息
rediscli = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 指定MongoDB数据库信息
mongocli = pymongo.MongoClient(host='localhost', port=27017)
# 创建数据库名
db = mongocli['demodb']
# 创建空间
sheet = db['fang']
while True:
# FIFO模式为 blpop,LIFO模式为 brpop,获取键值
source, data = rediscli.blpop(["demo:items"])
item = json.loads(data)
sheet.insert(item)
try:
print u"Processing: %(name)s <%(link)s>" % item
except KeyError:
print u"Error procesing: %r" % item
if __name__ == '__main__':
main()
十一、Python网络爬虫进阶实战(下)
18. App的信息爬取
- 之前我们讲解的都是Web网页信息爬取,随着移动互联的发展,越来越多的企业并没有提供Web网页端的服务,而是直接开发App。
- App的爬取相比Web端爬取更加容易,反爬中能力没有那么强,而且响应数据大多都是JSON形式,解析更加简单。
- 在APP端若想查看和分析内容那就需要借助抓包软件,常用的有:Filddler、Charles、mitmproxy、Appium等。
- mitmproxy是一个支持HTTP/HTTPS协议的抓包程序,类似Fiddler、Charles的功能,只不过世它通过控制台的形式操作。
![img]()
- Appium是移动端的自动化测试工具,类似于前面所说的Selenium、利用它可以驱动Android、IOS等设备完成自动化测试。
![img]()
18.1 Charles的介绍
- Charles是一个网络抓包工具,可以完成App的抓包分析,能够得到App运行过程中发生的所有网络请求和响应内容。
- 相关连接:

Charles主要功能:
* 支持SSL代理。可以截取分析SSL的请求。
* 支持流量控制。可以模拟慢速网络以及等待时间(latency)较长的请求。
* 支持AJAX调试。可以自动将json或xml数据格式化,方便查看。
* 支持AMF调试。可以将Flash Remoting 或 Flex Remoting信息格式化,方便查看。
* 支持重发网络请求,方便后端调试。
* 支持修改网络请求参数。
* 支持网络请求的截获并动态修改。
* 检查HTML,CSS和RSS内容是否符合W3C标准。

18.2 Charles的配置
① 网络共享配置:
- 实现手机通过电脑上网:就是电脑通过网线上网,然后共享Wifi,手机在链接此wifi。
- 查看本机电脑的网络链接:
![img]()
- 共享wifi设置:
![img]()
- 手机链接此wifi,实现手机和电脑连接到同一个局域下
![img]()
- 查看本机电脑的网络链接:
② 代理设置:
-
实现手机和电脑在同一局域网下的机上,完成Charles的代理设置:
-
首先查看电脑的打开Charles代理是否开启,具体操作是:
Proxy->Proxy Settings,打开代理设置界面,设置代理端口为:8888.![img]()
-
打开手机的网络配置,并设置使用代理配置:
![img]()
-
③ 证书配置:
-
安装完成后,我们还需要配置相关SSL证书 来抓取HTTPS协议的信息包。
-
Windows系统:
- 首先打开Charles,点击
Help->SSL Proxying->Install Charles Root Certificate,即可进入证书安装界面。 - 点击 “安装证书” 按钮,就会打开证书导入向导。
- 点击 “下一步” 按钮,此时需要选择证书存储区域“将所有证书放入下列存储”->点击"浏览"->选择“受信任的证书颁发机构”->"确定"->"下一步"->完成。
- 首先打开Charles,点击
-
Mac系统:
- 首先打开Charles,点击
Help->SSL Proxying->Install Charles Root Certificate,即可进入证书安装界面。 - 接下来,找到Charles的证书并双击,将 “信任” 设置为 “始终信任”即可 。
- 首先打开Charles,点击
-
IOS手机:-
在网络配置和代理开启的情况下,若是你的手机是IOS系统,可以按照下面的操作进行证书配置。
-
在手机浏览器上打开chls.pro/ssl后,便会打开证书安装页面,点击安装即可。
![img]()
-
在IOS手机上,点击“设置”->"通用"->"关于本机"->"证书信任设置",设置开启即可。
![img]()
-
⑤ Charles 配置 HTTPS 代理的乱码问题
-
在 Charles 设置 SSL 代理:
-
Proxy –> SSL Proxying Setting –> Enable SSL Proxying
![img]()
![img]()
-
18.3 Charles的运行原理和具体使用
① 运行原理:
- 首先Charles运行在自己的PC上,Charles运行的时候会在PC的8888端口开启一个代理服务,这就是一个HTTP/HTTPS的代理。
- 确保手机和PC在同一个局域网内,我们可以使用手机模拟器通过虚拟网络连接,也可使用手机真机和PC通过无线网连接。
- 设置手机代理为Charles的代理地址,这样手机访问互联网的数据包就会流经Charles,Charles再转发这些数据包到真实的服务器,同理相反也是如此。
② 具体使用
- 手机运行App访问要爬取的平台信息,使用Charles抓包分析。
- 知道了请求和响应的具体信息,通过分析得到请求的URL地址和参数的规律,直接使用程序模拟即可批量爬取。

19. mitmproxy的使用
- mitmproxy是一个支持HHTP/HTTPS协议的抓包程序,类似Fiddler、Charles的功能,只不过世它通过控制台的形式操作。
- mitmproxy还有两个关联组件:
- mitmdump:它是mitmproxy的命令行接口,利用它我们可以对接Python脚本,用Python实现监听后的处理。
- mitmweb: 它是一个Web程序,通过它我们可以清楚观察mimproxy捕获的请求。
- mitmproxy的功能:
- 拦截HTTP和HTTPS请求和响应
- 保存HTTP会话请进行分析
- 模拟客户端请求,模拟服务器返回响应
- 利用反向代理将流量转发给指定的服务器
- 支持Mac和Linux上的透明代理
- 利用Python对HTTP请求和响应进行实时处理
19.1 安装和配置:
- 安装:完成mitmproxy的安装,另外还附带安装了mitmdump和mimweb这两个组件
pip3 install mitmproxy
-
配置手机和PC处于同一局域网下:(具体步骤详见上一节内容)
-
打开手机的网络配置,并设置使用代理配置,端口监听
8080:(具体步骤详见上一节内容) -
配置mitmproxy的CA证书。
-
对于mitmproxy来说,如果想要截获HTTPS请求,就需要设置CA证书,而mitmproxy安装后就会提供一套CA证书,只要客户信任了此证书即可。
-
首先运行启动
mitmdump,就会在此命令下产生CA证书,我们可以从用户目录下的.mitmproxy目录下看到。localhost:app zhangtao$ mitmdump Proxy server listening at http://*:8080![img]()
-
文件说明:
mitmproxy-ca.pemPEM格式的证书私钥mitmproxy-ca-cert.pemPEM格式证书,适用于大多数非Windows平台mitmproxy-ca-cert.p12PKCS12格式的证书,适用于大多数Windows平台mitmproxy-ca-cert.cer与mitmproxy-ca-cert.pem相同(只是后缀名不同),适用于大部分Android平台mitmproxy-dhparam.pemPEM格式的秘钥文件,用于增强SSL安全性。
-
在Mac系统下双击
mitmproxy-ca-cert.pem即可弹出秘钥串管理页面,找到mitmproxy证书,打开设置选项,选择始终信任即可。![img]()
-
将
mitmproxy-ca-cert.pem文件发送到iPhone手机上,点击安装就可以了(在IOS上通过AirDrop共享过去的)。![img]()
-
在iphone上安装CA证书(Android手机直接复制文件点击安装即可)
![img]()
-
在IOS手机上,点击“设置” - > “通用” - > “关于本机” - > “证书信任设置”,设置开启即可
![img]()
-
19.2 mitmproxy的使用:
- 运行
mitmproxy命令就会打开一个监听窗口,此窗口就会输出一个App请求中的信息。
localhost:app zhangtao$ mitmproxy

- mitmproxy的按键操作说明
| 按键 | 说明 |
|---|---|
| q | 退出(相当于返回键,可一级一级返回) |
| d | 删除当前(黄色箭头)指向的链接 |
| D | 恢复刚才删除的请求 |
| G | 跳到最新一个请求 |
| g | 跳到第一个请求 |
| C | 清空控制台(C是大写) |
| i | 可输入需要拦截的文件或者域名(逗号需要用\来做转译,栗子:feezu.cn) |
| a | 放行请求 |
| A | 放行所有请求 |
| ? | 查看界面帮助信息 |
| ^ v | 上下箭头移动光标 |
| enter | 查看光标所在列的内容 |
| tab | 分别查看 Request 和 Response 的详细信息 |
| / | 搜索body里的内容 |
| esc | 退出编辑 |
| e | 进入编辑模式 |
19.3 mitmdump的使用:
- mitmdump是mitmproxy的命令行接口,同时还可以对接Python对请求进行处理。
- 使用命令启动mitmproxy,并将截获的数据保存到指定文件中,命令如下:
mitmdump -w outfile
- 使用指定命令截获的数据,如指定处理脚本文件为script.py.
mitmdump -s script.py
- 日志输出:
from mitmproxy import ctx
def request(flow):
# 修改请求头
flow.request.headers['User-Agent'] = 'MitmProxy'
ctx.log.info(str(flow.request.headers))
ctx.log.warn(str(flow.request.headers))
ctx.log.error(str(flow.request.headers))
- Request请求
from mitmproxy import ctx
# 所有的请求都会经过request
def request(flow):
info = ctx.log.info
# info(flow.request.url)
# info(str(flow.request.headers))
# info(str(flow.request.cookies))
# info(flow.request.host)
# info(flow.request.method)
# info(str(flow.request.port))
# info(flow.request.scheme)
print(flow.request.method,":",flow.request.url)
- Response响应
from mitmproxy import ctx
# 所有的请求都会经过request
def response(flow):
info = ctx.log.info
# info(flow.response.url)
# info(str(flow.response.headers))
# info(str(flow.response.cookies))
info(str(flow.response.status_code))
# info(str(flow.response.text))
- 运行手机浏览器访问:
http://httpbin.org/get测试:
20. App信息抓取实战
① 抓取目标:
-
我们的抓取目标是京东商城的App电子商品信息,并将信息保存到MongoDB数据库中。
-
我们将商品信息的id号、标题、单价、评价条数等信息
![img]()
② 准备工作和抓取分析
-
准备工作:
- 安装app抓包工具Charles、mitmproxy。
- 配置网络,确认手机和PC处于同一局域网下,并配置好代理服务
- 安装证书,确保可以抓取HTTPS的请求信息。
- 安装并开启MongoDB数据库。
-
抓取分析:
-
打开iCharles抓包工具,让后使用手机打开京东App应用程序,让后搜索
电脑商品信息。 -
在抓包工具中获取url地址:
http://api.m.jd.com/client.action?functionId=search -
抓取信息格式为json格式。具体如下图所示
![img]()
-
③ 代码编写:
import json
#import pymongo
from mitmproxy import ctx
#连接MongoDB数据库jddb,选择集合shop
#client = pymongo.MongoClient('localhost')
#db = client['jddb']
#collection = db['shop']
def response(flow):
#global collection
url = 'http://api.m.jd.com/client.action?functionId=search'
if flow.request.url.startswith(url):
text = flow.response.text
data = json.loads(text)
shops = data.get('wareInfo')
for shop in shops:
item = {
'spuId': shop.get('spuId'),
'wname': shop.get('wname'),
'price': shop.get('jdPrice'),
'reviews': shop.get('reviews')
}
ctx.log.info(str(item))
#写入MongoDB数据库
#collection.insert(data)
- 测试运行:
mitmdump -s script.py

21. 从API爬取天气预报数据
21.1 注册免费API和阅读文档
- 本节通过一个API接口(和风天气预报)爬取天气信息,该接口为个人开发者提供了一个免费的预报数据(有次数限制)。
- 首先访问和风天气网,注册一个账户。注册地址:https://console.heweather.com/

- 在登陆后的控制台中可以看到个人认证的key(密钥),这个key就是访问API接口的钥匙。

- 获取key之后阅读API文档:https://www.heweather.com/documents/api/s6

21.2 提取全国城市信息
- 通过API接口提取
3181个城市信息。URL地址:https://cdn.heweather.com/china-city-list.txt

# 从网上读取城市列表信息,并使用正则将数据解析出来。
import requests
import re
# 爬取城市信息列表
url = "https://cdn.heweather.com/china-city-list.txt"
res = requests.get(url)
data = res.content.decode('utf-8')
# 使用换行符拆分出每一条城市信息数据
dlist = re.split('[\n\r]+',data)
# 剔除前两条无用的数据
for i in range(2):
dlist.remove(dlist[0])
# 输出城市信息条数
print(len(dlist))
# 输出前20条信息
for i in range(20):
#使用空白符拆分出每个字段信息
item = re.split("\s+",dlist[i])
#输出
#print(item)
print(item[0],":",item[2])
21.3 获取指定城市的天气信息
- 此文档:https://www.heweather.com/documents/api/s6/weather
- 免费获取天气信息接口地址:https://free-api.heweather.com/s6/weather?location=城市&key=用户认证key

- 抓取指定城市天气信息
import requests
import time
#爬取指定城市的天气信息
url = "https://free-api.heweather.com/s6/weather?location=北京&key=a46fd5c4f1b54fda9ee71ba6711f09cd"
res = requests.get(url)
time.sleep(2)
#解析json数据
dlist = res.json()
data = dlist['HeWeather6'][0]
#输出部分天气信息
print("城市:",data['basic']['location'])
print("今日:",str(data['daily_forecast'][0]['date']))
print("温度:",data['daily_forecast'][0]['tmp_min'],"~",data['daily_forecast'][0]['tmp_max'])
print(data['daily_forecast'][0]['cond_txt_d']," ~ ",data['daily_forecast'][0]['cond_txt_n'])
print(data['daily_forecast'][0]['wind_dir'],data['daily_forecast'][0]['wind_sc'],'级')
- 输出结果:
城市: 北京
今日: 2018-06-13
温度: 18 ~ 28
雷阵雨 ~ 多云
东北风 1-2 级
21.4 综合实例
- 获取城市信息,并通过信息获取对应的天气信息。
# 从网上读取城市列表信息,并遍历部分城市信息,从API接口中爬取天气信息。
import requests
import re
import time
# 爬取城市信息列表
url = "https://cdn.heweather.com/china-city-list.txt"
res = requests.get(url)
data = res.content.decode('utf-8')
# 使用换行符拆分出每一条城市信息数据
dlist = re.split('[\n\r]+',data)
# 剔除前两条无用的数据
for i in range(2):
dlist.remove(dlist[0])
# 输出城市信息条数
print(len(dlist))
# 输出前10条信息
for i in range(10):
#使用空白符拆分出每个字段信息
item = re.split("\s+",dlist[i])
#输出
#print(item)
#print(item[0],":",item[2])
#爬取指定城市的天气信息
url = "https://free-api.heweather.com/s6/weather?location=%s&key=a46fd5c4f1b54fda9ee71ba6711f09cd"%(item[0])
res = requests.get(url)
time.sleep(2)
#解析json数据
datalist = res.json()
data = datalist['HeWeather6'][0]
#输出部分天气信息
print("城市:",data['basic']['location'])
print("今日:",str(data['daily_forecast'][0]['date']))
print("温度:",data['daily_forecast'][0]['tmp_min'],"~",data['daily_forecast'][0]['tmp_max'])
print(data['daily_forecast'][0]['cond_txt_d']," . ",data['daily_forecast'][0]['cond_txt_n'])
print(data['daily_forecast'][0]['wind_dir'],data['daily_forecast'][0]['wind_sc'],'级')
print("="*70)
- 结果:
3181
城市: 北京
今日: 2018-06-13
温度: 18 ~ 28
雷阵雨 . 多云
东北风 1-2 级
======================================================================
城市: 海淀
今日: 2018-06-14
温度: 18 ~ 30
多云 . 多云
南风 1-2 级
======================================================================
城市: 朝阳
... ...
22. 滑动验证码的识别
22.1 滑动验证码的识别介绍
- 本节目标:用程序识别极验滑动验证码的验证,包括分析识别思路、识别缺口位置、生成滑块拖动路径、模拟实现滑块拼合通过验证等步骤。
- 准备工作:本次案例我们使用Python库是Selenium,浏览器为Chrome。请确保已安装Selenium库和ChromeDriver浏览器驱动。
- 了解极验滑动验证码:
- 极验滑动验证码官网为:http://www.geetest.com/
- 验证方式为拖动滑块拼合图像,若图像完全拼合,则验证成功,否则需要重新验证,如图所示:

- 接下来我们链接地址:
https://account.geetest.com/login,打开极验的管理后台登录页面,完成自动化登录操作。
22.2 实现步骤:
① 初始化
- 初始化链接地址、创建模拟浏览器对象、设置登录账户和密码等信息。
EMAIL = '登录账户'
PASSWORD = '登录密码'
class CrackGeetest():
def __init__(self):
self.url = 'https://account.geetest.com/login'
self.browser = webdriver.Chrome()
#设置显示等待时间
self.wait = WebDriverWait(self.browser, 20)
self.email = EMAIL
self.password = PASSWORD
def crack():
pass
# 程序主入口
if __name__ == '__main__':
crack = CrackGeetest()
crack.crack()
② 模拟登录填写,点开滑块验证
- 在实例化CrackGeetest对象后调用crack()方法开始模拟登录验证...
- 调用open()方法,打开登录界面,获取账户和密码输入框节点,完成账户和密码的输入。
- 调用get_geetest_button()方法获取滑动验证按钮,并点击。
class CrackGeetest():
#...
def get_geetest_button(self):
''' 获取初始验证按钮,return:按钮对象 '''
button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
return button
def open(self):
''' 打开网页输入用户名密码, return: None '''
self.browser.get(self.url)
email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
email.send_keys(self.email)
password.send_keys(self.password)
def crack(self):
# 输入用户名密码
self.open()
# 点击验证按钮
button = self.get_geetest_button()
button.click()
#...
#...
③ 获取并储存有无缺口的两张图片
- 首先获取无缺口的验证图片,并保存到本地
- 获取滑块对象,并执行点击,让浏览器中显示有缺口图片
- 获取有缺口的验证图片,并保存到本地
def get_position(self):
''' 获取验证码位置, return: 验证码位置(元组) '''
img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
time.sleep(2)
location = img.location
size = img.size
top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']
return (top, bottom, left, right)
def get_screenshot(self):
''' 获取网页截图, return: 截图对象 '''
#浏览器截屏
screenshot = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
return screenshot
def get_geetest_image(self, name='captcha.png'):
''' 获取验证码图片, return: 图片对象 '''
top, bottom, left, right = self.get_position()
print('验证码位置', top, bottom, left, right)
screenshot = self.get_screenshot()
#从网页截屏图片中裁剪处理验证图片
captcha = screenshot.crop((left, top, right, bottom))
captcha.save(name)
return captcha
def get_slider(self):
''' 获取滑块, return: 滑块对象 '''
slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
return slider
def crack(self):
#...
# 获取验证码图片
image1 = self.get_geetest_image('captcha1.png')
# 点按呼出缺口
slider = self.get_slider()
slider.click()
# 获取带缺口的验证码图片
image2 = self.get_geetest_image('captcha2.png')
#...
④ 获取缺口位置
- 对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离
BORDER = 6
INIT_LEFT = 60
class CrackGeetest():
def get_gap(self, image1, image2):
''' 获取缺口偏移量, 参数:image1不带缺口图片、image2带缺口图片。返回偏移量 '''
left = 65
for i in range(left, image1.size[0]):
for j in range(image1.size[1]):
if not self.is_pixel_equal(image1, image2, i, j):
left = i
return left
return left
def is_pixel_equal(self, image1, image2, x, y):
'''
判断两个像素是否相同
:param image1: 图片1
:param image2: 图片2
:param x: 位置x
:param y: 位置y
:return: 像素是否相同
'''
# 取两个图片的像素点(R、G、B)
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 60
if abs(pixel1[0]-pixel2[0])<threshold and abs(pixel1[1]-pixel2[1])<threshold and abs(pixel1[2]-pixel2[2])<threshold:
return True
else:
return False
def crack(self):
#...
# 获取缺口位置
gap = self.get_gap(image1, image2)
print('缺口位置', gap)
# 减去缺口位移
gap -= BORDER
⑤ 获取移动轨迹
- 模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹
def get_track(self, distance):
'''
根据偏移量获取移动轨迹
:param distance: 偏移量
:return: 移动轨迹
'''
# 移动轨迹
track = []
# 当前位移
current = 0
# 减速阈值
mid = distance * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < distance:
if current < mid:
# 加速度为正2
a = 2
else:
# 加速度为负3
a = -3
# 初速度v0
v0 = v
# 当前速度v = v0 + at
v = v0 + a * t
# 移动距离x = v0t + 1/2 * a * t^2
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
def crack(self):
#...
# 获取移动轨迹
track = self.get_track(gap)
print('滑动轨迹', track)
⑥ 按照轨迹拖动,完全验证
def move_to_gap(self, slider, track):
'''
拖动滑块到缺口处
:param slider: 滑块
:param track: 轨迹
:return:
'''
ActionChains(self.browser).click_and_hold(slider).perform()
for x in track:
ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5)
ActionChains(self.browser).release().perform()
def crack(self):
#...
# 拖动滑块
self.move_to_gap(slider, track)
success = self.wait.until(
EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
print(success)
⑦ 完成登录
def login(self):
''' 执行登录 return: None '''
submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
submit.click()
time.sleep(10)
print('登录成功')
def crack(self):
#...
# 失败后重试
if not success:
self.crack()
else:
self.login()
22.3 完整代码:
import time
from io import BytesIO
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
EMAIL = '122794105@qq.com'
PASSWORD = 'python123'
BORDER = 6
class CrackGeetest():
def __init__(self):
self.url = 'https://account.geetest.com/login'
self.browser = webdriver.Chrome()
#设置显示等待时间
self.wait = WebDriverWait(self.browser, 20)
self.email = EMAIL
self.password = PASSWORD
def __del__(self):
#self.browser.close()
pass
def get_geetest_button(self):
''' 获取初始验证按钮,return:按钮对象 '''
button = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
return button
def get_position(self):
''' 获取验证码位置, return: 验证码位置(元组) '''
img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
time.sleep(2)
location = img.location
size = img.size
top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']
return (top, bottom, left, right)
def get_screenshot(self):
''' 获取网页截图, return: 截图对象 '''
#浏览器截屏
screenshot = self.browser.get_screenshot_as_png()
screenshot = Image.open(BytesIO(screenshot))
return screenshot
def get_slider(self):
''' 获取滑块, return: 滑块对象 '''
slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
return slider
def get_geetest_image(self, name='captcha.png'):
''' 获取验证码图片, return: 图片对象 '''
top, bottom, left, right = self.get_position()
print('验证码位置', top, bottom, left, right)
screenshot = self.get_screenshot()
#从网页截屏图片中裁剪处理验证图片
captcha = screenshot.crop((left, top, right, bottom))
captcha.save(name)
return captcha
def open(self):
''' 打开网页输入用户名密码, return: None '''
self.browser.get(self.url)
email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
email.send_keys(self.email)
password.send_keys(self.password)
def get_gap(self, image1, image2):
''' 获取缺口偏移量, 参数:image1不带缺口图片、image2带缺口图片。返回偏移量 '''
left = 65
for i in range(left, image1.size[0]):
for j in range(image1.size[1]):
if not self.is_pixel_equal(image1, image2, i, j):
left = i
return left
return left
def is_pixel_equal(self, image1, image2, x, y):
'''
判断两个像素是否相同
:param image1: 图片1
:param image2: 图片2
:param x: 位置x
:param y: 位置y
:return: 像素是否相同
'''
# 取两个图片的像素点(R、G、B)
pixel1 = image1.load()[x, y]
pixel2 = image2.load()[x, y]
threshold = 60
if abs(pixel1[0]-pixel2[0])<threshold and abs(pixel1[1]-pixel2[1])<threshold and abs(pixel1[2]-pixel2[2])<threshold:
return True
else:
return False
def get_track(self, distance):
'''
根据偏移量获取移动轨迹
:param distance: 偏移量
:return: 移动轨迹
'''
# 移动轨迹
track = []
# 当前位移
current = 0
# 减速阈值
mid = distance * 4 / 5
# 计算间隔
t = 0.2
# 初速度
v = 0
while current < distance:
if current < mid:
# 加速度为正2
a = 2
else:
# 加速度为负3
a = -3
# 初速度v0
v0 = v
# 当前速度v = v0 + at
v = v0 + a * t
# 移动距离x = v0t + 1/2 * a * t^2
move = v0 * t + 1 / 2 * a * t * t
# 当前位移
current += move
# 加入轨迹
track.append(round(move))
return track
def move_to_gap(self, slider, track):
'''
拖动滑块到缺口处
:param slider: 滑块
:param track: 轨迹
:return:
'''
ActionChains(self.browser).click_and_hold(slider).perform()
for x in track:
ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()
time.sleep(0.5)
ActionChains(self.browser).release().perform()
def login(self):
''' 执行登录 return: None '''
submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'login-btn')))
submit.click()
time.sleep(10)
print('登录成功')
def crack(self):
# 输入用户名密码
self.open()
# 点击验证按钮
button = self.get_geetest_button()
button.click()
# 获取验证码图片
image1 = self.get_geetest_image('captcha1.png')
# 点按呼出缺口
slider = self.get_slider()
slider.click()
# 获取带缺口的验证码图片
image2 = self.get_geetest_image('captcha2.png')
# 获取缺口位置
gap = self.get_gap(image1, image2)
print('缺口位置', gap)
# 减去缺口位移
gap -= BORDER
# 获取移动轨迹
track = self.get_track(gap)
print('滑动轨迹', track)
# 拖动滑块
self.move_to_gap(slider, track)
success = self.wait.until(
EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
print(success)
# 失败后重试
if not success:
self.crack()
else:
self.login()
# 程序主入口
if __name__ == '__main__':
crack = CrackGeetest()
crack.crack()
23. 爬虫项目需求分析
1 项目名称
- 《豆瓣读书信息爬取项目》
2 项目描述:
-
使用Python编程语言编写一个网络爬虫项目,将豆瓣读书网站上的所有图书信息爬取下来,并存储到MySQL数据库中。
-
爬取信息字段要求:
[ID号、书名、作者、出版社、原作名、译者、出版年、页数、定价、装帧、丛书、ISBN、评分、评论人数]![img]()
3 爬取网站过程分析:
- 打开豆瓣读书的首页:https://book.douban.com/

- 在豆瓣读书首页的右侧点击
所有热门标签,打开豆瓣图书标签页面: - 网址:https://book.douban.com/tag/?view=type&icn=index-sorttags-all

- 点击
豆瓣图书标签页面中所有的标签,进行对应标签下图书信息的列表页展示。

- 在豆瓣图书列表页中可以获取每本圖片詳情信息。

4 运行环境要求:
- 运行环境描述:
- 操作系统:Windows/Linux/Mac
- python语言3.5以上版本
- MySQL数据库
- Redis数据库
- Scrapy框架
- Scrapy-Redis
- 还有其他各种驱动组件,使用pip命令安装
5 项目中的建议:
- 本次项目信息爬取量大,建议使用分布式信息爬取。
- 访问时的错误:
检测到有异常请求从你的 IP 发出,请`登录`使用豆瓣。
最近需要爬取豆瓣的用户评分数据构造一个数据集,但是在爬取时却出了问题:
豆瓣封IP,白天一分钟可以访问40次,晚上一分钟可以访问60次,超过限制次数就会封IP。
于是,我便去代理IP网站上找了几个代理IP,但是爬取时又碰到了问题,明明已经使用代理IP,但是一旦超过限制次数爬虫仍然不能正常访问豆瓣。
问题出在Cookie上
豆瓣利用封IP+封Cookie来限制爬虫,因此只用代理IP的话也不行,Cookie也要更换。
想法一:
每次使用代理IP时,先访问豆瓣官网获取Cookie再访问用户的评论页面。本以为换了IP,Cookie随之也会更换,其实Cookie并没有改变。
想法二:
伪造Cookie。
观察豆瓣设置的Cookie格式,并进行伪造。
24. 爬虫项目架构设计
1. 数据库设计:
- 为了方便后续的数据处理,将所有图书信息都汇总的一张数据表中。
- 创建数据库:
doubandb - 进入数据库创建数据表:
books - 表中字段:
[
ID号、书名、作者、出版社、原作名、译者、出版年、页数、
定价、装帧、丛书、ISBN、评分、评论人数
]
- 数据表结构:
CREATE TABLE `books` (
`id` bigint(20) unsigned NOT NULL COMMENT 'ID号',
`title` varchar(255) DEFAULT NULL COMMENT '书名',
`author` varchar(64) DEFAULT NULL COMMENT '作者',
`press` varchar(255) DEFAULT NULL COMMENT '出版社',
`original` varchar(255) DEFAULT NULL COMMENT '原作名',
`translator` varchar(128) DEFAULT NULL COMMENT '译者',
`imprint` varchar(128) DEFAULT NULL COMMENT '出版年',
`pages` int(10) unsigned DEFAULT NULL COMMENT '页数',
`price` double(6,2) unsigned DEFAULT NULL COMMENT '定价',
`binding` varchar(32) DEFAULT NULL COMMENT '装帧',
`series` varchar(128) DEFAULT NULL COMMENT '丛书',
`isbn` varchar(128) DEFAULT NULL COMMENT 'ISBN',
`score` varchar(128) DEFAULT NULL COMMENT '评分',
`number` int(10) unsigned DEFAULT NULL COMMENT '评论人数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2. 项目结构:
- 本次项目设计分为四个模块,如下图所示:

- 说明:
- 模块一:实现豆瓣图书信息所有标签信息的爬取,并图书的标签信息写入到Redis数据库中,此模块可使用rquests简单实现。
- 模块二:负责从Redis中获取每个图书标签,并分页式的爬取每本图书的url信息,并将信息写入到redis中。
- 模块三:负责从Redis中获取每个图书的url地址,并爬取对应的图书详情,将每本图书详情信息写回到redis数据库中。
- 模块四:负责从Redis中获取每本图书的详情信息,并将信息依次写入到MySQL数据中,作为最终的爬取信息。
- 本次项目结构采用Scrapy-Redis主从分布式架构:
- 主master负责爬取每本图书的url地址(要去重),并将信息添加到Redis的url队列中(模块二)
- 从slave负责从Redis的url队列中获取每本书的url,并爬取对应的图书信息(过滤掉无用数据)(模块三)。
3. 具体实施描述
4. 项目中的规范:
25. 爬虫项目的代码实现
25.1 数据库的准备:
- 启动MySQL和Redis数据库
- 在MySQL数据库中创建数据库:
doubandb,并进入数据库中创建books数据表
CREATE TABLE `books` (
`id` bigint(20) unsigned NOT NULL COMMENT 'ID号',
`title` varchar(255) DEFAULT NULL COMMENT '书名',
`author` varchar(64) DEFAULT NULL COMMENT '作者',
`press` varchar(255) DEFAULT NULL COMMENT '出版社',
`original` varchar(255) DEFAULT NULL COMMENT '原作名',
`translator` varchar(128) DEFAULT NULL COMMENT '译者',
`imprint` varchar(128) DEFAULT NULL COMMENT '出版年',
`pages` int(10) unsigned DEFAULT NULL COMMENT '页数',
`price` double(6,2) unsigned DEFAULT NULL COMMENT '定价',
`binding` varchar(32) DEFAULT NULL COMMENT '装帧',
`series` varchar(128) DEFAULT NULL COMMENT '丛书',
`isbn` varchar(128) DEFAULT NULL COMMENT 'ISBN',
`score` varchar(128) DEFAULT NULL COMMENT '评分',
`number` int(10) unsigned DEFAULT NULL COMMENT '评论人数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
25.2 模块1的实现:
- 实现豆瓣图书信息所有标签信息的爬取,并图书的标签信息写入到Redis数据库中,此模块可使用rquests简单实现。
- 创建一个独立的python文件:load_tag_url.py 代码如下
#使用requests加pyquery爬取所有豆瓣图书标签信息,并将信息储存于redis中
import requests
from pyquery import PyQuery as pq
import redis
def main():
#使用requests爬取所有豆瓣图书标签信息
url = "https://book.douban.com/tag/?view=type&icn=index-sorttags-all"
res = requests.get(url)
print("status:%d" % res.status_code)
html = res.content.decode('utf-8')
# 使用Pyquery解析HTML文档
#print(html)
doc = pq(html)
#获取网页中所有豆瓣图书标签链接信息
items = doc("table.tagCol tr td a")
# 指定Redis数据库信息
link = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
#遍历封装数据并返回
for a in items.items():
#拼装tag的url地址信息
tag = a.attr.href
#将信息以tag:start_urls写入到Redis中
link.lpush("book:tag_urls",tag)
print("共计写入tag:%d个"%(len(items)))
#主程序入口
if __name__ == '__main__':
main()
- 运行:
python load_tag_url.py
25.3 模块2的实现:
- 此模块负责从Redis中获取每个图书标签,并分页式的爬取每本图书的url信息,并将信息写入到redis中。
- ① 首先在命令行编写下面命令,创建项目master(主)和爬虫文件
scrapy startproject master
cd master
scrapy genspider book book.douban.com
- ② 编辑
master/item.py文件
import scrapy
class MasterItem(scrapy.Item):
# define the fields for your item here like:
url = scrapy.Field()
#pass
- ③ 编辑
master/settings.py文件
...
ROBOTSTXT_OBEY = False
...
#下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:
DOWNLOAD_DELAY = 2
...
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
}
...
ITEM_PIPELINES = {
'master.pipelines.MasterPipeline': 300,
}
...
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去
REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost
REDIS_PORT = 6379
- ④ 编辑
master/spiders/book.py文件
# -*- coding: utf-8 -*-
import scrapy
from master.items import MasterItem
from scrapy import Request
from urllib.parse import quote
import redis,re,time,random
class BookSpider(scrapy.Spider):
name = 'master_book'
allowed_domains = ['book.douban.com']
base_url = 'https://book.douban.com'
def start_requests(self):
''' 从redis中获取,并爬取标签对应的网页信息 '''
r = redis.Redis(host=self.settings.get("REDIS_HOST"), port=self.settings.get("REDIS_PORT"), decode_responses=True)
while r.llen('book:tag_urls'):
tag = r.lpop('book:tag_urls')
url = self.base_url + quote(tag)
yield Request(url=url, callback=self.parse,dont_filter=True)
def parse(self, response):
''' 解析每页的图书详情的url地址信息 '''
print(response.url)
lists = response.css('#subject_list ul li.subject-item a.nbg::attr(href)').extract()
if lists:
for i in lists:
item = MasterItem()
item['url'] = i
yield item
#获取下一页的url地址
next_url = response.css("span.next a::attr(href)").extract_first()
#判断若不是最后一页
if next_url:
url = response.urljoin(next_url)
#构造下一页招聘列表信息的爬取
yield scrapy.Request(url=url,callback=self.parse)
- ⑤ 编辑
master/pipelines.py文件
import redis,re
class MasterPipeline(object):
def __init__(self,host,port):
#连接redis数据库
self.r = redis.Redis(host=host, port=port, decode_responses=True)
@classmethod
def from_crawler(cls,crawler):
'''注入实例化对象(传入参数)'''
return cls(
host = crawler.settings.get("REDIS_HOST"),
port = crawler.settings.get("REDIS_PORT"),
)
def process_item(self, item, spider):
#使用正则判断url地址是否有效,并写入redis。
bookid = re.findall("book.douban.com/subject/([0-9]+)/",item['url'])
if bookid:
if self.r.sadd('books:id',bookid[0]):
self.r.lpush('bookspider:start_urls', item['url'])
else:
self.r.lpush('bookspider:no_urls', item['url'])
- ⑥ 测试运行:
scarpy crawl master_book
25.4 模块3的实现:
- 负责从Redis中获取每个图书的url地址,并爬取对应的图书详情,将每本图书详情信息写回到redis数据库中。
- ① 首先在命令行编写下面命令,创建项目salve(从)和爬虫文件
scrapy startproject salve
cd salve
scrapy genspider book book.douban.com
- ② 编辑
salve/item.py文件
import scrapy
class BookItem(scrapy.Item):
# define the fields for your item here like:
id = scrapy.Field() #ID号
title = scrapy.Field() #书名
author = scrapy.Field() #作者
press = scrapy.Field() #出版社
original = scrapy.Field() #原作名
translator = scrapy.Field()#译者
imprint = scrapy.Field() #出版年
pages = scrapy.Field() #页数
price = scrapy.Field() #定价
binding = scrapy.Field() #装帧
series = scrapy.Field() #丛书
isbn = scrapy.Field() #ISBN
score = scrapy.Field() #评分
number = scrapy.Field() #评论人数
#pass
- ③ 编辑
salve/settings.py文件
BOT_NAME = 'slave'
SPIDER_MODULES = ['slave.spiders']
NEWSPIDER_MODULE = 'slave.spiders'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
...
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
}
...
ITEM_PIPELINES = {
#'slave.pipelines.SlavePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
...
# 指定使用scrapy-redis的去重
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
# 指定使用scrapy-redis的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 在redis中保持scrapy-redis用到的各个队列,从而允许暂停和暂停后恢复,也就是不清理redis queues
SCHEDULER_PERSIST = True
# 指定排序爬取地址时使用的队列,
# 默认的 按优先级排序(Scrapy默认),由sorted set实现的一种非FIFO、LIFO方式。
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.SpiderPriorityQueue'
# REDIS_URL = 'redis://localhost:6379' # 一般情况可以省去
REDIS_HOST = 'localhost' # 也可以根据情况改成 localhost
REDIS_PORT = 6379
- ④ 编辑
salve/spiders/book.py文件
# -*- coding: utf-8 -*-
import scrapy,re
from slave.items import BookItem
from scrapy_redis.spiders import RedisSpider
class BookSpider(RedisSpider):
name = 'slave_book'
#allowed_domains = ['book.douban.com']
#start_urls = ['http://book.douban.com/']
redis_key = "bookspider:start_urls"
def __init__(self, *args, **kwargs):
# Dynamically define the allowed domains list.
domain = kwargs.pop('domain', '')
self.allowed_domains = filter(None, domain.split(','))
super(BookSpider, self).__init__(*args, **kwargs)
def parse(self, response):
print("======================",response.status)
item = BookItem()
vo = response.css("#wrapper")
item['id'] = vo.re_first('id="collect_form_([0-9]+)"') #ID号
item['title'] = vo.css("h1 span::text").extract_first() #书名
#使用正则获取里面的info里面的图书信息
info = vo.css("#info").extract_first()
#print(info)
authors = re.search('<span.*?作者.*?</span>(.*?)<br>',info,re.S).group(1)
item['author'] = "、".join(re.findall('<a.*?>(.*?)</a>',authors,re.S)) #作者
item['press'] = " ".join(re.findall('<span.*?出版社:</span>\s*(.*?)<br>',info)) #出版社
item['original'] = " ".join(re.findall('<span.*?原作名:</span>\s*(.*?)<br>',info)) #原作名
yz = re.search('<span.*?译者.*?</span>(.*?)<br>',info,re.S)
if yz:
item['translator'] = "、".join(re.findall('<a.*?>(.*?)</a>',yz.group(1),re.S)) #译者
else:
item['translator'] = ""
item['imprint'] = re.search('<span.*?出版年:</span>\s*([0-9\-]+)<br>',info).group(1) #出版年
item['pages'] = re.search('<span.*?页数:</span>\s*([0-9]+)<br>',info).group(1) #页数
item['price'] = re.search('<span.*?定价:</span>.*?([0-9\.]+)元?<br>',info).group(1) #定价
item['binding'] = " ".join(re.findall('<span.*?装帧:</span>\s*(.*?)<br>',info,re.S)) #装帧
item['series'] = " ".join(re.findall('<span.*?丛书:</span>.*?<a .*?>(.*?)</a><br>',info,re.S)) #丛书
item['isbn'] = re.search('<span.*?ISBN:</span>\s*([0-9]+)<br>',info).group(1) #ISBN
item['score'] = vo.css("strong.rating_num::text").extract_first().strip() #评分
item['number'] = vo.css("a.rating_people span::text").extract_first() #评论人数
#print(item)
yield item
- ⑤ 编辑
salve/pipelines.py文件
class SlavePipeline(object):
def process_item(self, item, spider):
return item
- ⑥ 测试运行
# 在spider目录下和book.py在一起。
scrapy runspider book.py
25.5 模块4的实现:
- 负责从Redis中获取每本图书的详情信息,并将信息依次写入到MySQL数据中,作为最终的爬取信息。
- 在当前目录下创建一个:item_save.py的独立爬虫文件
#将Redis中的Item信息遍历写入到数据库中
import json
import redis
import pymysql
def main():
# 指定Redis数据库信息
rediscli = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
# 指定MySQL数据库信息
db = pymysql.connect(host="localhost",user="root",password="",db="doubandb",charset="utf8")
#使用cursor()方法创建一个游标对象cursor
cursor = db.cursor()
while True:
# FIFO模式为 blpop,LIFO模式为 brpop,获取键值
source, data = rediscli.blpop(["book:items"])
print(source)
try:
item = json.loads(data)
#组装sql语句
dd = dict(item)
keys = ','.join(dd.keys())
values=','.join(['%s']*len(dd))
sql = "insert into books(%s) values(%s)"%(keys,values)
#指定参数,并执行sql添加
cursor.execute(sql,tuple(dd.values()))
#事务提交
db.commit()
print("写入信息成功:",dd['id'])
except Exception as err:
#事务回滚
db.rollback()
print("SQL执行错误,原因:",err)
#主程序入口
if __name__ == '__main__':
main()
- 使用python命令测试即可
25.6 反爬处理:
- 降低爬取频率
- 浏览器伪装
- IP代理服务的使用
26. 使用web展示爬取信息
26.1 创建项目myweb和应用web
# 创建项目框架myweb
$ django-admin startproject myweb
$ cd myweb
# 在项目中创建一个web应用
$ python3 manage.py startapp web
# 创建模板目录
$ mkdir templates
$ mkdir templates/web
$ cd ..
$ tree myweb
myweb
├── myweb
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── web
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── templates
└── web
26.2 执行数据库连接配置,网站配置
- ① 编辑myweb/web/init.py文件,添加Pymysql的数据库操作支持
import pymysql
pymysql.install_as_MySQLdb()
- ② 编辑myweb/web/settings.py文件,配置数据库连接
...
#配置自己的服务器IP地址
ALLOWED_HOSTS = ['*']
...
#添加自己应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'web',
]
...
# 配置模板路径信息
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
# 数据库连接配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'doubandb',
'USER': 'root',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '3306',
}
...
26.3 定义Model类
- 编辑myweb/web/models.py
from django.db import models
#图书信息模型
class Books(models.Model):
title = models.CharField(max_length=255) #书名
author = models.CharField(max_length=64) #作者
press = models.CharField(max_length=255) #出版社
original = models.CharField(max_length=255)#原作名
translator = models.CharField(max_length=128)#译者
imprint = models.CharField(max_length=128)#出版年
pages = models.IntegerField(default=0)#页数
price = models.FloatField() #定价
binding = models.CharField(max_length=32) #装帧
series = models.CharField(max_length=128) #丛书
isbn = models.CharField(max_length=128) #ISBN
score = models.CharField(max_length=128) #评分
number = models.IntegerField(default=0) #评论人数
class Meta:
db_table = "books" # 更改表名
26.4 URL路由配置:
- 编辑 myweb/myweb/urls.py 根路由配置文件:
from django.conf.urls import url,include
urlpatterns = [
url(r'^',include('web.urls')),
]
- 创建web子路由文件:myweb/web/urls.py 并编写代码如下:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name="index"),
url(r'^/$', views.index, name="index"),
]
26.5 编写视图处理文件
- 编辑视图文件:myweb/web/views.py
from django.shortcuts import render
from django.core.paginator import Paginator
from web.models import Books
# Create your views here.
def index(request):
#获取商品信息查询对象
mod = Books.objects
list = mod.filter()
#执行分页处理
pIndex = int(request.GET.get("p",1))
page = Paginator(list,50) #以50条每页创建分页对象
maxpages = page.num_pages #最大页数
#判断页数是否越界
if pIndex > maxpages:
pIndex = maxpages
if pIndex < 1:
pIndex = 1
list2 = page.page(pIndex) #当前页数据
plist = page.page_range #页码数列表
#封装信息加载模板输出
context = {"booklist":list2,'plist':plist,'pIndex':pIndex,'maxpages':maxpages}
return render(request,"web/index.html",context)
26.5 编写模板输出文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>浏览图书信息</title>
<style type="text/css">
table{font-size:13px;line-height:25px;border-collapse: collapse;}
table,table tr th, table tr td { border:1px solid #dddddd; }
</style>
</head>
<body>
<center>
<h2>浏览图书信息</h2>
<table width="95%">
<tr style="background-color:#ddeedd;">
<th>ID号</th>
<th>标题</th>
<th>作者</th>
<th>出版社</th>
<th>出版年</th>
<th>单价</th>
<th>评分</th>
</tr>
{% for book in booklist %}
<tr>
<td>{{ book.id }}</td>
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.press }}</td>
<td>{{ book.imprint }}</td>
<td>{{ book.price }}</td>
<td>{{ book.score }}</td>
</tr>
{% endfor %}
</table>
<p>
{% for pindex in plist %}
{% if pIndex == pindex %}
{{pindex}}
{% else %}
<a href="{% url 'index'%}?p={{ pindex }}">{{ pindex }}</a>
{% endif %}
{% endfor %}
</center>
</body>
</html>
26.6 启动服务测试:
$ python manage.py runserver
使用浏览器访问测试

26.7 练习:
- 当数据很多时,请问如何实现百度的页面显示效果? 如:上一页 ... 10,11,12,13,14,15,16,17,18 ... 下一页





















浙公网安备 33010602011771号