0%

Python内建模块struct解析数据

最近收集了很多素材图片在一个文件夹中,想要统一修改名字(一个个手动改这操作太low——舍弃)。想要通过Python脚本进行批量重命名:

  1. 先遍历文件路径获取到该文件夹中的文件数组
  2. 获取文件类型,区分是否是我们要修改的图片
  3. 对比文件类型后,进行重命名

os.walk()获取文件列表

os.walk(PATH) PATH是个文件夹路径,当然可以用.或者../这样啦。返回一个三元素的tuple:当前路径、子文件夹名称、文件列表。

1
2
3
4
5
6
from os import walk

f = []
for (dirpath, dirnames, filenames) in walk('./picture'):
f.extend(filenames)
break

获取文件类型

思路分析

不同的文件,文件头是不一样的,各个类型的文件头是一致,通过获取文件头对比来判断文件类型。这里要注意的文件头的长度是不一样的,所以每次需要根据将要判断的类型文件头长度,去获文件头内容。

新建支持类型文件头列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def typeList():
return {
u"FFD8FF": 'TYPE_JPG',
u"89504E47": 'TYPE_PNG',
u"47494638": 'TYPE_GIF',
u"49492A00": 'TYPE_BMP',
u"68746D6C3E": 'TYPE_HTML',
u"255044462D312E": 'TYPE_PDF',
u"41564920": 'TYPE_AVI',
u"D0CF11E0": 'TYPE_MS_WORD_EXCEL',
u"57415645": 'TYPE_WAV',
u"3C3F786D6C": 'TYPE_XML',
u"52617221": 'TYPE_RAR',
u"504B0304": 'TYPE_ZIP'
}

获取文件类型

  • 二进制读取文件
  • 获取需要判断的文件头长度
  • 通过内建模块struct.unpack解析文件
  • 将解析的字节码转换16进制字符串
  • 对比判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 字节码转16进制字符串
def bytes2hex(bytes):
num = len(bytes)
hexstr = u""
for i in range(num):
t = u"%x" % bytes[i]
if len(t) % 2:
hexstr += u"0"
hexstr += t
return hexstr.upper()

# 获取文件类型
def filetype(filename):
binfile = open(filename, 'rb') # 必需二制字读取
tl = typeList()
ftype = 'unknown'
for hcode in tl.keys():
numOfBytes = len(hcode) / 2 # 需要读多少字节
binfile.seek(0) # 每次读取都要回到文件头,不然会一直往后读取
hbytes = struct.unpack_from('%sB' % int(numOfBytes), binfile.read(int(numOfBytes))) # 一个 "B"表示一个字节
f_hcode = bytes2hex(hbytes)
if f_hcode == hcode:
ftype = tl[hcode]
break

#不要忘记关闭打开的文件,避免出现异常
binfile.close()
return ftype

根据类型判断对文件进行重命名

重命名方法比较简单:**os.rename(oldName, newName)**

1
2
3
4
5
6
7
8
9
customHash = random.getrandbits(20) # 加一个随机数防止重名覆盖
for index, oldname in enumerate(f):
fileType = tools.query('./picture/%s' % oldname)
if fileType == 'TYPE_JPG':
print('====修改文件名: ./picture/pic%s_%s.png' % (customHash, index))
rename('./picture/%s' % oldname, './picture/pic%s_%s.png' % (customHash, index))
start += 1
else:
print('====不是图片类型文件:./picture/%s' % oldname)

重命名后下次获取文件列表顺序并不一致,再次遍历命名是容易导致文件覆盖。由于只是测试脚本,这几叫添加了一个随机数防止覆盖。

完整代码

一共两个文件,工具单独封装(伪封装,有点low)

判断类型工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# -*- coding: utf-8 -*-
import struct, os

# 支持文件类型
# 用16进制字符串的目的是可以知道文件头是多少字节
# 各种文件头的长度不一样,少半2字符,长则8字符
def typeList():
return {
u"FFD8FF": 'TYPE_JPG',
u"89504E47": 'TYPE_PNG',
u"47494638": 'TYPE_GIF',
u"49492A00": 'TYPE_BMP',
u"68746D6C3E": 'TYPE_HTML',
u"255044462D312E": 'TYPE_PDF',
u"41564920": 'TYPE_AVI',
u"D0CF11E0": 'TYPE_MS_WORD_EXCEL',
u"57415645": 'TYPE_WAV',
u"3C3F786D6C": 'TYPE_XML',
u"52617221": 'TYPE_RAR',
u"504B0304": 'TYPE_ZIP'
}

# 字节码转16进制字符串
def bytes2hex(bytes):
num = len(bytes)
hexstr = u""
for i in range(num):
t = u"%x" % bytes[i]
if len(t) % 2:
hexstr += u"0"
hexstr += t
return hexstr.upper()

# 获取文件类型
def filetype(filename):
binfile = open(filename, 'rb') # 必需二制字读取
tl = typeList()
ftype = 'unknown'
for hcode in tl.keys():
numOfBytes = len(hcode) / 2 # 需要读多少字节
binfile.seek(0) # 每次读取都要回到文件头,不然会一直往后读取
hbytes = struct.unpack_from('%sB' % int(numOfBytes), binfile.read(int(numOfBytes))) # 一个 "B"表示一个字节
f_hcode = bytes2hex(hbytes)
if f_hcode == hcode:
ftype = tl[hcode]
break

#不要忘记关闭打开的文件,避免出现异常
binfile.close()
return ftype

def query(filePath):
if os.path.isfile(filePath):
return filetype(filePath)
else:
print('不是文件')

重命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-
from os import walk, rename
import random
import tools

f = []
for (dirpath, dirnames, filenames) in walk('./picture'):
f.extend(filenames)
break

start = 1
customHash = random.getrandbits(20) # 加一个随机数防止重名覆盖
for oldname in f:
fileType = tools.query('./picture/%s' % oldname)
if fileType == 'TYPE_JPG':
rename('./picture/%s' % oldname, './picture/pic%s_%s.png' % (customHash, start))
start += 1
else:
print('不是图片类型文件:./picture/%s' % oldname)