Популярные текстовые форматы данных

Табулированный текст

Табулированный текст, это текстовые файлы в которых ширина столбцов является фиксированной. В таких файлах обычно есть комментарии отмеченные специальными символами.

#         1-minute GOES-15 Solar X-ray Flux
# UTC Date  Time   Julian  of the
# YR MO DA  HHMM    Day     Day       Short       Long
#-------------------------------------------------------
2017 11 06  1643   58063  60180     2.60e-09    4.30e-08
2017 11 06  1644   58063  60240     2.18e-09    4.03e-08
2017 11 06  1645   58063  60300     2.43e-09    3.88e-08
2017 11 06  1646   58063  60360     2.34e-09    3.95e-08
2017 11 06  1647   58063  60420     1.68e-09    4.17e-08

Чтение

Табулированный текст удобно построчно разбирать файл с использованием scanfpip.

import scanf

with open('test.txt') as file:
    for line in file:
        if len(line.strip()) == 0 or line[0] == '#':
            continue
        print(scanf.scanf("%d %d %d %d %d %d %f %f", line))

Запись

Для создания табулированного текста отлично подходит форматированная печать:

data = [[2017, 11, 6, 1643, 58063, 60180, 2.60e-09, 4.30e-08],
        [2017, 11, 6, 1644, 58063, 60240, 2.18e-09, 4.03e-08]]

with open('test.txt', 'w') as f:
    for d in data:
        print("%04d %02d %02d %04d %05d %05d %0.2e %0.2e" % tuple(d), file = f)

CSV

Используются для хранения табличных данных и как формат обмена между приложениями. Хотя на csv существует стандарт, ему почти никто не следует.

1,"Eldon Base for stackable storage shelf, platinum",Muhammed MacIntyre,3,-213.25,38.94,35,Nunavut,Storage & Organization,0.8
2,"1.7 Cubic Foot Compact ""Cube"" Office Refrigerators",Barry French,293,457.81,208.16,68.02,Nunavut,Appliances,0.58
3,"Cardinal Slant-Di Ring Binder, Heavy Gauge Vinyl",Barry French,293,46.71,8.69,2.99,Nunavut,Binders and Binder Accessories,0.39
4,R380,Clay Rozendal,483,1198.97,195.99,3.99,Nunavut,Telephones and Communication,0.58

Для чтения и записи таких файлов в стандартной библиотеке Python есть модуль csv.

Чтение

Чтение файлов осуществляется через промежуточный объект csv.reader или csv.DictReader. Все прочитанные значения будут строками.

import csv

fp = open('sampledata.csv', newline="")

rdr = csv.reader(fp, delimiter=',', quotechar='"')

for rec in rdr:
    print(rec)
# ['1', 'Eldon Base for stackable ...
# ['2', '1.7 Cubic Foot Compact ...

fp.close()

Запись

Запись осуществляется через объект csv.writer или csv.DictWriter. Методами writerow (принимает список или словарь соответственно) и writerows (принимает список списков или словарей соответственно).

import csv
with open('eggs.csv', 'w', newline="") as csvfile:
    spamwriter = csv.writer(csvfile, delimiter='\t', quotechar='"')
    spamwriter.writerow(["Test", "Value"])
    spamwriter.writerow(["1", "2"])
    spamwriter.writerows([["2", "3"],["4", "5"]])

Чтение CSV неизвестного формата

В модуле csv есть объект csv.Sniffer который позволяет автоматически определить формат CSV файла.

import csv

with open('example.csv', newline='') as csvfile:
    dialect = csv.Sniffer().sniff(csvfile.read(1024))
    csvfile.seek(0)
    reader = csv.reader(csvfile, dialect)

INI

Используются для хранения настроек многими программами.

[sectionA]
ValueOne = 42
ValueTwo = yes

[sectionB]
Test = hg

Для работы с ними используется модуль стандартной библиотеки configparser.

Чтение

import configparser
config = configparser.ConfigParser()
config.read('example.ini')

