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

четверг, 23 января 2014 г.

Постановка задачи "Парсинг URL в csv таблице" (итерация 1 продолжение)

В предыдущем посте мы определились с тем, как мы будем преобразовывать CSV таблицы на первом этапе, сначала во всех файлах мы приклеиваем (слева) дополнительные вспомогательные столбцы, а потом собираем все строки в один общий файл, где несколько тысяч строк.
В итоговом файле в строках последнего столбца содержатся URL вот такого вида:
In []:
"http://nissan.ru/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/"
Нам нужно будет:
1) преобразовать каждую строку в список,
2) выбрать из списка некоторые элементы и сформировать из них новый список строк
3) предусмотреть возможность замены некоторых эелементов, например, заменить элемент строки "1_6_2wd_mt" на "1.62WD mt"
4) преобразовать строку заголовка (добавить названия столбцов, например "комплектация")

1) преобразовать каждую строку в список

Пример выше не очень удачный. В нем строка почти идеальная. Для типичной строки php есть библиотека, которая выделяет базовый url и параметры запроса. Она предназначена для тонкой работы со строками, вот пример из репозитория библиотеки на GitHub
In [1]:
from furl import furl #url parser
In [8]:
f0 = furl('http://www.google.com/?one=1&two=2')
f0.args['three'] = '3'
del f0.args['one']
f0.url
#'http://www.google.com/?two=2&three=3'
Out[8]:
'http://www.google.com/?two=2&three=3'
Обратите внимание, из строки запроса (все, что после '?') образуется словарь аргументов...
In [3]:
dir(f)
Out[3]:
['DEFAULT_PORTS',
 '__abstractmethods__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__eq__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__metaclass__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_abc_cache',
 '_abc_negative_cache',
 '_abc_negative_cache_version',
 '_abc_registry',
 '_force_absolute',
 '_fragment',
 '_host',
 '_path',
 '_port',
 '_query',
 '_scheme',
 'add',
 'args',
 'copy',
 'fragment',
 'fragmentstr',
 'host',
 'join',
 'load',
 'netloc',
 'password',
 'path',
 'pathstr',
 'port',
 'query',
 'querystr',
 'remove',
 'scheme',
 'set',
 'strict',
 'url',
 'username']
In [8]:
f.__doc__
Out[8]:
"\n    Object for simple parsing and manipulation of a URL and its\n    components.\n\n      scheme://username:password@host:port/path?query#fragment\n\n    Attributes:\n      DEFAULT_PORTS: Map of various URL schemes to their default\n        ports. Scheme strings are lowercase.\n      strict: Boolean whether or not UserWarnings should be raised if\n        improperly encoded path, query, or fragment strings are provided\n        to methods that take such strings, like load(), add(), set(),\n        remove(), etc.\n      username: Username string for authentication. Initially None.\n      password: Password string for authentication with\n        <username>. Initially None.\n      scheme: URL scheme. A string ('http', 'https', '', etc) or None.\n        All lowercase. Initially None.\n      host: URL host (domain, IPv4 address, or IPv6 address), not\n        including port. All lowercase. Initially None.\n      port: Port. Valid port values are 1-65535, or None meaning no port\n        specified.\n      netloc: Network location. Combined host and port string. Initially\n      None.\n      path: Path object from URLPathCompositionInterface.\n      query: Query object from QueryCompositionInterface.\n      fragment: Fragment object from FragmentCompositionInterface.\n    "
In []:
Для нашего простого случая может применен метод f.pathstr ...Здесь есть и другие методы, вот примеры:
In [2]:
base_url="http://nissan.ru/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/"
f = furl(base_url)
f
Out[2]:
furl('http://nissan.ru/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/')
In [6]:
f.fragmentstr, f.host,f.path,f.pathstr,f.args,f.scheme
Out[6]:
('',
 'nissan.ru',
 Path('/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/'),
 '/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/',
 omdict1D([]),
 'http')
