第13章 批量处理文件
这就是 R。没有做不到,只有想不到。
— Simon Blomberg, April 2005
相信你和我一样,电脑里保存了大量的文件吧。比如数据,有时候需要从多个文件里提取有用的信息,手动查找和摘选十分耗时耗力;比如照片,动辄成百上千张,虽然有些软件可以自动整理,但毕竟难以随心所欲。
我们前边学了编程的结构和字符串处理,如果把这些结合起来,再学几个文件操作函数,就可以高效地对电脑文件为所欲为了。
13.1 批量整理照片文件
事实上,我们从第2章起就已经悄悄开始接触文件操作函数了。当时我们用dir.create()
创建了个文件夹,还用file.choose()
来选取文件。后者有几个双胞胎姐妹:choose.files()
函数可以用来选取多个文件,而choose.dir()
可以用来选取文件夹。请复习一下这几个函数,热热身。
下面,我们运行下面的代码,会从我们服务器下载一个压缩文件,并解压缩到指定文件夹。打开这个文件夹,你会看到3个图片文件。我们假定这是我们手机里拍的照片,我们的目标是修改这3个文件的名称为拍摄的时刻。一旦能实现这个目标,那么对成千上万个文件重新命名都将不费吹灰之力。
download.file(url = "http://dapengde.com/r4rookies/figren.zip",
destfile = "c:/r4r/figren.zip")
unzip(zipfile = "c:/r4r/figren.zip", exdir = "c:/r4r")
首先,我们使用dir()
函数,可以获取文件夹里的文件列表。如果将参数指定为完整文件名,那么得到的是文件的完整路径。
fotodir <- 'c:/r4r/figren'
fotofilefull <- dir(fotodir, full.names = TRUE)
fotofile <- dir(fotodir)
然后,使用file.info()
函数,可以获取文件的完整信息:
fotoinfo <- file.info(fotofilefull)
fotoinfo
## size isdir mode
## c:/r4r/figren/IMG_689.jpg 1716 FALSE 666
## c:/r4r/figren/IMG_690.jpg 56321 FALSE 666
## c:/r4r/figren/IMG_691.jpg 307 FALSE 666
## mtime
## c:/r4r/figren/IMG_689.jpg 2017-06-29 22:02:44
## c:/r4r/figren/IMG_690.jpg 2017-06-29 22:02:44
## c:/r4r/figren/IMG_691.jpg 2017-06-29 22:02:44
## ctime
## c:/r4r/figren/IMG_689.jpg 2017-06-29 22:02:44
## c:/r4r/figren/IMG_690.jpg 2017-06-29 22:02:44
## c:/r4r/figren/IMG_691.jpg 2017-06-29 22:02:44
## atime exe
## c:/r4r/figren/IMG_689.jpg 2017-06-29 22:02:44 no
## c:/r4r/figren/IMG_690.jpg 2017-06-29 22:02:44 no
## c:/r4r/figren/IMG_691.jpg 2017-06-29 22:02:44 no
其中的mtime
列,是我们需要的文件创建时刻,小时分秒之间使用冒号分隔。由于操作系统不允许文件名中含有冒号,我们利用前面学过的时刻格式处理函数,稍微调整一下格式,设定新的文件名:
fototime <- format(fotoinfo$mtime, '%Y-%m-%d-%H%M%S')
newname <- paste(
fotodir, '/', fototime, '_', fotofile, sep = '')
最后,我们用文件重命名函数file.rename()
函数,就可以对文件批量重命名了:
file.rename(fotofilefull, newname)
如果有成千上万张照片需要这样整理,R是极佳的选择。
我们还可以将照片按年份或月份归类整理到文件夹里。该怎么操作呢?下面就会讲到。
13.2 从网页批量下载和整理图片
我所在单位有个网站,有个页面展示着研究组野外观测照片缩略图,并按站点分了类36。新来到这里时,我想把本组的工作了解一番,在看图的时候,需要逐个点开才能看大图上的细节,将来需要某张图时,找起来很不方便,我就萌生了把图片全部下载到本地并按站点分类保存的想法。但是这几百张图,一一点开下载也太累了。如何批量下载网页上的图片呢?
方案有很多,列举如下:
可以通过浏览器的’保存网页全部内容’来实现,本地生成一个文件夹,包含了网页上的图片。这个我试了,但只保存下来了缩略图,没有大图。
可以安装迅雷、快车之类的软件,但是我不想装。有些软件臃肿庞大也就算了,关键是不知道他们在背后悄悄做了些什么。另外,他们无法解决图片分类保存的问题。
傲游、360等浏览器有批量下载功能,或者firefox+BatchDownload 插件也行,但我不想装,并且他们也无法解决图片分类的问题。
chrome浏览器有个fatkun插件,专门用来批量下载图片,能下载大图,是我最满意的方案了,但也并非完美。下载的图片文件名要么是原始文件名,要么只能简单编号。这样一来,所有观测站点的图片都混在了一起,这仍然不是我想要的。我希望下载到本地的图片能自动按观测站分类保存。
其实,查看一下网页的源代码(chrome浏览器里按快捷键ctrl+u),发现每张图片所属站点的信息,包含在了图片的链接里。比如Neustift观测站某图的链接是:
http://...gallery/neustift/img_8260_59_58_....jpg
这个链接里是含有站名信息的(neustift)。这就好办了,可以自己动手用R代码实现。
首先,我们把网页的源代码读进R,跟学习字符函数时读取千字文文件的方法一样:
urlink <- 'http://www.biomet.co.at/pictures/'
aa <- readLines(urlink, encoding='UTF-8') # 读取网页
然后,我们找到有图片链接的行,也就是含有下面字符串的行:
src="http://www.biomet.co.at/wp/wp-content/gallery
获取这些行的方法是我们学过的grep()
函数:
linkformat <-
'src="http://www.biomet.co.at/wp/wp-content/gallery'
bb <- aa[grep(linkformat, aa)]
接着,我们用循环函数,对得到的每一行进行处理,把图片的链接提取出来,并且去掉重复的图片链接:
for (i in 1:length(bb))
bb[i] <- substring(
bb[i],
regexpr("http", bb[i])[1],
regexpr(".jpg\"", bb[i])[1]+3) # 获取链接
bb <- unique(bb)
length(bb)
writeLines(bb, 'c:/r4r/links.txt')
每个链接里,从第47个字符开始就是观测站名了。为了简便,我们截取站名的前4个字母,并以此为名称,用dir.create()
函数新建一批文件夹:
stname <- substring(bb, 47, 50)
stname <- stname[-which(stname == '')]
for (i in unique(stname))
dir.create(paste('c:/r4r/', i, sep = ''))
快看看新的空文件夹是不是已经在那里了?
一切准备妥当了。下面,我们就用download.file()
将图片下载保存到对应的文件夹里。由于文件多,可以预料,整个过程可能会比较耗时,所以我们用第5.6节的方法,在循环中添加了一个print()
函数来提醒我们下载进度,并在循环结束后用winDialog()
函数来弹出一个任务完成的提示框。
for (i in 1:length(bb)) {
download.file(
url = bb[i],
destfile = paste(
'c:/r4r/', stname[i],'/', stname[i], i, '.jpg',
sep = ""),
method = 'curl', quiet = TRUE)
print(paste(i, 'of', length(bb), 'downloaded.'))
}
winDialog(type = c("ok"), message = '下载完毕!')
13.3 从大量文件里提取汇总信息
有时候,我们需要从大量的数据文件中,提取感兴趣的条目并汇总到一起进行分析。这里我们举个例子。
我国有数千个气象观测站。每隔一段时间,各气象站就把当地的气象要素观测数据上传到国家一级的服务器,在信息中心汇总成一个文件,每个观测站的数据占一行,这个汇总的文件就有几千行。如果要从中获取某个观测站气象要素的时间序列,就需要从每个这样的文件里找到来自该观测站的那一行,附加上时刻信息,合并为该观测站的一个数据文件进行后续分析。
这样的文件可以从我们的服务器下载。为了方便,我们把原来的几千行删减为几十行,并且仅给出6个文件供示范。
我们先把这样文件的一个压缩包下载到本地电脑,并解压缩为文件夹。
download.file(url = "http://dapengde.com/r4rookies/obs.zip",
destfile = "c:/r4r/obs.zip")
unzip(zipfile = "c:/r4r/obs.zip", exdir = "c:/r4r")
请用记事本打开任意一个文件。可以看出,每个文件的前两行是文件头。从第三行起是个数据表,第一列是观测站的编号,从第二列起是各种观测要素。时刻信息既包含在文件头里,也包含在文件头里。现在,假定我们要从这6个文件里提取编号为54527观测站的数据。
像前面的例子那样,我们先获取文件列表,把文件名作为时刻信息存储。
stn <- 54527
obsdir <- 'c:/r4r/obs'
obsfilefull <- dir(obsdir, full.names = TRUE)
obstime <- as.numeric(dir(obsdir))
然后,我们先准备好一个名为output的空变量,用来存放输出结果。我们使用循环语句,逐个读取每个文件,从中找到目标行,并将其作为新行追加到output后面,让这个新行的行名称为时刻信息。
output <- NULL
for (k in 1:length(obsfilefull))
{
input <- read.table(obsfilefull[k], header = FALSE,
skip = 2, sep="")
output_new <- input[which(input[, 1] == stn),]
if (nrow(output_new) != 0)
rownames(output_new) <- obstime[k]
output <- rbind(output, output_new)
}
output$time <- rownames(output)
得到的output数据框,就是目标观测站的时间序列。
名称 | 作用 |
---|---|
file.show() ,file.info() |
查看文件内容或信息 |
file.exists() |
检查文件是否存在 |
file.create() ,dir.create() |
新建文件或目录 |
file.copy() |
文件复制 |
file.remove() |
文件删除(注意!直接删除!不进入回收站!) |
file.rename() |
文件重命名 |
download.file() |
下载文件 |
unzip() |
解压缩文件 |
dir() |
查看目录下文件清单 |
file.choose() , choose.files() |
选取文件或目录 |
choose.dir() |
选取文件夹 |
13.4 课外活动:打通任督二脉
如果你熟悉WIndows操作系统,那么应该知道cmd,也就是命令行。同时按下键盘的windows键和r字母键,在弹出的小窗口里输入cmd,回车,出现的那个简陋的黑色小窗口就是cmd。在开始菜单里搜索cmd也能搜到。别看它又黑又丑,它的强大会让你惊叹。如果R和cmd强强联手,你的电脑就被打通了任督二脉,离练成绝世神功已经不远了。下面我们举几个例子。
让我们先打开任脉。请在cmd小黑窗里输入:
notepad
并回车,记事本就被打开了。这就是用cmd打开记事本的指令。
如果不使用cmd小黑窗,在R里也可以进行等同的操作,只需使用shell()函数:
shell('notepad')
shell()就是R用来呼唤cmd的方式。R可以呼唤电脑里已经安装的软件,例如网页浏览器:
shell('start iexplore http://xuer.pzhao.net')
或者打开qq:
shell('cmd /c "D:/Program Files/Tencent/QQProtect.exe"')
当然,你得把上面这条代码里QQProtect.exe的完整路径改成你自己电脑上的路径才行。
如果一段R代码在办公室处理大量数据尚未完成,而我又着急下班,那么我可以事先在R代码的最后加上一条打开qq的指令,就可以回家了。当我在手机上看到办公室电脑的qq上线了,就意味着R已经把数据处理完了。如果配合AutoHotKey这样的软件,我甚至可以让办公室的qq自动发一条“搞定!”的信息给自己的手机37。
cmd支持的命令非常丰富,可以完成很多原本复杂的工作。R调用cmd,如虎添翼。如果感兴趣,可以去学一下批处理脚本,把cmd要执行的多条命令写在.bat文件里,让R来调用,那必将是一片繁华。
现在,R呼唤cmd的任脉已经打通了,下面我们打通督脉——让cmd呼唤R。
假定你有一批打算要自动运行的R代码,保存在文件c:/r4r/timer.r
,并且假定你的R安装路径是D:/Program Files/R/bin/R.exe
,那么,请在cmd小黑窗里运行:
"D:/Program Files/R/bin/R.exe" CMD BATCH --vanilla --slave
"c:/r4r/timer.r"
这条命令的含义是由cmd呼唤R来运行timer.r
里的代码。一声召唤后,timer.r
里的代码全部自动运行,该画的图自动画,该写出的数据自动写,根本不用打开RStudio。
这有什么用呢?说说我是怎么用的吧。
有段时间,我参加了连续一个多月的野外科研观测,需要把这一个月的天气状况记录在案,每个小时是阴是晴,是雨是雪。天气状况其实在很多天气网站上都有,但是只能实时浏览,网页每天更新,并且不能下载最新一个月的历史记录。这种事当然可以安排个人每天手动把信息摘抄整理下来,但是我有R在手啊!于是,我写了一段R代码,可以获取天气网站当天显示的天气现象并保存到一个文件里,并做出需要的图表。然后,我又写了一句cmd代码保存在脚本文件.bat里,用来呼唤上述R代码文件。最后,在windows的“计划任务”里设置每天运行一次.bat文件。好了,一切每天都自动完成了。
我们在本章学了用R整理照片、下载图片和处理大量数据文件;在下一章,我们还将学会批量制作Word文档和PPT幻灯片。如果这些任务需要定期大量重复操作,那么,cmd+R+计划任务的组合,将节省大量的人工劳动。
这只是我作为一个菜鸟的想法。反正,任督二脉已经打通。在这个神奇世界里,发挥你的想象力,想怎么折腾就怎么折腾咯!能不能成为绝顶高手,就看你自己咯!
因斯布鲁克大学生态气象研究组:http://www.biomet.co.at/pictures/↩
事实上,R有专门的扩展包,可以自动发送电子邮件,比调用qq更爽快。↩