print(config['sectionA'].getboolean('ValueTwo'))
# True
print(config['sectionA'].getint('ValueOne'))
# 42
print(config['sectionB'].get('Test'))
# hg

Запись

Структура записи — словарь словарей строк (все значения должны быть приведены к строкам).

import configparser
config = configparser.ConfigParser()

config['sectionA'] = {}
config['sectionA']['ValueTwo'] = 'yes'
config['sectionA']['ValueOne'] = '42'

config['sectionB'] = {}
config['sectionB']['Test'] = 'hg'

with open('example.ini', 'w') as configfile:
    config.write(configfile)

JSON

Популярный формат обмена структурированными данными, особенно в веб-приложениях.

{
   "firstName": "Иван",
   "lastName": "Петров",
   "address": {
       "streetAddress": "Московское ш., 1, кв.13",
       "city": "Ленинград",
       "postalCode": 101101
   },
   "phoneNumbers": [
       "812 123-1234",
       "916 123-4567"
   ]
}

Структура — рекурсивный словарь. В качестве ключей всегда строки, а в качестве значений могут быть данные типов str, int, float, None, bool, списки или словари.

Чтение

Для чтения из файла используется метод json.load, для чтения из строки json.loads.

import json
with open('test.json') as jfile:
    data = json.load(jfile)
print(d)
# {'firstName': 'Иван', 'lastName': 'Петров', ...
print(d['address']['streetAddress'])
# 'Московское ш., 1, кв.13'

Запись

Для записи в файл используется метод json.dump,а для создания строки json.dumps.

import json

data = {"привет":123,
        "listval": [1,2,3]}

with open("wtest.json", "w") as jfile:
    json.dump(data, jfile, ensure_ascii=False)

Дополнительный ключ ensure_ascii необходим, чтобы полученный файл был в utf8, иначе все Unicode символы будут закодированы.

XML

Самый популярный формат структурированных данных. Используется повсеместно, в частности? на нем основаны HTML (то есть весь веб), docx/xlsx/odt/ods, и бессчетное число других форматов.

<person>
  <firstName>Иван</firstName>
  <lastName>Петров</lastName>
  <address city="Ленинград" pcode="101101">
    <streetAddress>Московское ш., 101, кв.101</streetAddress>
  </address>
  <phoneNumbers>
    <phoneNumber>812 123-1234</phoneNumber>
    <phoneNumber>916 123-4567</phoneNumber>
  </phoneNumbers>
</person>

XML — иерархическая база данных, состоящая из тегов с аргументами и значений, которые в свою очередь также могут содержать теги.

Открытие

Существует несколько идейных подходов к тому, как работать с XML. Рассмотрим самый простой и примитивный.

import xml.etree.ElementTree as ET

#Читаем данные из файла
tree = ET.parse('text.xml')
root = tree.getroot()