In [17]:
f.pathstr.split('/')
Out[17]:
['', 'catg', 'nissan', 'qashqai', '2956', '1_6_2wd_mt', 'xe', '']
In [19]:
print f.pathstr.split('/')[2]+' - Это третий элемент под номером 2 '
nissan - Это третий элемент под номером 2 

Ну и вот, мы сначала получили строку после названия хоста, а потом использователи обычный разделитель строки. Можно было бы обойтись одним разделителем:
In [27]:
base_url.split('/')
Out[27]:
['http:',
 '',
 'nissan.ru',
 'catg',
 'nissan',
 'qashqai',
 '2956',
 '1_6_2wd_mt',
 'xe',
 '']
In [28]:
base_url.split('//')[1].split('/')
Out[28]:
['nissan.ru', 'catg', 'nissan', 'qashqai', '2956', '1_6_2wd_mt', 'xe', '']
Можно использовать вот такую конструкцию (сначала разделитель "//" делит строку на две, мы берем вторую строку и ее разбиваем на подстроки с помощью разделителя "/" Обратите внимание, разделителем можно назначить например "catg"
In [29]:
base_url.split('catg')[1].split('/')
Out[29]:
['', 'nissan', 'qashqai', '2956', '1_6_2wd_mt', 'xe', '']
Данный подход неэстетичен (пустые строки в начале, конце, в середине...), но должен работать, если при формировании выборки url правильно задан фильтр.
На этом и остановимся. Надо будет выбирать фильтры для офрмирования url стольцов в загружаемых CSV файлах так, чтобы начало строк было одинаковым для все строк с столбце.

2) выбрать из списка некоторые элементы и сформировать из них новый список строк

Начнем с вопроса о том, какой длины должен быть новый список? Чтобы ответить на этот вопрос нам нужно найти самую длиную строку (во всех файлах) - она даст максимальное количество элементов нового списка.
Однако, в дальнейшем (при анализе данных таблицы) нам все равно нужно будет понять, что представляет из себя каждый столбец (элемент строки). Но самое главное, нам надо быть уверенными в том, что ...например элемент '1_6_2wd_mt' после парсинга всех строк файла окажется только в восьмом столбце.
Очевидно, что это зависит не только от фильтра, который мы задаем при формировании выборки (файла), но и от того, как формируются адреса страниц на сайте.
Пока я не могу придумать ничего лучше, чем тщательный визуальный анализ сайтов и выборок.
Более легкий вопрос о количестве столбцов. Сколько их должно быть? Очевидно, что (пока) нет смысла автоматизировать этот процесс, можно задать их количество с небольшим запасом... а потом найти строчку с максимальной длиной в итерациях парсинга URL...
Ясно, что таблица должна быть прямоугольной, а все пустые поля в строках должны быть заполнены строчкой 'Null'. Пока оставим размышления о количестве столбцов и займемся вопросом о том, куда и как нам записывать новые сформированные столбцы. Поскольку мы в цикле..., вернее в итераторе, перебираем строчки файла и парсим элементы URL (последнего столбца)... Что делать с промежуточным результатом? Можно построчно записать в текстовый файл, а можно в список строк... Но можно ли эти строки потом записать в файл? Можно... В предыдущем посте мы нашли способ. А есть еще какие-то временные файлы... Это, если список строк окажется слишком большим...
В предыдущем посте Постановка задачи "Процессинг csv таблиц" (итерация 1) мы рассмотрели команды для считывания текстовых строк и CSV файлов. А выше поробовали парсить текстовые строки. Теперь вспомним, схему из предыдущего поста:
In []:
1. Открываем файл для чтения --------------------------> парсим имя файла => список параметров имени fname[]
 \                                                    \   
  считываем строку  => список элементов rawstr[]       \              ---> формируем список строк rstr[]
   \                                                    \             \    \
    "чистим строку" -> rawstr[i]                         \__________   \     добавляем все строки в новый CSV файл
     \                                                              \  / 
      парсим некоторые элементы строки -> rawstr[i] = rawstri[] ---> собираем новую строку --- записываем строку в новый CSV файл 
