Python ZipFile извлечение директории с файлами с русскими названиями
Вот собственно код никак не могу извлечь файлы и самому директорию на русском языке , а если быть точнее то я в принципе не понимаю как работает open либо extract() в zipfile . Если просто extract , то естественно русские символы не читабельны , если декодируешь то появляется этот символ который не дает извлечь «/» так как если принтить namelist() то там будет что-то типа того «Ежегодник 2016/» и ещё два файла .doc внутри тоже с «/» на конце. Что с этим всем делать,потому что я читал документацию по zipfile но ничего не нашел.
#coding: utf-8 from zipfile import ZipFile import os with ZipFile('ezhegodnik-2016.zip', 'r') as zip: for name in zip.namelist(): print(name) unicode_name = name.encode('cp437').decode('cp866').replace('/','') print(unicode_name) with zip.open(name) as f: content = f.read() #fullpath = os.path.join(os.getcwd() ,unicode_name) fullpath = os.makedirs('E:\Learning\programming\Python\Python3/' + unicode_name) with open(fullpath,'wb') as f: f.write(content)
3 ответа 3
По спецификации, zip формат понимает только cp437 и utf-8 кодировки. Реализация zipfile модуля в Питоне следует этой спецификации.
На практике, были распространены архивы, которые используют OEM code page для установленного варианта Windows. Для русской Винды это cp866.
Если shutil.unpack_archive(‘ezhegodnik-2016.zip’, format=’zip’) не даёт желаемого результата (архив распаковывается, но вы видите кракозябры вместо имён в explorer), то это значит что utf-8 не используется и можно попробовать перекодировать имена файлов из архива из cp437 кодировки в cp866 (вероятный кандидат для однобайтовой кодировки с русским содержимым внутри zip-архива).
#!/usr/bin/env python3 import os import shutil import zipfile def unpack_zipfile(filename, extract_dir, encoding='cp437'): with zipfile.ZipFile(filename) as archive: for entry in archive.infolist(): name = entry.filename.encode('cp437').decode(encoding) # reencode. # don't extract absolute paths or ones with .. in them if name.startswith('/') or '..' in name: continue target = os.path.join(extract_dir, *name.split('/')) os.makedirs(os.path.dirname(target), exist_ok=True) if not entry.is_dir(): # file with archive.open(entry) as source, open(target, 'wb') as dest: shutil.copyfileobj(source, dest) unpack_zipfile('ezhegodnik-2016.zip', r'c:\kуда\положить', encoding='cp866')
Стоит заметить, что метод _extract_member() (реализующий .extract() ), выполняет больше манипуляций с именами прежде чем их распаковывать (перенесите код необходимый в вашем случае).
Альтернативно, можно сперва все файлы распаковать и только потом перекодировать их имена (не тестировано):
#!/usr/bin/env python3 import os import zipfile # unpack the zip archive to extract_dir extract_dir = r'c:\kуда\положить' with zipfile.ZipFile('ezhegodnik-2016.zip') as archive: archive.extractall(extract_dir) # reencode all paths in extract_dir def renamed(dirpath, names, encoding): new_names = [old.encode('cp437').decode(encoding) for old in names] for old, new in zip(names, new_names): os.rename(os.path.join(dirpath, old), os.path.join(dirpath, new)) return new_names encoding = 'cp866' os.chdir(extract_dir) # cd to avoid reencoding the parent dirname for dirpath, dirs, files in os.walk(os.curdir, topdown=True): renamed(dirpath, files, encoding) dirs[:] = renamed(dirpath, dirs, encoding)
Русские названия файлов и модуль playsound
я знаю, что можно впихнуть это аудио в одну папку с кодом и переименовать а ля английские язык и цифры, но всё же как использовать в путях к файлу и названия самого файла в python на русском языке? Заранее спасибо.
@DiHASTRO, откатил вопрос до более ранней версии. Не нужно «мусорить» в вопросе. Вопрос не только для вас, но и для других людей.
2 ответа 2
tl;dr: На 15 июля 2021 года проблема уже должна быть исправлена, судя по комментарию автора модуля
Это баг библиотеки playsound. Смотрим код:
def winCommand(*command): buf = c_buffer(255) command = ' '.join(command).encode(getfilesystemencoding()) #
Первая проблема - берется getfilesystemencoding() (под Windows 10, например, это utf-8), потом этой кодировкой кодируется команда (часть которой - ваш путь к файлу с русскими буквами). Получаем набор байт в кодировке utf-8. И дальше передаем этот набор байт в функцию, mciGetErrorStringA , буква A в имени которой говорит нам, что функция поддерживает только однобайтовые кодировки (в отличие от функций с буквой W в конце - они поддерживают utf-16). Функция не срабатывает, возвращает код ошибки.
Потом срабатывает if , пытаемся получить текст ошибки (опять же, с помощью ANSI версии функции mciGetErrorString ). Дальше команда, изначально закодированная в utf-8, декодируется кодировкой по-умолчанию (в этот раз повезло - это оказалась тоже utf-8).
Дальше берется буфер с текстом ошибки, также декодируется из кодировкой по-умолчанию (в вашем случае это скорее всего utf-8), но т.к. содержимое этого буфера заполнила функция, рассчитанная на однобайтовые кодировки, то в буфере, внезапно, оказывается текст на русском в кодировке cp1251. Получаем ошибку из вопроса.
def winCommand(*command): buf = c_buffer(512) command = ' '.join(command).encode('utf-16') errorCode = int(windll.winmm.mciSendStringW(command, buf, 511, 0)) if errorCode: errorBuffer = c_buffer(512) windll.winmm.mciGetErrorStringW(errorCode, errorBuffer, 511) exceptionMessage = ('\n Error ' + str(errorCode) + ' for command:' '\n ' + command.decode('utf-16') + '\n ' + errorBuffer.value.decode('utf-16')) raise PlaysoundException(exceptionMessage) return buf.value
В приниципе, можно добавить пулл-реквест к библиотеке.
Обновлено
Протестировал у себя, внес еще несколько исправлений, полный код функции _playsoundWin :
def _playsoundWin(sound, block = True): ''' Utilizes windll.winmm. Tested and known to work with MP3 and WAVE on Windows 7 with Python 2.7. Probably works with more file formats. Probably works on Windows XP thru Windows 10. Probably works with all versions of Python. Inspired by (but not copied from) Michael Gundlach 's mp3play: https://github.com/michaelgundlach/mp3play I never would have tried using windll.winmm without seeing his code. ''' import string from ctypes import c_buffer, windll from random import random from time import sleep from sys import getfilesystemencoding def winCommand(*command): buf = c_buffer(512) command = ' '.join(command) errorCode = int(windll.winmm.mciSendStringW(command.encode('utf-16'), buf, 511, 0)) if errorCode: errorBuffer = c_buffer(512) windll.winmm.mciGetErrorStringW(errorCode, errorBuffer, 511) exceptionMessage = ('\n Error ' + str(errorCode) + ' for command:' '\n ' + command + '\n ' + errorBuffer.raw.decode('utf-16').rstrip('\0')) raise PlaysoundException(exceptionMessage) return buf.raw.decode('utf-16').rstrip('\0') alias = ('playsound_' + str(random()))[:28] # Avoid aliases longer then 28 winCommand('open "' + sound + '" alias', alias) winCommand('set', alias, 'time format milliseconds') durationInMS = winCommand('status', alias, 'length') winCommand('play', alias, 'from 0 to', durationInMS) if block: sleep(float(durationInMS) / 1000.0)
Обновление 2
- Пулл-реквест: https://github.com/TaylorSMarks/playsound/pull/32
- Мой форк пакета с исправлением: https://github.com/insolor/playsound
Можно обновить пакет прямо с github (потребуется установленный git):
pip install -U git+https://github.com/insolor/playsound
Обновление 3
Судя по комментарию автора модуля, он выпустил свой вариант исправления. Я его не тестировал, но предполагаю, что исправление рабочее, поэтому свой форк удалил. Пулл реквест я также закрыл, но изменения в нем можно посмотреть при желании.
Как получить файл (кириллица в имени) с FTP?
Чтобы исключить проблемы с отображением не-ASCII строк в вашем окружении, покажите print(ascii(filelist[3])) , чтобы помочь с отладкой. Что ftp.encoding показывает?
1 ответ 1
Изначально (RFC 765, 959), FTP только 7-бит ASCII поддерживал, RFC 2640 расширяет поддержку до других кодировок, RFC 3659 уточняет, что команды такие как MLST могут вернуть пути либо в UTF-8 кодировке, либо это может быть произвольная каша байтов—за некоторыми исключениями такими как CRLF (новая строка, b'\x0d\x0a' ), одинокий Telnet IAC ( b'\xff' ).
На POSIX имена файлов могут быть произвольной последовательностью байтов за исключением b'\x2f' и b\x00' (слэш и ноль).
В Питоне 3, ftplib формально использует latin-1 кодировку по умолчанию, которая произвольную последовательность байт позволяет в Unicode декодировать.
В: В каком виде ftp.nlst() получает имена файлов?
ftp.nlst() возвращает список текстовых (Unicode) строк.
В: И как их привести к нормальному виду?
Если вы знаете, что сервер использует единственную кодировку для имён файлов в вашем случае (вероятно, utf-8 , если вывод команды feat её показывает) и нет непредставимых имён (PEP 383), то правильные имена можно получить декодированием:
filename = filename.encode(ftp.encoding).decode(your_encoding)
Можно передать 'surrogateescape' обработчик ошибок в .decode() , чтобы поддерживать и непредставимые имена (чтобы была возможность без потерь восстановить изначальные байты, одновременно позволяя печатать представимые имена в «нормальном виде»).
Судя по результату ascii() , в вашем случае ftp-сервер возвращает имена в cp1251 кодировке (ANSI codepage на русской Винде):
>>> print(ascii(filelist[6])) '\xc8\xed\xf1\xf2\xf0\xf3\xea\xf6\xe8\xff'
В этом случае, your_encoding='cp1251' , чтобы получить исходное имя:
filename = filelist[6].encode(ftp.encoding).decode('cp1251') # -> 'Инструкция'
Чтобы зря не перекодировать, можно перед вызовом ftp.nlst() выставить
ftp.encoding = 'cp1251' , если известно что все имена файлов в этой кодировке представимы. Тогда ftp.nlst() сразу вернёт правильно декодированные имена.
os.listdir и кириллица
С кодировками вечно какие-нибудь проблемы. Видимо, питон не справляется с угадыванием кодировки байтовой строки и не может корректно перевести её в кодировку файловой системы Windows.
Но в Python 2 у os.listdir (и некоторых других функций) есть особенность: если передать юникодную строку, то вернётся юникодный результат. При использовании юникода питон справляется с кодированием чего надо в какую надо кодировку, и никаких проблем нет.
Но начнём с того, что символ \ в строках является спецсимволом, и его нельзя ставить просто так. Его нужно экранировать так:
path = 'C:\\Users\\Алекс\\Desktop\\DefendMySystem'
path = r'C:\Users\Алекс\Desktop\DefendMySystem'
Сейчас path это байтовая строка, а для максимально корректной работы нужна юникодная. Строковый литерал можно сделать юникодым с помощью u'' :
# -*- coding: utf-8 -*- # вот этот ↑ комментарий должен стоять в первой строке файла! path = u'C:\\Users\\Алекс\\Desktop\\DefendMySystem' path = ur'C:\Users\Алекс\Desktop\DefendMySystem'
(Естественно, сам файл в таком случае тоже должен быть сохранён в кодировке UTF-8, желательно без BOM.)
Если это не строковый литерал, то можно декодировать байтовую строку в юникодную, если известна кодировка:
Теперь, когда юникодная строка получена, os.listdir будет работать без проблем:
print(type(path)) # → unicode files = os.listdir(path) # type: List[unicode]
А вот в Python 3 все строки юникодные, и там подобных проблем в принципе не бывает (кроме совсем уж специфических случаев).