Самый легкий способ парсинга таблиц с веб-страниц?

User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

Само собой, трудности будут. Но я все же решил посмотреть в сторону beautifulsoup на питоне — вдруг пойдет?
User avatar
Albert_al
Уже с Приветом
Posts: 2305
Joined: 14 Apr 1999 09:01
Location: Ural->CA

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Albert_al »

Если уж питон, то scrapy
Alcohol, Tobacco, Firearms, and Explosives. The makings of a great weekend in West Virginia!
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

Пишут, что вроде как bs попроще/полегче.
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

Какой-то beautifulsoup странный. У меня структура страницы вот такая:
<body>
<table>...#1</table>
<table>...#2</table>
<table>...#3</table>
<table>...#4</table>
</body>

Делаю так: len(ДОКУМЕНТ.find('body').find_all('table', recursive=False))
и получаю 1, хотя должно быть 4

Если убрать 'recursive=False' то находятся все тэги, но там проблема в том что ищутся еще и внуки, правнуки и т.д.(в документе имеются вложенные тэги таблицы). Поскольку мне нужны только дети <body>, то нужен запрет рекурсии. Но почему он тогда находит только одного ребенка, когда их должно быть четыре???

Прикрепляю скриншот дерева документа. Как вы считаете, я прав, что там детей 'body' с тэгом 'table' больше одного? Или это я ослеп?
beautifulsoup.png
UPD

Кажется, я понял в чем дело. Тэг <link href="header/66/66.css" rel="stylesheet" type="text/css"> перед вторым тэгом таблицы не закрывается (</...>), поэтому bs считает, что второй тэг таблицы уже не ребенок, а внук по отношению к <body>

Вот как так, а? :upset:
You do not have the required permissions to view the files attached to this post.
User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Flash-04 »

TC - учи албанский Python!
Not everyone believes what I believe but my beliefs do not require them to.
helg
Уже с Приветом
Posts: 4827
Joined: 15 May 2001 09:01

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by helg »

perasperaadastra wrote:Кажется, я понял в чем дело. Тэг <link href="header/66/66.css" rel="stylesheet" type="text/css"> перед вторым тэгом таблицы не закрывается (</...>), поэтому bs считает, что второй тэг таблицы уже не ребенок, а внук по отношению к <body>
Это валидный HTML4, и программа, если она претендует на универсальность, должна такое понимать.
User avatar
ALV00
Уже с Приветом
Posts: 1494
Joined: 08 Mar 2002 10:01
Location: NJ

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by ALV00 »

ЕМНИП линку не положено иметь закрывающий тег. Может просто вычистить ненужные теги для начала?
User avatar
fruit6
Уже с Приветом
Posts: 4207
Joined: 10 Jan 2004 01:22
Location: n-sk -> MD -> VA

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by fruit6 »

Flash-04 wrote:TC - учи албанский Python!
если выучить китайский HTML, то не понадобится Python
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

helg wrote:
perasperaadastra wrote:Кажется, я понял в чем дело. Тэг <link href="header/66/66.css" rel="stylesheet" type="text/css"> перед вторым тэгом таблицы не закрывается (</...>), поэтому bs считает, что второй тэг таблицы уже не ребенок, а внук по отношению к <body>
Это валидный HTML4, и программа, если она претендует на универсальность, должна такое понимать.
Вот и я так подумал, но что-то здесь сбивает парсер с толку. Я почитал интернеты и прямо вот-такого не нашел, но люди жалуются со схожими проблемами неправильной трактовки вложенных тегов. Как я понял, можно добавить в парсер свои правила, чтобы исправлять проблемы, но мне кажется, что в такой элементарном случае это должно работать без моего вмешательства.
ALV00 wrote:ЕМНИП линку не положено иметь закрывающий тег. Может просто вычистить ненужные теги для начала?
Хорошая мысль, но, увы, мне нужны будут некоторые ссылки в документе.

В принципе, этот баг не помешал мне выцарапать таблицу — просто пришлось искать нужный тэг через рекурсивный поиск. Перебором нашел, что это 8й элемент в списке. Можно было вообще не мучаться с позиционированием относительно корня, а сразу искать тэг таблицы с нужными свойствами (пожалуй, это было бы надежнее).

