多进程编程
forking工作原理
source执行脚本,不会开新进程执行,而其他的解释权会开新进程来执行
• fork(分岔)在Linux系统中使用非常广泛
• 当某一命令执行时,父进程(当前进程)fork出一个子进程
• 父进程将自身资源拷贝一份,命令在子进程中运行时,就具有和父进程完全一样的运行环境
进程的生命周期
• 父进程fork出子进程并挂起
• 子进程运行完毕后,释放大部分资源并通知父进程,这个时候,子进程被称作僵尸进程
• 父进程获知子进程结束,子进程所有资源释放
僵尸进程
• 僵尸进程没有任何可执行代码,也不能被调度
• 如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
• 对于系统管理员来说,可以试图杀死其父进程或重启系统来消除僵尸进程
forking编程
forking编程基本思路
os.fork返回值在父子进程中是不一样的,父进程中,值为非0;子进程中,返回值为0
父进程只负责生成子进程,子进程做具体的工作,子进程工作结束后,应该exit退出
• 需要使用os模块
• os.fork()函数实现forking功能
• python中,绝大多数的函数只返回一次,os.fork将返回两次
• 对fork()的调用,针对父进程返回子进程的PID;对于子进程,返回PID0
import os
print('starting...')
os.fork() #创建子进程
print('hello world') #父进程执行打印一遍,子进程打开后执行又打印一遍
父子进程是相对的,子进程还可以产生子进程
import os
print('starting...')
for i in range(3): #子进程在循环里面,子进程还会产生相对子进程,然后执行命令语句
retval = os.fork()
if not retval:
print('hello world!')
(5nsd1906) [student@room9pc01 py10]$ python myfork2.py
starting...
hello world!
hello world!
hello world!
hello world!
(5nsd1906) [student@room9pc01 py10]$ hello world!
hello world!
hello world!
exit介绍
import os
print('starting...')
for i in range(3):
retval = os.fork()
if not retval:
print('hello world!')
exit() #进程遇到exit会退出进程
print('Done')
例;扫描存活主机
- 通过ping测试主机是否可达
- 如果ping不通,不管什么原因都认为主机不可用
- 通过fork方式实现并发扫描
import subprocess
import time
def ping(host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)
if __name__ == '__main__':
ips = ('176.233.11.%s' % i for i in range(1, 255))
print(time.ctime())
for ip in ips:
ping(ip)
print(time.ctime())
例2:编写一个forking脚本
- 在父进程中打印“In parent”然后睡眠10秒
- 在子进程中编写循环,循环5次,输出当系统时间,每次循环结束后睡眠1秒
- 父子进程结束后,分别打印“parent exit”和“child exit”
import os
import time
retval = os.fork()
if retval:
print('parent')
time.sleep(45)
print('parent done')
else:
print('child')
time.sleep(15)
print('child done')
#watch -n1 ps a 表示每隔一秒显示ps进程信息
解决zombie问题
• 父进程通过os.wait()来得到子进程是否终止的信息
• 在子进程终止和父进程调用wait()之间的这段时间,子进程被称为zombie(僵尸)进程
• 如果子进程还没有终止,父进程先退出了,那么子进程会持续工作。系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
• python可以使用waitpid()来处理子进程
• waitpid()接受两个参数,第一个参数设置为-1,表示与wait()函数相同;第二参数如果设置为0表示挂起父进程,直到子程序退出,设置为1表示不挂起父进程
• waitpid()的返回值:如果子进程尚未结束则返回0,否则返回子进程的PID
import os, time
def reap():
result = os.waitpid(-1, 1)
print(‘Reaped child process %d’ % result[0])
pid = os.fork()
if pid:
print ‘In parent. Sleeping 15s…’
time.sleep(15)
reap()
time.sleep(5)
print(‘parent done’)
else:
print ‘In child. Sleeping 5s…’
time.sleep(5)
print(‘Child terminating.’)
import os
import time
retval = os.fork()
if retval:
print('parent')
#(-1,0)挂起父进程,处理完变成僵尸进程的子进程后才继续,此时result是(子进程pid,0)
#(-1,1)不挂起父进程,子进程是僵尸进程的就处理掉,不是则跳过,此时,result是(0,0)
result = os.waitpid(-1, 0)
print(result) #result是(子进程PID, 0)
time.sleep(10)
print('parent done')
else:
print('child')
time.sleep(15)
print('child done')
#watch -n1 ps a 表示每隔一秒显示ps进程信息
多线程工作原理
多线程的动机
• 在多线程(MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
• 无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
• 即使子任务相互独立,互相无关(即,一个子任务的结果不影响其它子任务的结果)时也是这样
• 如果并行运行这些相互独立的子任务可以大幅度地提升整个任务的效率
多线程任务的工作特点
• 它们本质上就是异步的,需要有多个并发事务
• 各个事务的运行顺序可以是不确定的,随机的,不可预测的
• 这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
• 根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果
什么是进程
====
• 计算机程序只不过是磁盘中可执行的、二进制(或其它类型)的数据
• 进程(有时被称为重量级进程)是程序的一次执行
• 每个进程都有自己的地址空间、内存以及其它记录其运行轨迹的辅助数据
• 操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间
什么是线程
多线程,非操作系统均支持,程序,就是磁盘 ,当程序运行时,就会出现进程.可以说,进程就是加载内存中的一系列指令,每个
• 线程(有时被称为轻量级进程)跟进程有些相似。不同的是,所有的线程运行在同一个进程中,共享相同的运行环境
• 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯
多线程相关模块
• thread(底层)和threading(高层)模块允许程序员创建和管理线程
• thread模块提供了基本的线程和锁的支持,而threading提供了更高级别、功能更强的线程管理功能
• 推荐使用更高级别的threading模块
import subprocess
import threading
def ping(host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)
if __name__ == '__main__':
ips = ('176.233.11.%s' % i for i in range(1, 255))
for ip in ips:
#创建工作线程
t = threading.Thread(target=ping, args=(ip,))
#启动工作线程,就是调用相应的函数,函数结束,工作线程也就结束
t.start() # 调用target(*args)
传递函数给Thread类
• 多线程编程有多种方法,传递函数给threading模块的Thread类是介绍的第一种方法
• Thread对象使用start()方法开始线程的执行,使用join()方法挂起程序,直到线程结束
传递可调用类给Thread类
• 传递可调用类给Thread类是介绍的第二种方法
• 相对于一个或几个函数来说,由于类对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活
import subprocess
import threading
class Ping:
def __call__(self, host):
result = subprocess.run(
'ping -c2 %s &> /dev/null' % host, shell=True
)
if result.returncode == 0:
print('%s:up' % host)
else:
print('%s:down' % host)
if __name__ == '__main__':
ips = ('176.233.11.%s' % i for i in range(1, 255))
for ip in ips:
#target是Ping的实例
t = threading.Thread(target=Ping(), args=(ip,))
t.start() #调用target(*args)
urllib模块
urllib简介
• 在Python2版本中,有urllib和urlib2两个库可以用来实现request的发送。而在Python3中,已经不存在urllib2这个库了,统一为urllib 实现http客户端功能
• urllib中包括了四个模块
– urllib.request可以用来发送request和获取request的结果
– urllib.error包含了urllib.request产生的异常
– urllib.parse用来解析和处理URL
– urllib.robotparse用来解析页面的robots.txt文件
爬虫网页
• 先需要导入用到的模块:urllib.request
• 在导入了模块之后,我们需要使用urllib.request.urlopen打开并爬取一个网页
• 读取内容常见的有3种方式:
– read()读取文件的全部内容,与readlines()不同的是,read()会把读取到的内容赋给一个字符串变量。
– readlines()读取文件的全部内容,readlines()会把读取到的内容赋值给一个列表变量。
– readline()读取文件的一行内容。
import urllib.request
html = urllib.request.urlopen(‘http://www.tedu.cn’)
html.readline()
html.read(4096)
html.readlines()
>>> from urllib import request
>>> url = 'http://www.163.com'
>>> html = request.urlopen(url)
ht>>> html.readline()
b' <!DOCTYPE HTML>\n'
>>> html.read(10)
b'<!--[if IE'
>>> html.readlines()
下载网络资源
• urllib不仅可以下载网页,其他网络资源均可下载
• 有些文件比较大,需要像读取文件一样,每次读取一部分数据
import urllib.request
html = urllib.request.urlopen(‘http://172.40.50.116/python.pdf’)
fobj = open(’/tmp/python.pdf’, ‘ab’)
while True:
data = html.read(4096)
if not data:
break
fobj.write(data)
fobj.close()
from urllib import request
import sys
def download(url, fname):
html = request.urlopen(url)
with open(fname, 'wb') as fobj:
while 1:
data = html.read(4096)
if not data:
break
fobj.write(data)
if __name__ == '__main__':
url = sys.argv[1]
fname = sys.argv[2]
download(url, fname)
模拟客户端
• 有些网页为了防止别人恶意采集其信息所以进行了一些反爬虫的设置,而我们又想进行爬取
• 可以设置一些Headers信息(User-Agent),模拟成浏览器去访问这些网站
import urllib.request
url=‘http://www.tedu.cn’
hearder={
‘User-Agent’:‘Mozilla/5.0 (X11; Fedora; Linux x86_64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110
Safari/537.36’
}
html=urllib.request.Request(url,headers=header)
data=urllib.request.urlopen(request).read()
urllib进阶
数据编码
• 一般来说,URL标准中只会允许一部分ASCII字符,比如数字、字母、部分符号等
• 而其他的一些字符,比如汉字等,>是不符合URL标准的。此时,我们需要编码。
• 如果要进行编码,可以使用urllib.request.quote()进行
urllib.request.quote(‘hello world!’)
‘hello%20world%21’
urllib.request.unquote(‘hello%20world%21’)
‘hello world!’
>>> url = 'https://www.sogou.com/web?query=' + request.quote('元旦放假') #不能直接使用url内部编码进行索引,应该在外+ request.quote('索引内容')
>>> url
'https://www.sogou.com/web?query=%E5%85%83%E6%97%A6%E6%94%BE%E5%81%87'
HTTP异常处理
• 如果访问的页面不存在或拒绝访问,程序将抛出异常
• 捕获异常需要导入urllib.error模块
html = urllib.request.urlopen(‘http://172.40.50.116/a.html’)
urllib.error.HTTPError: HTTP Error 404: Not Found
html = urllib.request.urlopen(‘http://172.40.50.116/aaa’)
urllib.error.HTTPError: HTTP Error 403: Forbidden
使用wget模块
>>> import wget
>>> url = 'https://img.ivsky.com/img/tupian/slides/201905/02/chongwumao-013.jpg'
>>> wget.download(url, '/tmp/cat.jpg')
100% [...................................................................] 66041 / 66041'/tmp/cat.jpg'
(5nsd1906) [student@room9pc01 py10]$ eog /tmp/cat.jpg #命令行查看图片
例:下载网易上所有的图片
找到所有图片的url,下载图片:
1.下载网易首页,保存为一个文件
2.从网易文件的每一行中查找图片网址,收集网址到一个列表中
3.遍历所有网址,循环下载
import wget
import os
import re
def get_url(fname, patt, encoding=None):
result = []
cpatt = re.compile(patt)
with open(fname, encoding=encoding) as fobj:
for line in fobj:
m = cpatt.search(line)
if m:
result.append(m.group())
return result
if __name__ == '__main__':
img_dir = '/tmp/163'
fname163 = '/tmp/163/163.html'
url163 = 'http://www.163.com'
# 如果不存在保存图片的目录,则创建
if not os.path.exists(img_dir):
os.mkdir(img_dir)
# 如果网易首页文件不存在,则下载
if not os.path.exists(fname163):
wget.download(url163, fname163)
# 取出网易首页中所有的图片地址
img_patt = '(http|https)://[-\w/.]+\.(jpg|png|jpeg|gif)'
# 网易网页使用的编码是gbk,不是utf8
img_list = get_url(fname163, img_patt, 'gbk')
# print(img_list)
# 下载图片
for url in img_list:
wget.download(url, img_dir)
paramiko
安装paramiko模块
• 本地安装
#yum install -y gcc gcc-c++ python-devel
#tar xzf paramiko-1.15.4.tar.gz
#python setup.py install
• 网络安装
#pip install paramiko
基础使用介绍
• SSHClient
– 创建用于连接ssh服务器的实例
ssh = paramiko.SSHClient()
• paramiko.AutoAddPolicy
– 设置自动添加主机密钥
• ssh.connect
– 连接ssh服务器
• ssh.exec_comand
– 在ssh服务器上执行指定命令
>>> import paramiko #导入模块
>>> ssh = paramiko.SSHClient() #创建SSHClient对象
>>> ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #自动接收服务器发来的秘钥,相对于是自动回答yes
>>> ssh.connect('192.168.1.137', username='root', password='a') #登录
>>> result = ssh.exec_command('id root; id zhangsan') #执行命令
>>> len(result) #分天邮件 析返回结果,result是一个长度为3的元组
3
>>> stdin, stdout, stderr = result #result中的三项分别是输入、输出和错误的类文件对象
>>> out = stdout.read() #读取输出信息
>>> err = stderr.read() #读取错误信息
>>> out
b'uid=0(root) gid=0(root) groups=0(root)\n'
>>> err
b'id: zhangsan: no such user\n'
>>> out.decode() #将bytes转为str
'uid=0(root) gid=0(root) groups=0(root)\n'
>>> ssh.close() #关闭连接
paramiko实例
• 编写用于实现ssh访问的脚本
– 创建SSHClient实例
– 设置添加主机密钥策略
– 连接ssh服务器
– 执行指定命令
– 在shell命令行中接受用于连接远程服务器的密码以及在远程主机上执行的命令
实现对某文件记录的ip地址的主机,批量命令操作
import paramiko
import sys
import getpass
import threading
import os
def rcmd(host, user='root', passwd=None ,port=22, cmd=None):
#未知参数都写前面,默认参数都写后面
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, username=user, port=port, password=passwd)
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read()
err = stderr.read()
if out: #如果有输出
print('[%s] OUT:\n%s' % (host, out.decode()))
if err:
print('[%s] ERROR:\n%s' % (host, err.decode()))
ssh.close()
if __name__ == '__main__':
# rcmd('192.168.1.137', passwd='a', cmd='id root; id zhangsan')
if len(sys.argv) != 3:
print("Usage: %s ipfile'command'" % sys.argv[0])
exit(1)
if not os.path.isfile(sys.argv[1]):
print('No such file:', sys.argv[1])
exit(2)
ipfile = sys.argv[1]
cmd = sys.argv[2]
passwd = getpass.getpass()
with open(ipfile) as fobj:
for line in fobj:
ip = line.strip() #取出\n
t = threading.Thread(
target=rcmd, args=(ip,),
kwargs={'passwd': passwd, 'cmd': cmd}
)
t.start()