1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/it-ebooks-pyda-2e-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
6.md 33 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 16:49 0ce2ae9

Глава 6. Загрузка данных, хранение и форматы файлов

Доступ к данным — это первый шаг в использовании инструментов, представленных в этой книге. Я сосредоточусь на представлении ввода и вывода данных с помощью pandas, хотя в других библиотеках также есть инструменты для этих целей.

Ввод и вывод можно разделить на несколько основных категорий:

  • чтение текстовых файлов и других более эффективных форматов хранения на диске;
  • загрузка данных из базы данных;
  • использование веб-API для работы с сетевыми ресурсами.

6.1 Чтение и запись данных в текстовом формате

Pandas предоставляет ряд функций для преобразования табличных данных в объекты DataFrame. В таблице 6-1 они обобщены, причём read_csv и read_table, вероятно, будут наиболее часто использоваться в будущем.

Я кратко опишу некоторые технологии, используемые этими функциями при преобразовании текстовых данных в DataFrame. Эти функции имеют параметры, которые можно разделить на следующие категории:

  • индекс: обработка одного или нескольких столбцов как возвращаемого DataFrame, а также получение имён столбцов из файла или от пользователя;
  • тип вывода и преобразование данных: включая преобразование пользовательских значений, а также использование пользовательского списка отсутствующих значений;
  • анализ даты: включая комбинированные функции, такие как объединение информации о дате и времени, которая разбросана по нескольким столбцам, в один столбец результата;
  • итерация: поддержка блочной итерации для больших файлов;
  • проблемы с нерегулярными данными: пропуск некоторых строк, колонтитулов, комментариев или других неважных элементов (например, числовых данных, разделённых запятыми).

Поскольку данные, с которыми вы можете столкнуться в работе, могут быть очень запутанными, некоторые функции загрузки данных (особенно read_csv) становятся всё более сложными. Работа с различными параметрами может вызывать головную боль (read_csv имеет более 50 параметров). В документации pandas приведены примеры этих параметров, и если вы обнаружите, что чтение определённого файла затруднительно, вы сможете найти правильные параметры, используя аналогичные примеры.

Некоторые функции, например pandas.read_csv, имеют функцию определения типа, поскольку тип данных столбца не является типом данных. Это означает, что вам не нужно указывать, является ли столбец числовым, целочисленным, логическим значением или строкой. Другие форматы данных, такие как HDF5, Feather и msgpack, хранят информацию о типе данных в самом формате.

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

In [8]: !cat examples/ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

Здесь я использую команду Unix cat для отображения содержимого файла на экране. Если вы используете Windows, вы можете использовать type для достижения того же эффекта.

Поскольку файл разделен запятыми, мы можем использовать read_csv для его чтения в DataFrame:

In [9]: df = pd.read_csv('examples/ex1.csv')

In [10]: df
Out[10]: 
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

Мы также можем использовать read_table и указать разделитель:

In [11]: pd.read_table('examples/ex1.csv', sep=',')
Out[11]: 
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

Не все файлы имеют заголовок строки. Рассмотрим следующий файл:

In [12]: !cat examples/ex2.csv
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

Есть два способа прочитать этот файл. Вы можете позволить pandas присвоить столбцам значения по умолчанию, или вы можете определить имена столбцов самостоятельно:

In [13]: pd.read_csv('examples/ex2.csv', header=None)
Out[13]: 
   0   1   2   3      4
0  1   2   3   4  hello
1  5   6   7   8  world
2  9  10  11  12    foo

In [14]: pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])
Out[14]: 
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

Если вы хотите сделать столбец message индексом DataFrame, вы можете явно указать, чтобы этот столбец был помещён в позицию индекса 4, или вы можете указать «message» с помощью параметра index_col:

In [15]: names = ['a', 'b', 'c', 'd', 'message']

In [16]: pd.read_csv('examples/ex2.csv', names=names, index_col='message')
Out[16]: 
         a   b   c   d
