python:多进程、多线程、urllib模块、爬取网页图片

什么是进程
• 计算机程序只不过是磁盘中可执行的、二进制(或其
它类型)的数据
• 进程(有时被称为重量级进程)是程序的一次执行
• 每个进程都有自己的地址空间、内存以及其它记录其运行轨迹的辅助数据
• 操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间
#########################################################
什么是线程
• 线程(有时被称为轻量级进程)跟进程有些相似。不同的是,所有的线程运行在同一个进程中,共享相同的运行环境
• 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯
#########################################################

什么是forking

  • linux采用fork方式执行程序。当运行程序(命令)时,系统将会把父进程的资源拷贝一份,生成子进程。程序、命令在子进程中运行。
  • 当命令执行完毕后,子进程销毁。

进程的生命周期
• 父进程fork出子进程并挂起
• 子进程运行完毕后,释放大部分资源并通知父进程,
这个时候,子进程被称作僵尸进程
• 父进程获知子进程结束,子进程所有资源释放

#执行shell的方式

  • bash xxx.sh: 指定使用bash解释脚本,fork执行
  • ./xxx.sh: 根据脚本第一行指定的解释器选择解释方法,fork执行
  • source xxx.sh: 在当前进程中执行指令,不采用fork方式

os.fork()方法
os.fork()针对父子进程都有返回值。父进程的返回值是非0值(子进程的ID号),子进程返回0

[root@room9pc01 day10]# python3 myfork.py
print('start...')
retval = os.fork() #创建子进程,后续代码将同时在父子进程中执行
if retval:
    print('hello from 父进程')
else:
    print('hello from 子进程')

print('hello from both')
[root@room9pc01 day10]# python3 myfork.py 
start...
hello from 父进程
hello from both
hello from 子进程
hello from both

os.fork()编程思路

  • 父进程负责生成子进程
  • 子进程负责做具体的工作
  • 子进程工作结束后需要彻底结束
[root@room9pc01 day10]# vim myfork2.py
import os

for i in range(3):
    revtal = os.fork()
    if not revtal:
        print('hello world!')
[root@room9pc01 day10]# python3 myfork2.py 
hello world!
hello world!
hello world!
hello world!
hello world!
hello world!
hello world!
#################################################
[root@room9pc01 day10]# vim myfork2.py
import os

for i in range(3):
    revtal = os.fork()
    if not revtal:
        print('hello world!')
        exit()  #进程遇到exit将会完全结束
[root@room9pc01 day10]# python3 myfork2.py 
hello world!
hello world!
hello world!

案例:ping176.130.1.0网段内主机,显示ping的结果

[root@room9pc01 day10]# vim forkping.py
import  os
import  subprocess

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.130.1.%s' % i for i in range(1, 255))
    for ip in ips:
        retval = os.fork()
        if not retval:
            ping(ip)
            exit()

僵尸进程

• 僵尸进程没有任何可执行代码,也不能被调度
• 如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
• 对于系统管理员来说,可以试图杀死其父进程或重启系统来消除僵尸进程

当子进程已经结束,但是父进程还未结束,父进程又没有处理僵尸进程的代码,僵尸进程就会一直存在,直到父进程工作结束。如果父进程结束了,子进程会变成孤儿进程,它将会被systemd接管。如果systemd发现子进程是僵尸进程,就会处理。

[root@room9pc01 day10]# vim zb.py
import os
import  time

print('starting...')
revtal = os.fork()
if revtal:
    print('in parent, sleeping')
    time.sleep(30)
    print('parent done')
else:
    print('in child, sleeping')
    time.sleep(15)
    print('child done')
    exit()
#打开两个终端,一个执行以上程序,另一个执行命令watch -n1 ps a(每隔一秒查看系统运行进程)

