更新了部分文档和代码

pull/569/merge
jackfrued 2020-06-24 08:10:58 +08:00
parent 3ef372196c
commit 60587f026d
106 changed files with 1130 additions and 2097 deletions

View File

@ -32,7 +32,7 @@
| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
| (?#) | 注释 | | |
| (exp) | 匹配exp并捕获到自动命名的组中 | | |
| (? <name>exp) | 匹配exp并捕获到名为name的组中 | | |
| (?<name>exp) | 匹配exp并捕获到名为name的组中 | | |
| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |

View File

@ -68,7 +68,7 @@
</li>
</ul>
<div>
<input @keydown.enter="addItem()" type="text" id="fname" v-model="fname">
<input @keydown.enter="addItem()" type="text" id="fname" v-model.trim="fname">
<button id="ok" @click="addItem()">确定</button>
</div>
</div>
@ -82,8 +82,8 @@
},
methods: {
addItem() {
if (this.fname.trim().length > 0) {
this.fruits.push(this.fname.trim())
if (this.fname.length > 0) {
this.fruits.push(this.fname)
}
this.fname = ''
},

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -65,7 +65,7 @@
<div class="search">
<!-- type属性是text的input标签代表文本框 可以接收用户输入 -->
<!-- placeholder是文本框的输入提示 -->
<input type="text" placeholder="请输入垃圾名字" v-model="word" @keydown.enter="search()">
<input type="text" placeholder="请输入垃圾名字" v-model.trim="word" @keydown.enter="search()">
<!-- button代表按钮 点击可以开始查询 -->
<button @click="search()">查询</button>
</div>
@ -75,9 +75,9 @@
<div v-for="result in results">
<p>
<!-- img是图像标签 可以用来实现图片-->
<img :src="pictures[result.type]" width="56" :alt="types[result.type]">
<img :src="'images/' + pictures[result.type]" width="56" :alt="types[result.type]">
&nbsp;&nbsp;
<!-- span是跨度标签 代表一个逻辑区域(不分段)-->
<!-- span是跨度标签 代表一个逻辑区域-->
<span>{{ result.name }}</span>
&nbsp;&nbsp;
<span class="pre" v-if="result.aipre == 1">(预测结果)</span>
@ -102,7 +102,7 @@
// 查询垃圾分类的函数
search() {
if (this.word.trim().length > 0) {
let key = '9aeb28ee8858a167c1755f856f830e22'
let key = '9636cec76ee2593ba6b195e5b770b394'
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
fetch(url)
.then(resp => resp.json())

View File

@ -74,6 +74,8 @@ MySQL在过去由于性能高、成本低、可靠性好已经成为最流行
```Shell
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
```

View File

@ -1,5 +1,5 @@
drop database if exists hrs;
create database hrs default charset utf8;
create database hrs default charset utf8mb4;
use hrs;
@ -14,6 +14,8 @@ dloc varchar(20) not null comment '所在地',
primary key (dno)
);
-- alter table tb_dept add constraint pk_dept_dno primary key(dno);
insert into tb_dept values
(10, '会计部', '北京'),
(20, '研发部', '成都'),
@ -29,11 +31,13 @@ mgr int comment '主管编号',
sal int not null comment '员工月薪',
comm int comment '每月补贴',
dno int comment '所在部门编号',
primary key (eno)
primary key (eno),
foreign key (dno) references tb_dept(dno),
foreign key (mgr) references tb_emp(eno)
);
alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
-- alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
-- alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
insert into tb_emp values
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
@ -70,4 +74,4 @@ insert into tb_emp values
-- 查询主管的姓名和职位
-- 查询月薪排名4~6名的员工姓名和月薪
-- 查询月薪排名4~6名的员工排名、姓名和月薪

View File

@ -0,0 +1,339 @@
## Django快速上手
Web开发的早期阶段开发者需要手动编写每个页面例如一个新闻门户网站每天都要修改它的HTML页面随着网站规模和体量的增大这种做法一定是非常糟糕的。为了解决这个问题开发人员想到了用程序来为Web服务器生成动态内容也就是说网页中的动态内容不再通过手动编写而是通过程序自动生成。最早的时候这项技术被称为CGI公共网关接口当然随着时间的推移CGI暴露出的问题也越来越多例如大量重复的样板代码总体性能较为低下等。在时代呼唤新英雄的背景下PHP、ASP、JSP这类Web应用开发技术在上世纪90年代中后期如雨后春笋般涌现。通常我们说的Web应用是指通过浏览器来访问网络资源的应用程序因为浏览器的普及性以及易用性Web应用使用起来方便简单免除了安装和更新应用程序带来的麻烦站在开发者的角度也不用关心用户使用什么样的操作系统甚至不用区分是PC端还是移动端。
### Web应用机制和术语
下图向我们展示了Web应用的工作流程其中涉及到的术语如下表所示。
![](./res/web-application.png)
> 说明:相信有经验的读者会发现,这张图中其实还少了很多东西,例如反向代理服务器、数据库服务器、防火墙等,而且图中的每个节点在实际项目部署时可能是一组节点组成的集群。当然,如果你对这些没有什么概念也不要紧,继续下去就行了,后面会给大家一一讲解的。
| 术语 | 解释 |
| ------------- | ------------------------------------------------------------ |
| **URL/URI** | 统一资源定位符/统一资源标识符,网络资源的唯一标识 |
| **域名** | 与Web服务器地址对应的一个易于记忆的字符串名字 |
| **DNS** | 域名解析服务可以将域名转换成对应的IP地址 |
| **IP地址** | 网络上的主机的身份标识通过IP地址可以区分不同的主机 |
| **HTTP** | 超文本传输协议构建在TCP之上的应用级协议万维网数据通信的基础 |
| **反向代理** | 代理客户端向服务器发出请求,然后将服务器返回的资源返回给客户端 |
| **Web服务器** | 接受HTTP请求然后返回HTML文件、纯文本文件、图像等资源给请求者 |
| **Nginx** | 高性能的Web服务器也可以用作[反向代理](https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86)[负载均衡](https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1) 和 [HTTP缓存](https://zh.wikipedia.org/wiki/HTTP%E7%BC%93%E5%AD%98) |
#### HTTP协议
这里我们先费一些笔墨来说说HTTP这个协议。HTTP超文本传输协议是构建于TCP传输控制协议之上应用级协议它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍设计HTTP最初的目的是为了提供一种发布和接收[HTML](https://zh.wikipedia.org/wiki/HTML)页面的方法也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况大家可以阅读[《HTTP 协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)、[《互联网协议入门》](http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html)系列以及[《图解HTTPS协议》](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)这几篇文章进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal抓包工具WireShark的前身截取的访问百度首页时的HTTP请求和响应的报文协议数据由于Ethereal截取的是经过网络适配器的数据因此可以清晰的看到从物理链路层到应用层的协议数据。
HTTP请求请求行+请求头+空行+[消息体]
![](./res/http-request.png)
HTTP响应响应行+响应头+空行+消息体):
![](./res/http-response.png)
> **说明**这两张图是在2009年9月10日凌晨获得的但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。当然如果没有专业的抓包工具也可以通过浏览器提供的“开发者工具”来查看HTTP请求和响应的数据格式。
### Django概述
Python的Web框架有上百个比它的关键字还要多。所谓Web框架就是用于开发Web服务器端应用的基础设施说得通俗一点就是一系列封装好的模块和工具。事实上即便没有Web框架我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架我们可以化繁为简降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
在上述Python的Web框架中Django无疑是最有代表性的重量级选手开发者可以基于Django快速的开发可靠的Web应用程序因为它减少了Web开发中不必要的开销对常用的设计和开发模式进行了封装并对MVC架构提供了支持Django中称之为MTV架构。MVC是软件系统开发领域中一种放之四海而皆准的架构它将系统中的组件分为模型Model、视图View和控制器Controller三个部分并借此实现模型数据和视图显示的解耦合。由于模型和视图进行了分离所以需要一个中间人将解耦合的模型和视图联系起来扮演这个角色的就是控制器。稍具规模的软件系统都会使用MVC架构或者是从MVC演进出的其他架构Django项目中我们称之为MTVMTV中的M跟MVC中的M没有区别就是代表数据的模型T代表了网页模板显示数据的视图而V代表了视图函数在Django框架中视图函数和Django框架本身一起扮演了MVC中C的角色。
![](./res/mvc.png)
Django框架诞生于2003年它是一个在真正的应用中成长起来的项目由劳伦斯出版集团旗下在线新闻网站的内容管理系统CMS研发团队主要是Adrian Holovaty和Simon Willison开发以比利时的吉普赛爵士吉他手Django Reinhardt来命名。Django框架在2005年夏天作为开源框架发布使用Django框架能用很短的时间构建出功能完备的网站因为它代替程序员完成了那些重复乏味的劳动剩下真正有意义的核心业务给程序员来开发这一点就是对DRYDon't Repeat Yourself理念的最好践行。许多成功的网站和应用都是基于Python语言进行开发的国内比较有代表性的网站包括知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等其中不乏使用了Django框架的产品。
### 快速上手
#### 第一个Django项目
1. 检查Python环境Django 1.11需要Python 2.7或Python 3.4以上的版本Django 2.0需要Python 3.4以上的版本Django 2.1和2.2需要Python 3.5以上的版本Django 3.0需要Python 3.6以上版本。
> **说明**Django框架不同版本所需的Python解释器环境可以在Django官方文档的[FAQ](https://docs.djangoproject.com/zh-hans/3.0/faq/install/#faq-python-version-support)中找到。
可以在macOS的终端中输入下面的命令检查Python解释器版本Windows系统可以在命令行提示符中输入`python --version`。
```Bash
python3 --version
```
也可以在Python的交互式环境中执行下面的代码来查看Python解释器的版本。
```Shell
import sys
sys.version
sys.version_info
```
2. 更新包管理工具并安装Django环境用于创建Django项目
> **说明**在更新这个文档时Django最新的正式版本是3.0.7Django 3.0提供了对ASGI的支持可以实现全双工的异步通信但是目前的使用体验一般所以暂时不推荐大家使用Django 3.0下面我们安装的是Django 2.2.13版本。使用`pip`安装三方库和工具时,可以通过`==`来指定安装的版本。
```Bash
pip3 install -U pip
pip3 install django==2.2.13
```
3. 检查Django环境并使用`django-admin`命令创建Django项目项目名称为hellodjango
```Shell
django-admin --version
django-admin startproject hellodjango
```
4. 用PyCharm打开创建好的Djang项目并为其添加虚拟环境。
![](res/pycharm-django-project.png)
如上图所示PyCharm的项目浏览器中最顶层的文件夹`hellodjango`是Python项目文件夹这个文件夹的名字并不重要Django项目也不关心这个文件夹叫什么名字。该文件夹下有一个同名的文件夹它是Django项目文件夹其中包含了`__init__.py`、`settings.py`、`urls.py`、`wsgi.py`四个文件,与名为`hellodjango`的Django项目文件夹同级的还有一个名为`manage.py` 的文件,这些文件的作用如下所示:
- `hellodjango/__init__.py`空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `hellodjango/settings.py`Django项目的配置文件。
- `hellodjango/urls.py`Django项目的URL映射声明就像是网站的“目录”。
- `hellodjango/wsgi.py`项目运行在WSGI兼容Web服务器上的入口文件。
- `manage.py` 管理Django项目的脚本程序。
> 说明WSGI全称是Web服务器网关接口维基百科上给出的解释是“为Python语言定义的[Web服务器](https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8)和[Web应用程序](https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F)或框架之间的一种简单而通用的接口”。
创建虚拟环境的界面如下图所示。
![pycharm-django-virtual-environment](res/pycharm-django-virtual-environment.png)
5. 安装项目依赖项。
方法一打开PyCharm的终端在终端中通过`pip`命令安装Django项目的依赖项。
> **说明**由于已经基于Python 3解释器环境为项目创建了虚拟环境所以虚拟环境中的`python`命令对应的是Python 3的解释器而`pip`命令对应的是Python 3的包管理工具。
```Shell
pip install django==2.2.13
```
方法二在PyCharm的偏好设置中可以找到项目的解释器环境和已经安装的三方库可以通过点击添加按钮来安装新的依赖项需要提醒大家的是在安装Django依赖项时需要指定版本号否则将默认安装更新本文时最新的3.0.7版本。
![](res/pycharm-install-django.png)
下图展示了Django版本和Python版本的对应关系请大家自行对号入座。
| Django版本 | Python版本 |
| ---------- | ----------------------------------------- |
| 1.8 | 2.7、3.2、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、3.7Django 1.11.17 |
| 2.0 | 3.4、3.5、3.6、3.7 |
| 2.1 | 3.5、3.6、3.7 |
| 2.2 | 3.5、3.6、3.7、3.8Django 2.2.8 |
| 3.0 | 3.6、3.7、3.8 |
6. 启动Django自带的服务器运行项目。
方法一在“Run”菜单选择“Edit Configuration”配置“Django server”运行项目适用于专业版PyCharm
![](res/pycharm-django-server.png)
方法二在“Run”菜单选择“Edit Configuration”配置运行“Python”程序运行项目适用于专业版和社区版PyCharm
![](res/pycharm-python-manage.png)
方法三在PyCharm的终端Terminal中通过命令运行项目适用于专业版和社区版PyCharm
```Shell
python manage.py runserver
```
7. 查看运行效果。
在浏览器中输入`http://127.0.0.1:8000`访问我们的服务器,效果如下图所示。
![](./res/django-index-1.png)
> **说明**
>
> 1. 刚刚启动的Django自带的服务器只能用于开发和测试环境因为这个服务器是纯Python编写的轻量级Web服务器不适合在生产环境中使用。
> 2. 如果修改了代码不需要为了让修改的代码生效而重新启动Django自带的服务器。但是在添加新的项目文件时该服务器不会自动重新加载这个时候就得手动重启服务器。
> 3. 可以在终端中通过`python manage.py help`命令查看Django管理脚本程序可用的命令参数。
> 4. 使用`python manage.py runserver`启动服务器时可以在后面添加参数来指定IP地址和端口号默认情况下启动的服务器将运行在本机的`8000`端口。
> 5. 在终端中运行的服务器可以通过Ctrl+C来停止它 。通过PyCharm的“运行配置”运行的服务器直接点击窗口上的关闭按钮就可以终止服务器的运行。
> 6. 不能在同一个端口上启动多个服务器因为会导致地址的冲突端口是对IP地址的扩展也是计算机网络地址的一部分
8. 修改项目的配置文件`settings.py`。
Django是一个支持国际化和本地化的框架因此刚才我们看到的Django项目的默认首页也是支持国际化的我们可以通过修改配置文件将默认语言修改为中文时区设置为东八区。
找到修改前的配置(在`settings.py`文件第100行以后
```Python
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
```
修改为以下内容。
```Python
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Chongqing'
```
刷新刚才的页面,可以看到修改语言代码和时区之后的结果。
![](./res/django-index-2.png)
#### 创建自己的应用
如果要开发自己的Web应用需要先在Django项目中创建“应用”一个Django项目可以包含一个或多个应用。
1. 在PyCharm的终端中执行下面的命令创建名为`first`的应用。
```Shell
python manage.py startapp first
```
执行上面的命令会在当前路径下创建`first`目录,其目录结构如下所示:
- `__init__.py`一个空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `admin.py`可以用来注册模型用于在Django框架自带的管理后台中管理模型。
- `apps.py`:当前应用的配置文件。
- `migrations`:存放与模型有关的数据库迁移信息。
- `__init__.py`一个空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `models.py`存放应用的数据模型MTV中的M
- `tests.py`:包含测试应用各项功能的测试类和测试函数。
- `views.py`处理用户HTTP请求并返回HTTP响应的函数或类MTV中的V
2. 修改应用目录下的视图文件`views.py`。
```Python
from django.http import HttpResponse
def show_index(request):
return HttpResponse('<h1>Hello, Django!</h1>')
```
4. 修改Django项目目录下的`urls.py`文件,将视图函数和用户在浏览器中请求的路径对应。
```Python
from django.contrib import admin
from django.urls import path, include
from first.views import show_index
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', show_index),
]
```
5. 重新运行项目,并打开浏览器中访问`http://127.0.0.1:8000/hello/`。
5. 上面我们通过代码为浏览器生成了内容,但仍然是静态内容,如果要生成动态内容,可以修改`views.py`文件并添加如下所示的代码。
```Python
from random import sample
from django.http import HttpResponse
def show_index(request):
fruits = [
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
]
selected_fruits = sample(fruits, 3)
content = '<h3>今天推荐的水果是:</h3>'
content += '<hr>'
content += '<ul>'
for fruit in selected_fruits:
content += f'<li>{fruit}</li>'
content += '</ul>'
return HttpResponse(content)
```
6. 刷新页面查看程序的运行结果,看看每次刷新的网页的时候,是不是可以看到不一样的内容。
#### 使用模板
上面通过拼接HTML代码的方式为浏览器生成动态内容的做法在实际开发中是无能接受的因为实际项目中的前端页面可能非常复杂无法用这种拼接动态内容的方式来完成这一点大家一定能够想到。为了解决这个问题我们可以提前准备一个模板页MTV中的T所谓模板页就是一个带占位符和模板指令的HTML页面。
Django框架中有一个名为`render`的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的模板指令和占位符当然这里的渲染称为后端渲染即在服务器端完成页面的渲染再输出到浏览器中。后端渲染的做法在Web应用的访问量较大时会让服务器承受较大的负担所以越来越多的Web应用会选择前端渲染的方式即服务器只提供页面所需的数据通常是JSON格式在浏览器中通过JavaScript代码获取这些数据并渲染页面上。关于前端渲染的内容我们会在后续的课程中为大家讲解目前我们使用的是通过模板页进行后端渲染的做法具体步骤如下所示。
使用模板页的步骤如下所示。
1. 在项目目录下创建名为templates文件夹。
![](res/pycharm-django-template.png)
2. 添加模板页`index.html`。
> **说明**实际项目开发中静态页由前端开发者提供后端开发者需要将静态页修改为模板页以便通过Python程序对其进行渲染这种做法就是上面提到的后端渲染。
```HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
#fruits {
font-size: 1.25em;
}
</style>
</head>
<body>
<h1>今天推荐的水果是:</h1>
<hr>
<ul id="fruits">
{% for fruit in fruits %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
</body>
</html>
```
在上面的模板页中我们使用了`{{ fruit }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令这些都是Django模板语言DTL的一部分。关于模板语法和指令大家可以看看官方文档相信这些内容还是很容易理解的并不需要过多的赘述大家也可以参考[官方文档]()了解模板指令和语法。
3. 修改`views.py`文件,调用`render`函数渲染模板页。
```Python
from random import sample
from django.shortcuts import render
def show_index(request):
fruits = [
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
]
selected_fruits = sample(fruits, 3)
return render(request, 'index.html', {'fruits': selected_fruits})
```
`render`函数的第一个参数是请求对象request第二个参数是我们要渲染的模板页的名字第三个参数是要渲染到页面上的数据我们通过一个字典将数据交给模板页字典中的键就是模板页中使用的模板指令或占位符中的变量名。
4. 到此为止,视图函数中的`render`还无法找到模板文件`index.html`,需要修改`settings.py`文件,配置模板文件所在的路径。修改`settings.py`文件,找到`TEMPLATES`配置,修改其中的`DIRS`配置。
```Python
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',
],
},
},
]
```
5. 重新运行项目或直接刷新页面查看结果。
### 总结
至此我们已经利用Django框架完成了一个非常小的Web应用虽然它并没有任何的实际价值但是可以通过这个项目对Django框架有一个感性的认识。学习Django最好的资料肯定是它的[官方文档](https://docs.djangoproject.com/zh-hans/2.0/)官方文档提供了对多国语言的支持而且有新手教程引导初学者学习使用Django框架建议大家通过阅读Django的官方文档来学习和使用这个框架。当然图灵社区出版的[《Django基础教程》](http://www.ituring.com.cn/book/2630)也是非常适合初学者的入门级读物,有兴趣的读者可以点击链接进行购买。

View File

@ -1,393 +0,0 @@
## 快速上手
Web开发的早期阶段开发者需要手动编写每个页面例如一个新闻门户网站每天都要修改它的HTML页面随着网站规模和体量的增大这种方式就变得极度糟糕。为了解决这个问题开发人员想到了用外部程序来为Web服务器生成动态内容也就是说HTML页面以及页面中的动态内容不再通过手动编写而是通过程序自动生成。最早的时候这项技术被称为CGI公共网关接口当然随着时间的推移CGI暴露出的问题也越来越多例如大量重复的样板代码总体性能较为低下等因此在时代呼唤新英雄的背景下PHP、ASP、JSP这类Web应用开发技术在上世纪90年代中后期如雨后春笋般涌现。通常我们说的Web应用是指通过浏览器来访问网络资源的应用程序因为浏览器的普及性以及易用性Web应用使用起来方便简单免除了安装和更新应用程序带来的麻烦而且也不用关心用户到底用的是什么操作系统甚至不用区分是PC端还是移动端。
### Web应用机制和术语
下图向我们展示了Web应用的工作流程其中涉及到的术语如下表所示。
![](./res/web-application.png)
> 说明:相信有经验的读者会发现,这张图中其实还少了很多东西,例如反向代理服务器、数据库服务器、防火墙等,而且图中的每个节点在实际项目部署时可能是一组节点组成的集群。当然,如果你对这些没有什么概念也不要紧,继续下去就行了,后面会给大家一一讲解的。
| 术语 | 解释 |
| ------------- | ------------------------------------------------------------ |
| **URL/URI** | 统一资源定位符/统一资源标识符,网络资源的唯一标识 |
| **域名** | 与Web服务器地址对应的一个易于记忆的字符串名字 |
| **DNS** | 域名解析服务可以将域名转换成对应的IP地址 |
| **IP地址** | 网络上的主机的身份标识通过IP地址可以区分不同的主机 |
| **HTTP** | 超文本传输协议构建在TCP之上的应用级协议万维网数据通信的基础 |
| **反向代理** | 代理客户端向服务器发出请求,然后将服务器返回的资源返回给客户端 |
| **Web服务器** | 接受HTTP请求然后返回HTML文件、纯文本文件、图像等资源给请求者 |
| **Nginx** | 高性能的Web服务器也可以用作[反向代理](https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86)[负载均衡](https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1) 和 [HTTP缓存](https://zh.wikipedia.org/wiki/HTTP%E7%BC%93%E5%AD%98) |
#### HTTP协议
这里我们稍微费一些笔墨来谈谈上面提到的HTTP。HTTP超文本传输协议是构建于TCP传输控制协议之上应用级协议它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍设计HTTP最初的目的是为了提供一种发布和接收[HTML](https://zh.wikipedia.org/wiki/HTML)页面的方法也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况大家可以阅读阮一峰老师的[《HTTP 协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)、[《互联网协议入门》](http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html)系列以及[《图解HTTPS协议》](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal抓包工具WireShark的前身截取的访问百度首页时的HTTP请求和响应的报文协议数据由于Ethereal截取的是经过网络适配器的数据因此可以清晰的看到从物理链路层到应用层的协议数据。
HTTP请求请求行+请求头+空行+[消息体]
![](./res/http-request.png)
HTTP响应响应行+响应头+空行+消息体):
![](./res/http-response.png)
> 说明这两张图是在2009年9月10日凌晨获得的但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。
### Django概述
Python的Web框架有上百个比它的关键字还要多。所谓Web框架就是用于开发Web服务器端应用的基础设施说得通俗一点就是一系列封装好的模块和工具。事实上即便没有Web框架我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架我们可以化繁为简降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
在上述Python的Web框架中Django无疑是最有代表性的重量级选手开发者可以基于Django快速的开发可靠的Web应用程序因为它减少了Web开发中不必要的开销对常用的设计和开发模式进行了封装并对MVC架构提供了支持Django中称之为MTV架构。许多成功的网站和应用都是基于Django框架构建的国内比较有代表性的网站包括知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等。
![](./res/mvc.png)
Django诞生于2003年它是一个在真正的应用中成长起来的项目由劳伦斯出版集团旗下在线新闻网站的内容管理系统CMS研发团队编写主要是Adrian Holovaty和Simon Willison以比利时的吉普赛爵士吉他手Django Reinhardt来命名在2005年夏天作为开源框架发布。使用Django能用很短的时间构建出功能完备的网站因为它代替程序员完成了所有乏味和重复的劳动剩下真正有意义的核心业务给程序员这一点就是对DRYDon't Repeat Yourself理念的最好践行。
### 快速上手
#### 准备工作
1. 检查Python环境Django 1.11需要Python 2.7或Python 3.4以上的版本Django 2.0需要Python 3.4以上的版本Django 2.1需要Python 3.5以上的版本。
> 说明我自己平时使用macOS和Linux系统做开发macOS和Linux系统在命令的使用上跟Windows系统还是有一些差别如果使用Windows平台做开发要使用Windows平台对应的命令。
```Shell
$ python3 --version
```
```Shell
$ python3
>>> import sys
>>> sys.version
>>> sys.version_info
```
2. 更新包管理工具并安装Django管理工具。
```Shell
$ pip3 install -U pip
$ pip3 install django
```
3. 使用Django管理工具创建Django项目项目名称为hellodjango
```Shell
$ django-admin startproject hellodjango
```
> 说明上面使用了Python自带的venv模块完成了虚拟环境的创建当然也可以使用virtualenv或pipenv这样的工具。要激活虚拟环境在Windows环境下可以通过"venv/Scripts/activate"执行批处理文件来实现。
4. 进入项目文件夹,创建并激活虚拟环境。
```Shell
$ cd hellodjango
$ python3 -m venv venv
$ source venv/bin/activate
```
> **提示**上面使用了Python 3自带的`venv`模块来创建虚拟环境,当然也可以使用如`virtualenv`这样的三方工具来创建虚拟环境激活虚拟环境后请注意终端中提示符的变化在虚拟环境下使用Python解释器和包管理工具时对应的命令是`python`和`pip`,而不再需要键入`python3`和`pip3`。
5. 在虚拟环境中安装项目依赖项。
```Shell
(venv)$ pip install django mysqlclient django-redis pillow requests
```
> **提示**:使用`pip`安装三方库时,可以通过如`django==1.11.27`的方式来指定三方库的版本。
下图展示了Django版本和Python版本的对应关系如果在安装时没有指定版本号将自动选择最新的版本在写作这段内容时Django最新的版本是2.2)。
| Django版本 | Python版本 |
| ---------- | ----------------------- |
| 1.8 | 2.7、3.2、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、3.7 |
| 2.0 | 3.4、3.5、3.6、3.7 |
| 2.1、2.2 | 3.5、3.6、3.7 |
刚才创建的Django项目其文件和文件夹如下所示
- `manage.py` 一个让你可以管理Django项目的工具程序。
- `hellodjango/__init__.py`一个空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `hellodjango/settings.py`Django项目的配置文件。
- `hellodjango/urls.py`Django项目的URL声明URL映射就像是你的网站的“目录”。
- `hellodjango/wsgi.py`项目运行在WSGI兼容Web服务器上的接口文件。
> 说明WSGI全称是Web服务器网关接口维基百科上给出的解释是“为Python语言定义的[Web服务器](https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8)和[Web应用程序](https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F)或框架之间的一种简单而通用的接口”。
6. 启动Django自带的服务器运行项目。
```Shell
(venv)$ python manage.py runserver
```
在浏览器中输入<http://127.0.0.1:8000>访问我们的服务器,效果如下图所示。
> **说明1**刚刚启动的是Django自带的用于开发和测试的服务器它是一个用纯Python编写的轻量级Web服务器但它并不是真正意义上的生产级别的服务器千万不要将这个服务器用于和生产环境相关的任何地方。
>
> **说明2**用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而一些动作比如添加新文件将不会触发自动重新加载这时你得自己手动重启服务器。
>
> **说明3**:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定将服务器运行于哪个IP地址和端口。
>
> **说明4**可以通过Ctrl+C来终止服务器的运行。
![](./res/django-index-1.png)
7. 修改项目的配置文件settings.pyDjango是一个支持国际化和本地化的框架因此刚才我们看到的默认首页也是支持国际化的我们将默认语言修改为中文时区设置为东八区。
```Shell
(venv)$ vim hellodjango/settings.py
```
```Python
# 此处省略上面的内容
# 设置语言代码
LANGUAGE_CODE = 'zh-hans'
# 设置时区
TIME_ZONE = 'Asia/Chongqing'
# 此处省略下面的内容
```
刷新刚才的页面,可以看到修改语言代码和时区之后的结果。
![](./res/django-index-2.png)
#### 动态页面
1. 创建名为hrs人力资源系统的应用一个Django项目可以包含一个或多个应用。
```Shell
(venv)$ python manage.py startapp hrs
```
执行上面的命令会在当前路径下创建hrs目录其目录结构如下所示
- `__init__.py`一个空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `admin.py`可以用来注册模型用于在Django的管理界面管理模型。
- `apps.py`:当前应用的配置文件。
- `migrations`:存放与模型有关的数据库迁移信息。
- `__init__.py`一个空文件告诉Python解释器这个目录应该被视为一个Python的包。
- `models.py`存放应用的数据模型即实体类及其之间的关系MVC/MTV中的M
- `tests.py`:包含测试应用各项功能的测试类和测试函数。
- `views.py`处理请求并返回响应的函数MVC中的CMTV中的V
2. 修改应用目录下的视图文件views.py。
```Shell
(venv)$ vim hrs/views.py
```
```Python
from django.http import HttpResponse
def index(request):
return HttpResponse('<h1>Hello, Django!</h1>')
```
3. 在应用目录创建一个urls.py文件并映射URL。
```Shell
(venv)$ touch hrs/urls.py
(venv)$ vim hrs/urls.py
```
```Python
from django.urls import path
from hrs import views
urlpatterns = [
path('', views.index, name='index'),
]
```
> 说明:上面使用的`path`函数是Django 2.x中新添加的函数除此之外还可以使用支持正则表达式的URL映射函数`re_path`函数Django 1.x中是用名为`url`函数来设定URL映射。
4. 修改项目目录下的urls.py文件对应用中设定的URL进行合并。
```Shell
(venv) $ vim oa/urls.py
```
```Python
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('hrs/', include('hrs.urls')),
]
```
> 说明:上面的代码通过`include`函数将hrs应用中配置URL的文件包含到项目的URL配置中并映射到`hrs/`路径下。
5. 重新运行项目,并打开浏览器中访问<http://localhost:8000/hrs>
```Shell
(venv)$ python manage.py runserver
```
6. 修改views.py生成动态内容。
```Shell
(venv)$ vim hrs/views.py
```
```Python
from io import StringIO
from django.http import HttpResponse
depts_list = [
{'no': 10, 'name': '财务部', 'location': '北京'},
{'no': 20, 'name': '研发部', 'location': '成都'},
{'no': 30, 'name': '销售部', 'location': '上海'},
]
def index(request):
output = StringIO()
output.write('<html>\n')
output.write('<head>\n')
output.write('\t<meta charset="utf-8">\n')
output.write('\t<title>首页</title>')
output.write('</head>\n')
output.write('<body>\n')
output.write('\t<h1>部门信息</h1>\n')
output.write('\t<hr>\n')
output.write('\t<table>\n')
output.write('\t\t<tr>\n')
output.write('\t\t\t<th width=120>部门编号</th>\n')
output.write('\t\t\t<th width=180>部门名称</th>\n')
output.write('\t\t\t<th width=180>所在地</th>\n')
output.write('\t\t</tr>\n')
for dept in depts_list:
output.write('\t\t<tr>\n')
output.write(f'\t\t\t<td align=center>{dept["no"]}</td>\n')
output.write(f'\t\t\t<td align=center>{dept["name"]}</td>\n')
output.write(f'\t\t\t<td align=center>{dept["location"]}</td>\n')
output.write('\t\t</tr>\n')
output.write('\t</table>\n')
output.write('</body>\n')
output.write('</html>\n')
return HttpResponse(output.getvalue())
```
7. 刷新页面查看程序的运行结果。
![](./res/show-depts.png)
#### 使用视图模板
上面通过拼接HTML代码的方式生成动态视图的做法在实际开发中是无能接受的这一点大家一定能够想到。为了解决这个问题我们可以提前准备一个模板页所谓模板页就是一个带占位符的HTML页面当我们将程序中获得的数据替换掉页面中的占位符时一个动态页面就产生了。
我们可以用Django框架中template模块的Template类创建模板对象通过模板对象的render方法实现对模板的渲染在Django框架中还有一个名为`render`的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的占位符当然这里的渲染称为后端渲染即在服务器端完成页面的渲染再输出到浏览器中这种做法的主要坏处是当并发访问量较大时服务器会承受较大的负担所以今天有很多的Web应用都使用了前端渲染即服务器只提供所需的数据通常是JSON格式在浏览器中通过JavaScript获取这些数据并渲染到页面上这个我们在后面的内容中会讲到。
1. 先回到manage.py文件所在的目录创建名为templates文件夹。
```Shell
(venv)$ mkdir templates
```
2. 创建模板页index.html。
```Shell
(venv)$ touch templates/index.html
(venv)$ vim templates/index.html
```
```HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>部门信息</h1>
<hr>
<table>
<tr>
<th>部门编号</th>
<th>部门名称</th>
<th>所在地</th>
</tr>
{% for dept in depts_list %}
<tr>
<td>{{ dept.no }}</td>
<td>{{ dept.name }}</td>
<td>{{ dept.location }}</td>
<tr>
{% endfor %}
</table>
</body>
</html>
```
在上面的模板页中我们使用了`{{ greeting }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令这些都是Django模板语言DTL的一部分。如果对此不熟悉并不要紧我们会在后续的内容中进一步的讲解而且我们刚才也说到了渲染页面还有更好的选择就是使用前端渲染当然这是后话。
3. 回到应用目录修改views.py文件。
```Shell
(venv)$ vim hrs/views.py
```
```Python
from django.shortcuts import render
depts_list = [
{'no': 10, 'name': '财务部', 'location': '北京'},
{'no': 20, 'name': '研发部', 'location': '成都'},
{'no': 30, 'name': '销售部', 'location': '上海'},
]
def index(request):
return render(request, 'index.html', {'depts_list': depts_list})
```
> 说明Django框架通过shortcuts模块的便捷函数`render`简化了渲染模板的操作,有了这个函数,就不用先创建`Template`对象再去调用`render`方法。。
到此为止我们还没有办法让views.py中的`render`函数找到模板文件index.html为此我们需要修改settings.py文件配置模板文件所在的路径。
4. 切换到项目目录修改settings.py文件。
```Shell
(venv)$ vim oa/settings.py
```
```Python
# 此处省略上面的内容
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',
],
},
},
]
# 此处省略下面的内容
```
5. 重新运行项目或直接刷新页面查看结果。
```Shell
(venv)$ python manage.py runserver
```
### 总结
至此我们已经利用Django框架完成了一个非常小的Web应用虽然它并没有任何的实际价值但是可以通过这个项目对Django框架有一个感性的认识。当然实际开发中我们可以用PyCharm来创建项目如果使用专业版的PyCharm可以直接创建Django项目。使用PyCharm的好处在于编写代码时可以获得代码提示、错误修复、自动导入等功能从而提升开发效率但是专业版的PyCharm需要按年支付相应的费用社区版的PyCharm中并未包含对Django框架直接的支持但是我们仍然可以使用它来创建Django项目只是在使用上没有专业版的方便。关于PyCharm的使用可以参考[《玩转PyCharm》](../玩转PyCharm.md)一文。此外Django最好的学习资料肯定是它的[官方文档](https://docs.djangoproject.com/zh-hans/2.0/),当然图灵社区出版的[《Django基础教程》](http://www.ituring.com.cn/book/2630)也是非常适合初学者的入门级读物。

View File

@ -1,20 +1,76 @@
## 深入模型
在上一个章节中我们提到了Django是基于MVC架构的Web框架MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据的表示所以通常也被称作“数据模型”。在实际的项目中数据模型通常通过数据库实现持久化操作而关系型数据库在过去和当下都是持久化的首选方案下面我们以MySQL为例来说明如何使用关系型数据库来实现持久化操作。
在上一个章节中我们提到了Django是基于MVC架构的Web框架MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据的表示所以通常也被称作“数据模型”。在实际的项目中数据模型通常通过数据库实现持久化操作而关系型数据库在过去和当下都是持久化的首选方案下面我们通过完成一个投票项目来讲解和模型相关的知识点。投票项目的首页会展示某在线教育平台所有的学科点击学科可以查看到该学科的老师及其信息用户登录后在查看老师的页面为老师投票可以投赞成票和反对票未登录的用户可以通过登录页进行登录尚未注册的用户可以通过注册页输入个人信息进行注册。在这个项目中我们使用MySQL数据库来实现数据持久化操作。
### 创建项目和应用
我们首先创建Django项目`vote`并为其添加虚拟环境和依赖项。接下来,在项目下创建名为`polls`的应用和保存模板页的文件夹`tempaltes`,项目文件夹的结构如下所示。
![](res/pycharm-vote-project.png)
根据上面描述的项目需求,我们准备了四个静态页面,分别是展示学科的页面`subjects.html`,显示学科老师的页面`teachers.html`,登录页面`login.html`,注册页面`register.html`稍后我们会将静态页修改为Django项目所需的模板页。
### 配置关系型数据库MySQL
我们继续来完善上一个章节中的OA项目首先从配置项目使用的数据库开始。
1. 在MySQL中创建数据库创建用户授权用户访问该数据库
1. 修改项目的settings.py文件首先将我们之前创建的应用hrs添加已安装的项目中然后配置MySQL作为持久化方案。
```Shell
(venv)$ vim oa/settings.py
```SQL
create database vote default charset utf8;
create user 'hellokitty'@'%' identified by 'Hellokitty.618';
grant all privileges on vote.* to 'hellokitty'@'%';
flush privileges;
```
```Python
# 此处省略上面的代码
2. 在MySQL中创建保存学科和老师信息的二维表保存用户信息的表稍后处理
```SQL
use vote;
-- 创建学科表
create table `tb_subject`
(
`no` integer auto_increment comment '学科编号',
`name` varchar(50) not null comment '学科名称',
`intro` varchar(1000) not null default '' comment '学科介绍',
`is_hot` boolean not null default 0 comment '是不是热门学科',
primary key (`no`)
);
-- 创建老师表
create table `tb_teacher`
(
`no` integer auto_increment comment '老师编号',
`name` varchar(20) not null comment '老师姓名',
`sex` boolean not null default 1 comment '老师性别',
`birth` date not null comment '出生日期',
`intro` varchar(1000) not null default '' comment '老师介绍',
`photo` varchar(255) not null default '' comment '老师照片',
`gcount` integer not null default 0 comment '好评数',
`bcount` integer not null default 0 comment '差评数',
`sno` integer not null comment '所属学科',
primary key (`no`),
foreign key (`sno`) references `tb_subject` (`no`)
);
```
3. 在虚拟环境中安装连接MySQL数据库所需的依赖项。
```Bash
pip install mysqlclient
```
> **说明**:如果因为某些原因无法安装`mysqlclient`三方库,可以使用它的替代品`pymysql``pymysql`是用纯Python开发的连接MySQL的Python库安装更容易成功但是需要在Django项目文件夹的`__init__.py`中添加如下所示的代码。
>
> ```Python
> import pymysql
>
> pymysql.install_as_MySQLdb()
> ```
>
> 如果使用Django 2.2及以上版本还会遇到PyMySQL跟Django框架的兼容性问题兼容性问题会导致项目无法运行需要按照GitHub上PyMySQL仓库[Issues](https://github.com/PyMySQL/PyMySQL/issues/790)中提供的方法进行处理。总体来说,使用`pymysql`会比较麻烦,强烈建议大家首选安装`mysqlclient`。
4. 修改项目的settings.py文件首先将我们创建的应用`polls`添加已安装的项目(`INSTALLED_APPS`然后配置MySQL作为持久化方案。
```Python
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@ -22,55 +78,197 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'hrs',
'polls',
]
DATABASES = {
'default': {
# 数据库引擎配置
'ENGINE': 'django.db.backends.mysql',
'NAME': 'oa',
'HOST': '1.2.3.4',
# 数据库的名字
'NAME': 'vote',
# 数据库服务器的IP地址本机可以写localhost或127.0.0.1
'HOST': 'localhost',
# 启动MySQL服务的端口号
'PORT': 3306,
'USER': 'yourname',
'PASSWORD': 'yourpass',
# 数据库用户名和口令
'USER': 'hellokitty',
'PASSWORD': 'Hellokitty.618',
# 数据库使用的字符集
'CHARSET': 'utf8',
# 数据库时间日期的时区设定
'TIME_ZONE': 'Asia/Chongqing',
}
}
# 此处省略下面的代码
```
在配置ENGINE属性时常用的可选值包括
- `'django.db.backends.sqlite3'`SQLite嵌入式数据库。
- `'django.db.backends.postgresql'`BSD许可证下发行的开源关系型数据库产品。
- `'django.db.backends.mysql'`转手多次目前属于甲骨文公司经济高效的数据库产品。
- `'django.db.backends.oracle'`:甲骨文公司关系型数据库旗舰产品。
- `'django.db.backends.mysql'`:甲骨文公司经济高效的数据库产品。
- `'django.db.backends.oracle'`:甲骨文公司关系型数据库旗舰产品。
其他的配置可以参考官方文档中[数据库配置](https://docs.djangoproject.com/zh-hans/2.0/ref/databases/#third-party-notes)的部分。
NAME属性代表数据库的名称如果使用SQLite它对应着一个文件在这种情况下NAME的属性值应该是一个绝对路径使用其他关系型数据库则要配置对应的HOST主机、PORT端口、USER用户名、PASSWORD口令等属性
5. Django框架提供了ORM来解决数据持久化问题ORM翻译成中文叫“对象关系映射”。因为Python是面向对象的编程语言我们在Python程序中使用对象模型来保存数据而关系型数据库使用关系模型用二维表来保存数据这两种模型并不匹配。使用ORM是为了实现对象模型到关系模型的**双向转换**这样就不用在Python代码中书写SQL语句和游标操作因为这些都会由ORM自动完成。利用Django的ORM我们可以直接将刚才创建的学科表和老师表变成Django中的模型类
2. 安装Python操作MySQL的依赖库Python 3中通常使用PyMySQLPython 2中通常用MySQLdb。
```Shell
(venv)$ pip install pymysql
```Bash
python manage.py inspectdb > polls/models.py
```
如果使用Python 3需要修改**项目目录**下的`__init__.py`文件并加入如下所示的代码这段代码的作用是将PyMySQL视为MySQLdb来使用从而避免Django找不到连接MySQL的客户端工具而询问你“Did you install mysqlclient? ”你安装了mysqlclient吗
我们可以对自动生成的模型类稍作调整,代码如下所示
```Python
import pymysql
from django.db import models
pymysql.install_as_MySQLdb()
class Subject(models.Model):
no = models.AutoField(primary_key=True, verbose_name='编号')
name = models.CharField(max_length=50, verbose_name='名称')
intro = models.CharField(max_length=1000, verbose_name='介绍')
is_hot = models.BooleanField(verbose_name='是否热门')
class Meta:
managed = False
db_table = 'tb_subject'
verbose_name = '学科'
verbose_name_plural = '学科'
class Teacher(models.Model):
no = models.AutoField(primary_key=True, verbose_name='编号')
name = models.CharField(max_length=20, verbose_name='姓名')
sex = models.BooleanField(default=True, verbose_name='性别')
birth = models.DateField(verbose_name='出生日期')
intro = models.CharField(max_length=1000, verbose_name='个人介绍')
photo = models.ImageField(max_length=255, verbose_name='照片')
good_count = models.IntegerField(default=0, db_column='gcount', verbose_name='好评数')
bad_count = models.IntegerField(default=0, db_column='bcount', verbose_name='差评数')
subject = models.ForeignKey(Subject, models.DO_NOTHING, db_column='sno')
class Meta:
managed = False
db_table = 'tb_teacher'
```
3. 如果之前没有为应用程序创建数据库那么现在是时候创建名为oa的数据库了。在MySQL中创建数据库的SQL语句如下所示
> **说明**:模型类都直接或间接继承自`Model`类,模型类跟关系型数据库的二维表对应,模型对象跟表中的记录对应,模型对象的属性跟表中的字段对应。如果对上面模型类的属性定义不是特别理解,可以看看本文后面提供的“模型定义参考”部分的内容。
```SQL
create database oa default charset utf8;
```
### 使用ORM完成模型的CRUD操作
4. Django框架本身有自带的数据模型我们稍后会用到这些模型为此我们先做一次迁移操作。所谓迁移就是根据模型自动生成关系数据库中的二维表命令如下所示
有了Django框架的ORM我们可以直接使用面向对象的方式来实现对数据的CRUD增删改查操作。我们可以在PyCharm的终端中输入下面的命令进入到Django项目的交互式环境然后尝试对模型的操作。
```Bash
python manage.py shell
```
#### 新增
```Python
from polls.models import Subject
subject1 = Subject(name='Python全栈开发', intro='当下最热门的学科', is_hot=True)
subject1.save()
subject2 = Subject(name='全栈软件测试', intro='学习自动化测试的学科', is_hot=False)
subject2.save()
subject3 = Subject(name='JavaEE分布式开发', intro='基于Java语言的服务器应用开发', is_hot=True)
```
#### 删除
```Python
subject = Subject.objects.get(no=2)
subject.delete()
```
#### 更新
```Shell
subject = Subject.objects.get(no=1)
subject.name = 'Python全栈+人工智能'
subject.save()
```
#### 查询
1. 查询所有对象。
```Shell
Subjects.objects.all()
```
2. 过滤数据。
```Shell
# 查询名称为“Python全栈+人工智能”的学科
Subject.objects.filter(name='Python全栈+人工智能')
# 查询名称包含“全栈”的学科(模糊查询)
Subject.objects.filter(name__contains='全栈')
# 查询所有热门学科
Subject.objects.filter(is_hot=True)
# 查询编号大于3小于10的学科
Subject.objects.filter(no__gt=3).filter(no__lt=10)
Subject.objects.filter(no__gt=3, no__lt=10)
# 查询编号在3到7之间的学科
Subject.objects.filter(no__ge=3, no__le=7)
Subject.objects.filter(no__range=(3, 7))
```
3. 查询单个对象。
```Shell
# 查询主键为1的学科
Subject.objects.get(pk=1)
Subject.objects.get(no=1)
Subject.objects.filter(no=1).first()
Subject.objects.filter(no=1).last()
```
4. 排序。
```Shell
# 查询所有学科按编号升序排列
Subject.objects.order_by('no')
# 查询所有部门按部门编号降序排列
Subject.objects.order_by('-no')
```
5. 切片(分页查询)。
```Shell
# 按编号从小到大查询前3个学科
Subject.objects.order_by('no')[:3]
```
6. 计数。
```Python
# 查询一共有多少个学科
Subject.objects.count()
```
7. 高级查询。
```Shell
# 查询编号为1的学科的老师
Teacher.objects.filter(subject__no=1)
Subject.objects.get(pk=1).teacher_set.all()
# 查询学科名称有“全栈”二字的学科的老师
Teacher.objects.filter(subject__name__contains='全栈')
```
> **说明1**:由于老师与学科之间存在多对一外键关联,所以能通过学科反向查询到该学科的老师(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是`类名小写_set`(如上面例子中的`teacher_set`),当然也可以在创建模型时通过`ForeingKey`的`related_name`属性指定反向查询属性的名字。如果不希望执行反向查询可以将`related_name`属性设置为`'+'`或者以`'+'`开头的字符串。
> **说明2**ORM查询多个对象时会返回QuerySet对象QuerySet使用了惰性查询即在创建QuerySet对象的过程中不涉及任何数据库活动等真正用到对象时对QuerySet求值才向数据库发送SQL语句并获取对应的结果这一点在实际开发中需要引起注意
> **说明3**如果希望更新多条数据不用先逐一获取模型对象再修改对象属性可以直接使用QuerySet对象的`update()`方法一次性更新多条数据。
1. Django框架本身有自带的数据模型我们稍后会用到这些模型为此我们先做一次迁移操作。所谓迁移就是根据模型自动生成关系数据库中的二维表命令如下所示
```Shell
(venv)$ python manage.py migrate
@ -93,309 +291,85 @@
Applying sessions.0001_initial... OK
```
5. 接下来我们为自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理我们可以先创建部门和员工数据模型代码如下所示。
```Shell
(venv)$ vim hrs/models.py
```
```Python
from django.db import models
class Dept(models.Model):
"""部门类"""
no = models.IntegerField(primary_key=True, db_column='dno', verbose_name='部门编号')
name = models.CharField(max_length=20, db_column='dname', verbose_name='部门名称')
location = models.CharField(max_length=10, db_column='dloc', verbose_name='部门所在地')
class Meta:
db_table = 'tb_dept'
class Emp(models.Model):
"""员工类"""
no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名')
job = models.CharField(max_length=10, verbose_name='职位')
# 多对一外键关联(自参照)
mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管')
sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪')
comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴')
# 多对一外键关联(参照部门模型)
dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')
class Meta:
db_table = 'tb_emp'
```
> 说明上面定义模型时使用了字段类及其属性其中IntegerField对应数据库中的integer类型CharField对应数据库的varchar类型DecimalField对应数据库的decimal类型ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键max_length用来设置字段的最大长度db_column用来设置数据库中与字段对应的列verbose_name则设置了Django后台管理系统中该字段显示的名称。如果对这些东西感到很困惑也不要紧文末提供了字段类、字段属性、元数据选项等设置的相关说明不清楚的读者可以稍后查看对应的参考指南。
6. 再次执行迁移操作,先通过模型生成迁移文件,再执行迁移创建二维表。
```Shell
(venv)$ python manage.py makemigrations hrs
Migrations for 'hrs':
hrs/migrations/0001_initial.py
- Create model Dept
- Create model Emp
(venv)$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, hrs, sessions
Running migrations:
Applying hrs.0001_initial... OK
```
执行完数据模型迁移操作之后可以在通过图形化的MySQL客户端工具查看到E-R图实体关系图
![](./res/er-graph.png)
### 利用Django后台管理模型
Django框架有自带的后台管理系统来实现对模型的管理。虽然实际应用中这个后台可能并不能满足我们的需求但是在学习Django框架时我们暂时可以利用Django自带的后台管理系统来管理我们的模型同时也可以了解一个项目的后台管理系统到底需要哪些功能
在创建好模型类之后可以通过Django框架自带的后台管理应用`admin`应用实现对模型的管理。虽然实际应用中这个后台可能并不能满足我们的需求但是在学习Django框架时我们可以利用`admin`应用来管理我们的模型同时也通过它来了解一个项目的后台管理系统需要哪些功能。使用Django自带的`admin`应用步骤如下所示。
1. 创建超级管理员账号。
1. 将`admin`应用所需的表迁移到数据库中。`admin`应用本身也需要数据库的支持,而且在`admin`应用中已经定义好了相关的数据模型类,我们只需要通过模型迁移操作就能自动在数据库中生成所需的二维表。
```Bash
python manage.py migrate
```
2. 创建访问`admin`应用的超级用户账号,这里需要输入用户名、邮箱和口令。
```Shell
(venv)$ python manage.py createsuperuser
Username (leave blank to use 'hao'): jackfrued
Email address: jackfrued@126.com
Password:
Password (again):
Superuser created successfully.
python manage.py createsuperuser
```
2. 启动Web服务器登录后台管理系统
> **说明**:输入口令时没有回显也不能退格,需要一气呵成完成输入。
```Shell
(venv)$ python manage.py runserver
```
3. 运行项目,在浏览器中访问`http://127.0.0.1:8000/admin`,输入刚才创建的超级用户账号和密码进行登录。
访问<http://127.0.0.1:8000/admin>,会来到如下图所示的登录界面。
![](./res/admin-login.png)
![](./res/django-admin-login.png)
登录后进入管理员操作平台。
![](./res/admin-welcome.png)
![](./res/django-admin-apps.png)
至此我们还没有看到之前创建的模型类需要在应用的admin.py文件中模型进行注册。
注意,我们暂时还没能在`admin`应用中看到之前创建的模型类,为此需要在`polls`应用的`admin.py`文件中对需要管理的模型进行注册。
3. 注册模型类。
```Shell
(venv)$ vim hrs/admin.py
```
4. 注册模型类。
```Python
from django.contrib import admin
from hrs.models import Emp, Dept
admin.site.register(Dept)
admin.site.register(Emp)
```
from polls.models import Subject, Teacher
admin.site.register(Subject)
admin.site.register(Teacher)
```
注册模型类后,就可以在后台管理系统中看到它们。
![](./res/django-admin-models.png)
![](./res/admin-model.png)
4. 对模型进行CRUD操作。
5. 对模型进行CRUD操作。
可以在管理员平台对模型进行C新增、R查看、U更新、D删除操作如下图所示。
- 添加新的部门
- 添加学科。
![](./res/admin-model-create.png)
![](./res/django-admin-create.png)
- 查看所有部门
- 查看所有学科。
![](./res/admin-model-read.png)
![](./res/django-admin-read.png)
- 更新和删除部门
- 删除和更新学科
![](./res/admin-model-delete-and-update.png)
![](./res/django-admin-delete-update.png)
5. 注册模型管理类。
6. 注册模型管理类。
可能大家已经注意到了刚才在后台查看部门信息的时候显示的部门信息并不直观为此我们再修改admin.py文件通过注册模型管理类可以在后台管理系统中更好的管理模型。
可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改`admin.py`文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
```Python
from django.contrib import admin
from hrs.models import Emp, Dept
class DeptAdmin(admin.ModelAdmin):
list_display = ('no', 'name', 'location')
ordering = ('no', )
class EmpAdmin(admin.ModelAdmin):
list_display = ('no', 'name', 'job', 'mgr', 'sal', 'comm', 'dept')
search_fields = ('name', 'job')
admin.site.register(Dept, DeptAdmin)
admin.site.register(Emp, EmpAdmin)
```
![](./res/admin-model-depts.png)
![](./res/django-admin-models-detail.png)
![](./res/admin-model-emps.png)
为了更好的查看模型数据,可以为`Subject`和`Teacher`两个模型类添加`__str__`魔法方法。修改代码后的效果如下图所示。
为了更好的查看模型数据可以为Dept和Emp两个模型类添加`__str__`魔法方法。
![](./res/django-amdin-models-detail-modified.png)
```Python
from django.db import models
class Dept(models.Model):
"""部门类"""
# 此处省略上面的代码
def __str__(self):
return self.name
# 此处省略下面的代码
class Emp(models.Model):
"""员工类"""
# 此处省略上面的代码
def __str__(self):
return self.name
# 此处省略下面的代码
```
### 补充内容
修改代码后刷新查看Emp模型的页面效果如下图所示。
![](./res/admin-model-emps-modified.png)
### 使用ORM完成模型的CRUD操作
在了解了Django提供的模型管理平台之后我们来看看如何从代码层面完成对模型的CRUDCreate / Read / Update / Delete操作。我们可以通过manage.py开启Shell交互式环境然后使用Django内置的ORM框架对模型进行CRUD操作。
```Shell
(venv)$ python manage.py shell
Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
```
#### 新增
```Shell
>>> from hrs.models import Dept, Emp
>>>
>>> dept = Dept(40, '研发2部', '深圳')
>>> dept.save()
```
#### 更新
```Shell
>>> dept.name = '研发3部'
>>> dept.save()
```
#### 查询
1. 查询所有对象。
```Shell
>>> Dept.objects.all()
<QuerySet [<Dept: 1>, <Dept: 1>, <Dept: 1>, <Dept: 3>]>
```
2. 过滤数据。
```Shell
>>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门
<QuerySet [<Dept: 3>]>
>>>
>>> Dept.objects.filter(name__contains='研发') # 查询部门名称包含“研发”的部门(模糊查询)
<QuerySet [<Dept: 1>, <Dept: 3>]>
>>>
>>> Dept.objects.filter(no__gt=10).filter(no__lt=40) # 查询部门编号大于10小于40的部门
<QuerySet [<Dept: 1>, <Dept: 1>]>
>>>
>>> Dept.objects.filter(no__range=(10, 30)) # 查询部门编号在10到30之间的部门
<QuerySet [<Dept: 1>, <Dept: 1>, <Dept: 1>]>
```
3. 查询单个对象。
```Shell
>>> Dept.objects.get(pk=10)
<Dept: 1>
>>>
>>> Dept.objects.get(no=20)
<Dept: 1>
>>>
>>> Dept.objects.get(no__exact=30)
<Dept: 1>
>>>
>>> Dept.objects.filter(no=10).first()
<Dept: 1>
```
4. 排序数据。
```Shell
>>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列
<QuerySet [<Dept: 1>, <Dept: 1>, <Dept: 1>, <Dept: 3>]>
>>>
>>> Dept.objects.order_by('-no') # 查询所有部门按部门编号降序排列
<QuerySet [<Dept: 3>, <Dept: 1>, <Dept: 1>, <Dept: 1>]>
```
5. 数据切片(分页查询)。
```Shell
>>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门
<QuerySet [<Dept: 1>, <Dept: 1>]>
>>>
>>> Dept.objects.order_by('no')[2:4] # 按部门编号排序查询3~4部门
<QuerySet [<Dept: 1>, <Dept: 3>]>
```
6. 高级查询。
```Shell
>>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工
<QuerySet [<Emp: >, <Emp: >, <Emp: >]>
>>>
>>> Emp.objects.filter(dept__name__contains='销售') # 查询名字包含“销售”的部门的员工
<QuerySet [<Emp: >]>
>>>
>>> Dept.objects.get(pk=10).emp_set.all() # 通过部门反查部门所有的员工
<QuerySet [<Emp: >, <Emp: >, <Emp: >]>
```
> 说明1由于员工与部门之间存在多对一外键关联所以也能通过部门反向查询该部门的员工从一对多关系中“一”的一方查询“多”的一方反向查询属性默认的名字是`类名小写_set`(如上面例子中的`emp_set`),当然也可以在创建模型时通过`ForeingKey`的`related_name`属性指定反向查询属性的名字。如果不希望执行反向查询可以将`related_name`属性设置为`'+'`或以`'+'`开头的字符串。
> 说明2查询多个对象的时候返回的是QuerySet对象QuerySet使用了惰性查询即在创建QuerySet对象的过程中不涉及任何数据库活动等真正用到对象时求值QuerySet才向数据库发送SQL语句并获取对应的结果这一点在实际开发中需要引起注意
> 说明3可以在QuerySet上使用`update()`方法一次更新多个对象。
#### 删除
```Shell
>>> Dept.objects.get(pk=40).delete()
(1, {'hrs.Dept': 1})
```
### Django模型最佳实践
#### Django模型最佳实践
1. 正确的为模型和关系字段命名。
2. 设置适当的`related_name`属性。
@ -414,9 +388,9 @@ Type "help", "copyright", "credits" or "license" for more information.
> 说明以上内容来自于STEELKIWI网站的[*Best Practice working with Django models in Python*](https://steelkiwi.com/blog/best-practices-working-django-models-python/),有兴趣的小伙伴可以阅读原文。
### 模型定义参考
#### 模型定义参考
#### 字段
##### 字段
对字段名称的限制
@ -425,114 +399,103 @@ Type "help", "copyright", "credits" or "license" for more information.
Django模型字段类
| 字段类 | 说明 |
| --------------------- | ------------------------------------------------------------ |
| AutoField |自增ID字段 |
| BigIntegerField |64位有符号整数 |
| BinaryField | 存储二进制数据的字段对应Python的bytes类型 |
| BooleanField | 存储True或False |
| CharField | 长度较小的字符串 |
| DateField | 存储日期有auto_now和auto_now_add属性 |
| DateTimeField | 存储日期和日期,两个附加属性同上 |
| DecimalField |存储固定精度小数有max_digits有效位数和decimal_places小数点后面两个必要的参数 |
| DurationField |存储时间跨度 |
| EmailField | 与CharField相同可以用EmailValidator验证 |
| FileField | 文件上传字段 |
| FloatField | 存储浮点数 |
| ImageField | 其他同FileFiled要验证上传的是不是有效图像 |
| IntegerField | 存储32位有符号整数。 |
| GenericIPAddressField | 存储IPv4或IPv6地址 |
| NullBooleanField | 存储True、False或null值 |
| PositiveIntegerField | 存储无符号整数(只能存储正数) |
| SlugField | 存储slug简短标注 |
| SmallIntegerField | 存储16位有符号整数 |
| TextField | 存储数据量较大的文本 |
| TimeField | 存储时间 |
| URLField | 存储URL的CharField |
| UUIDField | 存储全局唯一标识符 |
| 字段类 | 说明 |
| ----------------------- | ------------------------------------------------------------ |
| `AutoField` | 自增ID字段 |
| `BigIntegerField` | 64位有符号整数 |
| `BinaryField` | 存储二进制数据的字段对应Python的`bytes`类型 |
| `BooleanField` | 存储`True`或`False` |
| `CharField` | 长度较小的字符串 |
| `DateField` | 存储日期,有`auto_now``auto_now_add`属性 |
| `DateTimeField` | 存储日期和日期,两个附加属性同上 |
| `DecimalField` | 存储固定精度小数,有`max_digits`(有效位数)和`decimal_places`(小数点后面)两个必要的参数 |
| `DurationField` | 存储时间跨度 |
| `EmailField` | 与`CharField`相同,可以用`EmailValidator`验证 |
| `FileField` | 文件上传字段 |
| `FloatField` | 存储浮点数 |
| `ImageField` | 其他同`FileFiled`,要验证上传的是不是有效图像 |
| `IntegerField` | 存储32位有符号整数。 |
| `GenericIPAddressField` | 存储IPv4或IPv6地址 |
| `NullBooleanField` | 存储`True``False``null`值 |
| `PositiveIntegerField` | 存储无符号整数(只能存储正数) |
| `SlugField` | 存储slug简短标注 |
| `SmallIntegerField` | 存储16位有符号整数 |
| `TextField` | 存储数据量较大的文本 |
| `TimeField` | 存储时间 |
| `URLField` | 存储URL的`CharField` |
| `UUIDField` | 存储全局唯一标识符 |
#### 字段属性
##### 字段属性
通用字段属性
| 选项 | 说明 |
| -------------- | ------------------------------------------------------------ |
| null | 数据库中对应的字段是否允许为NULL默认为False |
| blank | 后台模型管理验证数据时,是否允许为NULL默认为False |
| choices | 设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值 |
| db_column | 字段对应到数据库表中的列名,未指定时直接使用字段的名称 |
| db_index | 设置为True时将在该字段创建索引 |
| db_tablespace | 为有索引的字段设置使用的表空间默认为DEFAULT_INDEX_TABLESPACE |
| default | 字段的默认值 |
| editable | 字段在后台模型管理或ModelForm中是否显示默认为True |
| error_messages | 设定字段抛出异常时的默认消息的字典其中的键包括null、blank、invalid、invalid_choice、unique和unique_for_date |
| help_text | 表单小组件旁边显示的额外的帮助文本。 |
| primary_key | 将字段指定为模型的主键未指定时会自动添加AutoField用于主键只读。 |
| unique | 设置为True时表中字段的值必须是唯一的 |
| verbose_name | 字段在后台模型管理显示的名称,未指定时使用字段的名称 |
| 选项 | 说明 |
| ---------------- | ------------------------------------------------------------ |
| `null` | 数据库中对应的字段是否允许为`NULL`,默认为`False` |
| `blank` | 后台模型管理验证数据时,是否允许为`NULL`,默认为`False` |
| `choices` | 设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值 |
| `db_column` | 字段对应到数据库表中的列名,未指定时直接使用字段的名称 |
| `db_index` | 设置为`True`时将在该字段创建索引 |
| `db_tablespace` | 为有索引的字段设置使用的表空间,默认为`DEFAULT_INDEX_TABLESPACE` |
| `default` | 字段的默认值 |
| `editable` | 字段在后台模型管理或`ModelForm`中是否显示,默认为`True` |
| `error_messages` | 设定字段抛出异常时的默认消息的字典,其中的键包括`null``blank``invalid``invalid_choice``unique``unique_for_date` |
| `help_text` | 表单小组件旁边显示的额外的帮助文本。 |
| `primary_key` | 将字段指定为模型的主键,未指定时会自动添加`AutoField`用于主键,只读。 |
| `unique` | 设置为`True`时,表中字段的值必须是唯一的 |
| `verbose_name` | 字段在后台模型管理显示的名称,未指定时使用字段的名称 |
ForeignKey属性
`ForeignKey`属性
1. limit_choices_to值是一个Q对象或返回一个Q对象用于限制后台显示哪些对象。
2. related_name用于获取关联对象的关联管理器对象反向查询如果不允许反向该属性应该被设置为`'+'`,或者以`'+'`结尾。
3. to_field指定关联的字段默认关联对象的主键字段。
4. db_constraint是否为外键创建约束默认值为True。
5. on_delete外键关联的对象被删除时对应的动作可取的值包括django.db.models中定义的
- CASCADE级联删除。
- PROTECT抛出ProtectedError异常阻止删除引用的对象。
- SET_NULL把外键设置为null当null属性被设置为True时才能这么做。
- SET_DEFAULT把外键设置为默认值提供了默认值才能这么做。
1. `limit_choices_to`值是一个Q对象或返回一个Q对象用于限制后台显示哪些对象。
2. `related_name`:用于获取关联对象的关联管理器对象(反向查询),如果不允许反向,该属性应该被设置为`'+'`,或者以`'+'`结尾。
3. `to_field`:指定关联的字段,默认关联对象的主键字段。
4. `db_constraint`:是否为外键创建约束,默认值为`True`
5. `on_delete`:外键关联的对象被删除时对应的动作,可取的值包括`django.db.models`中定义的:
- `CASCADE`:级联删除。
- `PROTECT`:抛出`ProtectedError`异常,阻止删除引用的对象。
- `SET_NULL`:把外键设置为`null`,当`null`属性被设置为`True`时才能这么做。
- `SET_DEFAULT`:把外键设置为默认值,提供了默认值才能这么做。
ManyToManyField属性
`ManyToManyField`属性
1. symmetrical是否建立对称的多对多关系。
2. through指定维持多对多关系的中间表的Django模型。
3. throughfields定义了中间模型时可以指定建立多对多关系的字段。
4. db_table指定维持多对多关系的中间表的表名。
1. `symmetrical`:是否建立对称的多对多关系。
2. `through`指定维持多对多关系的中间表的Django模型。
3. `throughfields`:定义了中间模型时可以指定建立多对多关系的字段。
4. `db_table`:指定维持多对多关系的中间表的表名。
#### 模型元数据选项
##### 模型元数据选项
| 选项 | 说明 |
| --------------------- | ------------------------------------------------------------ |
| abstract | 设置为True时模型是抽象父类 |
| app_label | 如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定 |
| db_table | 模型使用的数据表名称 |
| db_tablespace | 模型使用的数据表空间 |
| default_related_name | 关联对象回指这个模型时默认使用的名称,默认为<model_name>_set |
| get_latest_by | 模型中可排序字段的名称。 |
| managed | 设置为True时Django在迁移中创建数据表并在执行flush管理命令时把表移除 |
| order_with_respect_to | 标记对象为可排序的 |
| ordering | 对象的默认排序 |
| permissions | 创建对象时写入权限表的额外权限 |
| default_permissions | 默认为`('add', 'change', 'delete')` |
| unique_together | 设定组合在一起时必须独一无二的字段名 |
| index_together | 设定一起建立索引的多个字段名 |
| verbose_name | 为对象设定人类可读的名称 |
| verbose_name_plural | 设定对象的复数名称 |
| 选项 | 说明 |
| ----------------------- | ------------------------------------------------------------ |
| `abstract` | 设置为True时模型是抽象父类 |
| `app_label` | 如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定 |
| `db_table` | 模型使用的数据表名称 |
| `db_tablespace` | 模型使用的数据表空间 |
| `default_related_name` | 关联对象回指这个模型时默认使用的名称,默认为<model_name>_set |
| `get_latest_by` | 模型中可排序字段的名称。 |
| `managed` | 设置为True时Django在迁移中创建数据表并在执行flush管理命令时把表移除 |
| `order_with_respect_to` | 标记对象为可排序的 |
| `ordering` | 对象的默认排序 |
| `permissions` | 创建对象时写入权限表的额外权限 |
| `default_permissions` | 默认为`('add', 'change', 'delete')` |
| `unique_together` | 设定组合在一起时必须独一无二的字段名 |
| `index_together` | 设定一起建立索引的多个字段名 |
| `verbose_name` | 为对象设定人类可读的名称 |
| `verbose_name_plural` | 设定对象的复数名称 |
### 查询参考
#### 查询参考
按字段查找可以用的条件
##### 按字段查找可以用的条件
1. exact / iexact精确匹配/忽略大小写的精确匹配查询
2. contains / icontains / startswith / istartswith / endswith / iendswith基于`like`的模糊查询
3. in集合运算
4. gt / gte / lt / lte大于/大于等于/小于/小于等于关系运算
5. range指定范围查询SQL中的`between…and…`
6. year / month / day / week_day / hour / minute / second查询时间日期
7. isnull查询空值True或非空值False
8. search基于全文索引的全文检索
9. regex / iregex基于正则表达式的模糊匹配查询
Q对象用于执行复杂查询的使用
```Shell
>>> from django.db.models import Q
>>> Emp.objects.filter(
... Q(name__startswith='张'),
... Q(sal__gte=5000) | Q(comm__gte=1000)
... ) # 查询名字以“张”开头且工资大于等于5000或补贴大于等于1000的员工
<QuerySet [<Emp: >]>
```
1. `exact` / `iexact`:精确匹配/忽略大小写的精确匹配查询
2. `contains` / `icontains` / `startswith` / `istartswith` / `endswith` / `iendswith`:基于`like`的模糊查询
3. `in` :集合运算
4. `gt` / `gte` / `lt` / `lte`:大于/大于等于/小于/小于等于关系运算
5. `range`指定范围查询SQL中的`between…and…`
6. `year` / `month` / `day` / `week_day` / `hour` / `minute` / `second`:查询时间日期
7. `isnull`查询空值True或非空值False
8. `search`:基于全文索引的全文检索(一般很少使用)
9. `regex` / `iregex`:基于正则表达式的模糊匹配查询

View File

@ -54,7 +54,7 @@ class Teacher(models.Model):
> 注意为了给vote应用生成迁移文件需要修改Django项目settings.py文件在INSTALLED_APPS中添加vote应用。
完成模型迁移之后我们可以直接使用Django提供的后台管理来添加学科和老师信息这需要先注册模型类和模型管理类。
完成模型迁移之后我们可以直接使用Django提供的后台管理来添加学科和老师信息这需要先注册模型类和模型管理类,可以通过修改``
```SQL
from django.contrib import admin

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FirstConfig(AppConfig):
name = 'first'

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,11 @@
from random import sample
from django.shortcuts import render
def show_index(request):
fruits = [
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
]
return render(request, 'index.html', {'fruits': sample(fruits, 3)})

View File

@ -1,13 +1,13 @@
"""
Django settings for shop project.
Django settings for hellodjango project.
Generated by 'django-admin startproject' using Django 2.0.5.
Generated by 'django-admin startproject' using Django 2.2.13.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
@ -17,10 +17,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '+gqc54!5+uhvc^o0)fjvihmg&5uu^u+#s5m*fc+e+@bw*(+!w*'
SECRET_KEY = 'x)q$(0m0^ttqii@^zn^9bdbh&%l$)wzjm=nv&_y+^y9e!37=-z'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@ -37,7 +37,6 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cart',
]
MIDDLEWARE = [
@ -50,13 +49,12 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'shop.urls'
ROOT_URLCONF = 'hellodjango.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')]
,
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -69,26 +67,22 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'shop.wsgi.application'
WSGI_APPLICATION = 'hellodjango.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'Shop',
'HOST': 'localhost',
'PORT': 3306,
'USER': 'yourname',
'PASSWORD': 'yourpass',
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
@ -107,11 +101,11 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Chongqing'
TIME_ZONE = 'UTC'
USE_I18N = True
@ -121,6 +115,6 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'

View File

@ -1,7 +1,7 @@
"""shop URL Configuration
"""hellodjango URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
@ -16,11 +16,9 @@ Including another URLconf
from django.contrib import admin
from django.urls import path
from cart import views
from first.views import show_index
urlpatterns = [
path('', views.index),
path('show_cart', views.show_cart),
path('add_to_cart/<int:no>', views.add_to_cart),
path('admin/', admin.site.urls),
path('hello/', show_index),
]

View File

@ -1,16 +1,16 @@
"""
WSGI config for shop project.
WSGI config for hellodjango project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
application = get_wsgi_application()

View File

@ -1,9 +1,11 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@ -13,3 +15,7 @@ if __name__ == "__main__":
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首页</title>
<style>
#fruits {
font-size: 1.25em;
}
</style>
</head>
<body>
<h1>今天推荐的水果是:</h1>
<hr>
<ul id="fruits">
{% for fruit in fruits %}
<li>{{ fruit }}</li>
{% endfor %}
</ul>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

View File

@ -1,73 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户登录</title>
<style>
#container {
width: 400px;
margin: 10px auto;
}
.input, form+a {
margin: 20px 20px;
width: 250px;
}
.input>label {
display: inline-block;
width: 70px;
text-align: right;
}
.input:last-child {
text-align: center;
}
input[type=text], input[type=password] {
outline: none;
}
input[type=submit], input[type=reset] {
width: 60px;
}
form+a {
text-decoration: none;
color: blue;
}
.captcha {
width: 380px;
}
.captcha>img {
vertical-align: middle;
}
.hint {
margin-left: 20px;
color: red;
font-size: 0.8em;
height: 20px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户登录</h1>
<hr>
<form action="" method="post">
<div class="input">
<label>用户名:</label>
<input type="text" name="username">
</div>
<div class="input">
<label>密码:</label>
<input type="password" name="password">
</div>
<div class="input captcha">
<label>验证码:</label>
<input type="text" name="captcha">
<img id="code" src="images/captcha.jpg" width="120">
</div>
<div class="input">
<input type="submit" value="登录">
<input type="reset" value="重置">
</div>
</form>
<a href="register.html">注册新用户</a>
</div>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
#container {
width: 400px;
margin: 0 auto;
}
.input, form+a {
margin: 20px 20px;
width: 250px;
}
.input>label {
display: inline-block;
width: 80px;
text-align: right;
}
.input:last-child {
text-align: center;
}
input[type=text], input[type=password] {
outline: none;
}
input[type=submit], input[type=reset] {
width: 60px;
}
form+a {
text-decoration: none;
color: blue;
}
.hint {
margin-left: 20px;
color: red;
font-size: 0.8em;
height: 20px;
}
.mobile {
width: 300px;
}
.photo {
width: 350px;
}
.preview {
margin: 10px 50px;
}
.intro {
width: 720px;
}
</style>
</head>
<body>
<div id="container">
<h1>用户注册</h1>
<hr>
<form action="" method="post">
<div class="input">
<label>用户名:</label>
<input type="text" name="username" required minlength="4" maxlength="20">
</div>
<div class="input">
<label>密码:</label>
<input type="password" name="password">
</div>
<div class="input">
<label>确认密码:</label>
<input type="password" name="repassword">
</div>
<div class="input mobile">
<label>手机号:</label>
<input type="tel" name="tel">
<input type="button" id="sendBtn" value="发送验证码">
</div>
<div class="input">
<label>验证码:</label>
<input type="text" name="mobilecode">
</div>
<div class="input">
<input type="submit" value="注册">
<input type="reset" value="重置">
</div>
</form>
<a href="login.html">返回登录</a>
</div>
</body>
</html>

View File

@ -1,67 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学科信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
#main dt {
font-size: 1.5em;
}
#main dd {
font-size: 1.2em;
}
#main a {
text-decoration: none;
color: blue;
}
</style>
</head>
<body>
<div id="container">
<h1>千锋教育成都校区所有学科</h1>
<hr>
<div id="main">
<dl>
<dt>
<a href="teachers.html">Python全栈+人工智能</a>
<img src="images/hot-icon-small.png">
</dt>
<dd>
Python英国发音/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型、高级编程、通用型编程语言,
由吉多·范罗苏姆创造第一版发布于1991年。可以视之为一种改良加入一些其他编程语言的优点如面向对象的LISP。
Python的设计哲学强调代码的可读性和简洁的语法尤其是使用空格缩进划分代码块而非使用大括号或者关键词
相比于C++或JavaPython让开发者能够用更少的代码表达想法。不管是小型还是大型程序该语言都试图让程序的结构清晰明了。
</dd>
</dl>
<dl>
<dt>
<a href="">全栈软件测试</a>
</dt>
<dd>
软件测试在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
软件测试有许多方法,但对复杂的产品运行有效测试不仅仅是研究过程,更是创造并严格遵守某些呆板步骤的大事。
测试的其中一个定义:为了评估而质疑产品的过程;这里的“质疑”是测试员试着对产品做的事,而产品以测试者脚本行为反应作为回答。
</dd>
</dl>
<dl>
<dt>
<a href="">JavaEE+分布式开发</a>
<img src="images/hot-icon-small.png">
</dt>
<dd>
Java是一种广泛使用的计算机编程语言拥有跨平台、面向对象、泛型编程的特性广泛应用于企业级Web应用开发和移动应用开发。
该语言由当时任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形最初被命名为Oak
目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。
由于这些智能化家电的市场需求没有预期的高太阳计算机系统Sun公司放弃了该项计划。
随着1990年代互联网的发展Sun公司看见Oak在互联网上应用的前景于是改造了Oak于1995年5月以Java的名称正式发布。
Java伴随着互联网的迅猛发展而发展逐渐成为重要的网络编程语言。
</dd>
</dl>
</div>
</div>
</body>
</html>

