Поиск по блогу

среда, 29 января 2014 г.

Код для формирования имени загруженного файла CSV

Предположим, что мы скачиваем CSV файл. Нам нужно определить папку (путь) для записи и как-то назвать этот файл. Ранее мы подготовили соглашение о наименованиях (далее по тексту "СН") и собираемся ему следовать. Здесь мы проверим код для соответствующих функций на языке Python.
Все было бы просто, если бы не наше решение о формировании имен файлов и использовании шаблонов URL. Однако, для хранения большого количества файлов - это необходимость.
Необходимы также и служебные файлы в некоторых папках (.info .log), но эту часть СН мы обсудим в других постах.
В предыдущем посте Формируем список URL для скачивания файлов CSV со счетчика мы использовали шаблон строки URL для выбора скачиваемых файлов.
Здесь мы будем исходит из того, что скачивание (по списку файлов) происходит асинхронно (т.е. порядок скачивания из списка может нарушатся). В этом случае нам нужно будет придерживаться следующей последовательности действий:
  1. Сначала нам нужно будет получить URL скачанного файла.
  2. Распарсить URL
  3. Каждой части URL поставить в соответсвие подстроку нового имени
  4. Сцепить все подстроки нового имени в соответствии с СН
  5. Задать полный путь для сохранения нового файла.
  6. Внести запись в соответствующий лог-файл.
Предположим, что скачиваем файлы с помощью библиотеки Grab на GitHub
Для справки нам может понадобиться примеры библиотеки furl Примеры и описание из репозитория библиотеки furl на GitHub Ниже варианты импорта библиотек:
In []:
from grab.spider import Spider, Task
# from grab import Grab # do not need if we use import Spider
In [1]:
# Добавимв импорт Selector from grab.selector import Selector
from furl import furl #url parser

1. URL скачанного файл

In []:
# create furl object from grab response url
fr= furl(grab.response.url)
In [2]:
# we use simple string instead previous grab.response.url
sample_url='http://top.mail.ru/pages.csv?id=85837&period=2&date=2013-12-17&sf=0&pp=20&filter_type=0&filter=catalogue/nissan&gender=0&agegroup=0&'
fr= furl(sample_url)
Здесь постараемся обойтись без библиотеки grab, а потому просто зададим пример строки sample_url из предыдущего поста. Посмотрим, как бибилиотека furl разберет эту строку. Ранее мы уже это делали (Постановка задачи "Парсинг URL в csv таблице" (итерация 1 продолжение)), но здесь поробуем уточнить все детали:

2. Распарсить URL

In [5]:
help(furl)
Help on class furl in module furl.furl:

