понедельник, 21 июня 2010 г.

Парсинг html таблиц

Есть задача: извлечь данные из таблицы на некотором сайте.

Есть следующее решение.

libxml2dom

Скачиваем библиотеку libxml2dom.

Распаковываем архив, ищем файл setup.py, и устанавливаем командой sudo python setup.py install (конечно если у вас стоит Ubuntu ;) )

Далее создаем файл table_parser.py, со следующим содержимым:

# -*- coding: utf-8 -*-

#Извлечение данных из заданных столбцов html-таблицы

#Данные: html-исходник любого сайта
#Аргументы: список заголовков или номера столбцов (начиная с нулевого)
#Результат: список данных по рядам

import libxml2dom

def parse_tables(source, headers, table_index):
    """headers может быть списком строк, если таблица содержит заголовки или
       headers может быть списком целых чисел, если заголовки не заданы.

       Этот метод возвращает вложенные списки.
    """

    #Determine if the headers list is strings or ints and make sure they
    #are all the same type
    j = 0

    #print 'Printing headers: ',headers

    #route to the correct function
    #if the header type is int
    if type(headers[0]) == type(1):

        #run no_header function
        return no_header(source, headers, table_index)

    #if the header type is string
    elif type(headers[0]) == type('a'):

        #run the header_given function
        return header_given(source, headers, table_index)

    else:
        #return none if the headers aren't correct
        return None


#This function takes in the source code of the whole page a string list of
#headers and the index number of the table on the page. It returns a list of
#lists with the scraped information
def header_given(source, headers, table_index):
    #initiate a list to hole the return list
    return_list = []

    #initiate a list to hold the index numbers of the data in the rows
    header_index = []

    #get a document object out of the source code
    doc = libxml2dom.parseString(source,html=1)

    #get the tables from the document
    tables = doc.getElementsByTagName('table')

    try:
        #try to get focue on the desired table
        main_table = tables[table_index]
    except:
        #if the table doesn't exits then return an error
        return ['The table index was not found']

    #get a list of headers in the table
    table_headers = main_table.getElementsByTagName('th')


    #need a sentry value for the header loop
    loop_sentry = 0

    #loop through each header looking for matches
    for header in table_headers:

        #if the header is in the desired headers list
        if header.textContent in headers:

            #add it to the header_index
            header_index.append(loop_sentry)

        #add one to the loop_sentry
        loop_sentry+=1

    #get the rows from the table
    rows = main_table.getElementsByTagName('tr')

    #sentry value detecting if the first row is being viewed
    row_sentry = 0

    #loop through the rows in the table, skipping the first row
    for row in rows:

        #if row_sentry is 0 this is our first row
        if row_sentry == 0:

            #make the row_sentry not 0
            row_sentry = 1337
            continue
        #get all cells from the current row
        cells = row.getElementsByTagName('td')

        #initiate a list to append into the return_list
        cell_list = []

        #iterate through all of the header index's
        for i in header_index:

            #append the cells text content to the cell_list
            cell_list.append(cells[i].textContent)

        #append the cell_list to the return_list
        return_list.append(cell_list)

    #return the return_list
    return return_list

#This function takes in the source code of the whole page an int list of
#headers indicating the index number of the needed item and the index number
#of the table on the page. It returns a list of lists with the scraped info
def no_header(source, headers, table_index):

    #initiate a list to hold the return list
    return_list = []

    #get a document object out of the source code
    doc = libxml2dom.parseString(source, html=1)

    #get the tables from document
    tables = doc.getElementsByTagName('table')

    try:
        #Try to get focus on the desired table
        main_table = tables[table_index]
    except:
        #if the table doesn't exits then return an error
        return ['The table index was not found']

    #get all of the rows out of the main_table
    rows = main_table.getElementsByTagName('tr')

    #loop through each row
    for row in rows:

        #get all cells from the current row
        cells = row.getElementsByTagName('td')

        #initiate a list to append into the return_list
        cell_list = []

        #loop through the list of desired headers
        for i in headers:
            try:
                #try to add text from the cell into the cell_list
                cell_list.append(cells[i].textContent)
            except:
                #if there is an error usually an index error just continue
                continue
        #append the data scraped into the return_list
        return_list.append(cell_list)

    #return the return list
    return return_list



Чтобы воспользоваться этими функциями, можно использовать импорт:

from parse_tables import *

Непосредственно для извлечения данных можно использовать следующую команду python:

parse_tables(html_string, [0,1], 0)

где html_string - это строка, куда записан исходный код сайта с таблицей, [0,1] - номера колонок с данными для извлечения и третий аргумент 0 - это порядковый номер таблицы на сайте.

По материалам http://www.dreamincode.net

четверг, 17 июня 2010 г.

Как конвертировать xls в csv?

xls2csv и xls2tsv в одном флаконе ;)


Вот простая функция, которая конвертирует файл Excel в CSV:

def xls2csv(xls_file,csv_file,input_encoding,output_encoding,separator):
  data=[]
  for sheet_name, values in parse_xls(xls_file, input_encoding): # parse_xls(arg) -- default encoding
       matrix = [[]]
       for row_idx, col_idx in sorted(values.keys()):
    v = values[(row_idx, col_idx)]
    if isinstance(v, unicode):
        v = v.encode(output_encoding, 'backslashreplace')
    else:
        v = str(v)
    last_row, last_col = len(matrix), len(matrix[-1])
    while last_row < row_idx:
        matrix.extend([[]])
        last_row = len(matrix)

    while last_col < col_idx:
        matrix[-1].extend([''])
        last_col = len(matrix[-1])

    matrix[-1].extend([v])

       for row in matrix:
    csv_row = separator.join(row)
    #print csv_row
    data.append(csv_row+'\n')
  outfile = open(tsv_file, 'w')
  for i in data:
    outfile.write(i)
    #print tsv_file
    #print 'i=',i
    #print
  outfile.close()

Для использования этой функции нужно задать в качестве параметров xls_file и сsv_file соответственно путь к исходному и новому файлам, input_encoding output_encoding - кодировка этих файлов (например 'cp1251' и 'utf-8'), separator - разделитель данных в получаемом csv, например запятая: ',' или знак табуляции '\t' (в этом случае, вообще говоря, это будет уже не csv, а tsv файл). Здесь используется библиотека pyExcelerator. В Ubuntu ee можно установить следующим образом: sudo apt-get install python-excelerator

Альтернативные варианты

Теоретически для преобразования xls в csv можно также использовать библиотеки xlrd и xlwrt.
Также в Ubuntu можно использовать программу xls2csv из пакета catdoc, но к сожалению xls2csv из catdoc не умеет использовать табулятор в качестве разделителя и к тому же она не очень хорошо отлажена и иногда дает сбои.