View File

@ -1,110 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>老师信息</title>
<style>
#container {
width: 80%;
margin: 10px auto;
}
.teacher {
width: 100%;
margin: 0 auto;
padding: 10px 0;
border-bottom: 1px dashed gray;
overflow: auto;
}
.teacher div {
float: left;
}
.photo {
height: 140px;
border-radius: 75px;
overflow: hidden;
margin-left: 20px;
}
.info {
width: 75%;
margin-left: 30px;
}
.info div {
clear: both;
margin: 5px 10px;
}
.info span {
margin-right: 25px;
}
.info a {
text-decoration: none;
color: blue;
}
.user {
float: right;
margin: 10px 10px;
}
.user+h1 {
clear: both;
}
#photo {
border-radius: 32px 32px 32px 32px;
}
</style>
</head>
<body>
<div id="container">
<h1>Python全栈+人工智能学科的老师信息</h1>
<hr>
<div class="teacher">
<div class="photo">
<img src="images/luohao.png" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:骆昊</strong></span>
<span>性别:男</span>
<span>出生日期1980年11月28日</span>
</div>
<div class="intro">
10年以上软硬件产品和系统设计、研发、架构和管理经验2003年毕业于四川大学四川大学Java技术俱乐部创始人
四川省优秀大学毕业生在四川省网络通信技术重点实验室工作期间参与了2项国家自然科学基金项目、
1项中国科学院中长期研究项目和多项四川省科技攻关项目在国际会议和国内顶级期刊上发表多篇论文1篇被SCI收录3篇被EI收录
大规模网络性能测量系统DMC-TS的设计者和开发者perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家
在Github上参与和维护了多个高质量开源项目精通C/C++、Java、Python、R、Swift、JavaScript等编程语言
擅长OOAD、系统架构、算法设计、协议分析和网络测量主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发
一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。
</div>
<div class="comment">
<a href="">好评</a>&nbsp;&nbsp;(<strong>100</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="">差评</a>&nbsp;&nbsp;(<strong>50</strong>)
</div>
</div>
</div>
<div class="teacher">
<div class="photo">
<img src="images/yuting.png" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:余婷</strong></span>
<span>性别:女</span>
<span>出生日期1992年9月20日</span>
</div>
<div class="intro">
5年以上移动互联网项目开发经验和教学经验曾担任上市游戏公司高级软件研发工程师和移动端iOS技术负责人
参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,
以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言
熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微
性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。
</div>
<div class="comment">
<a href="">好评</a>&nbsp;&nbsp;(<strong>50</strong>)
&nbsp;&nbsp;&nbsp;&nbsp;
<a href="">差评</a>&nbsp;&nbsp;(<strong>100</strong>)
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,12 +0,0 @@
from django.contrib import admin
from cart.models import Goods
class GoodsAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price', 'image')
search_fields = ('name', )
admin.site.register(Goods, GoodsAdmin)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'cart'

View File

@ -1,27 +0,0 @@
# Generated by Django 2.0.5 on 2018-05-25 06:28
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Goods',
fields=[
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
('name', models.CharField(db_column='gname', max_length=50)),
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
('image', models.CharField(db_column='gimage', max_length=255)),
],
options={
'db_table': 'tb_goods',
'ordering': ('id',),
},
),
]