class furl(URLPathCompositionInterface, QueryCompositionInterface, FragmentCompositionInterface)
 |  Object for simple parsing and manipulation of a URL and its
 |  components.
 |  
 |    scheme://username:password@host:port/path?query#fragment
 |  
 |  Attributes:
 |    DEFAULT_PORTS: Map of various URL schemes to their default
 |      ports. Scheme strings are lowercase.
 |    strict: Boolean whether or not UserWarnings should be raised if
 |      improperly encoded path, query, or fragment strings are provided
 |      to methods that take such strings, like load(), add(), set(),
 |      remove(), etc.
 |    username: Username string for authentication. Initially None.
 |    password: Password string for authentication with
 |      <username>. Initially None.
 |    scheme: URL scheme. A string ('http', 'https', '', etc) or None.
 |      All lowercase. Initially None.
 |    host: URL host (domain, IPv4 address, or IPv6 address), not
 |      including port. All lowercase. Initially None.
 |    port: Port. Valid port values are 1-65535, or None meaning no port
 |      specified.
 |    netloc: Network location. Combined host and port string. Initially
 |    None.
 |    path: Path object from URLPathCompositionInterface.
 |    query: Query object from QueryCompositionInterface.
 |    fragment: Fragment object from FragmentCompositionInterface.
 |  
 |  Method resolution order:
 |      furl
 |      URLPathCompositionInterface
 |      PathCompositionInterface
 |      QueryCompositionInterface
 |      FragmentCompositionInterface
 |      __builtin__.object
 |  
 |  Methods defined here:
 |  
 |  __eq__(self, other)
 |  
 |  __init__(self, url='', strict=False)
 |      Raises: ValueError on invalid url.
 |  
 |  __repr__(self)
 |  
 |  __setattr__(self, attr, value)
 |  
 |  __str__(self)
 |  
 |  add(self, args=<object object>, path=<object object>, fragment_path=<object object>, fragment_args=<object object>, query_params=<object object>)
 |      Add components to a URL and return this furl instance, <self>.
 |      
 |      If both <args> and <query_params> are provided, a UserWarning is
 |      raised because <args> is provided as a shortcut for
 |      <query_params>, not to be used simultaneously with
 |      <query_params>. Nonetheless, providing both <args> and
 |      <query_params> behaves as expected, with query keys and values
 |      from both <args> and <query_params> added to the query - <args>
 |      first, then <query_params>.
 |      
 |      Parameters:
 |        args: Shortcut for <query_params>.
 |        path: A list of path segments to add to the existing path
 |          segments, or a path string to join with the existing path
 |          string.
 |        query_params: A dictionary of query keys and values or list of
 |          key:value items to add to the query.
 |        fragment_path: A list of path segments to add to the existing
 |          fragment path segments, or a path string to join with the
 |          existing fragment path string.
 |        fragment_args: A dictionary of query keys and values or list
 |          of key:value items to add to the fragment's query.
 |      
 |      Returns: <self>.
 |      
 |      Raises: UserWarning if redundant and possibly conflicting <args> and
 |      <query_params> were provided.
 |  
 |  copy(self)
 |  
 |  join(self, url)
 |  
 |  load(self, url)
 |      Parse and load a URL.
 |      
 |      Raises: ValueError on invalid URL (for example malformed IPv6
 |      address or invalid port).
 |  
 |  remove(self, args=<object object>, path=<object object>, fragment=<object object>, query=<object object>, query_params=<object object>, port=False, fragment_path=<object object>, fragment_args=<object object>, username=False, password=False)
 |      Remove components of this furl's URL and return this furl
 |      instance, <self>.
 |      
 |      Parameters:
 |        args: Shortcut for query_params.
 |        path: A list of path segments to remove from the end of the
 |          existing path segments list, or a path string to remove from
 |          the end of the existing path string, or True to remove the
 |          path entirely.
 |        query: If True, remove the query portion of the URL entirely.
 |        query_params: A list of query keys to remove from the query,
 |          if they exist.
 |        port: If True, remove the port from the network location
 |          string, if it exists.
 |        fragment: If True, remove the fragment portion of the URL
 |          entirely.
 |        fragment_path: A list of path segments to remove from the end
 |          of the fragment's path segments or a path string to remove
 |          from the end of the fragment's path string.
 |        fragment_args: A list of query keys to remove from the
 |          fragment's query, if they exist.
 |        username: If True, remove the username, if it exists.
 |        password: If True, remove the password, if it exists.
 |      Returns: <self>.
 |  
 |  set(self, args=<object object>, path=<object object>, fragment=<object object>, scheme=<object object>, netloc=<object object>, fragment_path=<object object>, fragment_args=<object object>, fragment_separator=<object object>, host=<object object>, port=<object object>, query=<object object>, query_params=<object object>, username=<object object>, password=<object object>)
 |      Set components of a url and return this furl instance, <self>.
 |      
 |      If any overlapping, and hence possibly conflicting, parameters
 |      are provided, appropriate UserWarning's will be raised. The
 |      groups of parameters that could potentially overlap are
 |      
 |        <netloc> and (<host> or <port>)
 |        <fragment> and (<fragment_path> and/or <fragment_args>)
 |        any two or all of <query>, <args>, and/or <query_params>
 |      
 |      In all of the above groups, the latter parameter(s) take
 |      precedence over the earlier parameter(s). So, for example
 |      
 |        furl('http://google.com/').set(
 |          netloc='yahoo.com:99', host='bing.com', port=40)
 |      
 |      will result in a UserWarning being raised and the url becoming
 |      
 |        'http://bing.com:40/'
 |      
 |      not
 |      
 |        'http://yahoo.com:99/
 |      
 |      Parameters:
 |        args: Shortcut for <query_params>.
 |        path: A list of path segments or a path string to adopt.
 |        fragment: Fragment string to adopt.
 |        scheme: Scheme string to adopt.
 |        netloc: Network location string to adopt.
 |        query: Query string to adopt.
 |        query_params: A dictionary of query keys and values or list of
 |          key:value items to adopt.
 |        fragment_path: A list of path segments to adopt for the
 |          fragment's path or a path string to adopt as the fragment's
 |          path.
 |        fragment_args: A dictionary of query keys and values or list
 |          of key:value items for the fragment's query to adopt.
 |        fragment_separator: Boolean whether or not there should be a
 |          '?' separator between the fragment path and fragment query.
 |        host: Host string to adopt.
 |        port: Port number to adopt.
 |        username: Username string to adopt.
 |        password: Password string to adopt.
 |      Raises:
 |        ValueError on invalid port.
 |        UserWarning if <netloc> and (<host> and/or <port>) are
 |          provided.
 |        UserWarning if <query>, <args>, and/or <query_params> are
 |          provided.
 |        UserWarning if <fragment> and (<fragment_path>,
 |          <fragment_args>, and/or <fragment_separator>) are provided.
 |      Returns: <self>.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  host
 |  
 |  netloc
 |  
 |  port
 |  
 |  scheme
 |  
 |  url
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  DEFAULT_PORTS = {'ftp': 21, 'http': 80, 'https': 443, 'ssh': 22}
 |  
 |  __abstractmethods__ = frozenset([])
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes inherited from URLPathCompositionInterface:
 |  
 |  __metaclass__ = <class 'abc.ABCMeta'>
 |      Metaclass for defining Abstract Base Classes (ABCs).
 |      
 |      Use this metaclass to create an ABC.  An ABC can be subclassed
 |      directly, and then acts as a mix-in class.  You can also register
 |      unrelated concrete classes (even built-in classes) and unrelated
 |      ABCs as 'virtual subclasses' -- these and their descendants will
 |      be considered subclasses of the registering ABC by the built-in
 |      issubclass() function, but the registering ABC won't show up in
 |      their MRO (Method Resolution Order) nor will method
 |      implementations defined by the registering ABC be callable (not
 |      even via super()).
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from PathCompositionInterface:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  path
 |  
 |  pathstr
 |      This method is deprecated. Use str(furl.path) instead.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from QueryCompositionInterface:
 |  
 |  args
 |      Shortcut method to access the query parameters, self._query.params.
 |  
 |  query
 |  
 |  querystr
 |      This method is deprecated. Use str(furl.query) instead.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from FragmentCompositionInterface:
 |  
 |  fragment
 |  
 |  fragmentstr
 |      This method is deprecated. Use str(furl.fragment) instead.