По сути, нам осталось расмотреть только последнюю цепочку (начинается со слов "парсим некоторые элементы строки -->" ). Причем, здравый смысл (но не орпыт) подсказывает, что не надо записыват строкив новый файл по одной, а лучше сформировать сначала список, а потом добавить сразу все строки в новый CSV файл. Но надо бы сделать и опробовать оба варианта...
Вот пример из прошого поста. Здесь мы перебираем строки из последнего столбца:
In []:
View, percent, page
13221, 39,38, http://nissan.ru/catg/nissan/
2464, 7,34, http://nissan.ru/catg/nissan/qashqai/
2057, 6,13, http://nissan.ru/catg/nissan/x-trail/
115, 0,34, http://nissan.ru/catg/nissan/qashqai/2956/1_6_2wd_mt/xe/
И формируем из них вот такой (например)список строк:
In []:
listOfLines ['"nissan","NULL","NULL","NULL"',
             '"nissan","qashqai","NULL","NULL"',
             '"nissan","x-trail","NULL","NULL"',
             '"nissan","qashqai","2956,1_6_2wd_mt","xe"', ]
Альтернатива - список списков
In []:
listOfLines [["nissan","NULL","NULL","NULL"],
             ["nissan","qashqai","NULL","NULL"],
             ["nissan","x-trail","NULL","NULL"],
             ["nissan","qashqai","2956,1_6_2wd_mt","xe"], ]
Со списком строк все просто, мы уже попробовали ( в предыдущем посте) встроенную функцию file.readlines(). Надо только будет обеспечить конроль количесвта элементов в строке(запятых и кавычек) перед записью в файл...
В идеале получается очень простой алгоритм: мы перебираем исходные строки, формируем из них новые..., каждую новую строку добавляем в конец списка. Потом весь список одним махом пишем в новый файл, потом в новый файл добавляем столбцы из старого (количество строк должно быть одинаковым).

Далее попробуем найти наиболее простой способ склейки файлов...

Во первых, мы забыли про такую простую задачку, как склейка строк из нескольких десяктов таблиц... И если в каждой таблице есть строка-заголовок. Добавим к предыдущему посту пример how to merge 200 csv files in Python
In []:
fout=open("result_1_2","a")
# first file:
for line in open("result_1"):
    fout.write(line)
# now the rest:    
for num in range(2,201):
    f = open("sh"+str(num)+".csv")
    f.next() # skip the header
    for line in f:
         fout.write(line)
    f.close() # not really needed
fout.close()
In []:
В этом примере мы использовали два файла
In [13]:
import pandas
In [22]:
result_1 = pandas.read_csv('result_1.txt')
In [23]:
result_1
Out[23]:
View percent page
13221 39 38 http://nissan.ru/catg/nissan/
2464 7 34 http://nissan.ru/catg/nissan/qashqai/
NaN NaN NaN NaN
In [21]:
result_2 = pandas.read_csv('result_2.txt')
result_2
Out[21]:
View percent page
114 0 34 http://nissan.ru/catg/nissan/maxima/
90 0 27 http://nissan.ru/catg/nissan/gt-r/2678/3_8_55...
Пример полезный, но доисторический, здесь почему-то файлы в папке перебираются по именам. А имена формируются в примере. В предыдущем посте мы договорились, что сначала создадим список полных имен файлов (посhедством скавозного поиска в подпапках)...
Так что время на разбор примера тратить не хочется, а оставляю я его здесь, из-а строчки for num in range(2,201)

И в заключение о склейке столбцов.

Все той же командой file.readlines() считывае файл построчно. Потом в итераторе по строкам осуществляем конкатенцию с заранее подготовленным списком строк (см. пример "listOfLines" выше)

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

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