View File

@ -1,15 +0,0 @@
from django.db import models
class Goods(models.Model):
"""商品模型类"""
id = models.AutoField(primary_key=True, db_column='gid')
name = models.CharField(max_length=50, db_column='gname')
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
image = models.CharField(max_length=255, db_column='gimage')
class Meta:
db_table = 'tb_goods'
ordering = ('id', )

View File

@ -1,77 +0,0 @@
from django.core import serializers
from django.shortcuts import render, redirect
from cart.models import Goods
def index(request):
goods_list = list(Goods.objects.all())
return render(request, 'goods.html', {'goods_list': goods_list})
class CartItem(object):
"""购物车中的商品项"""
def __init__(self, goods, amount=1):
self.goods = goods
self.amount = amount
@property
def total(self):
return self.goods.price * self.amount
class ShoppingCart(object):
"""购物车"""
def __init__(self):
self.items = {}
self.index = 0
def add_item(self, item):
if item.goods.id in self.items:
self.items[item.goods.id].amount += item.amount
else:
self.items[item.goods.id] = item
def remove_item(self, id):
if id in self.items:
self.items.remove(id)
def clear_all_items(self):
self.items.clear()
@property
def cart_items(self):
return self.items.values()
@property
def total(self):
val = 0
for item in self.items.values():
val += item.total
return val
def add_to_cart(request, id):
goods = Goods.objects.get(pk=id)
# 通过request对象的session属性可以获取到session
# session相当于是服务器端用来保存用户数据的一个字典
# session利用了Cookie保存sessionid
# 通过sessionid就可以获取与某个用户对应的会话(也就是用户数据)
# 如果在浏览器中清除了Cookie那么也就清除了sessionid
# 再次访问服务器时服务器会重新分配新的sessionid这也就意味着之前的用户数据无法找回
# 默认情况下Django的session被设定为持久会话而非浏览器续存期会话
# 通过SESSION_EXPIRE_AT_BROWSER_CLOSE和SESSION_COOKIE_AGE参数可以修改默认设定
# Django中的session是进行了持久化处理的因此需要设定session的序列化方式
# 1.6版开始Django默认的session序列化器是JsonSerializer
# 可以通过SESSION_SERIALIZER来设定其他的序列化器(例如PickleSerializer)
cart = request.session.get('cart', ShoppingCart())
cart.add_item(CartItem(goods))
request.session['cart'] = cart
return redirect('/')
def show_cart(request):
cart = serializers.deserialize(request.session.get('cart'))
return render(request, 'cart.html', {'cart': cart})