In [6]:
# scheme://username:password@host:port/path?query#fragment
fr.scheme, fr.path, 
Out[6]:
('http', Path('/pages.csv'))
In [12]:
sample_url
Out[12]:
'http://top.mail.ru/pages.csv?id=85837&period=2&date=2013-12-17&sf=0&pp=20&filter_type=0&filter=catalogue/nissan&gender=0&agegroup=0&'
In [14]:
fr.host, fr.path, fr.pathstr
Out[14]:
('top.mail.ru', Path('/pages.csv'), '/pages.csv')
In [11]:
fr.args['id'], fr.args['period'], fr.args['date'], fr.args['filter'],  fr.args['gender'], fr.args['agegroup'],
Out[11]:
('85837', '2', '2013-12-17', 'catalogue/nissan', '0', '0')

3. Каждой части URL поставить в соответсвие подстроку нового имени

В этом посте мы определили первоначальный шаблон строки имени Как создать структуру папок. Скрипты и правила (соглашения о наименованиях)b Скопируем оттуда предполагаемое имя файла:
In []:
# Шаблон для имен файлов
_f_catlogue-nissan_y_2013_m_1-2-3-4_g_m-w_a_18-30-45-60_cm_comment_

# _f_  строка фильтра с заменой "/" на "_"
# _y_  год
# _m_  список месяцев через "-"
# _g_  пол  список литералов-строк (m,w)
# _a_  возраст - цифры обозначают верхнюю границу интервала
# _cm_ комментарий - дополнительное поле 

