在Python中搜索/读取二进制数据

39

我正在阅读一个二进制文件(在这种情况下是jpg格式),需要在其中找到一些值。对于那些感兴趣的人,这个二进制文件是一个jpg文件,我正在尝试通过查找二进制结构来获取其尺寸,如详细介绍所述。

我需要在二进制数据中找到FFC0,然后跳过一定字节数,接着读取4个字节(这应该给我图像的尺寸)。

有没有好的方法搜索二进制数据中的值?是否有类似于“find”或re的功能?


1
你曾经研究过 Imagick 吗?如果我没记错的话,它也有一个 Python 库。 - txwikinger
1
我已经使用它了,而且效果很好,但仅仅为了查找文件的尺寸而言,它相当沉重。 - Parand
2
你应该使用适合这种情况的模块,例如http://snippets.dzone.com/posts/show/1021 - user177800
8个回答

30
你可以将文件加载到一个字符串中,并使用str.find()方法查找该字符串中的字节序列0xffc0。它适用于任何字节序列。
要执行此操作的代码取决于几个因素。如果你以二进制模式打开文件并且你正在使用Python 3(这两个都可能是此情况的最佳实践),则需要搜索字节字符串(而不是字符字符串),这意味着你必须在字符串前面加上b前缀。
with open(filename, 'rb') as f:
    s = f.read()
s.find(b'\xff\xc0')

如果您在Python 3中以文本模式打开文件,则需要搜索字符串:

with open(filename, 'r') as f:
    s = f.read()
s.find('\xff\xc0')
虽然没有特别的原因去这样做。这并不比以前的方法更有优势,而且如果你在一个将二进制文件和文本文件区分对待的平台上(如Windows),这可能会导致问题。
Python 2没有区分字节字符串和字符字符串,因此如果您使用该版本,则无论您是否在b'\xff\xc0'中包含或排除b,都没有关系。而且如果您的平台将二进制文件和文本文件视为相同(例如Mac或Linux),那么使用'r''rb'作为文件模式也无关紧要。但我仍然建议像以上第一个代码示例一样去编写,以便实现向前兼容-以防你将来切换到Python 3,就少了一件事需要修复。

18
如果文件非常大,一次性将其读入字符串可能不是一个好主意。 - icktoofay
3
我怀疑它会那么大到会成为一个问题。 - Chris B.
3
因为我只需要找到第一帧,所以我可能只需读取文件的一小部分并对其进行处理,而不是读取整个文件。 - Parand
Python生成器非常适合处理输入流。它们使代码变得简单,就好像一次性读取所有内容一样,但实际上并没有这样做。 - MarcH
1
@JannePaalijarvi 如果你的问题不同于原帖中的问题,那么适用于原帖的解决方案可能对你无效。我的评论与所描述的问题相关,而非你的问题。 - Chris B.
显示剩余4条评论

13

使用 mmap 模块,而不是将整个文件读入内存、搜索并将新文件写入磁盘,可以实现在原地修改的同时,避免全部将文件存储在内存中。

#!/usr/bin/python

import mmap

with open("hugefile", "rw+b") as f:
    mm = mmap.mmap(f.fileno(), 0)
    print mm.find('\x00\x09\x03\x03')

10

bitstring模块旨在用于此目的。对于您的情况,以下代码(我还没有测试)应该有所帮助:

from bitstring import ConstBitStream
# Can initialise from files, bytes, etc.
s = ConstBitStream(filename='your_file')
# Search to Start of Frame 0 code on byte boundary
found = s.find('0xffc0', bytealigned=True)
if found:
    print("Found start code at byte offset %d." % found[0])
    s0f0, length, bitdepth, height, width = s.readlist('hex:16, uint:16, 
                                                        uint:8, 2*uint:16')
    print("Width %d, Height %d" % (width, height))

那么 Bits.find 只返回一个布尔值并设置 Bits.bytepos 属性?也许在模块文档中,您应该警告 bitstring 不是线程安全的(当然,在这个答案中并不重要)。 - tzot
@ΤΖΩΤΖΙΟΥ:是的,你说得很对。我并不觉得具有突变性质或读取性质的方法不是线程安全的,但在位不可变对象上使用“find”方法是可以合理期望的。老实说,在此之前我从未遇到过这种情况,但这确实值得思考... - Scott Griffiths
只是一个想法:find 可以返回一个包含所有必要信息的对象,就像 re.matchre.search 一样。你可以将这个“BitMatch”类作为 bool 的子类,以保持向后兼容性。 - tzot
@ΤΖΩΤΖΙΟΥ:谢谢,这是个合理的想法,虽然我可以稍微破坏向后兼容性,也许只返回位位置作为单个项目元组,如果找到或者返回空元组如果没有找到。我想任何东西都比返回-1要好 :) - Scott Griffiths

5
在Python 3.x中,您可以通过以下方式搜索一个字节字符串:
>>> byte_array = b'this is a byte array\r\n\r\nXYZ\x80\x04\x95 \x00\x00\x00\x00\x00'
>>> byte_array.find('\r\n\r\n'.encode())
20
>>>

5
“re” 模块可以处理字符串和二进制数据(Python 2 中的 “str” 和 Python 3 中的 “bytes”),因此您可以将其与“str.find”一起用于您的任务。

3

find() 方法仅应在需要知道子字符串位置时使用,否则可以使用 in 运算符,例如:

with open("foo.bin", 'rb') as f:
    if b'\x00' in f.read():
        print('The file is binary!')
    else:
        print('The file is not binary!')

2
这对我很有帮助 - 我试图将一个字符串与一个字节字符串进行比较。我所要做的就是在搜索词前面加上 b,然后它就可以在字节字符串中找到了。 - pa1983

2
显然,有一个名为PIL的库可以使用。其中的图像模块具有大小属性。如果你想以你所建议的方式获取大小而不必加载文件,则必须逐行查看它。这并不是最好的方法,但它能够起到作用。

1

对于Python >=3.2:

import re

f = open("filename.jpg", "rb")
byte = f.read()
f.close()

matchObj = re.match( b'\xff\xd8.*\xff\xc0...(..)(..).*\xff\xd9', byte, re.MULTILINE|re.DOTALL)
if matchObj:
    # https://dev59.com/J3RB5IYBdhLWcg3w-8Lo
    print (int.from_bytes(matchObj.group(1), 'big')) # height
    print (int.from_bytes(matchObj.group(2), 'big')) # width

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接