View File

@ -1,15 +0,0 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

View File

@ -1,3 +0,0 @@
import pymysql
pymysql.install_as_MySQLdb()

View File

@ -1,129 +0,0 @@
"""
Django settings for shop project.
Generated by 'django-admin startproject' using Django 2.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '3(n^av%_kt*^2zhz0!iwkxv6_wp^ed7-dpow*vqr7ck0_6=9^e'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'cart.apps.CartConfig',
]
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',
]
ROOT_URLCONF = 'shop.urls'
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',
],
},
},
]
WSGI_APPLICATION = 'shop.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'shop',
'HOST': 'localhost',
'PORT': 3306,
'USER': 'yourname',
'PASSWORD': 'yourpass',
}
}
# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Chongqing'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
STATIC_URL = '/static/'

View File

@ -1,26 +0,0 @@
"""shop URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from cart import views
urlpatterns = [
path('', views.index),
path('add_to_cart/<int:id>', views.add_to_cart),
path('show_cart', views.show_cart),
path('admin/', admin.site.urls),
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; width: 280px; }
.price { color: red; font-size: 18px; }
a { display: inline-block; text-align: center; background-color: red; }
.back { width: 120px; height: 30px; line-height: 30px; }
.del { width: 60px; height: 20px; line-height: 20px; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
.total { text-align: right; }
</style>
</head>
<body>
<div class="left">
<h1>购物车列表</h1>
<hr>
</div>
<div class="right">
<a href="/" class="back">返回</a>
</div>
{% if cart %}
<table style="clear: both;">
<tr>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>商品总价</th>
<th>操作</th>
</tr>
{% for item in cart %}
<tr>
<td class="name">{{ item.goods.name }}</td>
<td class="price">&yen;{{ item.goods.price }}</td>
<td>{{ item.amount }}</td>
<td class="price">&yen;{{ item.total }}</td>
<td>
<a href="" class="del">删除</a>
</td>
</tr>
{% endfor %}
<tr>
<td colspan="5" class="total price">&yen;{{ cart.total }}元</td>
</tr>
</table>
<a href="" class="back">清空购物车</a>
{% else %}
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
{% endif %}
</body>
</html>

View File

@ -1,46 +0,0 @@
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<meta charset="utf-8">
<style>
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; }
.price { color: red; font-size: 18px; }
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
</style>
</head>
<body>
<div class="left">
<h1>商品列表</h1>
<hr>
</div>
<div class="right">
<a href="/show_cart">查看购物车</a>
</div>
<table style="clear:both;">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品图片</th>
<th>操作</th>
</tr>
{% for goods in goods_list %}
<tr>
<td class="name">{{ goods.name }}</td>
<td class="price">&yen;{{ goods.price }}</td>
<td>
<img src="{% static goods.image %}" alt="{{ goods.name }}">
</td>
<td>
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>