message               
hello    1   2   3   4
world    5   6   7   8
foo      9  10  11  12

Если вы хотите, чтобы несколько столбцов стали многоуровневым индексом, просто передайте список, состоящий из номеров столбцов или имён столбцов:

In [17]: !cat examples/csv_mindex.csv
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16

In [18]: parsed = pd.read_csv('examples/csv_mindex.csv',
   ....:                      index_col=['key1', 'key2'])

In [19]: parsed
Out[19]: 
           value1  value2
key1 key2                
one  a          1       2
     b          3       4
     c          5       6
     d          7       8
two  a          9      10
     b         11      12
     c         13      14
     d         15      16

Иногда таблицы могут не использовать фиксированный разделитель для разделения полей (например, пробелы или другие шаблоны). Рассмотрим следующий текстовый файл:

In [20]: list(open('examples/ex3.txt'))
Out[20]: 
['            A         B         C\n',
 'aaa -0.264438 -1.026059 -0.619500\n',
 'bbb  0.927272  0.302904 -0.032399\n',
 'ccc -0.264273 -0.386314 -0.217601\n',
 'ddd -0.871858 -0.348382  1.100491\n']

Хотя данные можно вручную привести в порядок, здесь поля разделены различным количеством пробелов. В этом случае вы можете передать регулярное выражение в качестве разделителя read_table. Выражение можно выразить как \s+, поэтому:

In [21]: result = pd.read_table('examples/ex3.txt', sep='\s+')

In [22]: result
Out[22]: 
            A         B         C
aaa -0.264438 -1.026059 -0.619500
bbb  0.927272  0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382  1.100491

Так как количество столбцов меньше, чем количество строк, read_table предполагает, что первая колонка должна быть индексом DataFrame.

Эти функции анализатора имеют множество других параметров, которые могут помочь вам обрабатывать различные форматы файлов (таблица 6-2 перечисляет некоторые из них). Например, вы можете пропустить первую, третью и четвёртую строки с помощью skiprows:

In [23]: !cat examples/ex4.csv
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
In [24]: pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])
Out[24]: 
   a   b   c   d message
0  1   2   3   4 **Обработка пропущенных значений в pandas**

Обработка пропущенных значений является важной частью задачи анализа файлов. Пропущенные данные часто представлены либо пустой строкой, либо специальным маркером. По умолчанию pandas использует набор часто встречающихся маркеров, таких как NA и NULL, для идентификации пропущенных данных.

В примере кода на Python используется функция pd.read_csv для чтения файла с данными. В этом файле присутствуют пропущенные значения, которые обозначены как NA. Затем код проверяет наличие пропущенных значений с помощью функции pd.isnull.

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

Можно использовать словарь для определения различных маркеров пропущенных значений для разных столбцов. Это полезно, когда разные столбцы имеют разные способы представления пропущенных данных.

**Чтение текстовых файлов по частям**

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

Перед началом работы с файлом устанавливается более компактный формат отображения данных с помощью команды pd.options.display.max_rows. Затем файл читается с помощью pd.read_csv.

Для чтения только нескольких строк файла можно указать количество строк с помощью nrows. Например, если нужно прочитать только первые пять строк файла, можно использовать команду pd.read_csv('examples/ex6.csv', nrows=5).

Чтобы читать файл по блокам, можно указать размер блока с помощью chunksize. Это позволит обрабатывать файл частями и выполнять операции над каждым блоком отдельно.

После чтения файла по блокам можно выполнить итерацию по каждому блоку и выполнить необходимые операции. В примере кода подсчитывается количество значений в каждом блоке и результаты объединяются в итоговую серию.

**Запись данных в текстовый формат**

Данные также могут быть выведены в виде текста с разделителями. Для этого используется метод to_csv фрейма данных pandas. Этот метод позволяет записать данные в файл с определённым разделителем, таким как запятая.