解决zombie问题
• 父进程通过os.wait()来得到子进程是否终止的信息
• 在子进程终止和父进程调用wait()之间的这段时间,子进程被称为zombie(僵尸)进程
• 如果子进程还没有终止,父进程先退出了,那么子进程会持续工作。系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
• python可以使用waitpid()来处理子进程
• waitpid()接受两个参数,第一个参数设置为-1,表示与wait()函数相同;第二参数如果设置为0表示挂起父进程,直到子程序退出,设置为1表示不挂起父进程
• waitpid()的返回值:如果子进程尚未结束则返回0,否则返回子进程的PID

[root@room9pc01 day10]# vim zb2.py
import os
import  time

revtal = os.fork()
if revtal:
    print('in parent')
    result = os.waitpid(-1, 0)  #挂起父进程,直到子进程结束才会继续
    print(result)
    time.sleep(10)
    print('parent done')
else:
    print('in child')
    time.sleep(10)
    print('child done')
    exit()
#以上程序共执行20秒,运行过程中不存在僵尸进程
###########################################################
[root@room9pc01 day10]# vim zb3.py
import os
import time

retval = os.fork()
if retval:
    print('in parent')
    result = os.waitpid(-1, 1)  # 不挂起父进程
    print(result)
    time.sleep(30)
    print('parent done')
else:
    print('in child')
    time.sleep(10)
    print('child done')
    exit()
#以上程序共执行30秒,运行至10秒时出现僵尸进程,再过20秒僵尸进程消失

多线程

  • 程序是计算机硬盘上存储的一些可执行文件,当程序运行后,就会加载到内存,产生进程。所以进程也可说是加载到内存中的一系列指令,一个进程可以包含很多线程。每个进程都有自己独立的运行环境,如内存等;但是同一进程内的多个线程,共享进程的运行环境。

