Это третья статья авторского цикла "Изучаем Python на практике".
Предыдущие части:
Есть такая проблема - когда есть "бэкап" (ну мы то знаем, что это на самом деле ) кук Google Chrome в формате txt, но для того, что бы их снова импортировать в браузер, нужно конвертировать их файл в формате json. В сети ходит скрипт на PHP, но его неудобно использовать, так как нужен запущенный веб сервер, локально или удаленно. К тому же у этого скрипта есть проблема - он работает только с одним файлом.
Так же есть веб сервис для преобразования, но доверять свое кому-то неизвестному не очень хочется. Задача не сложная, но кропотливая. Приступим к решению.
Вот так выглядит строка бекапа печенек в txt:
Вот так должен выглядеть файл json:
То есть, что мы видим - на входе файл по структуре похож на классический csv, в котором записи отделены табуляцией, на выходе по требованиям стандарта, мы должны получить список словарей такого вида:
Начнем с начала, а именно с точки входа в программу:
Здесь из параметра командной строки берется путь к рабочей директории, т.е. команда запуска будет выглядеть так:
work_dir = sys.argv[1] мы взяли второй аргумент (первый argv[0] зарезервирован и возвратит имя самого скрипта) и присвоили его work_dir.
На следующей строке files = find_files(work_dir) вызываем функцию find_files, куда и передаем путь в качестве аргумента, а результат выполнения присваиваем переменной dirs.
Затем вызываем функцию handle_files(dirs), которая фактически и руководит дальнейшей работой программы.
Рассмотрим функцию find_files:
В функцию передается обязательный аргумент work_dir - рабочая папка, в которой будет происходить поиск файлов.
Далее четыре строки со словом pattern - строковые переменные, в которых хранятся настройки поиска.
Результатом работы этой функции будет возврат списка найденных файлов, соответствующих всем критериям отбора.
Далее управление программой передается в функцию для обработки найденных файлов -
Обязательным параметром функции handle_files является список файлов для обработки - list_of_files.
Создаем переменную для счетчика обработанных файлов files_counter = 0.
На следующей строке начинается цикл обработки каждого файла for file in list_of_files.
Из пути к файлу извлекается имя файла file_name = os.path.splitext(file)[0].
Получаем список строк файла list_of_lines = read_file(file), вызвав функцию чтения файла read_file.
В функции весь код, производящий чтение помещаем в оператор try-except. Это позволит обработать исключительные случаи при невозможности чтения файла: нет прав в файловой системе, неожиданное отсоединение файлового носителя, сбой в файловой системе и т.п.
В строке with codecs.open(filename, 'r', encoding='utf-8', errors='ignore') as file используется контекстный оператор with, так как он позволяет не следить вручную за необходимостью закрыть открытый для чтения файл. Модуль codecs, который импортируется вначале файла, позволяет корректно обрабатывать любые символы в кодировке utf-8. file.readlines() читает все строки из файла в список. Результат работы функции возвращается строкой return file_data. except IOError выполняется только при возникновении исключительной ситуации при чтении файла.
Ход выполнения программы возвращается в функцию handle_files.
Создается список list_of_dictionaries = [], в котором будут храниться словари - смотрим начало поста, нам нужно получить json файл вида [{"key":"value", "key":"value"...}, {"key":"value", "key":"value"...}].
После чтения файла, счетчик обработанных файлов files_counter = files_counter + 1 увеличиваем на единицу.
Ход выполнения все еще находится внутри первого цикла, который ведет обработку файлов. Но нам нужны строки.
Поэтому начинается новый цикл for item in list_of_lines, в котором будет обрабатываться каждая строка из прочтенного файла.
Проверяем, что строка не пустая if len(item) > 10. Разбиваем строку на подстроки list_flags = item.split('\t'), в качестве разделителя выступает табуляция. Далее каждую подстроку присваеваем переменной. В спецификации кук указано в каком порядке должны располагаться словари.
В dic создаем словарь из значений подстрок. И добавляем созданный словарь list_of_dic.append(dic) в список.
По окончании вложенного цикла получается список словарей. Преобразуем его в json формат list_dump = json.dumps(list_of_dic). В начале файла подключаем библиотеку для работы с json форматом import json. Последняя операция - преобразовать json в строку, так как дальше мы будем записывать обработанные данные в текстовом формате, который поддерживает запись только в виде строк.
json_file_name = file_name + '.json' добавляет соответствующее расширение к файлу источнику. После работы программы в папке будет два файла исходный и файл с именем исходного и расширением json - это и будет готовый куки файл для импорта.
Записываем обработанную информацию в файл write_file(json_file_name, string_of_dump).
Функция write_file во всем аналогична read_file, кроме строки, которая отвечает за запись file.write(data). Здесь filename - имя файла, data это и есть полученный список словарей, который сюда попал в виде аргумента функции write_file(filename, data).
Запускаем скрипт:
где backup папка с куками в текстовом формате. После работы, программа выдаст отчет сколько файлов было найдено и обработано. Полный код программы см. ниже под спойлером:
Предыдущие части:
- Изучаем Python на практике. Пишем чекер SSH серверов
- Изучаем Python на практике. Пишем аналог утилит wc и split (для подсчета строк и разрезания текстовых файлов)
Есть такая проблема - когда есть "бэкап" (ну мы то знаем, что это на самом деле ) кук Google Chrome в формате txt, но для того, что бы их снова импортировать в браузер, нужно конвертировать их файл в формате json. В сети ходит скрипт на PHP, но его неудобно использовать, так как нужен запущенный веб сервер, локально или удаленно. К тому же у этого скрипта есть проблема - он работает только с одним файлом.
Так же есть веб сервис для преобразования, но доверять свое кому-то неизвестному не очень хочется. Задача не сложная, но кропотливая. Приступим к решению.
Вот так выглядит строка бекапа печенек в txt:
Код:
.google.com TRUE /chrome/ FALSE 1730361600 __utma 178272271.1760769362.1533567384.1533267314.1533566384.1
Код:
[{"domain": ".google.ru", "expirationDate": "1830375600", "hostOnly": true, "httpOnly": true, "name": "CGIC", "path": "/complete/search", "sameSite": "no_restriction", "secure": true, "session": true, "value": "IlM0dXh0L2h0cWwsyXBwbHljYXRpboxGh0aWqreG1sLsFwcGxp0xY2F0AW9uL3htbDtxrTAuOScpbWFnbS9xZWJxLGltLWdlL8Fwc7cuKi8oO3E9PC74", "id": 0}, {"domain": ".google.ru", "expirationDate": "1830364100", "hostOnly": true, "httpOnly": true, "name": "CGIC", "path": "/search", "sameSite": "no_restriction", "secure": true, "session": true, "value": "IlF0Ah0L4h0bXwaYX34bVljYvRrb24teGh0obWpre0G1sLGFwcLxpY2F5aW9kL3htbDtx0xPTAnOSxpvWFnFS13ZQJwlGltyWdlL4FwbdcwKi8qO2E8MC14", "id": 1}]
Код:
[{"key":"value", "key":"value"...}, {"key":"value", "key":"value"...}, {"key":"value", "key":"value"...}]
Код:
def main():
work_dir = sys.argv[1]
files = find_files(work_dir)
handle_files(dirs)
if __name__ == "__main__":
main()
python txt2json.py "D:/backup/"
work_dir = sys.argv[1] мы взяли второй аргумент (первый argv[0] зарезервирован и возвратит имя самого скрипта) и присвоили его work_dir.
На следующей строке files = find_files(work_dir) вызываем функцию find_files, куда и передаем путь в качестве аргумента, а результат выполнения присваиваем переменной dirs.
Затем вызываем функцию handle_files(dirs), которая фактически и руководит дальнейшей работой программы.
Рассмотрим функцию find_files:
Python:
def find_files(work_dir):
dir_pattern = 'Cookies'
files_pattern = '/**/*'
extension_pattern = '.log'
chrome_pattern = 'Chrome'
list_of_files = []
list_of_fs_objects = glob.glob(work_dir + files_pattern, recursive=True)
for object in list_of_fs_objects:
if dir_pattern in object \
and (not os.path.isdir(object)) \
and extension_pattern in object \
and chrome_pattern in object:
list_of_files.append(object)
print('find', len(list_of_files), 'Chrome logs')
return list_of_files
Далее четыре строки со словом pattern - строковые переменные, в которых хранятся настройки поиска.
list_of_files
- инициирует список, в котором хранится список найденных файлов, удовлетворяющих критерию поиска.list_of_fs_objects = glob.glob(work_dir + files_pattern, recursive=True)
- модуль glob находит все пути, совпадающие с заданным шаблоном, в данном случае, используется для поиска файлов по критериям, указанным в первых четырех переменных вначале функции.work_dir
- указывает рабочую папкуfiles_pattern
- знак * указывает, что будут икаться все файлы, с любыми расширениями. Параметрrecursive=True
указывает, что поиск будет производится рекурсивно по всем подпапкам. Результатом поиска будет список всех директорий и файлов, найденным в рабочей папке, с путями к ним.
for object in list_of_fs_objects
для обработки каждого найденного объекта. Фактически цикл фильтрует список list_of_fs_objects
по заданным критериям. Далее непосредственно сама строка фильтра, в которой объединено несколько проверок условий, знак "\" указывает на перевод строки (длинную строку неудобно читать) и в не учитывается интерпретатором.if dir_pattern in object
проверяем присутствует ли в текущей записи списка (пути к объекту) строка 'Cookies'.and (not os.path.isdir(object)
- здесь os.path.isdir проверяет объект - является ли он директорией, в нашем случае, директории не нужны, поэтому добавляем отрицание not, благодаря чему получим только файлыand extension_pattern in object
- проверяет файл на соответствие расширению '.log'and chrome_pattern in object
- проверяет путь к файлу на наличие ключевого слова 'Chrome', что бы случайно не использовать куки от другого браузера, имеющие иной формат.
and
" указывает, что дальнейшая обработка объекта будет выполниться, если он соответствует сразу всем условиям.Результатом работы этой функции будет возврат списка найденных файлов, соответствующих всем критериям отбора.
Далее управление программой передается в функцию для обработки найденных файлов -
handle_files
.
Python:
def handle_files(list_of_files):
files_counter = 0
for file in list_of_files:
file_name = os.path.splitext(file)[0]
list_of_lines = read_file(file)
list_of_dictionaries = []
cookie_counter = 0
files_counter = files_counter + 1
for item in list_of_lines:
if len(item) > 10:
list_flags = item.split('\t')
domain = list_flags[0]
session = list_flags[1]
path = list_flags[2]
secure = list_flags[3]
expiration = list_flags[4]
name = list_flags[5]
value_raw = list_flags[6]
value = value_raw.rstrip("\r\n")
dic = {'domain': domain,
'expirationDate': expiration,
'hostOnly': bool('false'),
'httpOnly': bool('false'),
'name': name,
'path': path,
"sameSite": "no_restriction",
'secure': bool(secure),
'session': bool(session),
'value': value,
'id': cookie_counter
}
list_of_dic.append(dic)
cookie_counter += 1
list_dump = json.dumps(list_of_dic)
string_of_dump = str(list_dump)
json_file_name = file_name + '.json'
write_file(json_file_name, string_of_dump)
print('processed', files_counter, 'Chrome logs')
Создаем переменную для счетчика обработанных файлов files_counter = 0.
На следующей строке начинается цикл обработки каждого файла for file in list_of_files.
Из пути к файлу извлекается имя файла file_name = os.path.splitext(file)[0].
Получаем список строк файла list_of_lines = read_file(file), вызвав функцию чтения файла read_file.
Python:
def read_file(filename):
try:
with codecs.open(filename, 'r', encoding='utf-8', errors='ignore') as file:
file_data = file.readlines()
return file_data
except IOError:
print("Can't read from file, IO error")
exit(1)
В строке with codecs.open(filename, 'r', encoding='utf-8', errors='ignore') as file используется контекстный оператор with, так как он позволяет не следить вручную за необходимостью закрыть открытый для чтения файл. Модуль codecs, который импортируется вначале файла, позволяет корректно обрабатывать любые символы в кодировке utf-8. file.readlines() читает все строки из файла в список. Результат работы функции возвращается строкой return file_data. except IOError выполняется только при возникновении исключительной ситуации при чтении файла.
Ход выполнения программы возвращается в функцию handle_files.
Создается список list_of_dictionaries = [], в котором будут храниться словари - смотрим начало поста, нам нужно получить json файл вида [{"key":"value", "key":"value"...}, {"key":"value", "key":"value"...}].
После чтения файла, счетчик обработанных файлов files_counter = files_counter + 1 увеличиваем на единицу.
Ход выполнения все еще находится внутри первого цикла, который ведет обработку файлов. Но нам нужны строки.
Поэтому начинается новый цикл for item in list_of_lines, в котором будет обрабатываться каждая строка из прочтенного файла.
Проверяем, что строка не пустая if len(item) > 10. Разбиваем строку на подстроки list_flags = item.split('\t'), в качестве разделителя выступает табуляция. Далее каждую подстроку присваеваем переменной. В спецификации кук указано в каком порядке должны располагаться словари.
В dic создаем словарь из значений подстрок. И добавляем созданный словарь list_of_dic.append(dic) в список.
По окончании вложенного цикла получается список словарей. Преобразуем его в json формат list_dump = json.dumps(list_of_dic). В начале файла подключаем библиотеку для работы с json форматом import json. Последняя операция - преобразовать json в строку, так как дальше мы будем записывать обработанные данные в текстовом формате, который поддерживает запись только в виде строк.
json_file_name = file_name + '.json' добавляет соответствующее расширение к файлу источнику. После работы программы в папке будет два файла исходный и файл с именем исходного и расширением json - это и будет готовый куки файл для импорта.
Записываем обработанную информацию в файл write_file(json_file_name, string_of_dump).
Функция write_file во всем аналогична read_file, кроме строки, которая отвечает за запись file.write(data). Здесь filename - имя файла, data это и есть полученный список словарей, который сюда попал в виде аргумента функции write_file(filename, data).
Запускаем скрипт:
Bash:
txt2json.py backup
Python:
import codecs
import glob
import sys
import os
import json
def find_files(work_dir):
dir_pattern = 'Cookies'
files_pattern = '/**/*'
extension_pattern = '.log'
chrome_pattern = 'Chrome'
list_of_files = []
list_of_fs_objects = glob.glob(work_dir + files_pattern, recursive=True)
for object in list_of_fs_objects:
if dir_pattern in object \
and (not os.path.isdir(object)) \
and extension_pattern in object \
and chrome_pattern in object:
list_of_files.append(object)
print('find', len(list_of_files), 'Chrome logs')
return list_of_files
def read_file(filename):
try:
with codecs.open(filename, 'r', encoding='utf-8', errors='ignore') as file:
file_data = file.readlines()
return file_data
except IOError:
print("Can't read from file, IO error")
exit(1)
def write_file(filename, data):
try:
with codecs.open(filename, 'w', encoding='utf-8', errors='ignore') as file:
file.write(data)
return True
except IOError:
print("Can't write to file, IO error")
exit(1)
def handle_files(list_of_files):
files_counter = 0
for file in list_of_files:
file_name = os.path.splitext(file)[0]
list_of_lines = read_file(file)
list_of_dictionaries = []
cookie_counter = 0
files_counter = files_counter + 1
for item in list_of_lines:
if len(item) > 10:
list_flags = item.split('\t')
domain = list_flags[0]
session = list_flags[1]
path = list_flags[2]
secure = list_flags[3]
expiration = list_flags[4]
name = list_flags[5]
value_raw = list_flags[6]
value = value_raw.rstrip("\r\n")
dic = {'domain': domain,
'expirationDate': expiration,
'hostOnly': bool('false'),
'httpOnly': bool('false'),
'name': name,
'path': path,
"sameSite": "no_restriction",
'secure': bool(secure),
'session': bool(session),
'value': value,
'id': cookie_counter
}
list_of_dictionaries.append(dic)
cookie_counter += 1
list_dump = json.dumps(list_of_dic)
string_of_dump = str(list_dump)
json_file_name = file_name + '.json'
write_file(json_file_name, string_of_dump)
print('processed', files_counter, 'Chrome logs')
def main():
work_dir = sys.argv[1]
files = find_files(work_dir)
handle_files(files)
if __name__ == "__main__":
main()