Python知识学习回顾
01.celery
必备: - celery模块 - pip3 install celery - 放置消息的队列:rabbmitMQ/redis 总结: 1. celery是一个基于Python实现的用于完成任务处理的组件。 2. celery依赖: - redis/rabbmitMQ - Python操作redis/rabbmitMQ的模块 3. 至少有两个程序: - worker,处理任务 - 命令:celery worker -A s1 -l info - 命令:celery worker -A celery_tasks -l info - 程序 - 添加任务 - 获取任务ID - 检查任务状态+ 获取任务结果 - 根据任务ID 4. 应用: - 上述示例可以应用到任务框架 - 特殊的django 5. @shared_task/@app1.task的区别? 6. flask示例 公司开发环境: 1. windows - 在windows上开发【坑】 - 代码部署在linux :centos 2. 双系统 - windows - linux: ubuntu+桌面版 - linux: centos+桌面版 - 代码部署在linux :centos 3. mac - linux:mac - 代码部署在linux :centos 4. vim开发 - 通过vim在:centos - 代码部署在linux :centos
02.Linux
Linux - CentOS 7.4 基础命令 系统优化+定时任务 nginx nginx+py搭建网站 Linux vmware fusion(mac) vmware workstation 12.0 (windows) 8.0 远程连接工具:xshell (windows) mac(iterm2) android juiceSSH IOS termius Linux Fedora Red Hat Enterprise Linux RHEL 7.5 CentOS 红帽收费去掉,logo去掉 Debian Ubuntu SUSE OpenSUSE https://mirrors.aliyun.com/centos/ F1 F2 F10 F12 intel virtual technology disabled(关闭) enabled(开启) intel vt 网卡名称 : eth0 ensxxx vmware相关服务 要开启 win+r 输入 services.msc VMware Authorization Service 正在运行/已启动 自动 VMware DHCP Service 正在运行/已启动 自动 VMware NAT Service 正在运行/已启动 自动 远程连接服务器 : 10.0.0.128 Xshell 免费 SecureCRT putty 屌丝去洗浴中心之路 1.道路是否通畅 你到服务器之间 本地Shell-CMD-windows 2.是否有人劫财劫色 3.是否提供特殊服务 1.道路是否通畅 你到服务器之间 本地Shell-CMD-windows [d:\~]$ ping 10.0.0.128 正在 Ping 10.0.0.128 具有 32 字节的数据: 来自 10.0.0.128 的回复: 字节=32 时间<1ms TTL=64 来自 10.0.0.128 的回复: 字节=32 时间<1ms TTL=64 来自 10.0.0.128 的回复: 字节=32 时间<1ms TTL=64 来自 10.0.0.128 的回复: 字节=32 时间<1ms TTL=64 10.0.0.128 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 0ms,最长 = 0ms,平均 = 0ms [d:\~]$ ping 10.0.0.130 正在 Ping 10.0.0.130 具有 32 字节的数据: 来自 10.0.0.1 的回复: 无法访问目标主机。 请求超时。 请求超时。 请求超时。 10.0.0.130 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 1,丢失 = 3 (75% 丢失), 2.是否有人劫财劫色 SeLinux 防火墙 3. 服务名称 搓澡 按摩 特殊服务 暗号(端口号) 80 443 22 远程连接服务 sshd 22 telnet 10.0.0.128 22 [d:\~]$ telnet 10.0.0.128 22 Connecting to 10.0.0.128:22... Connection established. To escape to local shell, press 'Ctrl+Alt+]'. SSH-2.0-OpenSSH_7.4 Protocol mismatch. Connection closed by foreign host. Disconnected from remote host(10.0.0.128:22) at 12:17:03. Type `help' to learn how to use Xshell prompt. [d:\~]$ telnet 10.0.0.128 25 Connecting to 10.0.0.128:25... Could not connect to '10.0.0.128' (port 25): Connection failed. Type `help' to learn how to use Xshell prompt. 道路不通排查过程 1.ip地址 2.vmware 编辑-虚拟网络编辑器 3.windows 服务 vmware相关服务 要开启 win+r 输入 services.msc VMware Authorization Service 正在运行/已启动 自动 VMware DHCP Service 正在运行/已启动 自动 VMware NAT Service 正在运行/已启动 自动 4.我的电脑/此电脑/文件夹 输入 网络连接 通过安装包修复 CCleaner 把vmware相关信息 清理掉 重新安装 h 总结: 1.创建虚拟机-部署CentOS 7.4 2.配置VMware虚拟网络编辑器 3.通过Xshell连接虚拟机 4.无法连接服务器排查过程(屌丝去洗浴中心之路) 5.xshell优化 下午: 操作与命令 空格和tab键 [root@oldboyedu-s8 ~]# #mkdir make directory [root@oldboyedu-s8 ~]# mkdir /data [root@oldboyedu-s8 ~]# #显示目录的内容 [root@oldboyedu-s8 ~]# #ls list [root@oldboyedu-s8 ~]# ls /data/ [root@oldboyedu-s8 ~]# ls -l /data/ total 0 相对路径与绝对路径: 绝对路径:从根开始的路径(位置) /data etc/hosts 从根开始的路径就是绝对路径。 [root@oldboyedu-s8 data]# touch /data/oldboy.txt [root@oldboyedu-s8 data]# ls -l /data/ total 0 -rw-r--r--. 1 root root 0 Apr 25 15:16 oldboy.txt 第1个里程碑-打开文件 vi /data/oldboy.txt 第2个里程碑-编辑文件 按i 进入到编辑模式 第3个里程碑-退出编辑模式 按esc退出编辑模式 第4个里程碑-保存退出 :wq write quit #保存退出 :q! #强制退出不保存 第5个里程碑-显示文件内容 [root@oldboyedu-s8 data]# cat /data/oldboy.txt I am studying linux. vi/vim 快捷键 复制 yy 粘贴 p 删除、剪切 dd 撤销 u 把光标所在行到文件最后一行删除 dG 移动光标 把光标移动到文件的最后一行 G 把光标移动到文件的第一行 gg #I am studying linux.I am studying linux.I am studying linux. #I am studying linux.I am studying linux.I am studying linux. #I am studying linux.I am studying linux.I am studying linux. #I am studying linux.I am studying linux.I am studying linux. 批量删除 第1个里程碑-按ctrl + v 进入批量编辑模式 第2个里程碑-选择 第3个里程碑-删除 d [root@oldboyedu-s8 data]# #copy cp [root@oldboyedu-s8 data]# [root@oldboyedu-s8 data]# cp /data/oldboy.txt /tmp/ [root@oldboyedu-s8 data]# ls -l /tmp/ total 8 -rwx------. 1 root root 836 Apr 25 11:03 ks-script-6cg4Xy -rw-r--r--. 1 root root 183 Apr 25 15:56 oldboy.txt drwx------. 3 root root 17 Apr 25 11:18 systemd-private-241350d318404b8eb4e0324ead618b12-chronyd.service-tG7NpS drwx------. 3 root root 17 Apr 25 11:18 systemd-private-241350d318404b8eb4e0324ead618b12-vgauthd.service-bhBRbW drwx------. 3 root root 17 Apr 25 11:18 systemd-private-241350d318404b8eb4e0324ead618b12-vmtoolsd.service-mHP4YZ -rw-------. 1 root root 0 Apr 25 10:58 yum.log 2.5 把 /data 移动到 /root目录下面 move mv [root@oldboyedu-s8 data]# mv /data/ /root/ [root@oldboyedu-s8 data]# ls -l /data ls: cannot access /data: No such file or directory [root@oldboyedu-s8 data]# ls -l /r root/ run/ [root@oldboyedu-s8 data]# ls -l /root/ total 4 -rw-------. 1 root root 1233 Apr 25 11:03 anaconda-ks.cfg drwxr-xr-x. 2 root root 46 Apr 25 16:00 data 进入/root目录下的data目录,删除oldboy.txt文件 remove rm [root@oldboyedu-s8 tmp]# cd /root/ [root@oldboyedu-s8 ~]# ls -l total 4 -rw-------. 1 root root 1233 Apr 25 11:03 anaconda-ks.cfg drwxr-xr-x. 2 root root 28 Apr 25 16:32 data [root@oldboyedu-s8 ~]# pwd /root [root@oldboyedu-s8 ~]# rm -f data rm: cannot remove ‘data’: Is a directory [root@oldboyedu-s8 ~]# ls -l total 4 -rw-------. 1 root root 1233 Apr 25 11:03 anaconda-ks.cfg drwxr-xr-x. 2 root root 28 Apr 25 16:32 data [root@oldboyedu-s8 ~]# rm -r data rm: descend into directory ‘data’? y rm: remove regular file ‘data/oldboy.txt.bak’? n rm: remove directory ‘data’? n [root@oldboyedu-s8 ~]# rm -rf data [root@oldboyedu-s8 ~]# find /root/ -type f -name "oldboy.txt" /root/oldboy.txt [root@oldboyedu-s8 ~]# [root@oldboyedu-s8 ~]# [root@oldboyedu-s8 ~]# [root@oldboyedu-s8 ~]# find /root/ -type f -name "*.txt" /root/oldboy.txt #*.txt 以.txt 结尾的文件 #find /root/ -type f -name "*.txt" # 在哪里找 -什么类型 f(file) # d(dir) 在/etc 找出以eth0结尾的文件 http://blog.51cto.com/lidao/1927347 老男孩教育每日一题-2017年5月18日-说说|(管道)与|xargs(管道xargs)的区别 [root@oldboyedu-s8 ~]# find /root/ -type f -name "*.txt" /root/oldboy.txt /root/alex.txt [root@oldboyedu-s8 ~]# find /root/ -type f -name "*.txt" |xargs ls -l -rw-r--r--. 1 root root 0 Apr 25 17:01 /root/alex.txt -rw-r--r--. 1 root root 0 Apr 25 17:01 /root/oldboy.txt # .. 当前目录的上一级目录 # . 当前目录 [root@oldboyedu-s8 sysconfig]# cd /etc/sysconfig/network-scripts/ [root@oldboyedu-s8 network-scripts]# cd ../../../../../../../../../ [root@oldboyedu-s8 ~]# cat /data/test.txt test liyao oldboy [root@oldboyedu-s8 ~]# #在文件中过滤 [root@oldboyedu-s8 ~]# #显示出文件中 你想要的或不想要的内容 [root@oldboyedu-s8 ~]# grep "oldboy" /data/test.txt oldboy [root@oldboyedu-s8 ~]# grep -v "oldboy" /data/test.txt test liyao [root@oldboyedu-s8 ~]# head -2 /data/test.txt test liyao 2.10 已知/tmp下已经存在test.txt文件,如何执行命令才能把/mnt/test.txt拷贝到/tmp下覆盖掉/tmp/test.txt,而让系统不提示是否覆盖(root权限下)。 \cp /tmp/test.txt /mnt/ #cp cp -i #别名 [root@oldboyedu-s8 ~]# alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde' #\cp 临时取消cp命令的别名 #别名相当于给命令设置了 昵称 或 快捷键 2.11 只查看ett.txt文件(共100行)内第20到第30行的内容 seq 40 > /oldboy/ett.txt cat /oldboy/ett.txt #sed默认输出文件的每一行 [root@oldboyedu-s8 oldboy]# sed -n '20p' ett.txt 20 #-n 取消默认输出 [root@oldboyedu-s8 oldboy]# [root@oldboyedu-s8 oldboy]# sed -n '20,30p' ett.txt 20 21 22 23 24 25 26 27 28 29 30 2.12 把/oldboy目录及其子目录下所有以扩展名 .sh结尾的文件中,文件包含./hostlists.txt(oldboy)的字符串全部替换为../idctest_iplist(oldgirl) [root@oldboyedu-s8 oldboy]# find /oldboy/ -type f -name "*.sh" /oldboy/test/del.sh /oldboy/test.sh /oldboy/t.sh [root@oldboyedu-s8 oldboy]# #把一个文件的oldboy替换为 oldgirl [root@oldboyedu-s8 oldboy]# #替换 [root@oldboyedu-s8 oldboy]# sed 's#oldboy#oldgirl#g' /oldboy/t.sh oldgirl [root@oldboyedu-s8 oldboy]# cat /oldboy/t.sh [root@oldboyedu-s8 oldboy]# cat /oldboy/t.sh oldboy [root@oldboyedu-s8 oldboy]# sed 's#oldboy#oldgirl#g' /oldboy/t.sh oldgirl 第1个里程碑-找出想要的文件 find /oldboy/ -type f -name "*.sh" 第2个里程碑-替换1个文件的内容 sed 's#oldboy#oldgirl#g' /oldboy/t.sh 第3个里程碑-把find命令找出的文件交给sed find /oldboy/ -type f -name "*.sh" |xargs sed 's#oldboy#oldgirl#g' [root@oldboyedu-s8 oldboy]# find /oldboy/ -type f -name "*.sh" /oldboy/test/del.sh /oldboy/test.sh /oldboy/t.sh [root@oldboyedu-s8 oldboy]# sed 's#oldboy#oldgirl#g' /oldboy/t.sh oldgirl [root@oldboyedu-s8 oldboy]# find /oldboy/ -type f -name "*.sh" |xargs sed 's#oldboy#oldgirl#g' oldgirl oldgirl oldgirl [root@oldboyedu-s8 oldboy]# cat /oldboy/t.sh oldboy [root@oldboyedu-s8 oldboy]# find /oldboy/ -type f -name "*.sh" |xargs sed -i 's#oldboy#oldgirl#g' [root@oldboyedu-s8 oldboy]# find /oldboy/ -type f -name "*.sh" |xargs cat oldgirl oldgirl oldgirl [root@oldboyedu-s8 network-scripts]# cd /etc/sysconfig/network-scripts/ [root@oldboyedu-s8 network-scripts]# pwd /etc/sysconfig/network-scripts [root@oldboyedu-s8 network-scripts]# cd /tmp/ [root@oldboyedu-s8 tmp]# cd - /etc/sysconfig/network-scripts [root@oldboyedu-s8 network-scripts]# #cd - 快速进入你上一次的位置 从哪里来回哪里去 [root@oldboyedu-s8 network-scripts]# cd - /tmp [root@oldboyedu-s8 tmp]# cat -n /root/oldboy.txt 1 oldboy 2 oldboy 3 oldboy 4 oldboy 5 oldboy vi/vim nu==== number :set nu 显示行号 :set nonu 取消显示行号 windows 创建压缩包 linux 打包压缩 创建一个压缩包 tar zcf /tmp/etc.tar.gz /etc z=== 通过gzip软件进行压缩 c=== create 创建 f=== file 指定压缩包 [root@oldboyedu-s8 tmp]# tar zcf /tmp/etc.tar.gz /etc/ tar: Removing leading `/' from member names [root@oldboyedu-s8 tmp]# ll /tmp/etc.tar.gz -rw-r--r--. 1 root root 9916889 Apr 25 19:20 /tmp/etc.tar.gz 查看压缩包内容 tar tf /tmp/etc.tar.gz t === list 显示压缩包内容 解压 [root@oldboyedu-s8 tmp]# cd /tmp/ [root@oldboyedu-s8 tmp]# tar xf etc.tar.gz 解压到指定位置 [root@oldboyedu-s8 tmp]# tar xf etc.tar.gz -C /mnt/ [root@oldboyedu-s8 tmp]# ls -l /mnt/ total 12 drwxr-xr-x. 80 root root 8192 Apr 25 16:30 etc -rw-r--r--. 1 root root 0 Apr 25 18:06 test.txt 总结: 1.创建虚拟机-部署CentOS 7.4 2.配置VMware虚拟网络编辑器 3.通过Xshell连接虚拟机 4.无法连接服务器排查过程(屌丝去洗浴中心之路) 5.xshell优化 6.必知必会命令 根据题目练习 7.find命令 8.打包压缩 9.三剑客 grep sed 预习: 0.ping baidu.com 1.单引号 双引号 区别 2.linux启动过程 运行级别 3.如何关闭Selinux和防火墙 4.定时任务 同步系统时间 5.nginx
Linux - CentOS 7.4 基础命令 系统优化+定时任务 nginx 1.虚拟机可以联网 ping baidu.com 2.linux下面安装软件 1)通过yum安装软件 需要你联网 2) 更改系统的yum源 阿里云 #https://opsx.alibaba.com/mirror 来源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 3)安装常用软件 yum install -y tree bash-completion wget vim Linux无法上网排查过程 1. 查看是否能上网 [root@bigdata ~]# ping baidu.com connect: 网络不可达 2. 验证是否DNS 域名解析 域名---->ip地址 [root@bigdata ~]# ping 223.5.5.5 connect: 网络不可达 3. 网关-验证你的网络配置(网卡 虚拟机vmwarexxx)是否有问题 ip r [root@bigdata ~]# ping 10.0.0.2 PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data. 64 bytes from 10.0.0.2: icmp_seq=1 ttl=128 time=0.206 ms 64 bytes from 10.0.0.2: icmp_seq=2 ttl=128 time=0.170 ms ^C --- 10.0.0.2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1000ms rtt min/avg/max/mdev = 0.170/0.188/0.206/0.018 ms 使用CentOS7.4 光盘镜像 1.把光盘插入到系统 2.在linux使用光盘 [root@oldboyedu-s8 ~]# mount /dev/cdrom /mnt/ mount: /dev/sr0 is write-protected, mounting read-only [root@oldboyedu-s8 ~]# #mount 给/dev/cdrom 创建一个入口 /mnt [root@oldboyedu-s8 ~]# cd /mnt/ [root@oldboyedu-s8 mnt]# ls -l total 664 -rw-rw-r--. 3 root root 14 Sep 5 2017 CentOS_BuildTag drwxr-xr-x. 3 root root 2048 Sep 5 2017 EFI -rw-rw-r--. 3 root root 227 Aug 30 2017 EULA -rw-rw-r--. 3 root root 18009 Dec 10 2015 GPL drwxr-xr-x. 3 root root 2048 Sep 5 2017 images drwxr-xr-x. 2 root root 2048 Sep 5 2017 isolinux drwxr-xr-x. 2 root root 2048 Sep 5 2017 LiveOS drwxrwxr-x. 2 root root 641024 Sep 5 2017 Packages drwxr-xr-x. 2 root root 4096 Sep 5 2017 repodata -rw-rw-r--. 3 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-7 -rw-rw-r--. 3 root root 1690 Dec 10 2015 RPM-GPG-KEY-CentOS-Testing-7 -r--r--r--. 1 root root 2883 Sep 6 2017 TRANS.TBL [root@oldboyedu-s8 mnt]# ls -l /mnt/Packages/ Display all 3895 possibilities? (y or n) [root@oldboyedu-s8 mnt]# ls -l /mnt/Packages/bash-completion-2.1-6.el7.noarch.rpm -rw-rw-r--. 2 root root 87272 Jul 4 2014 /mnt/Packages/bash-completion-2.1-6.el7.noarch.rpm [root@oldboyedu-s8 mnt]# rpm -ivh /mnt/Packages/bash-completion-2.1-6.el7.noarch.rpm Preparing... ################################# [100%] package bash-completion-1:2.1-6.el7.noarch is already installed [root@oldboyedu-s8 mnt]# ll /mnt/Packages/telnet- telnet-0.17-64.el7.x86_64.rpm telnet-server-0.17-64.el7.x86_64.rpm [root@oldboyedu-s8 mnt]# ll /mnt/Packages/telnet-0.17-64.el7.x86_64.rpm -rw-rw-r--. 2 root root 65632 Aug 11 2017 /mnt/Packages/telnet-0.17-64.el7.x86_64.rpm 1.Linux下面安装软件: yum 通过光盘安装 yum yum install rpm -ivh 编译安装 三部曲: 备菜切菜 做菜 上菜 nginx ./configure make make install 2.如何使用光盘安装软件 3.Linux无法上网排查过程 系统优化+定时任务 1.更改系统的yum源 #阿里云 mirrors.aliyun.com https://opsx.alibaba.com/mirror #清华 https://mirrors.tuna.tsinghua.edu.cn/help/centos/ #网易 http://mirrors.163.com/.help/centos.html 云服务 物理服务器 2.关闭SElinux rpm -qa #-qa query all rpm -qa 1#永久 修改配置文件 重启服务器之后生效 # enforcing - 已开启 正在运行 # permissive - selinux关闭 警告提示 # disabled - 彻底关闭 SELINUX=enforcing | ↓ SELINUX=disabled cp /etc/selinux/config /etc/selinux/config.bak #快捷键:esc + .(点) 使用上一个命令的最后一个东西(参数) sed 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config [root@oldboyedu-s8 ~]# cat /etc/selinux/config # This file controls the state of SELinux on the system. # SELINUX= can take one of these three values: # enforcing - SELinux security policy is enforced. # permissive - SELinux prints warnings instead of enforcing. # disabled - No SELinux policy is loaded. SELINUX=disabled # SELINUXTYPE= can take one of three two values: # targeted - Targeted processes are protected, # minimum - Modification of targeted policy. Only selected processes are protected. # mls - Multi Level Security protection. SELINUXTYPE=targeted 2#临时 重启服务器之后失效 [root@oldboyedu-s8 ~]# getenforce Enforcing [root@oldboyedu-s8 ~]# #显示当前selinux [root@oldboyedu-s8 ~]# #显示当前selinux的运行状态 [root@oldboyedu-s8 ~]# setenforce usage: setenforce [ Enforcing | Permissive | 1 | 0 ] [root@oldboyedu-s8 ~]# setenforce 0 [root@oldboyedu-s8 ~]# getenforce Permissive 小结: selinux关闭 0.操作前备份操作后检查 1.修改配置文件 2.命令行 防火墙 iptables firewalld [root@oldboyedu-s8 ~]# rpm -qa bash-completion bash-completion-2.1-6.el7.noarch #查询防火墙状态 systemctl status firewalld.service #当前正在运行的防火墙 --- 临时 systemctl stop firewalld.service #让防火墙不会开机自启动 --- 永久 systemctl disable firewalld.service #systemctl is-active firewalld.service #is-active 是否正在运行 是否健在 #systemctl is-enabled firewalld.service #is-enabled 是否开机自启动 [root@oldboyedu-s8 ~]# [root@oldboyedu-s8 ~]# systemctl is-active firewalld.service unknown [root@oldboyedu-s8 ~]# systemctl is-active crond.service active [root@oldboyedu-s8 ~]# systemctl is-enabled firewalld.service disabled [root@oldboyedu-s8 ~]# systemctl start firewalld.service [root@oldboyedu-s8 ~]# systemctl is-active firewalld.service active [root@oldboyedu-s8 ~]# [root@oldboyedu-s8 ~]# systemctl enable firewalld.service Created symlink from /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service to /usr/lib/systemd/system/firewalld.service. Created symlink from /etc/systemd/system/multi-user.target.wants/firewalld.service to /usr/lib/systemd/system/firewalld.service. [root@oldboyedu-s8 ~]# systemctl is-enabled firewalld.service enabled 小结: systemctl 管理服务 如何关闭防火墙 systemctl stop firewalld systemctl disable firewalld systemctl status firewalld systemctl is-active firewalld systemctl is-enabled firewalld systemctl enable firewalld.service crond 定时任务 同步系统时间 √ 备份 检查crond状态 [root@oldboyedu-s8 ~]# rpm -qa cronie cronie-1.4.11-17.el7.x86_64 [root@oldboyedu-s8 ~]# systemctl is-active crond.service active [root@oldboyedu-s8 ~]# systemctl is-enabled crond.service enabled #定时任务管理命令 [root@oldboyedu-s8 ~]# crontab -l no crontab for root [root@oldboyedu-s8 ~]# crontab -e #oldboyedu.com #-e edit #-l list 定时任务格式 每天的早上8:30到学校上车(go to school) 30 08 * * * go to school 每天的晚上12点整回家自己开车(go to bed) 00 00 * * * go to bed 如何修改系统时间 [root@oldboyedu-s8 ~]# date Thu Apr 26 12:13:53 CST 2018 [root@oldboyedu-s8 ~]# date -s "20180101 01:01:01" Mon Jan 1 01:01:01 CST 2018 [root@oldboyedu-s8 ~]# date Mon Jan 1 01:01:05 CST 2018 让系统自动同步时间 yum install ntpdate -y [root@oldboyedu-s8 ~]# ntpdate ntp1.aliyun.com 26 Apr 12:19:53 ntpdate[18819]: step time server 182.92.12.11 offset 9976502.795919 sec [root@oldboyedu-s8 ~]# date Thu Apr 26 12:20:24 CST 2018 ntpdate ntp1.aliyun.com ntpdate ntp2.aliyun.com ntpdate ntp3.aliyun.com ntpdate ntp4.aliyun.com ntpdate ntp5.aliyun.com ntpdate ntp6.aliyun.com ntpdate ntp7.aliyun.com #显示命令的绝对路径 [root@oldboyedu-s8 ~]# which ntpdate /usr/sbin/ntpdate [root@oldboyedu-s8 ~]# find / -type f -name "ntpdate" /etc/sysconfig/ntpdate /usr/sbin/ntpdate #每2分钟同步一次系统时间 ###1.命令行测试 [root@oldboyedu-s8 ~]# /usr/sbin/ntpdate ntp1.aliyun.com 26 Apr 12:28:11 ntpdate[19018]: adjust time server 182.92.12.11 offset 0.000723 sec ###2.命令写入定时任务 [root@oldboyedu-s8 ~]# crontab -l #sync time */2 * * * * /usr/sbin/ntpdate ntp1.aliyun.com ###3.进行检查与测试 [root@oldboyedu-s8 ~]# date -s "20190101" Tue Jan 1 00:00:00 CST 2019 [root@oldboyedu-s8 ~]# date Tue Jan 1 00:00:01 CST 2019 [root@oldboyedu-s8 ~]# date Thu Apr 26 12:31:25 CST 2018 总结: 1.安装常用软件 yum rpm 编译安装 2.linux优化 1)yum源配置 增加常用yum源 : epel源 yum install wget -y wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo yum repolist yum install sl cowsay -y 2)关闭selinux和防火墙 3.定时任务 执行命令 下午: nginx 作用: 1.web网站 2.nginx+其他fastcgi(php)/ uwsgi(python) 部署nginx 1#下载nginx wget http://nginx.org/download/nginx-1.12.2.tar.gz yum install pcre-devel openssl-devel -y #编译安装三部曲 : ./configure make make install tar xf nginx-1.12.2.tar.gz cd nginx-1.12.2 ./configure --prefix=/application/nginx-1.12.2 --with-http_stub_status_module --with-http_ssl_module make make install 故障1: error: the HTTP rewrite module requires the PCRE library. yum install pcre-devel -y [root@oldboyedu-s8 nginx-1.12.2]# ll /application/nginx-1.12.2/ total 4 drwxr-xr-x. 2 root root 4096 Apr 26 15:55 conf #configure nginx配置文件 drwxr-xr-x. 2 root root 40 Apr 26 15:55 html #站点目录 网站根目录 drwxr-xr-x. 2 root root 6 Apr 26 15:55 logs #日志 drwxr-xr-x. 2 root root 19 Apr 26 15:55 sbin #nginx管理命令 #检查语法 /application/nginx-1.12.2/sbin/nginx -t #启动nginx /application/nginx-1.12.2/sbin/nginx /application/nginx-1.12.2/sbin/nginx -s reload #nginx配置说明 nginx.conf #nginx配置文件 nginx.conf.default # #对比两个文件区别 diff conf/nginx.conf conf/nginx.conf.default egrep -v "#|^$" /application/nginx-1.12.2/conf/nginx.conf.default >/application/nginx-1.12.2/conf/nginx.conf 1 worker_processes 1; 2 events { 3 worker_connections 1024; 4 } 5 http { 6 include mime.types; #媒体类型 7 default_type application/octet-stream; 8 sendfile on; #开启高效的传输模式 9 keepalive_timeout 65; #超时时间 10 server { #一个server相当于是一个网站 虚拟主机 11 listen 80; #监听的端口 12 server_name www.etiantian.org; #网站名字 域名 13 location / { 14 root html; #根 站点的根目录 15 index index.html index.htm; #首页文件 16 } 21 } 22 } 总结: 1.nginx部署 配置 2.定时任务 3.系统优化 配置和增加yum源 关闭selinux和防火墙 4.安装软件方法 5.linux下面如何使用光盘 再约: 1.redis 2.lnmp(python)
03.redis
操作系统本身的内存管理机制: 1、分配内存 slab 2、浪费 buddy system 3、回收 buddy system LRU ========================================= redis安装配置 下载: wget http://download.redis.io/releases/redis-3.2.10.tar.gz 解压: tar xzf redis-3.2.10.tar.gz mv redis-3.2.10 redis 安装: cd redis make 启动: src/redis-server & -------- 配置文件的使用 vi /etc/redis.conf 是否后台运行: daemonize yes 默认端口: port 6379 日志文件位置 logfile /var/log/redis.log RDB持久化数据文件: dbfilename dump.rdb 持久化文件的位置: dir /data/redis mkdir -p /data/redis /application/redis/src/redis-server /etc/redis.conf /application/redis/src/redis-cli -p 1111 安全配置 Bind 指定IP进行监听 bind 10.0.0.200 127.0.0.1 禁止protected-mode protected-mode yes/no (保护模式,是否只允许本地访问) 增加requirepass {password} requirepass root 在redis-cli中使用 auth {password} 进行认证 修改安全配置 vi /etc/redis.conf 添加以下配置: bind 10.0.0.200 127.0.0.1 requirepass 123 添加完成后重启redis [root@oldboyedu-s6 ~]# /application/redis/src/redis-cli -p 1111 127.0.0.1:1111> shutdown /application/redis/src/redis-server /etc/redis.conf 登录测试: /application/redis/src/redis-cli -h 10.0.0.200 -p 1111 /application/redis/src/redis-cli -h 10.0.0.200 -a 123 -p 1111 或者: /application/redis/src/redis-cli -h 10.0.0.200 -p 1111 auth 123 在线修改配置 获取当前配置 CONFIG GET * 变更运行配置 CONFIG SET requirepass 123456 /application/redis/src/redis-cli -a 123456 -h 10.0.0.200 -p 1111 ------------------- redis持久化 RDB持久化 基于时间点快照的方式,复用方式进行数据持久化 比较常用的方式,效率较高,安全性相对较低 在 /etc/redis.conf中添加以下内容 dbfilename dump.rdb dir /data/redis save 900 1 save 300 10 save 60 10000 rdb文件名 rdb的放置路径 900秒(15分钟)内有1个更改 300秒(5分钟)内有10个更改 60秒内有10000个更改 AOF 只追加的方式记录所有redis中执行过的修改类命令 效率相对较低,安全性较高 appendonly yes appendfsync always或者everysec或者no ------------------------------ 数据类型: Key:value(string\hash\list\set\Sset) -----全局类操作 KEYS * 查看KEY支持通配符 DEL 删除给定的一个或多个key EXISTS 检查是否存在 RENAME 变更KEY名 TYPE 返回键所存储值的类型 EXPIRE\ PEXPIRE 以秒\毫秒设定生存时间 TTL\ PTTL 以秒\毫秒为单位返回生存时间 PERSIST 取消生存实现设置 --------string--------- 增 set mykey "test" 为键设置新值,并覆盖原有值 getset mycounter 0 设置值,取值同时进行 setex mykey 10 "hello" 设置指定 Key 的过期时间为10秒,在存活时间可以获取value setnx mykey "hello" 若该键不存在,则为键设置新值 mset key3 "zyx" key4 "xyz" 批量设置键 删 del mykey 删除已有键 改 append mykey "hello" 若该键并不存在,返回当前 Value 的长度 该键已经存在,返回追加后 Value的长度 incr mykey 值增加1,若该key不存在,创建key,初始值设为0,增加后结果为1 decrby mykey 5 值减少5 setrange mykey 20 dd 把第21和22个字节,替换为dd, 超过value长度,自动补0 查 exists mykey 判断该键是否存在,存在返回 1,否则返回0 get mykey 获取Key对应的value strlen mykey 获取指定 Key 的字符长度 ttl mykey 查看一下指定 Key 的剩余存活时间(秒数) getrange mykey 1 20 获取第2到第20个字节,若20超过value长度,则截取第2个和后面所有的 mget key3 key4 批量获取键 ----------------------------- 应用场景 常规计数:微博数,粉丝数等 incr incrby decr decrby ----------------------------- HASH类型 增 hset myhash field1 "s" 若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s ,若存在会覆盖原value hsetnx myhash field1 s 若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s, 若字段field1存在,则无效 hmset myhash field1 "hello" field2 "world 一次性设置多个字段 删 hdel myhash field1 删除 myhash 键中字段名为 field1 的字段 del myhash 删除键 改 hincrby myhash field 1 给field的值加1 查 hget myhash field1 获取键值为 myhash,字段为 field1 的值 hlen myhash 获取myhash键的字段数量 hexists myhash field1 判断 myhash 键中是否存在字段名为 field1 的字段 hmget myhash field1 field2 field3 一次性获取多个字段 hgetall myhash 返回 myhash 键的所有字段及其值 hkeys myhash 获取myhash 键中所有字段的名字 hvals myhash 获取 myhash 键中所有字段的值 应用场景: 存储部分变更的数据,如用户信息等。需要将MySQL表数据进行缓存时,可以使用此种数据类型。 -------------------------------- 增 lpush mykey a b 若key不存在,创建该键及与其关联的List,依次插入a ,b, 若List类型的key存在,则插入value中 lpushx mykey2 e 若key不存在,此命令无效, 若key存在,则插入value中 linsert mykey before a a1 在 a 的前面插入新元素 a1 linsert mykey after e e2 在e 的后面插入新元素 e2 rpush mykey a b 在链表尾部先插入b,在插入a rpushx mykey e 若key存在,在尾部插入e, 若key不存在,则无效 rpoplpush mykey mykey2 将mykey的尾部元素弹出,再插入到mykey2 的头部(原子性的操作) 删 del mykey 删除已有键 lrem mykey 2 a 从头部开始找,按先后顺序,值为a的元素,删除数量为2个,若存在第3个,则不删除 ltrim mykey 0 2 从头开始,索引为0,1,2的3个元素,其余全部删除 改 lset mykey 1 e 从头开始, 将索引为1的元素值,设置为新值 e,若索引越界,则返回错误信息 rpoplpush mykey mykey 将 mykey 中的尾部元素移到其头部 查 lrange mykey 0 -1 取链表中的全部元素,其中0表示第一个元素,-1表示最后一个元素。 lrange mykey 0 2 从头开始,取索引为0,1,2的元素 lrange mykey 0 0 从头开始,取第一个元素,从第0个开始,到第0个结束 lpop mykey 获取头部元素,并且弹出头部元素,出栈 lindex mykey 6 从头开始,获取索引为6的元素 若下标越界,则返回nil 应用场景 消息队列系统 比如sina微博: 在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。 但是做了限制不能超过5000个ID,因此获取ID的函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。 系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。 SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。 ------------------------ 集合 增 sadd myset a b c 若key不存在,创建该键及与其关联的set,依次插入a ,b,若key存在,则插入value中,若a 在myset中已经存在,则插入了 d 和 e 两个新成员。 删 spop myset 尾部的b被移出,事实上b并不是之前插入的第一个或最后一个成员 srem myset a d f 若f不存在, 移出 a、d ,并返回2 改 smove myset myset2 a 将a从 myset 移到 myset2, 查 sismember myset a 判断 a 是否已经存在,返回值为 1 表示存在。 smembers myset 查看set中的内容 scard myset 获取Set 集合中元素的数量 srandmember myset 随机的返回某一成员 sdiff myset1 myset2 myset3 1和2得到一个结果,拿这个集合和3比较,获得每个独有的值 sdiffstore diffkey myset myset2 myset3 3个集和比较,获取独有的元素,并存入diffkey 关联的Set中 sinter myset myset2 myset3 获得3个集合中都有的元素 sinterstore interkey myset myset2 myset3 把交集存入interkey 关联的Set中 sunion myset myset2 myset3 获取3个集合中的成员的并集 sunionstore unionkey myset myset2 myset3 把并集存入unionkey 关联的Set中 应用场景: 案例: 在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。 Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能, 对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。 --------------------------- 有序集合 增 zadd myzset 2 "two" 3 "three" 添加两个分数分别是 2 和 3 的两个成员 删 zrem myzset one two 删除多个成员变量,返回删除的数量 改 zincrby myzset 2 one 将成员 one 的分数增加 2,并返回该成员更新后的分数 查 zrange myzset 0 -1 WITHSCORES 返回所有成员和分数,不加WITHSCORES,只返回成员 zrank myzset one 获取成员one在Sorted-Set中的位置索引值。0表示第一个位置 zcard myzset 获取 myzset 键中成员的数量 zcount myzset 1 2 获取分数满足表达式 1 <= score <= 2 的成员的数量 zscore myzset three 获取成员 three 的分数 zrangebyscore myzset 1 2 获取分数满足表达式 1 < score <= 2 的成员 #-inf 表示第一个成员,+inf最后一个成员 #limit限制关键字 #2 3 是索引号 zrangebyscore myzset -inf +inf limit 2 3 返回索引是2和3的成员 zremrangebyscore myzset 1 2 删除分数 1<= score <= 2 的成员,并返回实际删除的数量 zremrangebyrank myzset 0 1 删除位置索引满足表达式 0 <= rank <= 1 的成员 zrevrange myzset 0 -1 WITHSCORES 按位置索引从高到低,获取所有成员和分数 #原始成员:位置索引从小到大 one 0 two 1 #执行顺序:把索引反转 位置索引:从大到小 one 1 two 0 #输出结果: two one zrevrange myzset 1 3 获取位置索引,为1,2,3的成员 #相反的顺序:从高到低的顺序 zrevrangebyscore myzset 3 0 获取分数 3>=score>=0的成员并以相反的顺序输出 zrevrangebyscore myzset 4 0 limit 1 2 获取索引是1和2的成员,并反转位置索引 应用场景: 排行榜应用,取TOP N操作 这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重, 比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。 -------------------------------------- PUBLISH channel msg 将信息 message 发送到指定的频道 channel SUBSCRIBE channel [channel ...] 订阅频道,可以同时订阅多个频道 PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所 有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有 以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类 PUNSUBSCRIBE [pattern [pattern ...]] 退订指定的规则, 如果没有参数则会退订所有规则 PUBSUB sub command [argument [argument ...]] 查看订阅与发布系统状态 注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之前发送的不会缓存,必须Provider和Consumer同时在线。 ------------------ DISCARD 取消事务,放弃执行事务块内的所有命令。 EXEC 执行所有事务块内的命令。 MULTI 标记一个事务块的开始。 UNWATCH 取消 WATCH 命令对所有 key 的监视。 WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 --------------- 具体的命令---- watch命令 例: redis 127.0.0.1:6379> watch ticket OK redis 127.0.0.1:6379> multi OK redis 127.0.0.1:6379> decr ticket QUEUED redis 127.0.0.1:6379> decrby money 100 QUEUED //现在已经对ticket进行了监控,另外一个窗口将ticket改动了 另一个窗口:> decr ticket redis 127.0.0.1:6379> exec (nil) // 返回nil,说明监视的ticket已经改动了,事务就取消了.队列就不执行了。 redis 127.0.0.1:6379>mget ticket money "0" "200" watch key1 key2 ... keyN 作用:监听key1 key2..keyN有没有变化,如果任意一个有变, 则事务取消 unwatch 作用: 取消所有watch监听 ----------- redis管理类操作 Info Clinet list Client kill ip:port config get * CONFIG RESETSTAT 重置统计 CONFIG GET/SET 动态修改 Dbsize FLUSHALL 清空所有数据 select 1 FLUSHDB 清空当前库 MONITOR 监控实时指令 SHUTDOWN 关闭服务器 save将当前数据保存 SLAVEOF host port 主从配置 SLAVEOF NO ONE SYNC 主从同步 ROLE返回主从角色 ------------------ redis主从复制:基于RDB的快照技术,但是不依赖于RDB持久化。 环境: 准备两个或两个以上redis实例 mkdir -p /data/6380/ mkdir -p /data/6381/ mkdir -p /data/6382/ ------------- /data/6380/redis.conf /data/6381/redis.conf /data/6382/redis.conf 三套配置文件示例: vim /data/6380/redis.conf ---------------- bind 127.0.0.1 10.0.0.200 port 6380 daemonize yes pidfile /data/6380/redis.pid loglevel notice logfile "/data/6380/redis.log" dbfilename dump.rdb dir /data/6380 appendonly no appendfilename "appendonly.aof" appendfsync everysec slowlog-log-slower-than 10000 slowlog-max-len 128 protected-mode no vim /data/6381/redis.conf ---------------- bind 127.0.0.1 10.0.0.200 port 6381 daemonize yes pidfile /data/6381/redis.pid loglevel notice logfile "/data/6381/redis.log" dbfilename dump.rdb dir /data/6381 appendonly no appendfilename "appendonly.aof" appendfsync everysec slowlog-log-slower-than 10000 slowlog-max-len 128 protected-mode no vim /data/6382/redis.conf ---------------- bind 127.0.0.1 10.0.0.200 port 6382 daemonize yes pidfile /data/6382/redis.pid loglevel notice logfile "/data/6382/redis.log" dbfilename dump.rdb dir /data/6382 appendonly no appendfilename "appendonly.aof" appendfsync everysec slowlog-log-slower-than 10000 slowlog-max-len 128 protected-mode no 启动: /application/redis/src/redis-server /data/6380/redis.conf /application/redis/src/redis-server /data/6381/redis.conf /application/redis/src/redis-server /data/6382/redis.conf 查看启动状态 netstat -lnp|grep 638 主节点:6380 从节点:6381、6382 开启主从: 6381/6382命令行: redis-cli -p 6381 SLAVEOF 127.0.0.1 6380 redis-cli -p 6382 SLAVEOF 127.0.0.1 6380 ------------------- mkdir /data/26380 cp /application/redis/src/redis-sentinel /data/26380 cd /data/26380 ./redis-sentinel ./sentinel.conf vim sentinel.conf port 26380 dir "/tmp" sentinel monitor mymaster 127.0.0.1 6380 1 sentinel down-after-milliseconds mymaster 5000 sentinel config-epoch mymaster 0 启动 ./redis-sentinel ./sentinel.conf Python连接redis-sentinel集群 1、安装python3.5 tar xf Python-3.5.2.tar.xz cd Python-3.5.2 ./configure make && make install 2、安装redis的python驱动 unzip redis-py-master.zip cd redis-py-master python3 setup.py install 3、python3连接redis sentinel集群 python3 >>>from redis.sentinel import Sentinel >>>sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1) >>>sentinel.discover_master('mymaster') >>>sentinel.discover_slaves('mymaster') >>>master = sentinel.master_for('mymaster', socket_timeout=0.1) >>>slave = sentinel.slave_for('mymaster', socket_timeout=0.1) >>>master.set('foo', 'bar') >>>slave.get('foo') ----------------- redis cluser 安装集群软件 EPEL源安装ruby支持 yum install ruby rubygems -y 使用国内源 gem sources -a http://mirrors.aliyun.com/rubygems/ gem sources --remove http://rubygems.org/ gem sources -l gem install redis -v 3.3.3 -------------- 集群节点准备 规划: 端口:7000-7005 路径: 创建节点目录: mkdir -p /data/7000 mkdir -p /data/7001 mkdir -p /data/7002 mkdir -p /data/7003 mkdir -p /data/7004 mkdir -p /data/7005 配置文件添加: ------------------------- vim /data/7000/redis.conf port 7000 daemonize yes pidfile /data/7000/redis.pid logfile "/var/log/redis7000.log" dbfilename dump.rdb dir /data/7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7001/redis.conf port 7001 daemonize yes pidfile /data/7001/redis.pid logfile "/var/log/redis7001.log" dbfilename dump.rdb dir /data/7001 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7002/redis.conf port 7002 daemonize yes pidfile /data/7002/redis.pid logfile "/var/log/redis7002.log" dbfilename dump.rdb dir /data/7002 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7003/redis.conf port 7003 daemonize yes pidfile /data/7003/redis.pid logfile "/var/log/redis7003.log" dbfilename dump.rdb dir /data/7003 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7004/redis.conf port 7004 daemonize yes pidfile /data/7004/redis.pid logfile "/var/log/redis7004.log" dbfilename dump.rdb dir /data/7004 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7005/redis.conf port 7005 daemonize yes pidfile /data/7005/redis.pid logfile "/var/log/redis7005.log" dbfilename dump.rdb dir /data/7005 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes 启动所有节点 /application/redis/src/redis-server /data/7000/redis.conf /application/redis/src/redis-server /data/7001/redis.conf /application/redis/src/redis-server /data/7002/redis.conf /application/redis/src/redis-server /data/7003/redis.conf /application/redis/src/redis-server /data/7004/redis.conf /application/redis/src/redis-server /data/7005/redis.conf ps -ef |grep 700 ------------------------- 3、集群创建 /application/redis/src/redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \ 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 注意要输入yes --------------------- 4、集群状态查看 /application/redis/src/redis-cli -p 7000 cluster nodes | grep master /application/redis/src/redis-cli -p 7000 cluster nodes | grep slave +++++++++++++++Python 连接 redis cluster+++++++++++++++++ (1) redis-py并没有提供redis-cluster的支持,去github找了一下,有个叫redis-py-cluster的源码, 但是和redis-py不是一个作者,地址为:https://github.com/Grokzen/redis-py-cluster watch,star,fork还算可以。 (2) 安装 unzip redis-py-cluster-unstable.zip cd redis-py-cluster-unstable python3 setup.py install (3) 使用 >>>from rediscluster import StrictRedisCluster >>>startup_nodes = [{"host": "127.0.0.1", "port": "7000"}] >>># Note: decode_responses must be set to True when used with python3 >>>rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True) >>>rc.set("foo", "bar") >>>print(rc.get("foo"))
day145
s8day145 - 内容回顾: 1. 为什么要使用redis? 2. redis和memcached的区别? - 数据类型 - 持久化 - 高可用 - 分布式 快慢:https://www.oschina.net/news/26691/memcached-timeout 3. redis数据类型 4. 使用连接池 5. 支持事务 6. watch 7. 主从复制 8. sentinel 9. 将集群按照分布式(分片)的方式安排: - cluster - codis - twemproxy - 程序 - 一致性哈希 hash_ring 10. 分布式锁redlock 11. Tornado 今日内容: - 项目部署,代码上线 https://www.cnblogs.com/wupeiqi/articles/8591782.html 内容详细: - 上线 1. 本地代码配置相关操作 2. 打包上传 测试版: windows: yum install lrzsz 压缩zip包,拖进来。 mac: scp /home/xx/s8day145.zip root@192.11.11.11:/data/ 3. 安装Python3 a. 下载Python:https://www.python.org/ftp/python/3.5.4/ b. 解压 tar -xvf Python-3.5.4.tgz c. cd Python-3.5.4 d. 先装依赖: - yum install openssl-devel - yum install sqlite-devel e. 编译安装 - ./configure - make - make install /usr/local/bin python3 f. 安装django 4. 第一版本上线:简单粗暴 python3 manage.py runserver 0.0.0.0:8000 5. 第二版本上线:uwsgi - 使用uwsgi启动一个脚本: app.py def application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return [b"Hello World"] uwsgi --http :9001 --wsgi-file app.py - 如果是flask程序: app.py app = Flask(....) @app. ... uwsgi --http :9002 --wsgi-file app.py --callable app - 如果是django程序 方式一: uwsgi --http :9003 --chdir /data/s8day145/ --wsgi-file s8day145/wsgi.py 方式二: s8day145_uwsgi.ini [uwsgi] http = 0.0.0.0:9005 chdir = /data/oldboy/ wsgi-file = oldboy/wsgi.py processes = 4 static-map = /static=/data/oldboy/allstatic uwsgi --ini s8day145_uwsgi.ini 收集静态文件: - settings.py DEBUG = False STATIC_ROOT = "allstatic" - python3 manage.py collectstatic 6. 第三版本上线:uwsgi + nginx nginx作用: - 处理静态文件 - 反向代理 - 负载均衡(LVS、haproxy) uwsig作用: - 处理动态请求 nginx配置:/etc/nginx/nginx.conf user root; worker_processes 4; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; include /etc/nginx/mime.types; default_type application/octet-stream; upstream django { server 127.0.0.1:8001; server 127.0.0.1:8002; } server { listen 80; charset utf-8; # max upload size client_max_body_size 75M; location /static { alias /data/s8day145/allstatic; } location / { uwsgi_pass django; include uwsgi_params; } } } uwsgi配置:/data/s8day145/ s8day145_uwsgi_8001.ini [uwsgi] socket = 127.0.0.1:8001 chdir = /data/s8day145/ wsgi-file = s8day145/wsgi.py processes = 1 s8day145_uwsgi_8002.ini [uwsgi] socket = 127.0.0.1:8002 chdir = /data/s8day145/ wsgi-file = s8day145/wsgi.py processes = 1 启动: nginx: /etc/init.d/nginx start /bin/systemctl restart nginx.service /bin/systemctl start nginx.service /bin/systemctl stop nginx.service uwsgi: uwsgi --ini s8day145_uwsgi_8001.ini & uwsgi --ini s8day145_uwsgi_8002.ini & 7. 如果进程挂了,supervisor帮助你自动将服务重新启动。 /etc/supervisord.conf [program:s8day145] command=/usr/local/bin/uwsgi --ini /data/s8day145/s8day145_uwsgi_8001.ini ;命令 priority=999 ; 优先级(越小越优先) autostart=true ; supervisord启动时,该程序也启动 autorestart=true ; 异常退出时,自动启动 startsecs=10 ; 启动后持续10s后未发生异常,才表示启动成功 startretries=3 ; 异常后,自动重启次数 exitcodes=0,2 ; exit异常抛出的是0、2时才认为是异常 stopsignal=QUIT ; 杀进程的信号 stopwaitsecs=10 ; 向进程发出stopsignal后等待OS向supervisord返回SIGCHILD 的时间。若超时则supervisord将 使用SIGKILL杀进程 user=root ; 设置启动该程序的用户 log_stdout=true ; 如果为True,则记录程序日志 log_stderr=false ; 如果为True,则记录程序错误日志 logfile=/var/log/cat.log ; 程序日志路径 logfile_maxbytes=1MB ; 日志文件最大大小 logfile_backups=10 ; 日志文件最大数量 systemctl start supervisord.service - 公司中的部署步骤及负责人: - 开发提交代码 - 测试人员开始测试 - 运维人员进行上线 使用软件:jenkins+自己写脚本,运维人员点点点。 - 从git上啦代码到一个服务器,在该服务器上对代码进行编译(c/vue/java) - 通过以下工具 将代码同步到每台服务器上 - saltstack - ansible - 执行命令启动程序 - 自己搭建yum源
day144
s8day144 内容回顾: 1. Flask和其他框架比较? 今日内容: 1. redis 2. 异步非阻塞 3. 代码部署 内容详细: 1. redis 你了解的redis? 你用redis做过什么? - 配合django做缓存,常用且不易修改的数据放进来(博客) - 购物车信息 - Session - 缓存配置文件 - session配置文件中指定使用缓存 - rest api中访问频率控制 - 基于flask、websocket实现的投票系统(redis做消息队列) - scrapy中 - 去重规则 - 调度器:先进先出、后进先出、优先级队列 - pipelines - 起始URL - 商品热点信息 - 计数器 - 排行 为什么redis要做主从复制? 目的是对redis做高可用,为每一个redis实例创建一个备份称为slave,让主和备之间进行数据同步,save/bsave。 主:写 从:读 优点: - 性能提高,从分担读的压力。 - 高可用,一旦主redis挂了,从可以直接代替。 存在问题:当主挂了之后,需要人为手工将从变成主。 redis的sentinel是什么? 帮助我们自动在主从之间进行切换 检测主从中 主是否挂掉,且超过一半的sentinel检测到挂了之后才进行进行切换。 如果主修复好了,再次启动时候,会变成从。 redis的cluster是什么? 集群方案: - redis cluster 官方提供的集群方案。 - codis,豌豆荚技术团队。 - tweproxy,Twiter技术团队。 redis cluster的原理? - 基于分片来完成。 - redis将所有能放置数据的地方创建了 16384 个哈希槽。 - 如果设置集群的话,就可以为每个实例分配哈希槽: - 192.168.1.20【0-5000】 - 192.168.1.21【5001-10000】 - 192.168.1.22【10001-16384】 - 以后想要在redis中写值时, set k1 123 将k1通过crc16的算法,将k1转换成一个数字。然后再将该数字和16384求余,如果得到的余数 3000,那么就将该值写入到 192.168.1.20 实例中。 redis是否可以做持久化? RDB:每隔一段时间对redis进行一次持久化。 - 缺点:数据不完整 - 优点:速度快 AOF:把所有命令保存起来,如果想到重新生成到redis,那么就要把命令重新执行一次。 - 缺点:速度慢,文件比较大 - 优点:数据完整 redis的过期策略。 voltile-lru: 从已设置过期时间的数据集(server.db[i].expires)中挑选最近频率最少数据淘汰 volatile-ttl: 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 allkeys-lru: 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 allkeys-random: 从数据集(server.db[i].dict)中任意选择数据淘汰 no-enviction(驱逐):禁止驱逐数据 redis的分布式锁实现。 - 写值并设置超时时间 - 超过一半的redis实例设置成功,就表示加锁完成。 - 使用:安装redlock-py from redlock import Redlock dlm = Redlock( [ {"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6379, "db": 0}, ] ) # 加锁,acquire my_lock = dlm.lock("my_resource_name",10000) if my_lock: # J进行操作 # 解锁,release dlm.unlock(my_lock) else: print('获取锁失败') http://www.redis.cn/ 2. 异步非阻塞框架:Tornado 目标:通过一个线程处理N个并发请求。 - 处理请求IO :牛逼起来 - 处理非请求IO:傻逼起来 使用支持tornado异步非阻塞的模块: MySQL Redis SQLALchemy Tornado异步非阻塞本质: 视图函数yield一个futrue对象,futrue对象默认: self._done = False ,请求未完成 self._result = None ,请求完成后返回值,用于传递给回调函数使用。 tornado就会一直去检测futrue对象的_done是否已经变成True。 如果IO请求执行完毕,自动会调用future的set_result方法: self._result = result self._done = True 参考:http://www.cnblogs.com/wupeiqi/p/6536518.html
day143
s8day143 内容回顾: 面试题 1. 谈谈你对restful 规范的理解? - restful其实就是一套编写接口的协议,协议规定如何编写以及如何设置返回值、状态码等信息。 - 最显著的特点: restful: 给用户一个url,根据method不同在后端做不同的处理,比如:post 创建数据、get获取数据、put和patch修改数据、delete删除数据。 no rest: 给调用者很多url,每个url代表一个功能,比如:add_user/delte_user/edit_user/ - 当然,还有协议其他的,比如: - 版本,来控制让程序有多个版本共存的情况,版本可以放在 url、请求头(accept/自定义)、GET参数 - 状态码,200/300/400/500 - url中尽量使用名词,restful也可以称为“面向资源编程” - api标示: api.luffycity.com www.luffycity.com/api/ ... ..... 2. 你的restful是怎么学的? - 因为之前公司要写这样项目 - 接口 - 公司要做前后端分离的项目 - 公司要做微信小程序的开发 - 所以就开始学习restful规范,看的技术文章 阮一峰的博客学到的规范。 3. 状态码都有哪些? 4. method都有哪些? 5. 常见请求头有哪些? 6. 你是用什么开发的restful接口? 使用django rest framework框架。 7. 为什么要使用django rest framework框架? 在编写接口时可以不适用django rest framework框架, 如果不使用:也可以做,那么就可以django的CBV来实现,开发者编写的代码会更多一些。 如果 使用:内部帮助我们提供了很多方便的组件,我们通过配置就可以完成相应操作,如: - 序列化,可以做用户请求数据校验+queryset对象的序列化称为json - 解析器,获取用户请求数据request.data,会自动根据content-type请求头的不能对数据进行解析 - 分页,将从数据库获取到的数据在页面进行分页显示。 还有其他: - 认证 - 权限 - 访问频率控制 - ... 8. rest framework 视图你都用过哪些基类? a. 继承 APIView 这个类属于rest framework中顶层类,内部帮助我们实现了只是基本功能:认证、权限、频率控制,但凡是数据库、分页等操作都需要手动去完成,比较原始。 class GenericAPIView(APIView) def post(...): pass b. 继承 GenericViewSet(ViewSetMixin, generics.GenericAPIView) 如果继承它之后,路由中的as_view需要填写对应关系 .as_view({'get':'list','post':'create'}) 在内部也帮助我们提供了一些方便的方法: - get_queryset - get_object - get_serializer 注意:要设置queryset字段,否则会跑出断言的异常。 # 只提供增加功能 class TestView(GenericViewSet): serializer_class = XXXXXXX def create(self,*args,**kwargs): pass # 获取数据并对数据进行操作 c. 继承 - ModelViewSet - mixins.CreateModelMixin,GenericViewSet - mixins.CreateModelMixin,DestroyModelMixin,GenericViewSet 对数据库和分页等操作不用我们在编写,只需要继承相关类即可。 示例:只提供增加功能 class TestView(mixins.CreateModelMixin,GenericViewSet): serializer_class = XXXXXXX 类的继承关系 9. 认证流程? - 如何编写?写类并实现authticate - 方法中可以定义三种返回值: - (user,auth),认证成功 - None , 匿名用户 - 异常 ,认证失败 - 流程: - dispatch - 再去request中进行认证处理 10. 访问频率控制 - 匿名用户,根据用户IP或代理IP作为标识进行记录,为每一个用户在redis中创建一个列表 { throttle_1.1.1.1:[1526868876.497521,152686885.497521...] throttle_1.1.1.2:[1526868876.497521,152686885.497521...] throttle_1.1.1.3:[1526868876.497521,152686885.497521...] throttle_1.1.1.4:[1526868876.497521,152686885.497521...] throttle_1.1.1.5:[1526868876.497521,152686885.497521...] } 每个用户再来访问时,需要先去记录中剔除以及过期时间,再根据列表的长度判断是否可以继续访问。 如何封IP:在防火墙中进行设置 - 注册用户,根据用户名或邮箱进行判断。 { throttle_xxxx1:[1526868876.497521,152686885.497521...] throttle_xxxx2:[1526868876.497521,152686885.497521...] throttle_xxxx3:[1526868876.497521,152686885.497521...] throttle_xxxx4:[1526868876.497521,152686885.497521...] } 每个用户再来访问时,需要先去记录中剔除以及过期时间,再根据列表的长度判断是否可以继续访问。 1分钟:40次 11. 接口的幂等性?(是否会造成2次伤害) 一个接口通过1次相同的访问,再对该接口进行N次相同的访问时候,对资源不造影响,那么就认为接口具有幂等性。 比如: GET, 第一次获取结果、第二次也是获取结果对资源都不会造成影响,幂等。 POST,第一次新增数据,第二次也会再次新增,非幂等。 PUT, 第一次更新数据,第二次不会再次更新,幂等。 PATCH,第一次更新数据,第二次不会再次更新,非幂等。 DELTE,第一次删除数据,第二次不在再删除,幂等。 12. Https和Http区别? 端口: http:80 https: 443 流程: - 自定义证书 - 认证机构 13. Flask框架的优势? 14. Flask内置功能依赖? 15. Flask第三方组件? - flask-session - flask-migrate - flask-script - flask-sqlalchemy(SQLAlchemy) - blinker - wtforms - dbutils - gevent-websocket - flask-login自定义组件 16. threading.local 以及作用? 17. Flask上下文管理流程以及和django比较? 18. Flask中的g的作用? 19. Flask中上下文管理主要涉及到了那些相关的类?并描述类主要作用? 20. 为什么要把Local的值维护成一个列表? 21. Flask中多app应用是怎么完成? 22. 原生SQL和ORM的区别? 23. SQLAlchemy中的 session 的创建有几种方式? https://www.cnblogs.com/wupeiqi/articles/8259356.html - 直接创建Session对象 engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/s6", max_overflow=0, pool_size=5) Session = sessionmaker(bind=engine) def task(arg): session = Session() obj1 = Users(name="alex1") session.add(obj1) session.commit() for i in range(10): t = threading.Thread(target=task, args=(i,)) t.start() - 基于scoped_session(Flask-SQLAlchemy中的连接默认使用该方法) engine = create_engine("mysql+pymysql://root:123@127.0.0.1:3306/s6", max_overflow=0, pool_size=5) Session = sessionmaker(bind=engine) session = scoped_session(Session) def task(arg): obj1 = Users(name="alex1") session.add(obj1) session.commit() for i in range(10): t = threading.Thread(target=task, args=(i,)) t.start() 日后内容: 1. redis 2. tornado异步非阻塞 3. 部署:nginx+uwsgi+django+supervisor 4. 面试和简历相关 5. 技术分享:代码发布系统 - saltstack - celery - git/gitlab - jekins 内容详细: 1. redis http://www.cnblogs.com/wupeiqi/articles/5132791.html - 基本操作 - 连接 - 直接连接: import redis r = redis.Redis(host='10.211.55.4', port=6379) r.set('foo', 'Bar') print r.get('foo') - 连接池: import redis pool = redis.ConnectionPool(host='10.211.55.4', port=6379) r = redis.Redis(connection_pool=pool) r.set('foo', 'Bar') print r.get('foo') - 5大数据类型 - 字符串 "sadf" - set('k1','123',ex=10) - get - mset - mget - incr - 超时时间: import redis r = redis.Redis(host='10.211.55.4', port=6379) r.set('foo', 'Bar',ex=10) - 字典 {'k1':'v1'} - hset(name, key, value) - hmset(name, mapping) - hget(name,key) - 超时时间(字典,列表、集合、有序结合相同): import redis conn = redis.Redis(host='10.211.55.4', port=6379) conn.hset('n1', 'k1','123123') conn.expire('n1',10) - 如果一个字典在redis中保存了10w个值,我需要将所有值全部循环并显示,请问如何实现? for item in r.hscan_iter('k2',count=100): print item - 列表 [11,11,22,33] - lpush - rpush - lpop - blpop - rpop - brpop - llen - lrange - 如果一个列表在redis中保存了10w个值,我需要将所有值全部循环并显示,请问如何实现? def list_scan_iter(name,count=3): start = 0 while True: result = conn.lrange(name, start, start+count-1) start += count if not result: break for item in result: yield item for val in list_scan_iter('num_list'): print(val) - 集合 {'alex','oldboy','日天'} - 有序集合 {('alex',59),('oldboy',100),('日天',1)} - 公共操作: - delete(*names) - keys(pattern='*') expire(name ,time) ... - 事务 import redis pool = redis.ConnectionPool(host='10.211.55.4', port=6379) conn = redis.Redis(connection_pool=pool) # pipe = r.pipeline(transaction=False) pipe = conn.pipeline(transaction=True) # 开始事务 pipe.multi() pipe.set('name', 'alex') pipe.set('role', 'sb') pipe.lpush('roless', 'sb') # 提交 pipe.execute() 注意:咨询是否当前分布式redis是否支持事务 - 检测,watch 面试题:你如何控制剩余的数量不会出问题? - 通过redis的watch实现 import redis conn = redis.Redis(host='127.0.0.1',port=6379) # conn.set('count',1000) val = conn.get('count') print(val) with conn.pipeline(transaction=True) as pipe: # 先监视,自己的值没有被修改过 conn.watch('count') # 事务开始 pipe.multi() old_count = conn.get('count') count = int(old_count) print('现在剩余的商品有:%s',count) input("问媳妇让不让买?") pipe.set('count', count - 1) # 执行,把所有命令一次性推送过去 pipe.execute() - 数据库的锁 - 发布和订阅,只要有任务就所有订阅者每人一份。 发布者: import redis conn = redis.Redis(host='127.0.0.1',port=6379) conn.publish('104.9MH', "hahahahahaha") 订阅者: import redis conn = redis.Redis(host='127.0.0.1',port=6379) pub = conn.pubsub() pub.subscribe('104.9MH') while True: msg= pub.parse_response() print(msg) - 主从复制 优势: - 高可用 - 分担主压力 注意: - slave设置只读 从的配置文件添加以下记录,即可: slaveof 1.1.1.1 3306 - sentinel,哨兵 启动主redis: redis-server /etc/redis-6379.conf 启动主redis redis-server /etc/redis-6380.conf 启动从redis 在linux中: 找到 /etc/redis-sentinel-8001.conf 配置文件,在内部: - 哨兵的端口 port = 8001 - 主redis的IP,哨兵个数的一半/1 找到 /etc/redis-sentinel-8002.conf 配置文件,在内部: - 哨兵的端口 port = 8002 - 主redis的IP, 1 启动两个哨兵 - redis-cluster redis集群、分片、分布式redis redis-py-cluster 问题: 目前你们公司项目1000用户,QPS=1000 ,如果用户猛增10000w? 项目如何提高的并发? 1. 数据库读写分离 2. 设置缓存 3. 负载均衡 2. 预习,项目部署 https://www.cnblogs.com/wupeiqi/articles/8591782.html
day141
s8day141 内容回顾: 面试题: 1. django和flask区别? 2. django请求生命周期? 3. wsgi? 4. 中间件都有哪些方法?以及执行流程? 5. 中间件的应用? - 权限 - cors - csrf - 缓存(中间件2/4) 6. 视图函数 - FBV - CBV FBV和CBV的区别? - 没什么区别,因为他们的本质都是函数。CBV的.as_view()返回的view函数,view函数中调用类的dispatch方法,在dispatch方法中通过反射执行get/post/delete/put等方法。 - CBV比较简洁,GET/POST等业务功能分别放在不同get/post函数中。FBV自己做判断进行区分。 CBV时的注意事项? - 装饰器 from django.views import View from django.utils.decorators import method_decorator def auth(func): def inner(*args,**kwargs): return func(*args,**kwargs) return inner class UserView(View): @method_decorator(auth) def get(self,request,*args,**kwargs): return HttpResponse('...') - csrf的装饰器要加到dispath from django.views import View from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt,csrf_protect class UserView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return HttpResponse('...') 或 from django.views import View from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt,csrf_protect @method_decorator(csrf_exempt,name='dispatch') class UserView(View): def dispatch(self, request, *args, **kwargs): return HttpResponse('...') 7. ORM操作 models.UserInfo.objects.create() obj = models.UserInfo(name='xx') obj.save() models.UserInfo.objects.bulk_create([models.UserInfo(name='xx'),models.UserInfo(name='xx')]) models.UserInfo.objects.all().delete() models.UserInfo.objects.all().update(age=18) models.UserInfo.objects.all().update(salary=F('salary')+1000) .filter(id__gt=1,) .exclude() .values() .values_list() .order_by() .order_by('-id') .anotate() .first() .laste() .get() .exsit() .reverse() .distinct() .extra() .raw() F和Q .select_related() title = models.CharField(max_length=32) class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) ut = models.ForeignKey(to='UserType') ut = models.ForeignKey(to='UserType') ut = models.ForeignKey(to='UserType') ut = models.ForeignKey(to='UserType') # 1次SQL # select * from userinfo objs = UserInfo.obejcts.all() for item in objs: print(item.name) # n+1次SQL # select * from userinfo objs = UserInfo.obejcts.all() for item in objs: # select * from usertype where id = item.id print(item.name,item.ut.title) # 1次SQL # select * from userinfo inner join usertype on userinfo.ut_id = usertype.id objs = UserInfo.obejcts.all().select_related('ut') for item in objs: print(item.name,item.ut.title) .prefetch_related() # select * from userinfo where id <= 8 # 计算:[1,2] # select * from usertype where id in [1,2] objs = UserInfo.obejcts.filter(id__lte=8).prefetch_related('ut') for obj in objs: print(obj.name,obj.ut.title) 补充: 无约束: class UserType(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) # 无数据库约束,但可以进行链表 ut = models.ForeignKey(to='UserType',db_constraint=False) 有约束: class UserType(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) # 有数据库约束,可以进行链表 ut = models.ForeignKey(to='UserType') .using .count .only .defer .[1,100] .aggregate .fiter(id__in=[1,2]) # 多对多 .add .set .remove 8. django的Form组件的作用? - 对用户请求的数据进行校验 - 生成HTML标签 PS: - form对象是一个可迭代对象。 - 问题:choice的数据如果从数据库获取可能会造成数据无法实时更新 - 重写构造方法,在构造方法中重新去数据库获取值。 - ModelChoiceField字段 from django.forms import Form from django.forms import fields from django.forms.models import ModelChoiceField class UserForm(Form): name = fields.CharField(label='用户名',max_length=32) email = fields.EmailField(label='邮箱') ut_id = ModelChoiceField(queryset=models.UserType.objects.all()) 依赖: class UserType(models.Model): title = models.CharField(max_length=32) def __str__(self): return self.title 9. 多数据库相关操作 python manage.py makemigraions python manage.py migrate app名称 --databse=配置文件数据名称的别名 手动操作: models.UserType.objects.using('db1').create(title='普通用户') result = models.UserType.objects.all().using('default') 自动操作: class Router1: def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ return 'db1' def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ return 'default' 配置: DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'db1': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db1.sqlite3'), }, } DATABASE_ROUTERS = ['db_router.Router1',] 使用: models.UserType.objects.create(title='VVIP') result = models.UserType.objects.all() print(result) 补充:粒度更细 class Router1: def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ if model._meta.model_name == 'usertype': return 'db1' else: return 'default' def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ return 'default' 问题: app01中的表在default数据库创建 app02中的表在db1数据库创建 # 第一步: python manage.py makemigraions # 第二步: app01中的表在default数据库创建 python manage.py migrate app01 --database=default # 第三步: app02中的表在db1数据库创建 python manage.py migrate app02 --database=db1 # 手动操作: m1.UserType.objects.using('default').create(title='VVIP') m2.Users.objects.using('db1').create(name='VVIP',email='xxx') # 自动操作: 配置: class Router1: def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ if model._meta.app_label == 'app01': return 'default' else: return 'db1' def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ if model._meta.app_label == 'app01': return 'default' else: return 'db1' DATABASE_ROUTERS = ['db_router.Router1',] 使用: m1.UserType.objects.using('default').create(title='VVIP') m2.Users.objects.using('db1').create(name='VVIP',email='xxx') 其他: 数据库迁移时进行约束: class Router1: def allow_migrate(self, db, app_label, model_name=None, **hints): """ All non-auth models end up in this pool. """ if db=='db1' and app_label == 'app02': return True elif db == 'default' and app_label == 'app01': return True else: return False # 如果返回None,那么表示交给后续的router,如果后续没有router,则相当于返回True def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ if model._meta.app_label == 'app01': return 'default' else: return 'db1' def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ if model._meta.app_label == 'app01': return 'default' else: return 'db1' 今日内容: 1. websocket 2. tornado 3. redis 内容详细: 1. websocket 回顾: - 什么是轮训? - 通过定时器让程序每隔n秒执行一次操作。 - 什么是长轮训? - 浏览器向后端发起请求,后端会将请求 hang 住,最多hang 30s。 如果一直不返回数据:则最多等待30s,紧接着用户立即再发送请求。 如果有数据返回:则操作数据并立即再发送请求。 PS:后台可以使用队列或redis的列表来hang主请求。 - 轮训和长轮训目的? 由于Http请求是无状态、短连接所以服务端无法向客户端实时推送消息, 所以,我们就是可以使用:轮训和长轮训去服务端获取实时数据。 作业:基于redis和长轮询实现投票。 websocket是什么? websocket是一套类似于http的协议。 扩展: http协议:\r\n分割、请求头和请求体\r\n分割、无状态、短连接。 websocket协议:\r\n分割、创建连接后不断开、 验证+数据加密; websocket本质: - 就是一个创建连接后不断开的socket - 当连接成功之后: - 客户端(浏览器)会自动向服务端发送消息,包含: Sec-WebSocket-Key: iyRe1KMHi4S4QXzcoboMmw== - 服务端接收之后,会对于该数据进行加密: base64(sha1(swk + magic_string)) - 构造响应头: HTTP/1.1 101 Switching Protocols\r\n Upgrade:websocket\r\n Connection: Upgrade\r\n Sec-WebSocket-Accept: 加密后的值\r\n WebSocket-Location: ws://127.0.0.1:8002\r\n\r\n - 发给客户端(浏览器) - 建立:双工通道,接下来就可以进行收发数据 - 发送的数据是加密,解密,根据payload_len的值进行处理: - payload_len <=125 - payload_len ==126 - payload_len ==127 - 获取内容: - mask_key - 数据 根据mask_key和数据进行位运算,就可以把值解析出来。 面试: a. 什么是websocket? websocket是给浏览器新建一套协议。协议规定:浏览器和服务端连接之后不断开,以此可以完成:服务端向客户端主动推送消息。 websocket协议额外做的一些前天操作: - 握手,连接前进行校验 - 发送数据加密 b. websocket本质 - socket - 握手,魔法字符串+加密 - 加密,payload_len=127/126/<=125 -> mask key 在项目中使用: - django: channel - flask: gevent-websocket - tornado: 内置 Flask示例: a. 安装 pip3 install gevent-websocket 作用: - 处理Http、Websocket协议的请求 -> socket - 封装Http、Websocket相关数据 -> request b. 基本结构 from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer @app.route('/test') def test(): ws = request.environ.get('wsgi.websocket') ws.receive() ws.send(message) ws.close() return render_template('index.html') if __name__ == '__main__': http_server = WSGIServer(('0.0.0.0', 5000,), app, handler_class=WebSocketHandler) http_server.serve_forever() c. WEB聊天室: 后端: from geventwebsocket.handler import WebSocketHandler from gevent.pywsgi import WSGIServer from flask import Flask,render_template,request import pickle app = Flask(__name__) app.secret_key = 'xfsdfqw' @app.route('/index') def index(): return render_template('index.html') WS_LIST = [] @app.route('/test') def test(): ws = request.environ.get('wsgi.websocket') if not ws: return '请使用WebSocket协议' # websocket连接已经成功 WS_LIST.append(ws) while True: # 等待用户发送消息,并接受 message = ws.receive() # 关闭:message=None if not message: WS_LIST.remove(ws) break for item in WS_LIST: item.send(message) return "asdfasdf" if __name__ == '__main__': http_server = WSGIServer(('0.0.0.0', 5000,), app, handler_class=WebSocketHandler) http_server.serve_forever() 前端: <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <style> .msg-item{ padding: 5px; border: 1px; margin: 0 5px; } </style> </head> <body> <h1>首页</h1> <div> <h2>发送消息</h2> <input id="msg" type="text" /> <input type="button" value="发送" onclick="sendMsg()"> <h2>接收消息</h2> <div id="container"> </div> </div> <script src="/static/jquery-3.3.1.min.js"></script> <script> ws = new WebSocket('ws://192.168.12.42:5000/test'); ws.onmessage = function (event) { var tag = document.createElement('div'); tag.className = 'msg-item'; tag.innerText = event.data; $('#container').append(tag); } function sendMsg() { ws.send($('#msg').val()); } </script> </body> </html> 问题: a. 实时消息推送,利用什么技术实现? - 轮训 ,优点:简单; 缺点:请求次数多,服务器压力大,消息延迟。 - 长轮训 ,优点:实时接收数据,兼容性好; 缺点:请求次数比原来少,但是相对来也不少。 - websocket ,优点:代码简单,不再反复创建连接。 缺点:兼容性。 b. 在框架中使用WebSocket: - django,channel - flask,gevent-websocket - tornado,内置 2. 初始tornado以及tornado websocket 什么是Tornado? Tornado是一个轻量级的Web框架,异步非阻塞+内置WebSocket功能。 安装: pip3 install tornado 示例: import tornado from tornado.web import Application from tornado.web import RequestHandler from tornado.websocket import WebSocketHandler class IndexHandler(RequestHandler): def get(self, *args, **kwargs): # self.write('Hello World') self.render('index.html') def post(self, *args, **kwargs): user = self.get_argument('user') self.write('成功') WS_LIST = [] class MessageHandler(WebSocketHandler): def open(self, *args, **kwargs): WS_LIST.append(self) def on_message(self, message): for ws in WS_LIST: ws.write_message(message) def on_close(self): WS_LIST.remove(self) settings = { 'template_path':'templates', 'static_path':'static', } app = Application([ (r"/index", IndexHandler), (r"/message", MessageHandler), ],**settings) if __name__ == '__main__': app.listen(address='0.0.0.0',port=9999) tornado.ioloop.IOLoop.instance().start()
day140
s8day141 内容回顾: 面试题: 1. django和flask框架的区别? django:大而全的全的框架,重武器;内置很多组件:ORM、admin、Form、ModelForm、中间件、信号、缓存、csrf等 flask: 微型框架、可扩展强,如果开发简单程序使用flask比较快速,如果实现负责功能就需要引入一些组件:flask-session/flask-SQLAlchemy/wtforms/flask-migrate/flask-script/blinker 这两个框架都是基于wsgi协议实现的,默认使用的wsgi模块不一样。 还有一个显著的特点,他们处理请求的方式不同: django: 通过将请求封装成Request对象,再通过参数进行传递。 flask:通过上下文管理实现。 延伸: - django组件 - flask组件,用途 - wsgi - 上下文管理 2. wsgi作用? web服务网关接口,一套协议。 实现了wsgi协议的模块本质上就是编写了socket服务端,用来监听用户请求,如果有请求到来,则将请求进行一次封装,然后将【请求】交给 web框架来进行下一步处理。 目前接触: wsgiref werkzurg uwsgi from wsgiref.simple_server import make_server def run_server(environ, start_response): """ environ: 封装了请求相关的数据 start_response:用于设置响应头相关数据 """ start_response('200 OK', [('Content-Type', 'text/html')]) return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] if __name__ == '__main__': httpd = make_server('', 8000, run_server) httpd.serve_forever() Django源码: class WSGIHandler(base.BaseHandler): request_class = WSGIRequest def __init__(self, *args, **kwargs): super(WSGIHandler, self).__init__(*args, **kwargs) self.load_middleware() def __call__(self, environ, start_response): # 请求刚进来之后 # set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ) request = self.request_class(environ) response = self.get_response(request) response._handler_class = self.__class__ status = '%d %s' % (response.status_code, response.reason_phrase) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): response_headers.append((str('Set-Cookie'), str(c.output(header='')))) start_response(force_str(status), response_headers) if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'): response = environ['wsgi.file_wrapper'](response.file_to_stream) return response 3. django 请求生命周期 a. wsgi, 创建socket服务端,用于接收用户请求并对请求进行初次封装。 b. 中间件,对所有请求到来之前,响应之前定制一些操作。 c. 路由匹配,在url和视图函数对应关系中,根据当前请求url找到相应的函数。 d. 执行视图函数,业务处理【通过ORM去数据库中获取数据,再去拿到模板,然后将数据和模板进行渲染】 e. 再经过所有中间件 f. 通过wsgi将响应返回给用户。 【图】 4. 中间件的执行流程? 所有方法: - process_request - process_view - process_template_response , 当视图函数的返回值对象中有render方法时,该方法才会被调用。 - process_response - process_excaption 5. 中间件的应用? - 登录验证,为什么:如果不适用就需要为每个函数添加装饰器,太繁琐。 - 权限处理,为什么:用户登录后,将权限放到session中,然后再每次请求时需要判断当前用户是否有权访问当前url,这检查的东西就可以放到中间件中进行统一处理。 - 还有一些内置: - csrf,为什么 - session,为什么 - 全站缓存 ,为什么 - 另外,还有一个就是处理:跨域 (前后端分离时,本地测试开发时使用的。) 6. csrf原理 目标:防止用户直接向服务端发起POST请求。 方案:先发送GET请求时,将token保存到:cookie、Form表单中(隐藏的input标签),以后再发送请求时只要携带过来即可。 问题:如果想后台发送POST请求? form表单提交: <form method="POST"> {% csrf_token %} <input type='text' name='user' /> <input type='submit' /> </form> ajax提交: $.ajax({ url:'/index', type:'POST', data:{csrfmiddlewaretoken:'{{ csrf_token }}',name:'alex'} }) 前提:引入jquery + 引入jquery.cookie $.ajax({ url: 'xx', type:'POST', data:{name:'oldboyedu'}, headers:{ X-CSRFToken: $.cookie('csrftoken') }, dataType:'json', // arg = JSON.parse('{"k1":123}') success:function(arg){ } }) 优化方案: <body> <input type="button" onclick="Do1();" value="Do it"/> <input type="button" onclick="Do2();" value="Do it"/> <input type="button" onclick="Do3();" value="Do it"/> <script src="/static/jquery-3.3.1.min.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $.ajaxSetup({ beforeSend: function(xhr, settings) { xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken')); } }); function Do1(){ $.ajax({ url:"/index/", data:{id:1}, type:'POST', success:function(data){ console.log(data); } }); } function Do2(){ $.ajax({ url:"/index/", data:{id:1}, type:'POST', success:function(data){ console.log(data); } }); } function Do3(){ $.ajax({ url:"/index/", data:{id:1}, type:'POST', success:function(data){ console.log(data); } }); } </script> </body> 爬虫: reqeusts.post() 7. 视图函数 FBV: def index(request): pass CBV: class IndexView(View): # 如果是crsf相关,必须放在此处 def dispach(self,request): # 通过反射执行post/get @method_decoretor(装饰器函数) def get(self,request): pass def post(self,request): pass 路由:IndexView.as_view() 8. ORM a. 增删改查 b. 常用 order_by group_by limit 练表/跨表 c. 靠近原生SQL - extra def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射,如:子查询 Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) - raw def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw('select id as nid,name as title from 其他表') # 为原生SQL设置参数 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 将获取的到列名转换为指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定数据库 models.UserInfo.objects.raw('select * from userinfo', using="default") - 原生 from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) d. 高级一点 - F - Q - select_related - prefech_related e. 其他: ################################################################## # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # ################################################################## def all(self) # 获取所有的数据对象 def filter(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def exclude(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def select_related(self, *fields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 model.tb.objects.all().select_related() model.tb.objects.all().select_related('外键字段') model.tb.objects.all().select_related('外键字段__外键字段') def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 # 获取所有用户表 # 获取用户类型表where id in (用户表中的查到的所有用户ID) models.UserInfo.objects.prefetch_related('外键字段') from django.db.models import Count, Case, When, IntegerField Article.objects.annotate( numviews=Count(Case( When(readership__what_time__lt=treshold, then=1), output_field=CharField(), )) ) students = Student.objects.all().annotate(num_excused_absences=models.Sum( models.Case( models.When(absence__type='Excused', then=1), default=0, output_field=models.IntegerField() ))) def annotate(self, *args, **kwargs) # 用于实现聚合group by查询 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 def distinct(self, *field_names) # 用于distinct去重 models.UserInfo.objects.values('nid').distinct() # select distinct nid from userinfo 注:只有在PostgreSQL中才能使用distinct进行去重 def order_by(self, *field_names) # 用于排序 models.UserInfo.objects.all().order_by('-id','age') def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射,如:子查询 Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) Entry.objects.extra(where=['headline=%s'], params=['Lennon']) Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) def reverse(self): # 倒序 models.UserInfo.objects.all().order_by('-nid').reverse() # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序 def defer(self, *fields): models.UserInfo.objects.defer('username','id') 或 models.UserInfo.objects.filter(...).defer('username','id') #映射中排除某列数据 def only(self, *fields): #仅取某个表中的数据 models.UserInfo.objects.only('username','id') 或 models.UserInfo.objects.filter(...).only('username','id') def using(self, alias): 指定使用的数据库,参数为别名(setting中的设置) ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw('select id as nid from 其他表') # 为原生SQL设置参数 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 将获取的到列名转换为指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定数据库 models.UserInfo.objects.raw('select * from userinfo', using="default") ################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) def values(self, *fields): # 获取每行数据为字典格式 def values_list(self, *fields, **kwargs): # 获取每行数据为元祖 def dates(self, field_name, kind, order='ASC'): # 根据时间进行某一部分进行去重查找并截取指定内容 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) # order只能是:"ASC" "DESC" # 并获取转换后的时间 - year : 年-01-01 - month: 年-月-01 - day : 年-月-日 models.DatePlus.objects.dates('ctime','day','DESC') def datetimes(self, field_name, kind, order='ASC', tzinfo=None): # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间 # kind只能是 "year", "month", "day", "hour", "minute", "second" # order只能是:"ASC" "DESC" # tzinfo时区对象 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC) models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai')) """ pip3 install pytz import pytz pytz.all_timezones pytz.timezone(‘Asia/Shanghai’) """ def none(self): # 空QuerySet对象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### def aggregate(self, *args, **kwargs): # 聚合函数,获取字典类型聚合结果 from django.db.models import Count, Avg, Max, Min, Sum result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) ===> {'k': 3, 'n': 4} def count(self): # 获取个数 def get(self, *args, **kwargs): # 获取单个对象 def create(self, **kwargs): # 创建对象 def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的个数 objs = [ models.DDD(name='r11'), models.DDD(name='r22') ] models.DDD.objects.bulk_create(objs, 10) def get_or_create(self, defaults=None, **kwargs): # 如果存在,则获取,否则,创建 # defaults 指定创建时,其他字段的值 obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2}) def update_or_create(self, defaults=None, **kwargs): # 如果存在,则更新,否则,创建 # defaults 指定创建时或更新时的其他字段 obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1}) def first(self): # 获取第一个 def last(self): # 获取最后一个 def in_bulk(self, id_list=None): # 根据主键ID进行查找 id_list = [11,21,31] models.DDD.objects.in_bulk(id_list) def delete(self): # 删除 def update(self, **kwargs): # 更新 def exists(self): 爬虫相关: - request/bs4 - requests模块 - 参数: - url - headers - cookies - data - json - params - proxy - 返回值: - content - iter_content - text - encoding="utf-8" - cookie.get_dict() - bs4 - 解析:html.parser -> lxml - find - find_all - text - attrs - get - 其他: 常见请求头: - user-agent - host - referer - cookie - content-type 套路: - 先给你cookie,然后再给你授权。 - 凭证 轮询+长轮询 - scrapy - 高性能相关,单线程并发发送Http请求 - twisted - gevent - asyncio 本质:基于IO多路复用+非阻塞的socket客户端实现 问题:异步非阻塞? 问题:什么是协程? - scrapy框架 - scrapy执行流程(包含所有组件) - 记录爬虫爬取数据深度(层级),request.meta['depth'] - 传递cookie - 手动 - 自动:meta={'cookiejar':True} - 起始URL - 持久化:pipelines/items - 去重 - 调度器 - 中间件 - 下载中间件 - agent - proxy - 爬虫中间件 - depth - 扩展+信号 - 自定义命令 - scrapy-redis组件,本质:去重、调度器任务、pipeline、起始URL放到redis中。 - 去重,使用的redis的集合。 - 调度器, - redis列表 - 先进先出队列 - 后进先出栈 - redis有序集合 - 优先级队列 PS:深度和广度优先 - pipelines - redis列表 - 起始URL - redis列表 - redis集合 补充: 自定义encoder实现序列化时间等特殊类型: json.dumps(xx,cls=MyEncoder) - scrapy 今日内容: - django - websocket - redis 今日内容: 1. django Form: - 用于对用户请求的数据校验。 - 生成HTML标签 from django.forms import Form from django.forms import fields class UserForm(Form): name = fields.CharField(label='用户名',max_length=32) email = fields.EmailField(label='邮箱') ut_id = fields.ChoiceField( # choices=[(1,'二笔用户'),(2,'闷骚')] choices=[] ) def __init__(self,*args,**kwargs): super(UserForm,self).__init__(*args,**kwargs) # 每次实例化,重新去数据库获取数据并更新 self.fields['ut_id'].choices = models.UserType.objects.all().values_list('id','title') def user(request): if request.method == "GET": form = UserForm() return render(request,'user.html',{'form':form}) 2. 在线实时投票系统 方案一:用户手动刷新 方案二:轮询实现票数实时显示 方案三:长轮询实现票数实时显示 作业:基于redis的列表实现。
day139
s8day139 爬虫 内容回顾: 面试题: 前端: 1. HTML、CSS、JS 2. 框架和类库: - jQuery - BootStrap - Vue.js 3. 响应式布局 @media (min-width: 768px){ .pg-header{ background-color: green; } } @media (min-width: 992px){ .pg-header{ background-color: pink; } } 4. jQuery 5. jQuery Ajax 和 原生Ajax jQuery ajax: $.ajax(...) 原生 ajax:XMLHttpRequest 6. 跨域 - JSONP - CORS - 简单请求 - 复杂请求 Web框架: 1. 你了解哪些Web框架和区别? 2. Django请求生命周期? 3. 什么是wsgi? 是web服务网关接口,是一套协议。以下模块实现了wsgi协议: - wsgiref - werkzurg - uwsgi 以上模块本质:实现socket监听请求,获取请求后将数据封装,然后交给web框架处理。 4. 中间件 中间件的作用?对所有的请求进行批量处理,在视图函数执行前后进行自定义操作。 中间件的应用?cors跨域/用户登录校验/权限处理/CSRF/session/缓存 中间件中方法?5个方法 PS: csrf本质? - 用户先发送GET获取csrf token: Form表单中一个隐藏的标签 + cookie - 发起POST请求时,需要携带之前发送给用户的csrf token; - 在中间件的process_view方法中进行校验。 5. 路由系统 6. 视图 FBV CBV scrapy框架 今日内容: - scrapy-redis - scrapy实现机制 内容详细: 1. scrapy-redis pip3 install scrapy-redis 目标:帮助开发者实现分布式爬虫程序。 a. 利用scrapy-redis做去重规则: # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" # 自定义去重规则 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" 如果想要对redis-scrapy的去重规则进行扩展: from scrapy_redis.dupefilter import RFPDupeFilter class MyRFPDupeFilter(RFPDupeFilter): pass # 自定义去重规则 DUPEFILTER_CLASS = "wenwen.dup.MyRFPDupeFilter" b. 调度器 # 有引擎来执行:自定义调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.FifoQueue' # 默认使用优先级队列(默认),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表) SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 调度器中请求存放在redis中的key SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 对保存到redis中的数据进行序列化,默认使用pickle SCHEDULER_PERSIST = True # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空 SCHEDULER_FLUSH_ON_START = True # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重规则,在redis中保存时对应的key chouti:dupefilter SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 去重规则对应处理的类 DUPEFILTER_DEBUG = False 1. scrapy中去重规则是如何实现? class RFPDupeFilter(BaseDupeFilter): """Request Fingerprint duplicates filter""" def __init__(self, path=None, debug=False): self.fingerprints = set() @classmethod def from_settings(cls, settings): debug = settings.getbool('DUPEFILTER_DEBUG') return cls(job_dir(settings), debug) def request_seen(self, request): # 将request对象转换成唯一标识。 fp = self.request_fingerprint(request) # 判断在集合中是否存在,如果存在则返回True,表示已经访问过。 if fp in self.fingerprints: return True # 之前未访问过,将url添加到访问记录中。 self.fingerprints.add(fp) def request_fingerprint(self, request): return request_fingerprint(request) 2. scrapy-redis中去重规则是如何实现? class RFPDupeFilter(BaseDupeFilter): """Redis-based request duplicates filter. This class can also be used with default Scrapy's scheduler. """ logger = logger def __init__(self, server, key, debug=False): # self.server = redis连接 self.server = server # self.key = dupefilter:123912873234 self.key = key @classmethod def from_settings(cls, settings): # 读取配置,连接redis server = get_redis_from_settings(settings) # key = dupefilter:123912873234 key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())} debug = settings.getbool('DUPEFILTER_DEBUG') return cls(server, key=key, debug=debug) @classmethod def from_crawler(cls, crawler): return cls.from_settings(crawler.settings) def request_seen(self, request): fp = self.request_fingerprint(request) # This returns the number of values added, zero if already exists. # self.server=redis连接 # 添加到redis集合中:1,添加工程;0,已经存在 added = self.server.sadd(self.key, fp) return added == 0 def request_fingerprint(self, request): return request_fingerprint(request) def close(self, reason=''): self.clear() def clear(self): """Clears fingerprints data.""" self.server.delete(self.key) 3. scrapy中的调度器是如何实现? 将request对象全部放到内存维护的队列:self.q = deque() 将request对象全部放到硬盘维护的队列:文件操作 SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleLifoDiskQueue' SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.LifoMemoryQueue' SCHEDULER_PRIORITY_QUEUE = 'queuelib.PriorityQueue' class Scheduler(object): def __init__(self, dupefilter, jobdir=None, dqclass=None, mqclass=None, logunser=False, stats=None, pqclass=None): self.df = dupefilter self.dqdir = self._dqdir(jobdir) self.pqclass = pqclass self.dqclass = dqclass self.mqclass = mqclass self.logunser = logunser self.stats = stats @classmethod def from_crawler(cls, crawler): settings = crawler.settings dupefilter_cls = load_object(settings['DUPEFILTER_CLASS']) dupefilter = dupefilter_cls.from_settings(settings) pqclass = load_object(settings['SCHEDULER_PRIORITY_QUEUE']) dqclass = load_object(settings['SCHEDULER_DISK_QUEUE']) mqclass = load_object(settings['SCHEDULER_MEMORY_QUEUE']) logunser = settings.getbool('LOG_UNSERIALIZABLE_REQUESTS', settings.getbool('SCHEDULER_DEBUG')) return cls(dupefilter, jobdir=job_dir(settings), logunser=logunser, stats=crawler.stats, pqclass=pqclass, dqclass=dqclass, mqclass=mqclass) def has_pending_requests(self): return len(self) > 0 def open(self, spider): self.spider = spider self.mqs = self.pqclass(self._newmq) self.dqs = self._dq() if self.dqdir else None return self.df.open() def close(self, reason): if self.dqs: prios = self.dqs.close() with open(join(self.dqdir, 'active.json'), 'w') as f: json.dump(prios, f) return self.df.close(reason) def enqueue_request(self, request): # request.dont_filter=False # self.df.request_seen(request): # - True,已经访问 # - False,未访问 # request.dont_filter=True,全部加入到调度器 if not request.dont_filter and self.df.request_seen(request): self.df.log(request, self.spider) return False # 如果往下走,把请求加入调度器 dqok = self._dqpush(request) if dqok: self.stats.inc_value('scheduler/enqueued/disk', spider=self.spider) else: self._mqpush(request) self.stats.inc_value('scheduler/enqueued/memory', spider=self.spider) self.stats.inc_value('scheduler/enqueued', spider=self.spider) return True def next_request(self): request = self.mqs.pop() if request: self.stats.inc_value('scheduler/dequeued/memory', spider=self.spider) else: request = self._dqpop() if request: self.stats.inc_value('scheduler/dequeued/disk', spider=self.spider) if request: self.stats.inc_value('scheduler/dequeued', spider=self.spider) return request def __len__(self): return len(self.dqs) + len(self.mqs) if self.dqs else len(self.mqs) def _dqpush(self, request): if self.dqs is None: return try: reqd = request_to_dict(request, self.spider) self.dqs.push(reqd, -request.priority) except ValueError as e: # non serializable request if self.logunser: msg = ("Unable to serialize request: %(request)s - reason:" " %(reason)s - no more unserializable requests will be" " logged (stats being collected)") logger.warning(msg, {'request': request, 'reason': e}, exc_info=True, extra={'spider': self.spider}) self.logunser = False self.stats.inc_value('scheduler/unserializable', spider=self.spider) return else: return True def _mqpush(self, request): self.mqs.push(request, -request.priority) def _dqpop(self): if self.dqs: d = self.dqs.pop() if d: return request_from_dict(d, self.spider) def _newmq(self, priority): return self.mqclass() def _newdq(self, priority): return self.dqclass(join(self.dqdir, 'p%s' % priority)) def _dq(self): activef = join(self.dqdir, 'active.json') if exists(activef): with open(activef) as f: prios = json.load(f) else: prios = () q = self.pqclass(self._newdq, startprios=prios) if q: logger.info("Resuming crawl (%(queuesize)d requests scheduled)", {'queuesize': len(q)}, extra={'spider': self.spider}) return q def _dqdir(self, jobdir): if jobdir: dqdir = join(jobdir, 'requests.queue') if not exists(dqdir): os.makedirs(dqdir) return dqdir 4. scrapy-redis中的调度器是如何实现? 将请求通过pickle进行序列化,然后添加到redis: 列表或有序结合中。 SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' class Scheduler(object): def __init__(self, server, persist=False, flush_on_start=False, queue_key=defaults.SCHEDULER_QUEUE_KEY, queue_cls=defaults.SCHEDULER_QUEUE_CLASS, dupefilter_key=defaults.SCHEDULER_DUPEFILTER_KEY, dupefilter_cls=defaults.SCHEDULER_DUPEFILTER_CLASS, idle_before_close=0, serializer=None): if idle_before_close < 0: raise TypeError("idle_before_close cannot be negative") self.server = server self.persist = persist self.flush_on_start = flush_on_start self.queue_key = queue_key self.queue_cls = queue_cls self.dupefilter_cls = dupefilter_cls self.dupefilter_key = dupefilter_key self.idle_before_close = idle_before_close self.serializer = serializer self.stats = None def __len__(self): return len(self.queue) @classmethod def from_settings(cls, settings): kwargs = { 'persist': settings.getbool('SCHEDULER_PERSIST'), 'flush_on_start': settings.getbool('SCHEDULER_FLUSH_ON_START'), 'idle_before_close': settings.getint('SCHEDULER_IDLE_BEFORE_CLOSE'), } # If these values are missing, it means we want to use the defaults. optional = { # TODO: Use custom prefixes for this settings to note that are # specific to scrapy-redis. 'queue_key': 'SCHEDULER_QUEUE_KEY', 'queue_cls': 'SCHEDULER_QUEUE_CLASS', 'dupefilter_key': 'SCHEDULER_DUPEFILTER_KEY', # We use the default setting name to keep compatibility. 'dupefilter_cls': 'DUPEFILTER_CLASS', 'serializer': 'SCHEDULER_SERIALIZER', } for name, setting_name in optional.items(): val = settings.get(setting_name) if val: kwargs[name] = val # Support serializer as a path to a module. if isinstance(kwargs.get('serializer'), six.string_types): kwargs['serializer'] = importlib.import_module(kwargs['serializer']) server = connection.from_settings(settings) # Ensure the connection is working. server.ping() return cls(server=server, **kwargs) @classmethod def from_crawler(cls, crawler): instance = cls.from_settings(crawler.settings) # FIXME: for now, stats are only supported from this constructor instance.stats = crawler.stats return instance def open(self, spider): self.spider = spider try: self.queue = load_object(self.queue_cls)( server=self.server, spider=spider, key=self.queue_key % {'spider': spider.name}, serializer=self.serializer, ) except TypeError as e: raise ValueError("Failed to instantiate queue class '%s': %s", self.queue_cls, e) try: self.df = load_object(self.dupefilter_cls)( server=self.server, key=self.dupefilter_key % {'spider': spider.name}, debug=spider.settings.getbool('DUPEFILTER_DEBUG'), ) except TypeError as e: raise ValueError("Failed to instantiate dupefilter class '%s': %s", self.dupefilter_cls, e) if self.flush_on_start: self.flush() # notice if there are requests already in the queue to resume the crawl if len(self.queue): spider.log("Resuming crawl (%d requests scheduled)" % len(self.queue)) def close(self, reason): if not self.persist: self.flush() def flush(self): self.df.clear() self.queue.clear() def enqueue_request(self, request): if not request.dont_filter and self.df.request_seen(request): self.df.log(request, self.spider) return False if self.stats: self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider) self.queue.push(request) return True def next_request(self): block_pop_timeout = self.idle_before_close request = self.queue.pop(block_pop_timeout) if request and self.stats: self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider) return request def has_pending_requests(self): return len(self) > 0 相关Queue源码: class Base(object): """Per-spider base queue class""" def __init__(self, server, spider, key, serializer=None): """Initialize per-spider redis queue. Parameters ---------- server : StrictRedis Redis client instance. spider : Spider Scrapy spider instance. key: str Redis key where to put and get messages. serializer : object Serializer object with ``loads`` and ``dumps`` methods. """ if serializer is None: # Backward compatibility. # TODO: deprecate pickle. serializer = picklecompat if not hasattr(serializer, 'loads'): raise TypeError("serializer does not implement 'loads' function: %r" % serializer) if not hasattr(serializer, 'dumps'): raise TypeError("serializer '%s' does not implement 'dumps' function: %r" % serializer) self.server = server self.spider = spider self.key = key % {'spider': spider.name} self.serializer = serializer def _encode_request(self, request): """Encode a request object""" obj = request_to_dict(request, self.spider) return self.serializer.dumps(obj) def _decode_request(self, encoded_request): """Decode an request previously encoded""" obj = self.serializer.loads(encoded_request) return request_from_dict(obj, self.spider) def __len__(self): """Return the length of the queue""" raise NotImplementedError def push(self, request): """Push a request""" raise NotImplementedError def pop(self, timeout=0): """Pop a request""" raise NotImplementedError def clear(self): """Clear queue/stack""" self.server.delete(self.key) class FifoQueue(Base): """Per-spider FIFO queue""" def __len__(self): """Return the length of the queue""" return self.server.llen(self.key) def push(self, request): """Push a request""" self.server.lpush(self.key, self._encode_request(request)) def pop(self, timeout=0): """Pop a request""" if timeout > 0: data = self.server.brpop(self.key, timeout) if isinstance(data, tuple): data = data[1] else: data = self.server.rpop(self.key) if data: return self._decode_request(data) class PriorityQueue(Base): """Per-spider priority queue abstraction using redis' sorted set""" def __len__(self): """Return the length of the queue""" return self.server.zcard(self.key) def push(self, request): """Push a request""" data = self._encode_request(request) score = -request.priority # We don't use zadd method as the order of arguments change depending on # whether the class is Redis or StrictRedis, and the option of using # kwargs only accepts strings, not bytes. self.server.execute_command('ZADD', self.key, score, data) def pop(self, timeout=0): """ Pop a request timeout not support in this queue class """ # use atomic range/remove using multi/exec pipe = self.server.pipeline() pipe.multi() pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0) results, count = pipe.execute() if results: return self._decode_request(results[0]) class LifoQueue(Base): """Per-spider LIFO queue.""" def __len__(self): """Return the length of the stack""" return self.server.llen(self.key) def push(self, request): """Push a request""" self.server.lpush(self.key, self._encode_request(request)) def pop(self, timeout=0): """Pop a request""" if timeout > 0: data = self.server.blpop(self.key, timeout) if isinstance(data, tuple): data = data[1] else: data = self.server.lpop(self.key) if data: return self._decode_request(data) # TODO: Deprecate the use of these names. SpiderQueue = FifoQueue SpiderStack = LifoQueue SpiderPriorityQueue = PriorityQueue 爬虫爬取数据时存在层级和优先级:爬虫中间件实现 使用scrapy-redis组件: 情况一:只用它的去重规则功能 配置: # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" # 自定义去重规则 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" 情况二:只用它的调度器 # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" # 有引擎来执行:自定义调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 默认使用优先级队列(默认广度优先),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表) SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 调度器中请求存放在redis中的key SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 对保存到redis中的数据进行序列化,默认使用pickle SCHEDULER_PERSIST = True # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空 SCHEDULER_FLUSH_ON_START = False # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重规则,在redis中保存时对应的key chouti:dupefilter SCHEDULER_DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' # 去重规则对应处理的类 #去重规则对应处理的类 DUPEFILTER_DEBUG = False 情况三:去重+调度去 # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" # 有引擎来执行:自定义调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 默认使用优先级队列(默认广度优先),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表) SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 调度器中请求存放在redis中的key SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 对保存到redis中的数据进行序列化,默认使用pickle SCHEDULER_PERSIST = True # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空 SCHEDULER_FLUSH_ON_START = False # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重规则,在redis中保存时对应的key chouti:dupefilter SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 去重规则对应处理的类 DUPEFILTER_DEBUG = False 情况四:使用scrapy-redis内置的pipeline做持久化:就是将item对象保存到redis的列表中。 配置: # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300, } 以上功能全部应用的配置: # ############ 连接redis 信息 ################# REDIS_HOST = '127.0.0.1' # 主机名 REDIS_PORT = 6379 # 端口 # REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL(优先于以上配置) REDIS_PARAMS = {} # Redis连接参数 默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,}) # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块 默认:redis.StrictRedis REDIS_ENCODING = "utf-8" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 有引擎来执行:自定义调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue' # 默认使用优先级队列(默认广度优先),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表) SCHEDULER_QUEUE_KEY = '%(spider)s:requests' # 调度器中请求存放在redis中的key SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat" # 对保存到redis中的数据进行序列化,默认使用pickle SCHEDULER_PERSIST = True # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空 SCHEDULER_FLUSH_ON_START = False # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空 # SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。 SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重规则,在redis中保存时对应的key chouti:dupefilter SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 去重规则对应处理的类 DUPEFILTER_DEBUG = False # 深度和优先级相关 DEPTH_PRIORITY = 1 情况五:让scrapy-redis的起始URL不再通过start_reuqests执行,而是去redis中获取。 配置: REDIS_START_URLS_BATCH_SIZE = 1 # REDIS_START_URLS_AS_SET = True # 把起始url放到redis的集合 REDIS_START_URLS_AS_SET = False # 把起始url放到redis的列表 爬虫: from scrapy_redis.spiders import RedisSpider from scrapy.http import Request from ..items import WenwenItem class ChoutiSpider(RedisSpider): name = 'chouti' allowed_domains = ['chouti.com'] def parse(self, response): # 随着深度的增加、优先级一直在递减 print(response) 放置起始URL: import redis conn = redis.Redis(host='127.0.0.1',port=6379) # 起始url的Key: chouti:start_urls conn.lpush("chouti:start_urls",'https://dig.chouti.com/r/ask/hot/12')
day138
2s8day138 scrapy框架 内容回顾: 面试题: 1. 引擎 2. 设计表:FK、M2M 3. 操作表: select id from tb select id from tb where id > 2 select id from tb where id in (select id from tb) select 部门ID,max(id) from 用户表 group 部门ID select 部门ID,max(id) from 用户表 group 部门ID having count(id)>3 select * from tb1 left join tb2 on tb1.xx = tb2.xx # tb1所有都显示,如果tb2中未找到对应项则显示null select * from tb1 inner join tb2 on tb1.xx = tb2.xx # null行全部去掉 select id, 1, (select max(id) from tb ) as b from tb 4. 触发器、函数、存储过程、视图 触发器:在对表进行 增删改 前后进行自定义一些SQL操作。 函数: select max(id),xx(name) from tb select sleep(2) - 聚合 - 字符串 - 时间 存储过程:在数据库中定义一些操作SQL语句,以后在代码中通过名称即可调用。 问题:存储过程和函数的区别? 函数: - 参数 - 返回值: return 存储过程: - 参数 - 返回值:out/inout - 返回结果集:select * from tb; 视图:虚拟表->通过sql帮助我们实时查询的数据。 5. 索引:5+2 6.其他: 慢日志 执行计划 7.优化: - 命中索引 - 不用select * - 固定长度放在前面 - 读写分离 - 分库 - 分表 - 水平 - 垂直 - 查询单条数据:limit 1 - 缓存 - 分页 scrapy: 第一步: scrapy startproject xxxx cd xxxx scrapy genspider chouti chouti.com # 编写代码 scrapy crawl chouti --nolog 第二步:start_requests 第三步:parse函数 - 解析器 - yield item - yield Request() 第四步:携带cookie - 手动 - 自动 第五步:去重规则 第六步:下载中间件 今日内容: 1. 下载中间件 2. 爬虫中间件 3. 扩展:信号 4. 其他 5. 自定义命令 6. 自定义scrapy框架 7. scrapy-redis组件 内容详细: 1. 下载中间件 问题:scrapy中如何添加代理? 解决方案: 方式一:内置添加代理功能 # -*- coding: utf-8 -*- import os import scrapy from scrapy.http import Request class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['https://dig.chouti.com/'] def start_requests(self): os.environ['HTTP_PROXY'] = "http://192.168.11.11" for url in self.start_urls: yield Request(url=url,callback=self.parse) def parse(self, response): print(response) 方式二:自定义下载中间件 import random import base64 import six def to_bytes(text, encoding=None, errors='strict'): """Return the binary representation of `text`. If `text` is already a bytes object, return it as-is.""" if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError('to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type(text).__name__) if encoding is None: encoding = 'utf-8' return text.encode(encoding, errors) class MyProxyDownloaderMiddleware(object): def process_request(self, request, spider): proxy_list = [ {'ip_port': '111.11.228.75:80', 'user_pass': 'xxx:123'}, {'ip_port': '120.198.243.22:80', 'user_pass': ''}, {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, {'ip_port': '101.71.27.120:80', 'user_pass': ''}, {'ip_port': '122.96.59.104:80', 'user_pass': ''}, {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ] proxy = random.choice(proxy_list) if proxy['user_pass'] is not None: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass'])) request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) else: request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) 配置: DOWNLOADER_MIDDLEWARES = { # 'xiaohan.middlewares.MyProxyDownloaderMiddleware': 543, } 问题:scrapy中如何处理https 掏钱: pass 不掏钱: from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions(self): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read()) return CertificateOptions( privateKey=v1, # pKey对象 certificate=v2, # X509对象 verify=False, method=getattr(self, 'method', getattr(self, '_ssl_method', None)) ) 配置: DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "xiaohan.middlewares.MySSLFactory" 总结: 问:下载中间件的作用? 答:在每次下载前和下载后对请求和响应可以定制功能。例如:user-agent/代理/cookie 2. 爬虫中间件 编写: middlewares.py class XiaohanSpiderMiddleware(object): # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the spider middleware does not modify the # passed objects. def __init__(self): pass @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() return s # 每次下载完成之后,未执行parse函数之前。 def process_spider_input(self, response, spider): # Called for each response that goes through the spider # middleware and into the spider. # Should return None or raise an exception. print('process_spider_input',response) return None def process_spider_output(self, response, result, spider): # Called with the results returned from the Spider, after # it has processed the response. # Must return an iterable of Request, dict or Item objects. print('process_spider_output',response) for i in result: yield i def process_spider_exception(self, response, exception, spider): # Called when a spider or process_spider_input() method # (from other spider middleware) raises an exception. # Should return either None or an iterable of Response, dict # or Item objects. pass # 爬虫启动时,第一次执行start_requests时,触发。(只执行一次) def process_start_requests(self, start_requests, spider): # Called with the start requests of the spider, and works # similarly to the process_spider_output() method, except # that it doesn’t have a response associated. # Must return only requests (not items). print('process_start_requests') for r in start_requests: yield r 应用: SPIDER_MIDDLEWARES = { 'xiaohan.middlewares.XiaohanSpiderMiddleware': 543, } 3. 扩展:信号 单纯扩展: extends.py class MyExtension(object): def __init__(self): pass @classmethod def from_crawler(cls, crawler): obj = cls() return obj 配置: EXTENSIONS = { 'xiaohan.extends.MyExtension':500, } 扩展+信号: extends.py from scrapy import signals class MyExtension(object): def __init__(self): pass @classmethod def from_crawler(cls, crawler): obj = cls() # 在爬虫打开时,触发spider_opened信号相关的所有函数:xxxxxxxxxxx crawler.signals.connect(obj.xxxxxxxxxxx1, signal=signals.spider_opened) # 在爬虫关闭时,触发spider_closed信号相关的所有函数:xxxxxxxxxxx crawler.signals.connect(obj.uuuuuuuuuu, signal=signals.spider_closed) return obj def xxxxxxxxxxx1(self, spider): print('open') def uuuuuuuuuu(self, spider): print('close') return obj 配置: EXTENSIONS = { 'xiaohan.extends.MyExtension':500, } 4. 其他:配置文件 5. 自定义命令 from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax(self): return '[options]' def short_desc(self): return 'Runs all of the spiders' def run(self, args, opts): spider_list = self.crawler_process.spiders.list() for name in spider_list: self.crawler_process.crawl(name, **opts.__dict__) self.crawler_process.start() PS:源码 def run(self, args, opts): from scrapy.crawler import CrawlerProcess CrawlerProcess.crawl CrawlerProcess.start """ self.crawler_process对象中含有:_active = {d,} """ self.crawler_process.crawl('chouti',**opts.__dict__) self.crawler_process.crawl('cnblogs',**opts.__dict__) # self.crawler_process.start() 分享:源码 总结: 下载中间件(*****) 爬虫中间件(***) 扩展:信号(***) 配置(*****) 自定义命令(*****)
day136
s8day136 内容回顾: 1. 面试题部分 - 引擎 - 设计表 - FK - M2M - 基本操作 - 库 - 表 - 存储过程、视图、函数、触发器 - 触发器,在数据库某个表进行‘增删改’前后定义一些操作。 - 函数,在SQL语句中使用函数。 - select sleep(5) - select date_format(ctime,'%m') from tb; - 存储过程,将SQL语句保存到数据库中,并命名;以后在代码中调用时,直接发送名称。 参数: - in - out - inout 代码: #!/usr/bin/env python # -*- coding:utf-8 -*- import pymysql conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123', db='t1') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 执行存储过程 cursor.callproc('p1', args=(1, 22, 3, 4)) # 获取执行完存储的参数 cursor.execute("select @_p1_0,@_p1_1,@_p1_2,@_p1_3") result = cursor.fetchall() conn.commit() cursor.close() conn.close() print(result) - 视图,对某些表进行SQL查询,将结果显示实时显示出来(只能查) v = select * from tb where id <1000 select * from v select * from (select * from tb where id <1000) as v PS: 都是保存在数据库 - 索引 - 命中索引 - 优化 - 不用 select * - 固定长度字段列,往前放 - char和varchar - 固定数据放入内存:choice - 读写分离,利用数据库的主从进行分离:主,用于删除、修改更新;从,查。 - 分库,当数据库中表太多,将表分到不同的数据库;例如:1w张表 - 分表, - 水平分表,将某些列拆分到另外一张表;例如:博客+博客详细 - 垂直分表,将历史信息分到另外一张表中;例如:账单 - 缓存:利用redis、memcache - 其它: - 慢日志 - 执行计划 - 分页 补充: text类型,创建索引; 2. 高性能相关 - 通过异步非阻塞模块实现。 - 常见单线程异步非阻塞模块实现: - asyncio - gevent - twisted (scrapy内部依赖) - 什么是异步非阻塞? - IO多路复用 3. scrapy框架 基本命令: scrapy startproject sp1 cd sp1 scrapy genspider chouti chouti.com scrapy crawl chouti --nolog 编写: a. 起始URL b. parse函数 c. pipelines d. 解析器 今日内容: 1. start_requests 2. 数据解析 3. pipelines 4. POST/请求头/Cookie 5. 去重规则 6. 下载中间件/爬虫中间件 7. 扩展 8. 自定义命令 内容详细: 1. start_requests def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse2) def start_requests(self): req_list = [] for url in self.start_urls: req_list.append(Request(url=url,callback=self.parse2)) return req_list 因为scrapy内部会将返回值转换成迭代器。 2. 解析器 将字符串转换成对象: - 方式一: response.xpath('//div[@id='content-list']/div[@class='item']') - 方式二: hxs = HtmlXPathSelector(response=response) items = hxs.xpath("//div[@id='content-list']/div[@class='item']") 查找规则: //a //div/a //a[re:test(@id, "i\d+")] items = hxs.xpath("//div[@id='content-list']/div[@class='item']") for item in items: item.xpath('.//div') 解析: 标签对象:xpath('/html/body/ul/li/a/@href') 列表: xpath('/html/body/ul/li/a/@href').extract() 值: xpath('//body/ul/li/a/@href').extract_first() PS: 单独应用 from scrapy.selector import Selector, HtmlXPathSelector from scrapy.http import HtmlResponse html = """<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li class="item-"><a id='i1' href="link.html">first item</a></li> <li class="item-0"><a id='i2' href="llink.html">first item</a></li> <li class="item-1"><a href="llink2.html">second item<span>vv</span></a></li> </ul> <div><a href="llink2.html">second item</a></div> </body> </html> """ response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8') obj = response.xpath('//a[@id="i1"]/text()').extract_first() print(obj) chrome xpath 3. pipelines - pipelines基础 class FilePipeline(object): def process_item(self, item, spider): print('写入文件',item['href']) return item def open_spider(self, spider): """ 爬虫开始执行时,调用 :param spider: :return: """ print('打开文件') def close_spider(self, spider): """ 爬虫关闭时,被调用 :param spider: :return: """ print('关闭文件') - 多pipelines(值越小优先级越高) - 多pipelines,返回值会传递给下一个pipelines的process_item PS:如果想要丢弃,不给后续pipeline使用: from scrapy.exceptions import DropItem class FilePipeline(object): def process_item(self, item, spider): print('写入文件',item['href']) # return item raise DropItem() - 根据配置文件读取相关值,再进行pipeline处理 class FilePipeline(object): def __init__(self,path): self.path = path self.f = None @classmethod def from_crawler(cls, crawler): """ 初始化时候,用于创建pipeline对象 :param crawler: :return: """ path = crawler.settings.get('XL_FILE_PATH') return cls(path) def process_item(self, item, spider): self.f.write(item['href']+'\n') return item def open_spider(self, spider): """ 爬虫开始执行时,调用 :param spider: :return: """ self.f = open(self.path,'w') def close_spider(self, spider): """ 爬虫关闭时,被调用 :param spider: :return: """ self.f.close() 4. POST/请求头/Cookie 自动登录抽屉+点赞 POST+请求头: from scrapy.http import Request req = Request( url='http://dig.chouti.com/login', method='POST', body='phone=8613121758648&password=woshiniba&oneMonth=1', headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, cookies={}, callback=self.parse_check_login, ) cookies: 手动: cookie_dict = {} cookie_jar = CookieJar() cookie_jar.extract_cookies(response, response.request) for k, v in cookie_jar._cookies.items(): for i, j in v.items(): for m, n in j.items(): cookie_dict[m] = n.value req = Request( url='http://dig.chouti.com/login', method='POST', headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, body='phone=8615131255089&password=pppppppp&oneMonth=1', cookies=cookie_dict, # 手动携带 callback=self.check_login ) yield req 自动: class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['chouti.com'] start_urls = ['http://dig.chouti.com/',] def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse_index,meta={'cookiejar':True}) def parse_index(self,response): req = Request( url='http://dig.chouti.com/login', method='POST', headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, body='phone=8613121758648&password=woshiniba&oneMonth=1', callback=self.parse_check_login, meta={'cookiejar': True} ) yield req def parse_check_login(self,response): # print(response.text) yield Request( url='https://dig.chouti.com/link/vote?linksId=19440976', method='POST', callback=self.parse_show_result, meta={'cookiejar': True} ) def parse_show_result(self,response): print(response.text) 配置文件制定是否允许操作cookie: # Disable cookies (enabled by default) # COOKIES_ENABLED = False 5. 去重规则 配置: DUPEFILTER_CLASS = 'xianglong.dupe.MyDupeFilter' 编写类: class MyDupeFilter(BaseDupeFilter): def __init__(self): self.record = set() @classmethod def from_settings(cls, settings): return cls() def request_seen(self, request): if request.url in self.record: print('已经访问过了', request.url) return True self.record.add(request.url) def open(self): # can return deferred pass def close(self, reason): # can return a deferred pass 问题:为请求创建唯一标识 http://www.oldboyedu.com?id=1&age=2 http://www.oldboyedu.com?age=2&id=1 from scrapy.utils.request import request_fingerprint from scrapy.http import Request u1 = Request(url='http://www.oldboyedu.com?id=1&age=2') u2 = Request(url='http://www.oldboyedu.com?age=2&id=1') result1 = request_fingerprint(u1) result2 = request_fingerprint(u2) print(result1,result2) 问题:记录到低要不要放在数据库?【使用redis集合存储】 访问记录可以放在redis中。 补充:dont_filter到低在哪里? from scrapy.core.scheduler import Scheduler def enqueue_request(self, request): # request.dont_filter=False # self.df.request_seen(request): # - True,已经访问 # - False,未访问 # request.dont_filter=True,全部加入到调度器 if not request.dont_filter and self.df.request_seen(request): self.df.log(request, self.spider) return False # 如果往下走,把请求加入调度器 dqok = self._dqpush(request) 6. 中间件 问题:对爬虫中所有请求发送时,携带请求头? 方案一:在每个Request对象中添加一个请求头 方案二:下载中间件 配置: DOWNLOADER_MIDDLEWARES = { 'xianglong.middlewares.UserAgentDownloaderMiddleware': 543, } 编写类: class UserAgentDownloaderMiddleware(object): @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() return s def process_request(self, request, spider): # Called for each request that goes through the downloader # middleware. # Must either: # - return None: continue processing this request # - or return a Response object # - or return a Request object # - or raise IgnoreRequest: process_exception() methods of # installed downloader middleware will be called request.headers['User-Agent'] = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" # return None # 继续执行后续的中间件的process_request # from scrapy.http import Request # return Request(url='www.baidu.com') # 重新放入调度器中,当前请求不再继续处理 # from scrapy.http import HtmlResponse # 执行从最后一个开始执行所有的process_response # return HtmlResponse(url='www.baidu.com',body=b'asdfuowjelrjaspdoifualskdjf;lajsdf') def process_response(self, request, response, spider): # Called with the response returned from the downloader. # Must either; # - return a Response object # - return a Request object # - or raise IgnoreRequest return response def process_exception(self, request, exception, spider): # Called when a download handler or a process_request() # (from other downloader middleware) raises an exception. # Must either: # - return None: continue processing this exception # - return a Response object: stops process_exception() chain # - return a Request object: stops process_exception() chain pass 方案三:内置下载中间件 配置文件: USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' 总结: 1. 存储过程、触发器函数等作用? 2. 优化补充: - 读写分离,利用数据库的主从进行分离:主,用于删除、修改更新;从,查。 原生SQL: select * from db.tb ORM: model.User.objects.all().using("default") PS: 路由 db router - 分库,当数据库中表太多,将表分到不同的数据库;例如:1w张表 - 分表 - 缓存:利用redis、memcache 3. 创建索引 text列如果想要创建索引,必须执行长度。 4. start_requests - 可迭代对象 - 生成器 5. pipelines - 配置 ITEM_PIPELINES = { 'xianglong.pipelines.FilePipeline': 300, } - 写类 class FilePipeline(object): def __init__(self,path):pass @classmethod def from_crawler(cls, crawler): pass def process_item(self, item, spider): pass return item def open_spider(self, spider): pass def close_spider(self, spider): pass 6. 去重 - 配置 DUPEFILTER_CLASS = 'xianglong.dupe.MyDupeFilter' - 写类 class MyDupeFilter(BaseDupeFilter): def __init__(self): pass @classmethod def from_settings(cls, settings): pass def request_seen(self, request): pass def open(self): # can return deferred pass def close(self, reason): # can return a deferred pass 7. 下载中间件 - 配置 DOWNLOADER_MIDDLEWARES = { 'xianglong.middlewares.UserAgentDownloaderMiddleware': 543, } - 类 class UserAgentDownloaderMiddleware(object): @classmethod def from_crawler(cls, crawler): pass def process_request(self, request, spider): pass def process_response(self, request, response, spider): pass def process_exception(self, request, exception, spider): pass 8. POST/请求头/Cookie

浙公网安备 33010602011771号