python反复执行某个命令、直到用户希望停止_《让繁琐工作自动化》十五、保持时间、计划任务和启动程序...

1. 基础语法

1.1 time模块

(1)time.time()函数

import time

time = time.time()

#返回一个浮点数,称为UNIX纪元时间戳,即1970.1.1 00:00 开始的秒数

(2)time.sleep()函数

time.sleep(5)

#希望程序暂停的时间

(3)round()四舍六入五成双

now = time.time()

round(now,2)

#round函数取到小数点后几位,5前为奇数,舍5入1;5前为偶数,舍5不进

1.2 datetime模块

import datetime

dt = datetime.datetime.now()

#返回一个datetime对象(年,月,日,时,分,秒),dt.year,dt.month等得到特定时刻的datetime对象

(1)datetime 与 time转换

datetime.datetime.fromtimestamp(10000)

>>>datetime.datetime(1970,1,12,5,46,40)

datetime.datetime.fromtimestamp(time.time())

>>>datetime.datetime(2015,2,27,11,13,0,604980)

#函数 fromtimestamp相互转换

(2)timedelta数据类型

delta = datetime.timedelta(days,hours,minutes,seconds)

#返回以(天,秒,微秒)来表示

delta.total_seconds()

#返回以秒计算的时间

#timedelta表示一段时间,并且可以计算

(3)将datetime对象转换为字符串

datetime.strftime()函数将datetime对象转换为格式化的字符串

datetime.strptime()函数将格式化的字符串转换为datetime对象

strftime指令strftime指令含义%Y 带世纪的年份,如‘2014’

%y 不带世纪的年份,‘00’-‘99’

%m 数字表示的月份,‘01’-‘12’

%B 完整的月份,‘July’

%b 简写的月份,‘Jul’

%d 一月中的第几天,‘01’-‘31’

%j 一年中的第几天,‘001’-‘366’

%w 一周中的第几天,‘0’(周日)-‘6’(周六)

%A 完整的周几,‘Monday’%a简写的周几,‘Mon’

%H 小时(24h),'00'-'23'%h小时(12h),‘01’-‘12’

%M 分

%S 秒

%p ‘AM/'PM’

%% %

dt = datetime.datetime(2017,11,12,11,12,0)