Пример кода показывает, как прочитать данные из CSV-файла, а затем записать их в новый файл с использованием метода to_csv. **Перевод текста на русский язык:**

[46]: данные.to_csv(sys.stdout, sep='|')
|что-то|a|b|c|d|сообщение
0|один|1|2|3.0|4|
1|два|5|6||8|мир
2|три|9|10|11.0|12|foo

В выходных данных отсутствующие значения будут представлены в виде пустой строки. Вы можете захотеть представить их с помощью другого маркера:

In [47]: данные.to_csv(sys.stdout, na_rep='NULL')
,что-то,a,b,c,d,сообщение
0,один,1,2,3.0,4,NULL
1,два,5,6,NULL,8,мир
2,три,9,10,11.0,12,foo

Если не заданы другие параметры, будут выведены метки строк и столбцов. Конечно, их также можно отключить:

In [48]: данные.to_csv(sys.stdout, index=False, header=False)
один,1,2,3.0,4
два,5,6,,8,мир
три,9,10,11.0,12,foo

Кроме того, вы можете вывести только часть столбцов в указанном порядке:

In [49]: данные.to_csv(sys. stdout, index=False, columns=['a', 'b', 'c'])
a,b,c
1,2,3.0
5,6,
9,10,11.0

Series также имеет метод to_csv:

In [50]: даты = pd.date_range('1/1/2000', периоды=7)

In [51]: ts = pd.Series(np.arange(7), индекс=даты)

In [52]: ts.to_csv('examples/tseries.csv')

In [53]: !cat examples/tseries. csv
2000-01-01,0
2000-01-02,1
2000-01-03,2
2000-01-04,3
2000-01-05,4
2000-01-06,5
2000-01-07,6

Обработка разделителей формата

Большинство табличных данных, хранящихся на диске, можно загрузить с помощью pandas.read_table. Однако иногда требуется выполнить некоторую ручную обработку. Проблемы с read_table из-за искажённых файлов не редкость. Чтобы проиллюстрировать эти основные инструменты, рассмотрим этот простой файл CSV:

In [54]: !cat examples/ex7.csv
"a","b","c"
"1","2","3"
"1","2","3"

Для любого односимвольного файла разделителя можно напрямую использовать встроенный модуль Python csv. Передайте любой открытый файл или файловый объект в csv.reader:

import csv
f = open('examples/ex7.csv')
reader = csv.reader(f)

Итерация по этому читателю даст кортеж для каждой строки (с удаленными всеми кавычками):

In [56]: для строки в читателе:
   ....:     печать (строка)
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']

Теперь, чтобы привести данные в требуемый формат, вам нужно выполнить некоторые организационные работы. Мы сделаем это шаг за шагом. Сначала прочитайте файл в список строк:

In [57]: с открытым ('examples/ex7.csv'), как f:
   ....:     строки = список (csv.reader (f))

Затем мы разделим эти строки на заголовок строки и строки данных:

In [58]: заголовок, значения = строки [0], строки [1:]

Затем мы можем использовать словарное понимание и zip(*значения), чтобы создать словарь данных столбца:

In [59]: data_dict = {h: v для h, v в zip (заголовок, zip (*значения))}

In [60]: data_dict
Out[60]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}

CSV файлы бывают разных форм. Просто определите подкласс csv.Dialect, чтобы определить новый формат (например, специальные разделители, соглашения о цитировании строк, символы конца строки и т. д.):

class my_dialect (csv.Dialect):
    lineterminator = '\n'
    разделитель = ';'
    quotechar = '"'
    цитирование = csv.QUOTE_MINIMAL
читатель = csv.reader (f, диалект = my_dialect)

Параметры отдельных CSV-языков также могут быть предоставлены csv.reader в форме ключевых слов без определения подкласса:

читатель = csv. reader (f, разделитель = '|')