View File

@ -1,11 +0,0 @@
from django.contrib import admin
from cart.models import Goods
class GoodsAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price', 'image')
admin.site.register(Goods, GoodsAdmin)

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class CartConfig(AppConfig):
name = 'cart'

View File

@ -1,27 +0,0 @@
# Generated by Django 2.0.5 on 2018-05-25 05:11
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Goods',
fields=[
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
('name', models.CharField(db_column='gname', max_length=50)),
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
('image', models.CharField(db_column='gimage', max_length=255)),
],
options={
'db_table': 'tb_goods',
'ordering': ('id',),
},
),
]

View File

@ -1,13 +0,0 @@
from django.db import models
class Goods(models.Model):
id = models.AutoField(primary_key=True, db_column='gid')
name = models.CharField(max_length=50, db_column='gname')
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
image = models.CharField(max_length=255, db_column='gimage')
class Meta:
db_table = 'tb_goods'
ordering = ('id',)

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,16 +0,0 @@
from django.shortcuts import render
from cart.models import Goods
def index(request):
goods_list = list(Goods.objects.all())
return render(request, 'goods.html', {'goods_list': goods_list})
def show_cart(request):
return render(request, 'cart.html')
def add_to_cart(request, no):
pass

View File

@ -1,3 +0,0 @@
import pymysql
pymysql.install_as_MySQLdb()