dt.strftime('%Y/%m/%d%H:%M:%S) #datetime 到字符串

datetime.datetime.strptime('2015/10/21 16:29:00' , '%Y/%m/%d%H:%M:%S') #格式化字符串到datetime对象

>>> dt = datetime.datetime(2017,11,12,11,12,0)

>>> dt.strftime('%Y/%m/%d%H:%M:%S')

'2017/11/12 11:12:00'

>>> datetime.datetime.strptime('2015/10/21 16:29:00' , '%Y/%m/%d%H:%M:%S')

datetime.datetime(2015, 10, 21, 16, 29)

回顾 Python 的时间函数

在 Python 中,日期和时间可能涉及好几种不同的数据类型和函数。下面回顾了表示时间的 3 种不同类型的值:Unix 纪元时间戳(time 模块中使用)是一个浮点值或整型值,表示自 1970 年1 月 1 日午夜 0 点(UTC)以来的秒数。

datetime 对象(属于 datetime 模块)包含一些整型值,保存在 year、 month、 day、hour、 minute 和 second 等属性中。

timedelta 对象(属于 datetime 模块)表示的一段时间,而不是一个特定的时刻。

下面回顾了时间函数及其参数和返回值:time.time()函数返回一个浮点值,表示当前时刻的 Unix 纪元时间戳。

time.sleep(seconds)函数让程序暂停 seconds 参数指定的秒数。

datetime.datetime(year, month, day, hour, minute, second)函数返回参数指定的时刻的 datetime 对象。如果没有提供 hour、 minute 或 second 参数,它们默认为 0。

datetime.datetime.now()函数返回当前时刻的 datetime 对象。

datetime.datetime.fromtimestamp(epoch)函数返回 epoch 时间戳参数表示的时刻的 datetime 对象。

datetime.timedelta(weeks, days, hours, minutes, seconds, milliseconds, microseconds)函数返回一个表示一段时间的 timedelta 对象。该函数的关键字参数都是可选的,不包括 month 或 year。

total_seconds()方法用于 timedelta 对象,返回 timedelta 对象表示的秒数。

strftime(format)方法返回一个字符串,用 format 字符串中的定制格式来表示datetime 对象表示的时间。详细格式参见表 15-1。

datetime.datetime.strptime(time_string, format)函数返回一个 datetime 对象,它的时刻由 time_string 指定,利用 format 字符串参数来解析。详细格式参见表 15-1。

1.3 多线程

(1)多线程启动

import threading

#创建一个Thread对象

threadobj = threading.Thread(target = fx) #fx为想要以第二线程启动的函数名称

#第二个线程开始

threadobj.start()

(2)多参数

#常规参数作为一个列表传给args,关键字参数作为一个字典传给kwargs

threadobj = threading.Thread(target = print , args =['cats','dog','rabbit'] , kwargs = {'sep}':'&')

threadobj.start()

>>>'cats&dog&rabbit

注意:多线程可能会由于同时读写变量,导致相互干扰产生并发问题,当创建一个新的Thread对象时,要确保目标函数只使用该函数中的局部变量,从而避免并发问题。

1.4 从python启动其他程序

import subprocess

calobj= subprocess.Popen('C:\\Windows\\System32\\calc.exe')

#exe的存放地址,返回一个Popen 对象

Popen对象有两个方法:POLL方法和WAIT方法

calobj.poll():若此进程在调用poll方法时仍在执行,则返回None,若进程终止:返回0为无错终止,返回1则为错误导致终止

calobj.wait():将阻塞,直到启动的进程终止(希望你的程序暂停,知道用户完成其他程序)

Popen的命令行参数:想Popen穿第一个列表,第一个字符是可执行程序名,后续字符串是该程序启东市,传递给该程序的命令行参数。

subprocess.Popen(['C:\\Windows\\notepad.exe'.'C:\\'hello.txt'])

2.项目

2.1 超级秒表

假设要记录在没有自动化的枯燥任务上花了多少时间。你没有物理秒表,要为笔记本或智能手机找到一个免费的秒表应用,没有广告,且不会将你的浏览历史发送给市场营销人员,又出乎意料地困难(在你同意的许可协议中,它说它可以这样做。

你确实阅读了许可协议,不是吗?)。你可以自己用 Python 写一个简单的秒表程序。

总的来说,你的程序需要完成:记录从按下回车键开始,每次按键的时间,每次按键都是一个新的“单圈”。

打印圈数、总时间和单圈时间。

这意味着代码将需要完成以下任务:在程序开始时,通过调用 time.time()得到当前时间,将它保存为一个时间戳。在每个单圈开始时也一样。

记录圈数,每次用户按下回车键时加 1。

用时间戳相减,得到计算流逝的时间。

处理 KeyboardInterrupt 异常,这样用户可以按 Ctrl-C 退出。

打开一个新的文件编辑器窗口,并保存为 stopwatch.py。

第 1 步:设置程序来记录时间

#! python3

# stopwatch.py - A simple stopwatch program.

import time

# Display the program's instructions.

print('Press ENTER to begin. Afterwards, press ENTER to "click" the stopwatch.Press Ctrl-C to quit.')

input() # press Enter to begin

print('Started.')

startTime = time.time() # get the first lap's start time

lastTime = startTime

lapNum = 1

# TODO: Start tracking the lap times.

第 2 步:记录并打印单圈时间

#! python3

# stopwatch.py - A simple stopwatch program.

import time

--snip--

# Start tracking the lap times.

try:

while True:

input()

lapTime = round(time.time() - lastTime, 2)

totalTime = round(time.time() - startTime, 2)

print('Lap #%s:%s(%s)' % (lapNum, totalTime, lapTime), end='')

lapNum += 1

lastTime = time.time() # reset the last lap time

except KeyboardInterrupt:

# Handle the Ctrl-C exception to keep its error message from displaying.

print('\nDone.')

第 3 步:类似程序的想法

时间追踪为程序打开了几种可能性。虽然可以下载应用程序来做其中一些事情,但自己编程的好处是它们是免费的,而且不会充斥着广告和无用的功能。可以编写类似的程序来完成以下任务:创建一个简单的工时表应用程序,当输入一个人的名字时,用当前的时间记录下他们进入或离开的时间。

为你的程序添加一个功能,显示自一项处理开始以来的时间,诸如利用 requests模块进行的下载(参见第 11 章)。

间歇性地检查程序已经运行了多久,并为用户提供了一个机会,取消耗时太长的任务。

2.2 多线程下载XKCD线程

第 1 步:修改程序以使用函数

#! python3

# multidownloadXkcd.py - Downloads XKCD comics using multiple threads.

import requests, os, bs4, threading

os.makedirs('xkcd', exist_ok=True) # store comics in ./xkcd

def downloadXkcd(startComic, endComic):

for urlNumber in range(startComic, endComic):

# Download the page.

print('Downloading page http://xkcd.com/%s...' % (urlNumber))

res = requests.get('http://xkcd.com/%s' % (urlNumber))

res.raise_for_status()

soup = bs4.BeautifulSoup(res.text)

# Find the URL of the comic image.

comicElem = soup.select('#comic img')

if comicElem == []:

print('Could not find comic image.')

else:

comicUrl = comicElem[0].get('src')

# Download the image.

print('Downloading image%s...' % (comicUrl))

res = requests.get(comicUrl)

res.raise_for_status()

# Save the image to ./xkcd.

imageFile = open(os.path.join('xkcd', os.path.basename(comicUrl)), 'wb')

for chunk in res.iter_content(100000):

imageFile.write(chunk)

imageFile.close()

# TODO: Create and start the Thread objects.

# TODO: Wait for all threads to end.

第 2 步:创建并启动线程

#! python3

# multidownloadXkcd.py - Downloads XKCD comics using multiple threads.

--snip--

# Create and start the Thread objects.

downloadThreads = [] # a list of all the Thread objects

for i in range(0, 1400, 100): # loops 14 times, creates 14 threads

downloadThread = threading.Thread(target=downloadXkcd, args=(i, i + 99))

downloadThreads.append(downloadThread)

downloadThread.start()

第 3 步:等待所有线程结束

#! python3

# multidownloadXkcd.py - Downloads XKCD comics using multiple threads.

--snip--

# Wait for all threads to end.

for downloadThread in downloadThreads:

downloadThread.join()

print('Done.')

Unix 哲学

程序精心设计,能被其他程序启动,这样的程序比单独使用它们自己的代码更强大。 Unix 的哲学是一组由 UNIX 操作系统(现代的 Linux 和 OS X 也是基于它)的程序员建立的软件设计原则。它认为:编写小的、目的有限的、能互操作的程序,胜过大的、功能丰富的应用程序。

较小的程序更容易理解,通过能够互操作,它们可以是更强大的应用程序的构建块。智能手机应用程序也遵循这种方式。如果你的餐厅应用程序需要显示一间咖啡店的方位,开发者不必重新发明轮子,编写自己的地图代码。餐厅应用程序只是启动一个地图应用程序,同时传入咖啡店的地址,就像 Python 代码调用一个函数,并传入参数一样。

你在本书中编写的 Python 程序大多符合 Unix 哲学,尤其是在一个重要的方面:它们使用命令行参数,而不是 input()函数调用。如果程序需要的所有信息都可以事先提供,最好是用命令行参数传入这些信息,而不是等待用户键入它。这样,命令行参数可以由人类用户键入,也可以由另一个程序提供。这种互操作的方式,让你的程序可以作为另一个程序的部分而复用。

唯一的例外是,你不希望口令作为命令行参数传入,因为命令行可能记录它们,作为命令历史功能的一部分。在需要输入口令时,程序应该调用 input()函数。

2.3 项目:简单的倒计时程序

就像很难找到一个简单的秒表应用程序一样,也很难找到一个简单的倒计时程序。让我们来写一个倒计时程序,在倒计时结束时报警。

总的来说,程序要做到:从 60 倒数。

倒数至 0 时播放声音文件(alarm.wav)。

这意味着代码将需要做到以下几点:在显示倒计时的每个数字之间,调用 time.sleep()暂停一秒。

调用 subprocess.Popen(),用默认的应用程序播放声音文件。

打开一个新的文件编辑器窗口,并保存为 countdown.py。

第 1 步:倒计时

#! python3

# countdown.py - A simple countdown script.

import time, subprocess

timeLeft = 60

while timeLeft > 0:

print(timeLeft, end='')

time.sleep(1)

timeLeft = timeLeft - 1

# TODO: At the end of the countdown, play a sound file.

第 2 步:播放声音文件

#! python3

# countdown.py - A simple countdown script.

import time, subprocess

--snip--

# At the end of the countdown, play a sound file.

subprocess.Popen(['start', 'alarm.wav'], shell=True)

第 3 步:类似程序的想法

倒计时是简单的延时,然后继续执行程序。这也可以用于其他应用程序和功能,

诸如:利用 time.sleep()给用户一个机会,按下 Ctrl-C 取消的操作,例如删除文件。你的程序可以打印“Press Ctrl-C to cancel”,然后用 try 和 except 语句处理所有KeyboardInterrupt 异常。

对于长期的倒计时,可以用 timedelta 对象来测量直到未来某个时间点(生日?周年纪念?)的天、时、分和秒数。

小结

对于许多编程语言,包括 Python, Unix 纪元(1970 年 1 月 1 日午夜, UTC)是一个标准的参考时间。虽然 time.time()函数模块返回一个 Unix 纪元时间戳(也就是自 Unix 纪元以来的秒数的浮点值),但 datetime 模块更适合执行日期计算、格式化和解析日期信息的字符串。

time.sleep()函数将阻塞(即不返回)若干秒。它可以用于在程序中暂停。但如果想安排程序在特定时间启动, http://nostarch.com/automatestuff/上的指南可以告诉你如何使用操作系统已经提供的调度程序。

threading 模块用于创建多个线程,如果需要下载多个文件或同时执行其他任务,这非常有用。但是要确保线程只读写局部变量,否则可能会遇到并发问题。

最后, Python 程序可以用 subprocess.Popen()函数,启动其他应用程序。命令行参数可以传递给 Popen()调用,用该应用程序打开特定的文档。另外,也可以用 Popen()启动 start、 open 或 see 程序,利用计算机的文件关联,自动弄清楚用来打开文件的应用程序。通过利用计算机上的其他应用程序, Python 程序可以利用它们的能力,满足你的自动化需求。

3.习题

1.什么是 Unix 纪元?

许多日期和时间程序使用的一个参考时刻。该时刻是 1970 年 1 月 1 日,UTC。

2.什么函数返回自 Unix 纪元以来的秒数?

time.time()

3.如何让程序刚好暂停 5 秒?

time.sleep(5)

4.round()函数返回什么?

返回与传入参数最近的整数。例如,round(2.4)返回 2。

5.datetime 对象和 timedelta 对象之间的区别是什么?

datetime 对象表示一个特定的时刻。timedelta 对象表示一段时间。

6.假设你有一个函数名为 spam()。如何在一个独立的线程中调用该函数并运行其中的代码?

threadObj = threading.Thread(target=spam)

7.为了避免多线程的并发问题,应该怎样做?

threadObj.start()

8.如何让Python 程序运行 C:\ Windows\System32 文件夹中的calc.exe 程序?

subprocess.Popen('c:\\Windows\\System32\\calc.exe')

4.实践项目

4.1 美化的秒表

#! python3

# stopwatch.py - A simple stopwatch program.

import time

# Display the program's instructions.

print('Press enter to begin. Afterwards, press ENTER to "click" the stopwatch. Press Ctrl-C to quit.')

input() # press Enter to begin

print('Started.')

startTime = time.time() # get the first lap's start time

lastTime = startTime

lapNum = 1

# Start tracking the lap times.

try:

while True:

input()

lapTime = round(time.time() - lastTime, 2)

totalTime = round(time.time() - startTime, 2)

print('Lap #' + str(lapNum).rjust(2) + ':' + ('%.2f' % totalTime).rjust(6) + ' (' + ('%.2f' % lapTime).rjust(5) + ')', end=' ')

lapNum += 1

lastTime = time.time() # reset the last lap time

except KeyboardInterrupt:

# Handle the Ctrl-C exception to keep its error message from displaying.

print('\nDone.')

input('click any key to exit')

4.2 计划的web漫画下载

在按规定的时刻检查是否有漫画更新,有的话进行下载。这里用到了schedule模块,以运行计算器为例:

import schedule,subprocess

def job():

subprocess.Popen('C:\\Windows\\System32\\calc.exe')

schedule.every().day.at("20:41").do(job) #在每一天的规定时刻运行job

while True:

schedule.run_pending() #运行所有可以运行的任务

参考:

1.计算明天和昨天的日期

#! /usr/bin/env python

#coding=utf-8

# 获取今天、昨天和明天的日期

# 引入datetime模块

import datetime

#计算今天的时间

today = datetime.date.today()

#计算昨天的时间

yesterday = today - datetime.timedelta(days = 1)

#计算明天的时间

tomorrow = today + datetime.timedelta(days = 1)

#打印这三个时间

print(yesterday, today, tomorrow)

2.计算上一个的时间

方法一:

#! /usr/bin/env python

#coding=utf-8

# 计算上一个的时间

#引入datetime,calendar两个模块

import datetime,calendar

last_friday = datetime.date.today()

oneday = datetime.timedelta(days = 1)

while last_friday.weekday() != calendar.FRIDAY:

last_friday -= oneday

print(last_friday.strftime('%A,%d-%b-%Y'))

方法二:借助模运算寻找上一个星期五

#! /usr/bin/env python

#coding=utf-8

# 借助模运算,可以一次算出需要减去的天数,计算上一个星期五

#同样引入datetime,calendar两个模块

import datetime

import calendar

today = datetime.date.today()

target_day = calendar.FRIDAY

this_day = today.weekday()

delta_to_target = (this_day - target_day) % 7

last_friday = today - datetime.timedelta(days = delta_to_target)

print(last_friday.strftime("%d-%b-%Y"))

3.计算歌曲的总播放时间

#! /usr/bin/env python

#coding=utf-8

# 获取一个列表中的所有歌曲的播放时间之和

import datetime

def total_timer(times):

td = datetime.timedelta(0)

duration = sum([datetime.timedelta(minutes = m, seconds = s) for m, s in times], td)

return duration

times1 = [(2, 36),

(3, 35),

(3, 45),

]

times2 = [(3, 0),

(5, 13),

(4, 12),

(1, 10),

]

assert total_timer(times1) == datetime.timedelta(0, 596)

assert total_timer(times2) == datetime.timedelta(0, 815)

print("Tests passed.\n"

"First test total:%s\n"

"Second test total:%s" % (total_timer(times1), total_timer(times2)))

4.反复执行某个命令

#! /usr/bin/env python

#coding=utf-8

# 以需要的时间间隔执行某个命令

import time, os

def re_exe(cmd, inc = 60):

while True:

os.system(cmd);

time.sleep(inc)

re_exe("echo %time%", 5)

5.定时任务

#! /usr/bin/env python

#coding=utf-8

#这里需要引入三个模块

import time, os, sched

# 第一个参数确定任务的时间,返回从某个特定的时间到现在经历的秒数

# 第二个参数以某种人为的方式衡量时间

schedule = sched.scheduler(time.time, time.sleep)

def perform_command(cmd, inc):

os.system(cmd)

def timming_exe(cmd, inc = 60):

# enter用来安排某事件的发生时间,从现在起第n秒开始启动

schedule.enter(inc, 0, perform_command, (cmd, inc))

# 持续运行,直到计划时间队列变成空为止

schedule.run()

print("show time after 10 seconds:")

timming_exe("echo %time%", 10)

6.利用sched实现周期调用

#! /usr/bin/env python

#coding=utf-8

import time, os, sched

# 第一个参数确定任务的时间,返回从某个特定的时间到现在经历的秒数

# 第二个参数以某种人为的方式衡量时间

schedule = sched.scheduler(time.time, time.sleep)

def perform_command(cmd, inc):

# 安排inc秒后再次运行自己,即周期运行

schedule.enter(inc, 0, perform_command, (cmd, inc))

os.system(cmd)

def timming_exe(cmd, inc = 60):

# enter用来安排某事件的发生时间,从现在起第n秒开始启动

schedule.enter(inc, 0, perform_command, (cmd, inc))

# 持续运行,直到计划时间队列变成空为止

schedule.run()

print("show time after 10 seconds:")

timming_exe("echo %time%", 10)

网络爬虫判断页面是否更新

1、304页面http状态码

当第二次请求页面访问的时候,该页面如果未更新,则会反馈一个304代码,而搜索引擎也会利用这个304http状态码来进行判断页面是否更新。

首先第一次肯定是要爬取网页的,假设是A.html,这个网页存储在磁盘上,相应地有个修改时间(也即是更新这个文件的时间)。

那么第二次爬取的时候,如果发现这个网页本地已经有了,例如A.html,这个时候,你只需要向服务器发送一个If-Modified-Since的请求,把A.html的修改时间带上去。

如果这段时间内,A.html更新了,也就是A.html过期了,服务器就会HTTP状态码200,并且把新的文件发送过来,这时候只要更新A.html即可。 如果这段时间内,A.html的内容没有变,服务器就会返返回HTTP状态码304(不返回文件内容),这个时候就不需要更新文件。

2、Last-Modified文件最后修改时间

这是http头部信息中的一个属性,主要是记录页面最后一次的修改时间,往往我们会发现,一些权重很高的网站,及时页面内容不更新,但是快照却还是能够每日更新,这其中就有Last-Modified的作用。通产情况下,下载网页我们使用HTTP协议,向服务器发送HEAD请求,可以得到页面的最后修改时间LastModifed,或者标签ETag。将这两个变量和上次下载记录的值的比较就可以知道一个网页是否跟新。这个策略对于静态网页是有效的。是对于绝大多数动态网页如ASP,JSP来说,LastModifed就是服务器发送Response的时间,并非网页的最后跟新时间,而Etag通常为空值。所以对于动态网页使用LastModifed和Etag来判断是不合适的,因此Last-Modified只是蜘蛛判断页面是否更新的一个参考值,而不是条件。

3、比对文件大小

搜索引擎还会取出之前页面文件,和现在的文件进行对比,不过因为大部分网站都是一种替换式更新,往往比对文件大小很难说明问题,因此常见与页面链接变化配合使用。

4、使用MD5数字签名

每次下载网页时,把服务器返回的数据流ResponseStream先放在内存缓冲区,然后对ResponseStream生成MD5数字签名S1,下次下载同样生成签名S2,比较S2和S1,如果相同,则页面没有跟新,否则网页就有跟新。需要说明的是用md5算法对文本刘签名的速度是极快的,M级的数据可以在毫秒内完成。这种策略虽然也把页面数据从服务器传输到了本地机,但是省掉了页面的I/O操作,对系统性能的提升是很有帮助的。


版权声明:本文为weixin_39955418原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。