Доступные опции (атрибуты csv.Dialect) и их функции показаны в таблице 6-3.

Примечание: Для файлов, использующих сложные разделители или многосимвольные разделители, модуль csv бесполезен. В этом случае вам придётся использовать методы split строк или регулярные выражения re.split для разделения строк и других организационных работ.

Чтобы вручную вывести файл разделителя, вы можете использовать csv.writer. Он принимает открытый и доступный для записи файловый объект, а также те же самые языковые стандарты и параметры форматирования, что и csv.reader:

с открытым ('mydata.csv', 'w') как f:
    писатель = csv. writer (f, диалект = my_dialect)
    writer.writerow (('one', 'two', 'three'))
    writer.writerow (('1', '2', '3'))
    writer.writerow (('4', '5', '6'))
    writer.writerow (('7', '8', '9'))

Данные JSON

JSON (сокращение от JavaScript Object Notation) стал стандартным форматом для отправки данных через HTTP-запросы между веб-браузерами и другими приложениями. Это гораздо более гибкий формат данных, чем табличный текст (например, CSV). Вот пример:

obj = """
{"name": "Wes",
 "places_lived": ["United States", "Spain", "Germany"],
 "pet": null,
 "siblings": [{"name": "Scott", "age": 30, "pets": ["Zeus", "Zuko"]},
              {"name": "Katie", "age": 38,
               "pets": ["Sixes", "Stache", "Cisco"]}]
}
"""

За исключением его нулевых значений и некоторых других незначительных различий (например, в конце списка не допускается наличие лишних запятых), JSON очень похож на действительный код Python. Основные типы включают объекты (словари), массивы (списки), строки, числа, логические значения и нулевые значения. Все ключи объекта должны быть строками. Многие библиотеки Python могут читать и записывать данные JSON. Я буду использовать json, потому что он встроен в стандартную библиотеку Python. Преобразуйте строку JSON в форму Python с помощью json.loads:

In [62]: import json

In [63]: результат = json. loads (obj)

In [64]: результат
Out[64]: 
{'name': 'Wes',
 'pet': None,
 'places_lived': ['United States', 'Spain', 'Germany'],
 'siblings': [{'age': 30, 'name': 'Scott', 'pets': ['Zeus', 'Zuko']},
  {'age': 38, 'name': 'Katie', 'pets': ['Sixes', 'Stache', 'Cisco']}]}

json.dumps преобразует объект Python в формат JSON:

In [65]: asjson = json. dumps (результат)

Как преобразовать (один или несколько) объектов JSON в DataFrame или другую структуру данных, удобную для анализа, зависит от вас. Самый простой способ — передать список словарей (исходные объекты JSON) конструктору DataFrame и выбрать подмножество полей данных:

In [66]: братья и сестры = pd.DataFrame (результат ['братья и сестры'], столбцы = ['имя', 'возраст'])

In [67]: братья и сёстры
Out[67]: 
    имя  возраст
0  Скотт  30
1  Кэти  38 **pandas.read_json** может автоматически преобразовывать особенно форматированные данные JSON в Series или DataFrame.