В общем, у меня получилось сделать довольно уродский скрипт, который работает. Конечно, потратил на все это дело уже часов 10, но по крайней мере, узнал что-то на будущее. :-)
Flash-04 wrote:TC - учи албанский Python!
Так, вроде, этим и занимаюсь. Етот beautifulsoup для Змеюки и есть. :)
User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Flash-04 »

Вот и отлично :)
Not everyone believes what I believe but my beliefs do not require them to.
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

Поздравляйте! Допилил я все-таки скрипт для парсинга избиркомовских страниц. :appl: :appl: :appl:

Code: Select all

# -*- encoding: UTF-8 -*-
import os, sys, numpy, itertools
from bs4 import BeautifulSoup

# Obtain file paths and polling station IDs
def file_paths():
    rootdir = u'/Users/mbp/Desktop/Out/VCIK'
    for root, dirs, files in os.walk(rootdir):
        for file in files:
            if not (os.path.splitext(file)[0] in dirs) and file!='.DS_Store':
                filepath = os.path.join(root, file)
                # remove rootdir from name <.replace(rootdir, '')>
                # remove another level from name <split('/')[2:]>
                # remove .html, and finally join everything back into a string with / separators
                id = '/'.join(filepath.replace(rootdir, '').split('/')[2:]).replace(u'.html', '')
                yield filepath, id

# Parse initial file
def parse_init(filepath, id):
    source = open(filepath, 'r').read()
    soup = BeautifulSoup(source, from_encoding="cp1251", )
    # create headers (categories)
    data = [[u'id']]
    table = soup.findAll('table')[8]
    for j,row in enumerate(table.findAll('tr')):
        element = row.findAll('td')[1]
        if element:
            data.append([element.text.strip().split('\n', 1)[0]])
    # populate table with figures
    table = soup.findAll('table')[9]
    for j,row in enumerate(table.findAll('tr')):
        
        cols = row.find_all('td')
        for element in cols:
            if element:
                if j == 0: data[0].extend([id])
                data[j+1].extend([element.text.strip().split('\n', 1)[0]])
    return data

# Parse remaining files
def parse(filepath, id, data):
    source = open(filepath, 'r').read()
    soup = BeautifulSoup(source, from_encoding="cp1251", )
    table_check = soup.findAll('table')[8]
    table = soup.findAll('table')[9]

    for j,row in enumerate(table.findAll('tr')):
        cols = row.find_all('td')
        # check if headers (categories) match, and populate table with figures
        if data[j+1][0] == table_check.findAll('tr')[j].findAll('td')[1].text.strip():
            for element in cols:
                if element:
                    if j == 0: data[0].extend([id])
                    data[j+1].extend([element.text.strip().split('\n', 1)[0],])
        else:
            print("ERRROR! ", filename)
    return data

#transpose data set
def transpose(d):
    trans = map(list, itertools.izip_longest(*d, fillvalue='-'))
    return trans


# write out to tab delimited file
def write_out (data):
    for j in range(len(data)):
        for k in range(len(data[j])):
            if data[j][k]:
                output.write(data[j][k].encode('utf8'))
                output.write(u"\u0009")
        output.write(u"\u000D")


data = []                                       #dataframe
init_flag = 1                                   #initialization flag
files = list(file_paths())                      #files to process

for i, (filepath, id) in enumerate(files):
    print(id)
    if init_flag:
        data = parse_init(filepath, id)
        init_flag = 0
    else:
        data = parse(filepath, id, data)

if data:
    data = transpose (data)
    output = open('/Users/mbp/Desktop/elections_2016.txt', 'w')
    write_out(data)