View File

@ -1,16 +0,0 @@
"""
WSGI config for shop project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
application = get_wsgi_application()

View File

@ -1,7 +0,0 @@
insert into tb_goods values
(default, '乐事Lays无限薯片', 8.2, 'images/lay.jpg'),
(default, '旺旺 仙贝 加量装 540g', 18.5, 'images/wang.jpg'),
(default, '多儿比Dolbee黄桃水果罐头', 6.8, 'images/dolbee.jpg'),
(default, '王致和 精制料酒 500ml', 7.9, 'images/wine.jpg'),
(default, '陈克明 面条 鸡蛋龙须挂面', 1.0, 'images/noodle.jpg'),
(default, '鲁花 菜籽油 4L', 69.9, 'images/oil.jpg');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,55 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; width: 280px; }
.price { color: red; font-size: 18px; }
a { display: inline-block; text-align: center; background-color: red; }
.back { width: 120px; height: 30px; line-height: 30px; }
.del { width: 60px; height: 20px; line-height: 20px; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
.total { text-align: right; }
</style>
</head>
<body>
<div class="left">
<h1>购物车列表</h1>
<hr>
</div>
<div class="right">
<a href="list_goods" class="back">返回</a>
</div>
{% if cart_items %}
<table style="clear: both;">
<tr>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>商品总价</th>
<th>操作</th>
</tr>
{% for item in cart_items %}
<tr>
<td class="name">{{ item.name }}</td>
<td class="price">&yen;{{ item.unit_price }}</td>
<td>{{ item.amount }}</td>
<td class="price">&yen;{{ item.total_price }}</td>
<td>
<a href="" class="del">删除</a>
</td>
</tr>
{% endfor %}
<tr>
<td colspan="5" class="total price">&yen;{{ cart.total }}元</td>
</tr>
</table>
<a href="clear_cart" class="back">清空购物车</a>
{% else %}
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
{% endif %}
</body>
</html>

View File

@ -1,46 +0,0 @@
<!DOCTYPE html>
{% load staticfiles %}
<html>
<head>
<meta charset="utf-8">
<style>
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
.name { font-size: 14px; font-weight: bolder; }
.price { color: red; font-size: 18px; }
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
a:link, a:visited { color: white; text-decoration: none; }
.left { float: left; width: 1000px;}
.right { float: right; }
</style>
</head>
<body>
<div class="left">
<h1>商品列表</h1>
<hr>
</div>
<div class="right">
<a href="/show_cart">查看购物车</a>
</div>
<table style="clear:both;">
<tr>
<th>商品名称</th>
<th>商品价格</th>
<th>商品图片</th>
<th>操作</th>
</tr>
{% for goods in goods_list %}
<tr>
<td class="name">{{ goods.name }}</td>
<td class="price">&yen;{{ goods.price }}</td>
<td>
<img src="{% static goods.image %}" alt="{{ goods.name }}">
</td>
<td>
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
</td>
</tr>
{% endfor %}
</table>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

View File

@ -27,7 +27,6 @@
大多数网站都会定义robots.txt文件下面以淘宝的[robots.txt](http://www.taobao.com/robots.txt)文件为例,看看该网站对爬虫有哪些限制。
```
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
@ -108,17 +107,17 @@ HTTP响应响应行+响应头+空行+消息体):
![](./res/chrome-developer-tools.png)
2. POSTMAN功能强大的网页调试与RESTful请求工具。
2. Postman功能强大的网页调试与RESTful请求工具。
![](./res/postman.png)
3. HTTPie命令行HTTP客户端。
```Shell
```Bash
pip3 install httpie
```
```Shell
```Bash
http --header http://www.scu.edu.cn
HTTP/1.1 200 OK
Accept-Ranges: bytes
@ -138,9 +137,9 @@ HTTP响应响应行+响应头+空行+消息体):
X-Frame-Options: SAMEORIGIN
```
4. BuiltWith:识别网站所用技术的工具。
4. `builtwith`:识别网站所用技术的工具。
```Shell
```Bash
pip3 install builtwith
```
@ -155,9 +154,9 @@ HTTP响应响应行+响应头+空行+消息体):
{'web-servers': ['Tengine'], 'web-frameworks': ['Twitter Bootstrap', 'Ruby on Rails'], 'programming-languages': ['Ruby']}
```
5. python-whois查询网站所有者的工具。
5. `python-whois`库:查询网站所有者的工具。
```Shell
```Bash
pip3 install python-whois
```
@ -167,7 +166,7 @@ HTTP响应响应行+响应头+空行+消息体):
{'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'}
```
6. robotparser解析robots.txt的工具。
6. `robotparser`模块:解析`robots.txt`的工具。
```Python
>>> from urllib import robotparser
@ -199,110 +198,106 @@ HTTP响应响应行+响应头+空行+消息体):
下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
```Python
from urllib.error import URLError
from urllib.request import urlopen
import re
import pymysql
import ssl
from collections import deque
from urllib.parse import urljoin
from pymysql import Error
import requests
LI_A_PATTERN = re.compile(r'<li class="item">.*?</li>')
A_TEXT_PATTERN = re.compile(r'<a\s+[^>]*?>(.*?)</a>')
A_HREF_PATTERN = re.compile(r'<a\s+[^>]*?href="(.*?)"\s*[^>]*?>')
def decode_page(page_bytes, charsets=('utf-8',)):
"""通过指定的字符集对页面进行解码(不是每个网站都将字符集设置为utf-8)"""
page_html = None
def decode_page(page_bytes, charsets):
"""通过指定的字符集对页面进行解码"""
for charset in charsets:
try:
page_html = page_bytes.decode(charset)
break
return page_bytes.decode(charset)
except UnicodeDecodeError:
pass
# logging.error('Decode:', error)
return page_html
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
"""获取页面的HTML代码(通过递归实现指定次数的重试操作)"""
page_html = None
try:
page_html = decode_page(urlopen(seed_url).read(), charsets)
except URLError:
# logging.error('URL:', error)
if retry_times > 0:
return get_page_html(seed_url, retry_times=retry_times - 1,
charsets=charsets)
return page_html
def get_matched_parts(content_string, pattern):
"""从字符串中提取所有跟正则表达式匹配的内容"""
return pattern.findall(content_string, re.I) \
if content_string else []
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
"""从页面中提取需要的部分(通常是链接也可以通过正则表达式进行指定)"""
pattern_regex = re.compile(pattern_str, pattern_ignore_case)
return pattern_regex.findall(page_html) if page_html else []
def get_matched_part(content_string, pattern, group_no=1):
"""从字符串中提取跟正则表达式匹配的内容"""
match = pattern.search(content_string)
if match:
return match.group(group_no)
def start_crawl(seed_url, match_pattern, *, max_depth=-1):
"""开始执行爬虫程序并对指定的数据进行持久化操作"""
conn = pymysql.connect(host='localhost', port=3306,
database='crawler', user='root',
password='123456', charset='utf8')
try:
with conn.cursor() as cursor:
url_list = [seed_url]
# 通过下面的字典避免重复抓取并控制抓取深度
visited_url_list = {seed_url: 0}
while url_list:
current_url = url_list.pop(0)
depth = visited_url_list[current_url]
if depth != max_depth:
# 尝试用utf-8/gbk/gb2312三种字符集进行页面解码
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
links_list = get_matched_parts(page_html, match_pattern)
param_list = []
for link in links_list:
if link not in visited_url_list:
visited_url_list[link] = depth + 1
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
headings = get_matched_parts(page_html, r'<h1>(.*)<span')
if headings:
param_list.append((headings[0], link))
cursor.executemany('insert into tb_result values (default, %s, %s)',
param_list)
conn.commit()
except Error:
pass
# logging.error('SQL:', error)
finally:
conn.close()
def get_page_html(seed_url, *, charsets=('utf-8', )):
"""获取页面的HTML代码"""
resp = requests.get(seed_url)
if resp.status_code == 200:
return decode_page(resp.content, charsets)
def repair_incorrect_href(current_url, href):
"""修正获取的href属性"""
if href.startswith('//'):
href = urljoin('http://', href)
elif href.startswith('/'):
href = urljoin(current_url, href)
return href if href.startswith('http') else ''
def start_crawl(seed_url, pattern, *, max_depth=-1):
"""开始爬取数据"""
new_urls, visited_urls = deque(), set()
new_urls.append((seed_url, 0))
while new_urls:
current_url, depth = new_urls.popleft()
if depth != max_depth:
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk'))
contents = get_matched_parts(page_html, pattern)
for content in contents:
text = get_matched_part(content, A_TEXT_PATTERN)
href = get_matched_part(content, A_HREF_PATTERN)
if href:
href = repair_incorrect_href(href)
print(text, href)
if href and href not in visited_urls:
new_urls.append((href, depth + 1))
def main():
"""主函数"""
ssl._create_default_https_context = ssl._create_unverified_context
start_crawl('http://sports.sohu.com/nba_a.shtml',
r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']',
max_depth=2)
start_crawl(
seed_url='http://sports.sohu.com/nba_a.shtml',
pattern=LI_A_PATTERN,
max_depth=2
)
if __name__ == '__main__':
main()
```
由于使用了MySQL实现持久化操作所以要先启动MySQL服务器并创建名为`crawler`的数据库和名为`tb_result`的二维表才能运行该程序。
### 爬虫注意事项
通过上面的例子,我们对爬虫已经有了一个感性的认识,在编写爬虫时有以下一些注意事项:
1. 处理相对链接。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接这种情况下需要将其与URL前缀进行拼接`urllib.parse`中的`urljoin()`函数可以完成此项操作)
1. 上面的代码使用了`requests`三方库来获取网络资源,这是一个非常优质的三方库,关于它的用法可以参考它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)
2. 设置代理服务。有些网站会限制访问的区域例如美国的Netflix屏蔽了很多国家的访问有些爬虫需要隐藏自己的身份在这种情况下可以设置使用代理服务器代理服务器有免费的服务器和付费的商业服务器但后者稳定性和可用性都更好强烈建议在商业项目中使用付费的代理服务器。可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器
2. 上面的代码中使用了双端队列(`deque`来保存待爬取的URL。双端队列相当于是使用链式存储结构的`list`在双端队列的头尾添加和删除元素性能都比较好刚好可以用来构造一个FIFO先进先出的队列结构
3. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次下载之间添加延时从而对爬虫进行限速
3. 处理相对路径。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接这种情况下需要将其与URL前缀进行拼接`urllib.parse`中的`urljoin()`函数可以完成此项操作)
4. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时爬虫就不再像队列中添加该网页中的链接了
4. 设置代理服务。有些网站会限制访问的区域例如美国的Netflix屏蔽了很多国家的访问有些爬虫需要隐藏自己的身份在这种情况下可以设置使用代理服务器代理服务器有免费的服务器和付费的商业服务器但后者稳定性和可用性都更好强烈建议在商业项目中使用付费的商业代理服务器。如果使用`requests`三方库,可以在请求方法中添加`proxies`参数来指定代理服务器;如果使用标准库,可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器
5. SSL相关问题。在使用`urlopen`打开一个HTTPS链接时会验证一次SSL证书如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”可以通过以下两种方式加以解决
5. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次获取页面数据之间添加延时从而对爬虫进行限速。
6. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时,爬虫就不再像队列中添加该网页中的链接了。
7. 避开蜜罐链接。网站上的有些链接是浏览器中不可见的这种链接通常是故意诱使爬虫去访问的蜜罐一旦访问了这些链接服务器就会判定请求是来自于爬虫的这样可能会导致被服务器封禁IP地址。如何避开这些蜜罐链接我们在后面为大家进行讲解。
8. SSL相关问题。如果使用标准库的`urlopen`打开一个HTTPS链接时会验证一次SSL证书如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”可以通过以下两种方式加以解决
- 使用未经验证的上下文

View File

@ -2,12 +2,12 @@
通过上一个章节的讲解,我们已经了解到了开发一个爬虫需要做的工作以及一些常见的问题,下面我们给出一个爬虫开发相关技术的清单以及这些技术涉及到的标准库和第三方库,稍后我们会一一介绍这些内容。
1. 下载数据 - **urllib** / **requests** / **aiohttp**
1. 下载数据 - **urllib** / **requests** / **aiohttp** / **httpx**
2. 解析数据 - **re** / **lxml** / **beautifulsoup4** / **pyquery**
3. 缓存和持久化 - **pymysql** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**
3. 缓存和持久化 - **mysqlclient** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**
4. 生成数字签名 - **hashlib**
5. 序列化和压缩 - **pickle** / **json** / **zlib**
6. 调度器 - 多进程(**multiprocessing** / 多线程(**threading**
6. 调度器 - **multiprocessing** / **threading** / **concurrent.futures**
### HTML页面
@ -79,47 +79,93 @@
</footer>
</div>
<script>
// 此处省略JavaScript代码
/* 此处省略JavaScript代码 */
</script>
</body>
</html>
```
果你对上面的代码并不感到陌生,那么你一定知道HTML页面通常由三部分构成分别是用来承载内容的Tag标签、负责渲染页面的CSS层叠样式表以及控制交互式行为的JavaScript。通常我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构当然我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
上所示的HTML页面通常由三部分构成分别是用来承载内容的Tag标签、负责渲染页面的CSS层叠样式表以及控制交互式行为的JavaScript。通常我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构当然我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
#### 使用requests获取页面
在上一节课的代码中我们使用了三方库`requests`来获取页面,下面我们对`requests`库的用法做进一步说明。
1. GET请求和POST请求。
```Python
import requests
resp = requests.get('http://www.baidu.com/index.html')
print(resp.status_code)
print(resp.headers)
print(resp.cookies)
print(resp.content.decode('utf-8'))
resp = requests.post('http://httpbin.org/post', data={'name': 'Hao', 'age': 40})
print(resp.text)
data = resp.json()
print(type(data))
```
2. URL参数和请求头。
```Python
resp = requests.get(
url='https://movie.douban.com/top250',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
)
print(resp.status_code)
```
3. 复杂的POST请求文件上传
```Python
resp = requests.post(
url='http://httpbin.org/post',
files={'file': open('data.xlsx', 'rb')}
)
print(resp.text)
```
4. 操作Cookie。
```Python
cookies = {'key1': 'value1', 'key2': 'value2'}
resp = requests.get('http://httpbin.org/cookies', cookies=cookies)
print(resp.text)
jar = requests.cookies.RequestsCookieJar()
jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
resp = requests.get('http://httpbin.org/cookies', cookies=jar)
print(resp.text)
```
5. 设置代理服务器。
```Python
requests.get('https://www.taobao.com', proxies={
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
})
```
> **说明**:关于`requests`库的相关知识,还是强烈建议大家自行阅读它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)。
6. 设置请求超时。
> 说明关于requests的详细用法可以参考它的[官方文档](http://docs.python-requests.org/zh_CN/latest/user/quickstart.html)。
```Python
requests.get('https://github.com', timeout=0.01)
```
### 页面解析
@ -131,13 +177,41 @@
| XPath解析 | lxml | 快 | 一般 | 需要安装C语言依赖库<br/>唯一支持XML的解析器 |
| CSS选择器解析 | bs4 / pyquery | 不确定 | 简单 | |
> 说明BeautifulSoup可选的解析器包括Python标准库html.parser、lxml的HTML解析器、lxml的XML解析器和html5lib
> **说明**`BeautifulSoup`可选的解析器包括Python标准库中的`html.parser`、`lxml`的HTML解析器、`lxml`的XML解析器和`html5lib`
#### 使用正则表达式解析页面
如果你对正则表达式没有任何的概念,那么推荐先阅读[《正则表达式30分钟入门教程》]()然后再阅读我们之前讲解在Python中如何使用正则表达式一文。
下面的例子演示了如何用正则表达式解析“豆瓣电影Top250”中的中文电影名称。
```Python
import random
import re
import time
import requests
PATTERN = re.compile(r'<a[^>]*?>\s*<span class="title">(.*?)</span>')
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
},
)
items = PATTERN.findall(resp.text)
for item in items:
print(item)
time.sleep(random.randint(1, 5))
```
#### XPath解析和lxml
@ -196,13 +270,37 @@ XPath还支持通配符用法如下所示。
| //title \| //price | 选取文档中的所有 title 和 price 元素。 |
| /bookstore/book/title \| //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
> 说明:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
> **说明**:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
当然如果不理解或者不太熟悉XPath语法可以在Chrome浏览器中按照如下所示的方法查看元素的XPath语法。
![](./res/douban-xpath.png)
下面的例子演示了如何用XPath解析“豆瓣电影Top250”中的中文电影名称。
```Python
from lxml import etree
import requests
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
)
html = etree.HTML(resp.text)
spans = html.xpath('/html/body/div[3]/div[1]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]')
for span in spans:
print(span.text)
```
### BeautifulSoup的使用
@ -219,42 +317,63 @@ BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库。它
- find / find_all
- select_one / select
> 说明更多内容可以参考BeautifulSoup的[官方文档](https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html)。
> **说明**更多内容可以参考BeautifulSoup的[官方文档](https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/)。
### PyQuery的使用
pyquery相当于jQuery的Python实现可以用于解析HTML网页。
### 实例 - 获取知乎发现上的问题链接
下面的例子演示了如何用CSS选择器解析“豆瓣电影Top250”中的中文电影名称。
```Python
from urllib.parse import urljoin
import random
import time
import re
import bs4
import requests
from bs4 import BeautifulSoup
for page in range(10):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={page * 25}',
headers={
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/83.0.4103.97 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
},
)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
elements = soup.select('.info>div>a')
for element in elements:
span = element.select_one('.title')
print(span.text)
time.sleep(random.random() * 5)
```
### 例子 - 获取知乎发现上的问题链接
```Python
import re
from urllib.parse import urljoin
import bs4
import requests
def main():
headers = {'user-agent': 'Baiduspider'}
proxies = {
'http': 'http://122.114.31.177:808'
}
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
resp = requests.get(seed_url,
headers=headers,
proxies=proxies)
soup = BeautifulSoup(resp.text, 'lxml')
resp = requests.get(urljoin(base_url, 'explore'), headers=headers)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
link_set = set()
links_set = set()
for a_tag in soup.find_all('a', {'href': href_regex}):
if 'href' in a_tag.attrs:
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
link_set.add(full_url)
print('Total %d question pages found.' % len(link_set))
links_set.add(full_url)
print('Total %d question pages found.' % len(links_set))
print(links_set)
if __name__ == '__main__':

View File

@ -2,60 +2,112 @@
### 存储海量数据
数据持久化的首选方案应该是关系型数据库关系型数据库的产品很多包括Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据文档数据库也是不错的选择MongoDB是文档数据库中的佼佼者之前我们已经讲解过MongDB的相关知识在此不再进行赘述
数据持久化的首选方案应该是关系型数据库关系型数据库的产品很多包括Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据文档数据库也是不错的选择MongoDB是文档数据库中的佼佼者有兴趣的读者可以自行研究
### 数据缓存
下面的代码演示了如何使用MySQL来保存从知乎发现上爬取到的链接和页面。
通过[《网络数据采集和解析》](./02.数据采集和解析.md)一文我们已经知道了如何从指定的页面中抓取数据以及如何保存抓取的结果但是我们没有考虑过这么一种情况就是我们可能需要从已经抓取过的页面中提取出更多的数据重新去下载这些页面对于规模不大的网站倒是问题也不大但是如果能够把这些页面缓存起来对应用的性能会有明显的改善。可以使用Redis来提供高速缓存服务关于Redis的知识我们在[《NoSQL入门》](../Day36-40/NoSQL入门.md)一文中已经做过简要的介绍。
```SQL
create database zhihu default charset utf8;
create user 'hellokitty'@'%' identified by 'Hellokitty.618';
grant all privileges on zhihu.* to 'hellokitty'@'%';
flush privileges;
### 实例 - 缓存知乎发现上的链接和页面代码
use zhihu;
create table `tb_explore`
(
`id` integer auto_increment,
`url` varchar(1024) not null,
`page` longblob not null,
`digest` char(48) unique not null,
`idate` datetime default now(),
primary key (`id`)
);
```
```Python
from hashlib import sha1
from urllib.parse import urljoin
import hashlib
import pickle
import re
import requests
import zlib
from urllib.parse import urljoin
from bs4 import BeautifulSoup
from redis import Redis
import MySQLdb
import bs4
import requests
conn = MySQLdb.connect(host='1.2.3.4', port=3306,
user='hellokitty', password='Hellokitty.618',
database='zhihu', charset='utf8',
autocommit=True)
def write_to_db(url, page, digest):
try:
with conn.cursor() as cursor:
cursor.execute(
'insert into tb_explore (url, page, digest) values (%s, %s, %s) ',
(url, page, digest)
)
except MySQLdb.MySQLError as err:
print(err)
def main():
# 指定种子页面
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
# 创建Redis客户端
client = Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
# 设置用户代理(否则访问会被拒绝)
headers = {'user-agent': 'Baiduspider'}
# 通过requests模块发送GET请求并指定用户代理
try:
resp = requests.get(seed_url, headers=headers)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
for a_tag in soup.find_all('a', {'href': href_regex}):
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
digest = hashlib.sha1(full_url.encode()).hexdigest()
html_page = requests.get(full_url, headers=headers).text
zipped_page = zlib.compress(pickle.dumps(html_page))
write_to_db(full_url, zipped_page, digest)
finally:
conn.close()
if __name__ == '__main__':
main()
```
### 数据缓存
通过[《网络数据采集和解析》](./67.数据采集和解析.md)一文我们已经知道了如何从指定的页面中抓取数据以及如何保存抓取的结果但是我们没有考虑过这么一种情况就是我们可能需要从已经抓取过的页面中提取出更多的数据重新去下载这些页面对于规模不大的网站倒是问题也不大但是如果能够把这些页面缓存起来对应用的性能会有明显的改善。下面的例子演示了如何使用Redis来缓存知乎发现上的页面。
```Python
import hashlib
import pickle
import re
import zlib
from urllib.parse import urljoin
import bs4
import redis
import requests
def main():
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore')
client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
headers = {'user-agent': 'Baiduspider'}
resp = requests.get(seed_url, headers=headers)
# 创建BeautifulSoup对象并指定使用lxml作为解析器
soup = BeautifulSoup(resp.text, 'lxml')
soup = bs4.BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question')
# 将URL处理成SHA1摘要(长度固定更简短)
hasher_proto = sha1()
# 查找所有href属性以/question打头的a标签
for a_tag in soup.find_all('a', {'href': href_regex}):
# 获取a标签的href属性值并组装完整的URL
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
# 传入URL生成SHA1摘要
hasher = hasher_proto.copy()
hasher.update(full_url.encode('utf-8'))
field_key = hasher.hexdigest()
# 如果Redis的键'zhihu'对应的hash数据类型中没有URL的摘要就访问页面并缓存
if not client.hexists('zhihu', field_key):
field_key = hashlib.sha1(full_url.encode()).hexdigest()
if not client.hexists('spider:zhihu:explore', field_key):
html_page = requests.get(full_url, headers=headers).text
# 对页面进行序列化和压缩操作
zipped_page = zlib.compress(pickle.dumps(html_page))
# 使用hash数据类型保存URL摘要及其对应的页面代码
client.hset('zhihu', field_key, zipped_page)
# 显示总共缓存了多少个页面
print('Total %d question pages found.' % client.hlen('zhihu'))
client.hset('spider:zhihu:explore', field_key, zipped_page)
print('Total %d question pages found.' % client.hlen('spider:zhihu:explore'))
if __name__ == '__main__':

View File

@ -1,14 +1,12 @@
## 并发下载
### 多线程和多进程回顾
在前面的[《进程和线程》](../Day01-15/Day13/进程和线程.md)一文中我们已经对在Python中使用多进程和多线程实现并发编程进行了简明的讲解在此我们补充几个知识点。
### 多线程和多进程补充知识点
#### threading.local类
使用线程时最不愿意遇到的情况就是多个线程竞争资源,在这种情况下为了保证资源状态的正确性,我们可能需要对资源进行加锁保护的处理,这一方面会导致程序失去并发性,另外如果多个线程竞争多个资源时,还有可能因为加锁方式的不当导致[死锁](https://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81)。要解决多个线程竞争资源的问题,其中一个方案就是让每个线程都持有资源的副本(拷贝),这样每个线程可以操作自己所持有的资源,从而规避对资源的竞争。
要实现将资源和持有资源的线程进行绑定的操作最简单的做法就是使用threading模块的local类在网络爬虫开发中就可以使用local类为每个线程绑定一个MySQL数据库连接或Redis客户端对象这样通过线程可以直接获得这些资源既解决了资源竞争的问题又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”Redis版的实例代码。
要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用`threading`模块的`local`类,在网络爬虫开发中,就可以使用`local`类为每个线程绑定一个MySQL数据库连接或Redis客户端对象这样通过线程可以直接获得这些资源既解决了资源竞争的问题又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”Redis版的实例代码。
#### concurrent.futures模块
@ -29,7 +27,7 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。
> 说明协程适合处理的是I/O密集型任务处理CPU密集型任务并不是它的长处如果要提升CPU的利用率可以考虑“多进程+协程”的模式。
> **说明**协程适合处理的是I/O密集型任务处理CPU密集型任务并不是它擅长的如果要提升CPU的利用率可以考虑“多进程+多线程”或者“多进程+协程”的工作模式。
#### 历史回顾
@ -39,176 +37,39 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
4. Python 3.4:引入`asyncio.coroutine`装饰器用来标记作为协程的函数,协程函数和`asyncio`及其事件循环一起使用来实现异步I/O操作。
5. Python 3.5:引入了`async`和`await`,可以使用`async def`来定义一个协程函数,这个函数中不能包含任何形式的`yield`语句,但是可以使用`return`或`await`从协程中返回值。
#### 示例代码
协程实现了协作式并发通过提高CPU的利用率来达到改善性能的目的。著名的三方库[`aiohttp`](https://github.com/aio-libs/aiohttp)就是通过协程的方式实现了HTTP客户端和HTTP服务器的功能较之`requests`有更好的获取数据的性能,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
1. 生成器 - 数据的生产者。
```Python
import asyncio
import aiohttp
```Python
from time import sleep
# 倒计数生成器
def countdown(n):
while n > 0:
yield n
n -= 1
def main():
for num in countdown(5):
print(f'Countdown: {num}')
sleep(1)
print('Countdown Over!')
if __name__ == '__main__':
main()
```
生成器还可以叠加来组成生成器管道,代码如下所示。
async def download(url):
print('Fetch:', url)
async with aiohttp.ClientSession() as session:
async with session.get(url, ssl=False) as resp:
print(url, '--->', resp.status)
print(url, '--->', resp.headers)
print('\n\n', await resp.text())
```Python
# Fibonacci数生成器
def fib():
a, b = 0, 1
while True:
a, b = b, a + b
yield a
# 偶数生成器
def even(gen):
for val in gen:
if val % 2 == 0:
yield val
def main():
gen = even(fib())
for _ in range(10):
print(next(gen))
if __name__ == '__main__':
main()
```
2. 协程 - 数据的消费者。
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com/',
'http://www.sina.com.cn/',
'https://www.taobao.com/',
'http://jd.com/'
]
tasks = [download(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
```Python
from time import sleep
# 生成器 - 数据生产者
def countdown_gen(n, consumer):
consumer.send(None)
while n > 0:
consumer.send(n)
n -= 1
consumer.send(None)
# 协程 - 数据消费者
def countdown_con():
while True:
n = yield
if n:
print(f'Countdown {n}')
sleep(1)
else:
print('Countdown Over!')
def main():
countdown_gen(5, countdown_con())
if __name__ == '__main__':
main()
```
> 说明上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器通俗的说就是让生成器执行到有yield关键字的地方挂起当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。
```Python
from functools import wraps
def coroutine(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
gen = fn(*args, **kwargs)
next(gen)
return gen
return wrapper
```
这样就可以使用`@coroutine`装饰器对协程进行预激操作,不需要再写重复代码来激活协程。
3. 异步I/O - 非阻塞式I/O操作。
```Python
import asyncio
@asyncio.coroutine
def countdown(name, n):
while n > 0:
print(f'Countdown[{name}]: {n}')
yield from asyncio.sleep(1)
n -= 1
def main():
loop = asyncio.get_event_loop()
tasks = [
countdown("A", 10), countdown("B", 5),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
4. `async`和`await`。
```Python
import asyncio
import aiohttp
async def download(url):
print('Fetch:', url)
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
print(url, '--->', resp.status)
print(url, '--->', resp.cookies)
print('\n\n', await resp.text())
def main():
loop = asyncio.get_event_loop()
urls = [
'https://www.baidu.com',
'http://www.sohu.com/',
'http://www.sina.com.cn/',
'https://www.taobao.com/',
'https://www.jd.com/'
]
tasks = [download(url) for url in urls]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
```
上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库它实现了HTTP客户端和HTTP服务器的功能对异步操作提供了非常好的支持有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
if __name__ == '__main__':
main()
```
### 实例 - 多线程爬取“手机搜狐网”所有页面

View File

@ -79,4 +79,4 @@ selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executabl
export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
```
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。当然更为简单的办法是把chromedriver直接放在虚拟环境中跟Python解释器位于同一个路径下就可以了。

View File

@ -1 +0,0 @@
## 爬虫项目实战

Some files were not shown because too many files have changed in this diff Show More