# для парсинга подстроки _m_ используется конструкция  _m_*_
# длина переменной может изменятся
# порядок переменных важен не всегда
Выглядит довольно уныло... Напомню, что это строка имени для СУММАРНОГО файла, а мы здесь рассмтриваем первичные файлы. Сейчас надо определить, как именовать сохраняемые файлы.
1. Мы можем просто заменить в строках слеши (/)на (-) дефисы.
2. Или привести файлы к единому виду, который даст преимущества при последующе обработке.
In [25]:
su=sam ple_url.split('//')
su=su[1].replace('/','-')
print su
top.mail.ru-pages.csv?id=85837&period=2&date=2013-12-17&sf=0&pp=20&filter_type=0&filter=catalogue-nissan&gender=0&agegroup=0&

При самой простой замене нам все равно нужно посмотреть, что там за символы в строке, как их заменит (windows это любит) операционная система. Вот наверху пример, нам здесь пришлось убирать 'http://', но все равно имя файла содержит непривычные символы.
И самое главное, мы ведь будем работать с разными сайтами (счетчиками), потому все приводить к единому формату очень заманчиво... Попробуем!

4. Сцепить все подстроки нового имени в соответствии с СН

Соглашение о наименовании файлов - строка шаблона. По сути вопрос в том, как преобразовать подстроки из URL в подстроки шаблона. Для этого нужны будут (среди прочего) и замены одних подстрок на другие. Потому начнем со словарей.
In []:
# Напишем код для преобразования в этот Шаблон для имен файлов
_f_catlogue-nissan_y_2013_m_1-2-3-4_g_m-w_a_18-30-45-60_cm_comment_
In [62]:
# Define Dictionaries
agegd = {"0":"All", "1":"до 12","2":"12-18","3":"19-24","4":"25-30","5":"31-35",
             "6":"36-40","7":"41-45","8":"46-50","9":"over 50 "} 
genderd = {"0":"All", "1":"Male","2":"Female"} 
Наконец-то пришло время использовать словари. Как "перевести" символ возраста из "0" в "all"? Мы используем вот такую конструкцию:
In [66]:
# Словарь позволяет заменить Значение "0" из строки URL на его расшифровку
fr.args['agegroup'], agegd[fr.args['agegroup']]
Out[66]:
('0', 'All')
In [63]:
# Replace and convert
_f_=fr.args['filter'].replace('/','-')# catalogue-nissan
_y_=fr.args['date'].split('-')[0] #2013 -12-17
_m_=fr.args['date'].split('-')[1] #2013 -12- 17
_g_=genderd[fr.args['gender']] # gd = {'0':'all','1':'m','2':'w'}
_a_=agegd[fr.args['agegroup']]
_cm_= 'test'
In [64]:
fname='_f_'+_f_+'_y_'+_y_+'_m_'+_m_+'_g_'+_g_+'_a_'+_a_+'_cm_'+_cm_+'_'
fname
Out[64]:
'_f_catalogue-nissan_y_2013_m_12_g_All_a_All_cm_test_'
Как редактировать и проверять преобразования? Нам нужно иметь под рукой
1. значения aргументов URL строки (см. In [11])
2. шаблон имен файлов
3. словари (In [62])
Однако, этого не достаточно. Нужны еще тесты для каждой строчки (In [63]). И, конечно, нужен перехват ошибок в "боевом модуле"...

5. Задать полный путь для сохранения нового файла. 6. Внести запись в соответствующий лог-файл

Очевидно, что рассмотренные здесь процессы следует отнести к одной функции формирования строки. Полный путь мы будем задавать в другой (родительской) функции записи файлов. Об этом в следующем посте.
А запись строки в лог-файл - это еще одна отдельная функция (или класс), и предмет отдельного поста. Вот только пока не знаю, как скоро он опявится.

В заключение немного порассуждаем о настройках и ошибках.

Пока я настраивал файл пришлось скроллить эту страницу "вверх-вниз", бороться с синтаксическими ошибками... Но шаблон строки URL я запомнил, он у меня перед глазами третий день. Полагаю, что через пару месяцев я этот шаблон с удовольствием забуду. В случае сбоя, который произойдет через эти два месяца из-за того, что где-нибудь в URL изменят слеш или дефис, мне нужно будет быстро вспомнить шаблон строки и локализовать ошибку. Может быть сразу (заранее) сделать скрипт для отладки в Notebook?


Посты чуть ниже также могут вас заинтересовать

Комментариев нет:

Отправить комментарий