А вот этот скрипт использовался для кролинга (не мое: http://users.livejournal.com/-winnie/32 ... ad=4293731)

Code: Select all

# -*- encoding: UTF-8 -*-

import re, urllib, os

root_url = 'http://www.vybory.izbirkom.ru/region/region/izbirkom?action=show&root=1&tvd=100100067795854&vrn=100100067795849&region=0&global=true&sub_region=99&prver=0&pronetvd=0&vibid=100100067795854&type=233'

def escape_filename(name):
    for bad_fn_char in '%' + "/\\:*?<>|":
        if bad_fn_char in name:
            name = name.replace(bad_fn_char, '%02x' % ord(bad_fn_char))
    return name
    
download_counter = 0
download_log = open('download.log', 'w')
    
def get_page(url, path, name):
    page = None
    if os.path.exists(get_save_name(path, name)):
        f = open(get_save_name(path, name), 'rb')
        try:
            page = f.read()
            if page.rstrip().endswith('</html>'):
                return page
            else:
                print 'warning, cached page is not valid html', url, repr(get_save_name(path, name))
                page = None
        finally:
            f.close()
            
    if page == None:
        global download_counter
        download_counter += 1
        print >> download_log, download_counter, url
        return urllib.urlopen(url).read()

def get_sub(page, url_diagnostic):
    subs = []
    
    #--------------------- combobox ------------
    combobox_text = re.findall('<select name="gs">(.*?)</select>', page)
    assert len(combobox_text) <= 1
    
    if len(combobox_text) > 0:
        for m in re.finditer('<option value="(.*?)">(.*?)</option>', combobox_text[0]):
            url, name = (m.group(1).strip().replace('&', '&'), m.group(2).decode('cp1251').strip())
            if url:
                subs.append((url, name))
                
                
    #-------------- site ref ------------
    
    m = re.search(u'перейдите на < a href = "(.*?)" > сайт'.encode('cp1251').replace(' ', '\s*'), page)
    if m:
        url = m.group(1).strip().replace('&', '&')
        name = None
        for tr in re.finditer('<tr (.|\n)*?</tr>', page):
            if u'Наименование Избирательной комиссии'.encode('cp1251') in tr.group(0):
                name_match = re.search(
                    u' <b> Наименование Избирательной комиссии </b> </td> <td> (.*) </td> </tr> '.replace(' ', '\s*').encode('cp1251'), 
                    tr.group(0))
                assert name_match, url_diagnostic
                name = name_match.group(1).strip().decode('cp1251')
        assert name, url_diagnostic
        subs.append((url, name))
                
    return subs
    #TODO: see site.

def get_save_name(path, name):
    return path + '/' + name + '.html'
    
def save(page, path, name):
    fn = get_save_name(path, name)
    if not os.path.isdir(os.path.dirname(fn)):
        os.makedirs(os.path.dirname(fn))
    f = open(fn, 'wb')
    try:
        f.write(page)
    finally: #py 2.5 compat
        f.close()
    
filenames = dict()

url_to_filename_file = open('url_2_filename.txt', 'wb')

def recursive_download(url, name, path, print_progress):
    page = get_page(url, path, name)
    if get_save_name(path, name) in filenames:
        print 'warning, duplicated filename %s from %s and %s' % (repr(get_save_name(path, name)), url, filenames[get_save_name(path, name)])
    else:
        save(page, path, name)
        filenames[get_save_name(path, name)] = url
    print >> url_to_filename_file, url, get_save_name(path, name).encode('UTF-8')
    
    sub_pages = get_sub(page, url)
    for i, (sub_url, sub_name) in enumerate(sub_pages):
        if print_progress:
            print '%s/%s - %s' % (i + 1, len(sub_pages), sub_name.encode('UTF-8'))
        #print sub_url, sub_name.encode('UTF-8')
        sub_name = escape_filename(sub_name)
        recursive_download(sub_url, sub_name, path + '/' + name, print_progress = False)
            
recursive_download(root_url, 'VCIK', './out/', print_progress = True)
url_to_filename_file.close()

User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Flash-04 »

молодец! :great:
web-скреперы - очень полезная вещь и используются очень часто для разных задач.
Not everyone believes what I believe but my beliefs do not require them to.
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by perasperaadastra »

Для закрепления успехов, я решил слизать у амазона рейтинг ревьюеров (1000 страниц). Проблема в том, что амазон обнаруживает роботов и периодически выдает капчу. К счастью, тупое повторение запроса в итоге приводит к нужному результату, но на скорости кролинга это сказывается очень отрицательно. За час я добыл только 900 страниц... Прямо хоть ботнет свой заводи...
Deckel
Ник закрыт за хамство.
Posts: 357
Joined: 16 Feb 2014 18:34

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Deckel »

Тебя посодют, а ты не воруй!
User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: Самый легкий способ парсинга таблиц с веб-страниц?

Post by Flash-04 »

:D между прочим на другом форуме (с) есть человек который использует краулиг Амазона для своего бизнеса и очень хорошо на этом зарабатывает.
Not everyone believes what I believe but my beliefs do not require them to.

Return to “Вопросы и новости IT”