#Или из строки
root = ET.fromstring("<person><firstName>Иван...

# root - корневой элемент документа имеет тип Element

Чтение состоит в анализе дерева элементов, а запись в его модификации.

Структура объекта Element

root.tag == 'person' # True
root.attrib == {} # True
root.text == '\n    ' # True

lmnt = root[2]

lmnt.attrib == {'city': 'Ленинград', 'pcode': '101101'} # True
lmnt.get('city') == 'Ленинград' # True
lmnt[0].tag == 'streetAddress' # True
lmnt[0].text == 'Московское ш., 101, кв.101' # True

Обход объекта Element

list(root) # Все дочерние узлы
# [<Element 'firstName' at 0x7fc59f52c8b8>,
#  <Element 'lastName' at 0x7fc59f52cea8>,
#  ...
#  <Element 'phoneNumbers' at 0x7fc59f316458>]

list(root.iter()) # Все дочерние узлы рекурсивно
# [<Element 'person' at 0x7fc5a0c94098>,
#  <Element 'firstName' at 0x7fc59f52c8b8>,
#  ...
#  <Element 'phoneNumber' at 0x7fc59ee24638>]
   
list(root[3].findall('phoneNumber')) # Все дочерние узлы с тегом
# [<Element 'phoneNumber' at 0x7fc59ee245e8>,
#  <Element 'phoneNumber' at 0x7fc59ee24638>]

list(root.iter('phoneNumber')) # Все дочерние узлы с тегом рекурсивно
# [<Element 'phoneNumber' at 0x7fc59ee245e8>,
#  <Element 'phoneNumber' at 0x7fc59ee24638>]

Модификация

#Задание текста
lmnt.text = str('ул. Арбат, д.1, кв.13')

#Установка атрибутов
lmnt.set('city', 'Москва')

#Добавление элемента
lmnt.append(ET.Element('test'))
lmnt.insert(0, ET.Element('test'))

#Удаление элемента
root.remove(lmnt)

Запись

#Создание строки
ET.tostring(root, encoding="unicode")

#Запись в файл
with open("test2.xml","w") as fp:
    tree = ET.ElementTree(root)
    tree.write(fp, encoding="unicode")

HTML

Язык разметки Web страниц, по сути менее строгая реализация XML. Если разбирать HTML с помощью ET, то вы скорее всего получите ошибку (меньшинство реальных Web страниц генерирует 100% валидный XHTML).

Для работы с HTML есть пакет beautifulsoup4[pip].

from bs4 import BeautifulSoup

# Из файла
with open("index.html") as fp:
    soup = BeautifulSoup(fp, "html.parser")

# Из строки
soup = BeautifulSoup("<html>...", "html.parser")

Чтение

# Доступ к элементу по тегу
titleelem = soup.html.head.title

#Текст элемента
titleelem.string

#Атрибуты
titleelem['width']

#Поиск по значению атрибута (первый элемент)
soup.find('title', align="center")

#Поиск по значению атрибута (все элементы)
soup.findAll('title', width=re.compile('[0-9]+'))

Подробная справка по BeautifulSoup.

Запись

Одним из самых популярных способов создания HTML (а также любого документа с текстовой разметкой) в Python, является пакет jinja2[pip], см. документацию.

Процесс состоит из двух этапов: создание шаблона и генерация документа.

Создание шаблона

Шаблоны создаются на специальном макроязыке jinja. Например:

Hello <b>{{ username }}</b>!<br/>
Available tables for you:
{% for item in tables %}
        <em>{{ item['name'] }}</em>: {{ item['size'] }}<br>
{% endfor %}

Генерация документа

import jinja2

# Инициализация jinja
templates_folder = '.'
jenv = jinja2.Environment(
       loader=jinja2.FileSystemLoader(templates_folder))

# Загрузка шаблона
template = jenv.get_template('test.j2')

# Раскрытие шаблонов
s = template.render(username="User",
                tables=[{'name':'test1',
                         'size':33},
                        {'name':'test2',
                         'size':42}])
# s == 'Hello <b>User</b>!<br/>\n
#       Available tables for you:\n\n
#       <em>test1</em>: 33<br>\n\n
#       <em>test2</em>: 42<br>\n'

# Сохранение в файл
with open("outfile.txt","w") as fobj:
    fobj.write(s)

Что еще нужно знать о текстовых форматах

  • Локализованный Excel создает CSV со следующими параметрами по умолчанию: разделитель полей ;, символ кавычек ", символ конца строки \r\n.
  • В pip есть более мощная библиотека для работы с XML: lxmlpip, в частности у узлов есть метод parent() позволяющий получить родительский узел.
  • JSON и XML поддерживают механизмы валидации при помощи схем (описание допустимой структуры файла), что полезно, если вы анализируете документ полученный из стороннего источника. См. модули jsonschemapip и модуль валидации библиотеки lxmlpip.
  • У JSON есть идейно (но не синтаксически!) близкий формат YAML, который считается более человекочитаемым. Для него есть модуль pyyamlpip.
  • Не следует использовать регулярные выражения для разбора рекурсивных форматов (XML, YAML, HTML, JSON).