Например:
```python
In [68]: !cat examples/example.json
[{"a": 1, "b": 2, "c": 3},
 {"a": 4, "b": 5, "c": 6},
 {"a": 7, " "b": 8, "c": 9}]

Параметры по умолчанию pandas.read_json предполагают, что каждый объект в массиве JSON является строкой таблицы:

In [69]: data = pd.read_json('examples/example.json')

In [70]: data
Out[70]: 
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

В главе 7 подробно рассматривается чтение и обработка данных JSON (включая вложенные записи) на примере базы данных USDA Food Database.

Если вам нужно вывести данные из pandas в формате JSON, вы можете использовать метод to_json:

In [71]: print(data.to_json())
{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}

In [72]: print(data.to_json(orient='records'))
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]

XML и HTML: сбор веб-информации

Python имеет множество библиотек для чтения и записи данных в распространённых форматах HTML и XML, включая lxml, Beautiful Soup и html5lib. lxml работает быстрее, но другие библиотеки лучше обрабатывают некорректные файлы HTML или XML.

У pandas есть встроенная функция read_html, которая использует lxml и Beautiful Soup для автоматического анализа таблиц в файлах HTML. Для демонстрации я скачал файл HTML от Федеральной корпорации страхования вкладов США (FDIC), который содержит информацию о банкротствах банков. Сначала необходимо установить библиотеки, используемые read_html:

conda install lxml
pip install beautifulsoup4 html5lib

Если вы используете не conda, можно использовать pip install lxml.

pandas.read_html имеет несколько параметров, и по умолчанию он ищет и пытается анализировать таблицы в тегах

. Результатом является список объектов DataFrame:
In [73]: tables = pd.read_html('examples/fdic_failed_bank_list.html')

In [74]: len(tables)
Out[74]: 1

In [75]: failures = tables[0]

In [76]: failures.head()
Out[76]: 
                      Bank Name             City  ST   CERT  \
0                   Allied Bank         Mulberry  AR     91   
1  The Woodbury Banking Company         Woodbury  GA  11297   
2        First CornerStone Bank  King of Prussia  PA  35312   
3            Trust Company Bank          Memphis  TN   9956   
4    North Milwaukee State Bank        Milwaukee  WI  20364   
                 Acquiring Institution        Closing Date       Updated Date  
0                         Today's Bank  September 23, 2016  November 17, 2016  
1                          United Bank     August 19, 2016  November 17, 2016  
2  First-Citizens Bank & Trust Company         May 6, 2016  September 6, 2016  
3           The Bank of Fayette County      April 29, 2016  September 6, 2016  
4  First-Citizens Bank & Trust Company      March 11, 2016      June 16, 2016

Поскольку в failures много столбцов, pandas добавил символ новой строки .

Здесь мы можем выполнить некоторую очистку и анализ данных (которые будут рассмотрены в следующих главах), например, подсчитать количество обанкротившихся банков по годам:

In [77]: close_timestamps = pd.to_datetime(failures['Closing Date'])

In [78]: close_timestamps.dt.year.value_counts()
Out[78]: 
2010    157
2009    140
2011     92
2012     51
2008     25
       ... 
2004      4
2001      4
2007      3
2003      3
2000      2
Name: Closing Date, Length: 15, dtype: int64

Использование lxml.objectify для анализа XML

XML (Extensible Markup Language) — это ещё один распространённый формат структурированных данных, поддерживающий иерархические данные и метаданные. Файлы, которые используются в этой книге, фактически взяты из большого XML-документа.

Ранее я представил функцию pandas.read_html, использующую lxml или Beautiful Soup для анализа данных из HTML. Структуры XML и HTML похожи, но XML более универсален. Здесь я покажу пример того, как использовать lxml для анализа данных из XML.

Управление транспорта Нью-Йорка (MTA) публикует некоторые данные о своих услугах общественного транспорта и поездов (http://www.mta.info/developers/download.html). Здесь мы рассмотрим данные о работе, содержащиеся в наборе файлов XML (например, файл Metro-North Railroad — Performance_MNR.xml), где каждая запись XML представляет собой месячный набор данных:

<INDICATOR>
  <INDICATOR_SEQ>373889</INDICATOR_SEQ>
  <PARENT_SEQ></PARENT_SEQ>
  <AGENCY_NAME>Metro-North Railroad</AGENCY_NAME>
  <INDICATOR_NAME>Escalator Availability</INDICATOR_NAME>
  <DESCRIPTION>Percent of the time that escalators are operational
  systemwide. The availability rate is based on physical observations performed
  the morning of regular business days only. This is a new indicator the agency
  began reporting in 2009.</DESCRIPTION>
  <PERIOD_YEAR>2011</PERIOD_YEAR>
  <PERIOD_MONTH>12</PERIOD_MONTH>
  <CATEGORY>Service Indicators</CATEGORY>
  <FREQUENCY>M</FREQUENCY>
  <DESIRED_CHANGE>U</DESIRED_CHANGE>
  <INDICATOR_UNIT>%</INDICATOR_UNIT>
  <DECIMAL_PLACES>1</DECIMAL_PLACES>
  <YTD_TARGET>97.00</YTD_TARGET>
  <YTD_ACTUAL></YTD_ACTUAL>
  <MONTHLY_TARGET>97.00</MONTHLY_TARGET>
  <MONTHLY_ACTUAL></MONTHLY_ACTUAL>
</INDICATOR>

Сначала мы используем lxml.objectify для разбора файла, а затем получаем ссылку на корневой узел этого XML-файла с помощью getroot:

from lxml import objectify

path = 'datasets/mta_perf/Performance_MNR.xml'
parsed = objectify.parse(open(path))
root = parsed.getroot()
``` ```
[('Atlanta', 'Georgia', 1.25, 6),
 ('Tallahassee', 'Florida', 2.6, 3),
 ('Sacramento', 'California', 1.7, 5)]

В [127]: stmt = "INSERT INTO test VALUES(?, ?, ?, ?)"

В [128]: con.executemany(stmt, data) Out[128]: <sqlite3.Cursor at 0x7f6b15c66ce0>

При выборе данных из таблицы большинство драйверов Python SQL (PyODBC, psycopg2, MySQLdb, pymssql и др.) возвращают список кортежей:

В [130]: cursor = con.execute('select * from test')

В [131]: rows = cursor.fetchall()

В [132]: rows
Out[132]: 
[('Atlanta', 'Georgia', 1.25, 6),
 ('Tallahassee', 'Florida', 2.6, 3),
 ('Sacramento', 'California', 1.7, 5)]

Этот список кортежей можно передать конструктору DataFrame, но также необходимо указать имена столбцов (находятся в свойстве description курсора):

В [133]: cursor.description
Out[133]: 
(('a', None, None, None, None, None, None),
 ('b', None, None, None, None, None, None),
 ('c', None, None, None, None, None, None),
 ('d', None, None, None, None, None, None))

В [134]: pd.DataFrame(rows, columns=[x[0] for x in cursor.description])
Out[134]: 
             a           b     c  d
0      Atlanta     Georgia  1.25  6
1  Tallahassee     Florida  2.60  3
2   Sacramento  California  1.70  5

Подобные операции с данными встречаются довольно часто, и вы наверняка не хотите каждый раз заново переписывать код при обращении к базе данных. Проект SQLAlchemy — популярный инструмент для работы с SQL в Python, который абстрагирует многие распространённые различия между базами данных SQL. В pandas есть функция read_sql, которая позволяет легко считывать данные из соединения SQLAlchemy. Здесь мы используем SQLAlchemy для подключения к SQLite и считываем данные из ранее созданной таблицы:

В [135]: import sqlalchemy as sqla

В [136]: db = sqla.create_engine('sqlite:///mydata.sqlite')

В [137]: pd.read_sql('select * from test', db)
Out[137]: 
             a           b     c  d
0      Atlanta     Georgia  1.25  6
1  Tallahassee     Florida  2.60  3
2   Sacramento  California  1.70  5

6.5 Заключение

Доступ к данным обычно является первым шагом в анализе данных. В этой главе мы изучили некоторые полезные инструменты. В следующих главах мы более подробно рассмотрим такие темы, как очистка данных, визуализация данных, анализ временных рядов и другие.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/it-ebooks-pyda-2e-zh.git
git@api.gitlife.ru:oschina-mirror/it-ebooks-pyda-2e-zh.git
oschina-mirror
it-ebooks-pyda-2e-zh
it-ebooks-pyda-2e-zh
master