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

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

Импорт


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']) )

Предложенный код может быть широко использован при парсинге (и не только) с учетом особенностей сайтов.
Поделиться с друзьями
-->

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


  1. Scorobey
    05.03.2017 14:53
    -4

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


  1. LingvoLena
    05.03.2017 15:10
    -2

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


  1. andreymal
    05.03.2017 15:11
    +2

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


    1. SomeOneWhoCares
      05.03.2017 15:20
      -3

      На данном сайте нет защиты по IP. Но если вас это беспокоит IP-сервера можете найти в моей статье здесь.


    1. MeGaPk
      05.03.2017 16:00
      -4

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


  1. Skycker
    05.03.2017 16:40
    +7

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


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


    1. VovanZ
      05.03.2017 17:12

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


      1. VovanZ
        05.03.2017 21:40

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


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


  1. VovanZ
    05.03.2017 17:13
    +4

    1. Зачем вам многопоточность? Я не верю, что вы упираетесь в CPU.
    2. Почему вы пишете про многопоточность, но в коде используете multiprocessing?
    3. Зачем писать это всё самому, когда есть Scrapy (как уже заметил комментатор выше)?


  1. alekseev_ap
    05.03.2017 22:45
    -4

    Не про питон, но по поводу парсинга сайтов: Make Collection. Из плюсов: можно качать на выбор картинки, видео, текст, звук. И есть возможность генерировать имена файлов используя окружение. Ну и до кучи — экспорт в SQLite.


  1. kalbas
    06.03.2017 12:35
    +1

    Более ужасно нечитаемого кода еще поискать.


  1. MrGobus
    06.03.2017 15:39
    +1

    Везет вам на питоне, я вот на node.js недавно парсер писал, так там обратная история, пришлось заморачиваться чтобы ограничить число потоков так как сервера не успевали отдать всю информацию и умирали от таймаута из за чего данные получались битыми =(


  1. TOBBOT
    06.03.2017 16:23
    +1

    Увы, плодить потоки проще и дешевле процессов. А с потоками у Python «проблема». В итоге сделать так, чтобы работало быстро можно, но не просто и не так лаконично, как в примерах выше. А пот Windows так и вообще фантастика. Хотя, я мог пропустить некий переломный момент произошедший с момента выхода Python 3.4.


  1. madkite
    06.03.2017 21:34

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


    1. homm
      06.03.2017 21:39

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


  1. andjel
    07.03.2017 15:03
    +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