多线程的动机
• 在多线程(MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
• 无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
• 即使子任务相互独立,互相无关(即,一个子任务的结果不影响其它子任务的结果)时也是这样
• 如果并行运行这些相互独立的子任务可以大幅度地提升整个任务的效率

多线程任务的工作特点
• 它们本质上就是异步的,需要有多个并发事务
• 各个事务的运行顺序可以是不确定的,随机的,不可预测的
• 这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
• 根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果

多线程编程思路

  • 主线程生成工作线程
  • 工作线程做具体工作。当工作线程工作完成后,它就自动结束了,也不会产生僵尸进程。

多线程相关模块
• thread和threading模块允许程序员创建和管理线程
• thread模块提供了基本的线程和锁的支持,而threading提供了更高级别、功能更强的线程管理功能
• 推荐使用更高级别的threading

模块传递函数给Thread类
• 多线程编程有多种方法,传递函数给threading模块的Thread类是介绍的第一种方法
• Thread对象使用start()方法开始线程的执行,使用join()方法挂起程序,直到线程结束

传递可调用类给Thread类
• 传递可调用类给Thread类是介绍的第二种方法
• 相对于一个或几个函数来说,由于类对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活

############################################################
案例4:扫描存活主机

  1. 通过ping测试主机是否可达
  2. 如果ping不通,不管什么原因都认为主机不可用
  3. 通过多线程方式实现并发扫描
[root@room9pc01 day10]# vim mtping.py
import  threading
import  subprocess

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.130.1.%s' % i for i in range(1, 255))
    for ip in ips:
        t = threading.Thread(target=ping, args=(ip,))
        t.start()   #运行target(*args)
########################################################
[root@room9pc01 day10]# vim mtping2.py
import  threading
import  subprocess

class Ping:
    def __init__(self, host):
        self.host = host

    def __call__(self):
        result = subprocess.run(
            'ping -c2 %s &> /dev/null' % self.host,
            shell = True
        )
        if result.returncode == 0:
            print('%s:up' % self.host)
        else:
            print('%s:down' % self.host)

if __name__ == '__main__':
    ips = ('176.130.1.%s' % i for i in range(1, 255))
    for ip in ips:
        t = threading.Thread(target=Ping(ip))
        t.start()   #运行target()

#####################################################################
urllib模块
urllib中包括了四个模块

  • urllib.request可以用来发送request和获取request的结果
  • urllib.error包含了urllib.request产生的异常
  • urllib.parse用来解析和处理URL
  • urllib.robotparse用来解析页面的robots.txt文件

爬取网页
• 先需要导入用到的模块:urllib.request
• 在导入了模块之后,我们需要使用
urllib.request.urlopen打开并爬取一个网页
• 读取内容常见的有3种方式:
(1) read()读取文件的全部内容,与readlines()不同的是,read()会把读取到的内容赋给一个字符串变量。
(2) readlines()读取文件的全部内容,readlines()会把读取到的内容赋值给一个列表变量。
(3) readline()读取文件的一行内容。

>>> from urllib import request
>>> html =  request.urlopen('https://upload-images.jianshu.io/upload_images/12347101-bc5e84e92e23c692.jpg')
>>> data = html.read()
>>> with open('/tmp/fork.jpg', 'wb') as fobj:
...   fobj.write(data)
... 
178500
>>>
[root@room9pc01 day10]# eog /tmp/fork.jpg		//查看获取网页图片链接地址后写入的图片
#############################################################
[root@room9pc01 day10]# vim download.py		//下载指定单张图片
rom urllib import request
import sys

def download(url, fname):
    html = request.urlopen(url)

    with open(fname, 'wb') as fobj:
        while True:
            data = html.read(1024)
            if not data:
                break
            fobj.write(data)

if __name__ == '__main__':
    url = sys.argv[1]
    fname = sys.argv[2]
    download(url, fname)
[root@room9pc01 day10]# python3 download.py https://upload-images.jianshu.io/upload_images/12347101-bc5e84e92e23c692.jpg /tmp/picture.jpg
[root@room9pc01 day10]# eog /tmp/picture.png
####################################################################################################################
[root@room9pc01 day10]# get_136_img.py		//爬取163网站首页所有图片
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__':
    url_163 = 'https://www.163.com'
    fname_163 = '/tmp/163.html'
    dst_dir = '/tmp/163'
    if not os.path.exists(fname_163):
        wget.download(url_163, fname_163)
    if not os.path.exists(dst_dir):
        os.mkdir(dst_dir)

    img_patt = '(http|https)://[-\w./]+\.(jpg|jpeg|png|gif)'
    img_urls = get_url(fname_163, img_patt, 'gbk')		 # 注意网易网站的字符编码用的是“简体中文”,而不是utf8,所以编码要用gbk
    # print(img_urls)
    for url in img_urls:
        try:
            wget.download(url, dst_dir)
        except:
            pass

模拟客户端访问
当客户机访问服务器时,客户机发送的请求头中使用User-Agent说明自己用的客户端是什么。服务器将在它的访问日志中记录。可以修改请求头,模拟正常的客户端访问,而不是通过python代码的访问。

编码
url只允许一部分字符,如果需要用到其他字符,需要对这些字符进行编码。
如果直接使用汉字,将会报错:

>>> html = request.urlopen('https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&rsv_idx=2&tn=baiduhome_pg&wd=中国')		//报错

需要进行以下转换:

>>> url = 'https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&rsv_idx=2&tn=baiduhome_pg&wd=' + request.quote('中国')
>>> url
'https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&rsv_idx=2&tn=baiduhome_pg&wd=%E4%B8%AD%E5%9B%BD'

wget模块

[root@room9pc01 day10]# pip3 install wget
>>> import wget
>>> wget.download('https://upload-images.jianshu.io/upload_images/12347101-bc5e84e92e23c692.jpg', '/tmp/1234.jpg')
100% [........................................................] 178500 / 178500'/tmp/1234.jpg'
[root@room9pc01 day10]# eog /tmp/1234.jpg
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值