Как скопировать первые 100 файлов из каталога тысяч файлов с помощью Python?
У меня есть огромный каталог, который постоянно обновляется. Я пытаюсь перечислить только последние 100 файлов в каталоге, используя Python. Я пытался использовать os.listdir(), но когда размер каталога приближается к 1000000 файлов, кажется, что listdir () падает (или я не ждал достаточно долго). Мне нужны только первые 100 файлов (или имен файлов) для дальнейшей обработки, поэтому я не хочу, чтобы listdir () заполнялся всеми 100000 файлами. Есть ли хороший способ сделать это в Python?
PS: я очень новичок в программировании
2 ответа
Вот ваш ответ о том, как пройти большой каталог файл за файлом!
Я искал как маньяк для Windows DLL, которая позволит мне делать то, что делается в Linux, но не повезло.
Итак, я пришел к выводу, что единственный способ - создать свою собственную DLL, которая предоставит мне эти статические функции, но потом я вспомнил pywintypes. И, ДА! это уже сделано там. И даже более того, функция итератора уже реализована! Здорово!
Windows DLL с FindFirstFile(), FindNextFile() и FindClose() все еще может быть где-то там, но я не нашел его. Итак, я использовал pywintypes.
РЕДАКТИРОВАТЬ: Я обнаружил (очень поздно), что эти функции доступны из kernel32.dll. Целое время прячется прямо перед моим носом.
Извините за зависимость. Но я думаю, что вы можете извлечь win32file.pyd из папки...\site-packages\win32 и возможные зависимости и распространять ее независимо от win32types с вашей программой, если это необходимо.
Как вы увидите из тестов скорости, генератор возвращается очень быстро.
После этого вы сможете переходить от файла к файлу и делать все, что захотите.
NOTE: win32file.FindFilesIterator() returns whole stat of the file/dir, therefore, using my listdir() to get the name and afterwards os.path.get*time() or os.path.is*() doesn't make sense. Better modify my listdir() for those checks.
Теперь получить полное решение вашей проблемы по-прежнему проблематично.
Плохая новость для вас заключается в том, что это начинается с первого элемента в каталоге, который ему нравится, и вы не можете выбрать, каким он будет. В моих тестах он всегда возвращал отсортированный каталог. (в Windows)
Неприятная новость заключается в том, что в Windows вы можете использовать подстановочные знаки для управления тем, какие файлы вы будете перечислять. Таким образом, чтобы использовать это в постоянно заполняющейся директории, вы можете пометить новые поступающие файлы с версией и сделать что-то вроде:
bunch = 1
while True:
for file in listdir("mydir\\*bunch%i*" % bunch): print file
sleep(5); bunch += 1
Но вам придется разработать это очень умно, иначе у вас будут файлы, которые поступили, но вы не нашли их, потому что они опоздали.
Я не знаю, будет ли FindFilesIterator() продолжать обнаруживать новые файлы, когда они появятся, если вы введете задержку между оборотами цикла.
Если это так, это также может быть вашим решением.
Вы всегда можете заранее создать итератор, а затем вызвать метод next (), чтобы получить следующий файл:
i = listdir(".")
while True:
try: name = i.next()
except StopIteration: sleep(1)
# This probably won't work as imagined though
Вы можете решить, как долго ждать новых файлов, исходя из размера последних поступивших файлов. Дикое предположение, что все входящие файлы будут примерно одинакового размера плюс или минус что-то.
Тем не менее, win32file предлагает вам некоторые функции, которые могут помочь вам отслеживать каталог на предмет изменений, и я думаю, что это ваш лучший выбор.
На тестах скорости вы также можете увидеть, что создание списка из этого итератора медленнее, чем вызов os.listdir(), но os.listdir() будет блокировать, а мой listdir () - нет. Его целью не является создание списков файлов в любом случае. Почему эта потеря скорости появляется, я не знаю. Можно только догадываться о вызовах DLL, построении списков, сортировке или чем-то в этом роде. os.listdir() полностью написан на C.
Некоторые примеры использования вы можете увидеть в блоке name== "main". Сохраните код в listdir.py и 'from listdir import *'.
Here is the code:
#! /usr/bin/env python
"""
An equivalent of os.listdir() but as a generator using ctypes on
Unixoides and pywintypes on Windows.
On Linux there is shared object libc.so that contains file manipulation
functions we need: opendir(), readdir() and closedir().
On Windows those manipulation functions are provided
by static library header windows.h. As pywintypes is a wrapper around
this API we will use it.
kernel32.dll contains FindFirstFile(), FindNextFile() and FindClose() as well and they can be used directly via ctypes.
The Unix version of this code is an adaptation of code provided by user
'jason-orendorff' on Stack Overflow answering a question by user 'adrien'.
The original URL is:
http://stackru.com/questions/4403598/list-files-in-a-folder-as-a-stream-to-begin-process-immediately
The Unix code is tested on Raspbian for now and it works. A reasonable
conclusion is that it'll work on all Debian based distros as well.
NOTE: dirent structure is not the same on all distros, so the code will break on some of them.
The code is also tested on Cygwin using cygwin1.dll and it
doesn't work.
If platform isn't Windows or Posix environment, listdir will be
redirected back to os.listdir().
NOTE: There is scandir module implementing this code with no dependencies, excellent error handling and portability. I found it only after putting together this code. scandir() is now included in standardlib of Python 3.5 as os.scandir().
You definitely should use scandir, not this code.
Scandir module is available on pypi.python.org.
"""
import sys, os
__all__ = ["listdir"]
if sys.platform.startswith("win"):
from win32file import FindFilesIterator
def listdir (path):
"""
A generator to return the names of files in the directory passed in
"""
if "*" not in path and "?" not in path:
st = os.stat(path) # Raise an error if dir doesn't exist or access is denied to us
# Check if we got a dir or something else!
# Check gotten from stat.py (for fast checking):
if (st.st_mode & 0170000) != 0040000:
e = OSError()
e.errno = 20; e.filename = path; e.strerror = "Not a directory"
raise e
path = path.rstrip("\\/")+"\\*"
# Else: Decide that user knows what she/he is doing
for file in FindFilesIterator(path):
name = file[-2]
# Unfortunately, only drives (eg. C:) don't include "." and ".." in the list:
if name=="." or name=="..": continue
yield name
elif os.name=="posix":
if not sys.platform.startswith("linux"):
print >> sys.stderr, "WARNING: Environment is Unix but platform is '"+sys.platform+"'\nlistdir() may not work properly."
from ctypes import CDLL, c_char_p, c_int, c_long, c_ushort, c_byte, c_char, Structure, POINTER
from ctypes.util import find_library
class c_dir(Structure):
"""Opaque type for directory entries, corresponds to struct DIR"""
pass
c_dir_p = POINTER(c_dir)
class c_dirent(Structure):
"""Directory entry"""
# FIXME not sure these are the exactly correct types!
_fields_ = (
('d_ino', c_long), # inode number
('d_off', c_long), # offset to the next dirent
('d_reclen', c_ushort), # length of this record
('d_type', c_byte), # type of file; not supported by all file system types
('d_name', c_char * 4096) # filename
)
c_dirent_p = POINTER(c_dirent)
c_lib = CDLL(find_library("c"))
# Extract functions:
opendir = c_lib.opendir
opendir.argtypes = [c_char_p]
opendir.restype = c_dir_p
readdir = c_lib.readdir
readdir.argtypes = [c_dir_p]
readdir.restype = c_dirent_p
closedir = c_lib.closedir
closedir.argtypes = [c_dir_p]
closedir.restype = c_int
def listdir(path):
"""
A generator to return the names of files in the directory passed in
"""
st = os.stat(path) # Raise an error if path doesn't exist or we don't have permission to access it
# Check if we got a dir or something else!
# Check gotten from stat.py (for fast checking):
if (st.st_mode & 0170000) != 0040000:
e = OSError()
e.errno = 20; e.filename = path; e.strerror = "Not a directory"
raise e
dir_p = opendir(path)
try:
while True:
p = readdir(dir_p)
if not p: break # End of directory
name = p.contents.d_name
if name!="." and name!="..": yield name
finally: closedir(dir_p)
else:
print >> sys.stderr, "WARNING: Platform is '"+sys.platform+"'!\nFalling back to os.listdir(), iterator generator will not be returned!"
listdir = os.listdir
if __name__ == "__main__":
print
if len(sys.argv)!=1:
try: limit = int(sys.argv[2])
except: limit = -1
count = 0
for name in listdir(sys.argv[1]):
if count==limit: break
count += 1
print repr(name),
print "\nListed", count, "items from directory '%s'" % sys.argv[1]
if len(sys.argv)!=1: sys.exit()
from timeit import *
print "Speed test:"
dir = ("/etc", r"C:\WINDOWS\system32")[sys.platform.startswith("win")]
t = Timer("l = listdir(%s)" % repr(dir), "from listdir import listdir")
print "Measuring time required to create an iterator to list a directory:"
time = t.timeit(200)
print "Time required to return a generator for directory '"+dir+"' is", time, "seconds measured through 200 passes"
t = Timer("l = os.listdir(%s)" % repr(dir), "import os")
print "Measuring time required to create a list of directory in advance using os.listdir():"
time = t.timeit(200)
print "Time required to return a list for directory '"+dir+"' is", time, "seconds measured through 200 passes"
t = Timer("l = []\nfor file in listdir(%s): l.append(file)" % repr(dir), "from listdir import listdir")
print "Measuring time needed to create a list of directory using our listdir() instead of os.listdir():"
time = t.timeit(200)
print "Time required to create a list for directory '"+dir+"' using our listdir() instead of os.listdir() is", time, "seconds measured through 200 passes"
Вы можете попытаться прочитать каталог напрямую (в виде файла) и выбрать данные оттуда. Насколько успешным это будет - вопрос о файловой системе, в которой вы находитесь. Попробуйте сначала команды ls или dir, чтобы увидеть, кто возвращается быстрее. os.listdir() или эта забавная маленькая программа. Вы поймете, что оба в беде. Здесь ключ как раз в том, что ваш каталог залит новыми файлами. Это создает вид бутылочного горлышка.