Реализация на Python многопоточной обработки данных для парсинга сайтов

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

    Привожу код программы для сокращение времени обработки вдвое.

    Импорт


    import requests                   #выполняет HTTP-запросы
    from bs4 import BeautifulSoup     #работа с HTML
    import csv                        #работа с форматом данных CSV
    from multiprocessing import Pool  #предоставляет возможность параллельных процессов
    

    Главная процедура


    def main():
        url = 'http://banknotes.finance.ua/'
        links = []
             #получение всех ссылок для парсинга с главной страницы
        all_links = get_all_links(get_html(url), links)
             #обеспечение многопоточности
             #функции смотри help
        with Pool(2) as p:           
           p.map(make_all, all_links)
         
    if __name__ == '__main__':
        main()
    

    Получение URL


    def get_html(url):
        r = requests.get(url)
        return r.text
    

    Функции многопоточности


    def make_all(url):
        html = get_html(url)
        data = get_page_data(html)
        write_csv(data)
    

    Получение URL главной страницы


    def get_all_links(html, links):
            #очистка содержимого файла - без его удаления
        f=open('coin.csv', 'w')
        f.close()
            #работа с html-кодом, задаются параметры блоков и адрес сайта
        soup = BeautifulSoup(html, 'lxml')
        href = soup.find_all('div', class_= "wm_countries")
        for i in href:
          for link in i.find_all('a'):
            links += [link['href']]
        return links
    

    Парсинг вложенных страниц


    def get_page_data(html):
        soup = BeautifulSoup(html, 'lxml')
        try:
            name = soup.find('div', 'pagehdr').find('h1').text
        except:
            name = ''
        try:
            massiv_price = [pn.find('b').text for pn in soup.find('div', class_ = 'wm_exchange').find_all('a', class_ = 'button', target = False)]+[pr.text for pr in soup.find('div', class_ = 'wm_exchange').find_all('td', class_ = 'amount')]
            if len(massiv_price)==6:   massiv_price=massiv_price[0]+massiv_price[3]+massiv_price[1]+massiv_price[4]+massiv_price[2]+massiv_price[5]
            elif  len(massiv_price)==4:
                 massiv_price=massiv_price[0]+massiv_price[2]+massiv_price[1]+massiv_price[3]
        except:
            massiv_price = ''
        data = {'name': name, 'price': massiv_price}
        return data

    Запись файла


    def write_csv(data):
        with open('coin.csv', 'a') as f:
            writer = csv.writer(f)
            writer.writerow( (data['name'], data['price']) )
    

    Предложенный код может быть широко использован при парсинге (и не только) с учетом особенностей сайтов.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 16

      –4

      Просто и подробно о сложном — это о статье

        –2
        Статья интересна, спасибо. Прошу рассмотреть парсинг сайтов с использованием lxml, urlib3 и pyparcing.
          +2

          А потом банят на неделю за слишком большую частоту запросов.

            –3
            На данном сайте нет защиты по IP. Но если вас это беспокоит IP-сервера можете найти в моей статье здесь.
              –4
              для каждого пула можно заюзать прокси + паузы в потоках, что бы не попасть под бан. Если сайт еще работает на ipv6, то покупаешь 100шт за 50 рублей и радуешься.
              +7

              Для многопоточного парсинга я бы рекомендовал Scrapy. У нее внутри twisted, код будет менее многословным, чем кастомное решение на bs4 и шататных питоновских возможностях работы с потоками/процессами. К тому же работа с разметкой там гораздо лаконичнее. Как-то на работе появилась задача напистать скрипт для периодического парсинга примерно 20 ресурсов на предмет упоминаний о компании клиента. Со scrapy получилось уложиться в несколько часов.


              И позвольте на минуту включить зануду и немного покритиковать оформление кода. У вас импорты не по PEP8 оформлены. В кучу смешаны вендорные пакеты и встроеные. В функции get_all_links зачем-то идут строки очистки файла, но, судя по названию, её задача — вытащить ссылки с главной страницы. Принцип одной отвественности говорит, что котлеты с рыбой смешивать не нужно. Да и все ссылки на сайты, имена файлов и подобное хорошо бы вынести вверх скрипта в константы. Если захотите сохранять результаты не в coin.csv, а в foobar.csv, то придется править код в двух местах. В небольшом скрипте такое, конечно, не критично, но в реальных прикладных проектах может сэкономить время и нервы коллег, поддерживающих ваш код

                0

                Scrapy однопоточный.

                  0

                  А за что минус? Почитайте, что ли, как twisted работает и что такое асинхронность. Scrapy работает в одном процессе, в одном потоке.


                  Ну, при желании можно запускать много отдельных процессов с помощью Scrapyd или распределённый краулинг в Scrapy Cluster, но сам Scrapy — однопоточный.

                +4
                1. Зачем вам многопоточность? Я не верю, что вы упираетесь в CPU.
                2. Почему вы пишете про многопоточность, но в коде используете multiprocessing?
                3. Зачем писать это всё самому, когда есть Scrapy (как уже заметил комментатор выше)?
                  –4
                  Не про питон, но по поводу парсинга сайтов: Make Collection. Из плюсов: можно качать на выбор картинки, видео, текст, звук. И есть возможность генерировать имена файлов используя окружение. Ну и до кучи — экспорт в SQLite.
                    +1
                    Более ужасно нечитаемого кода еще поискать.
                      +1
                      Везет вам на питоне, я вот на node.js недавно парсер писал, так там обратная история, пришлось заморачиваться чтобы ограничить число потоков так как сервера не успевали отдать всю информацию и умирали от таймаута из за чего данные получались битыми =(
                        +1
                        Увы, плодить потоки проще и дешевле процессов. А с потоками у Python «проблема». В итоге сделать так, чтобы работало быстро можно, но не просто и не так лаконично, как в примерах выше. А пот Windows так и вообще фантастика. Хотя, я мог пропустить некий переломный момент произошедший с момента выхода Python 3.4.
                          0

                          2017 год… А люди до сих пор используют блокирующий I/O для работы с сетью и плодят потоки, чтобы ждать ответа от сервера.

                            0

                            *и плодят потоки, чтобы не ждать ответа от сервера.

                            +1
                            Не делайте так никогда
                            massiv_price = [pn.find('b').text for pn in soup.find('div', class_ = 'wm_exchange').find_all('a', class_ = 'button', target = False)]+[pr.text for pr in soup.find('div', class_ = 'wm_exchange').find_all('td', class_ = 'amount')]
                            

                            А еще лучше прогоните код через Flake

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое