темный логотип proxyscrape

Параллелизм и параллелизм: Существенные различия для веб-скрапинга

Скрапбукинг, Различия, Ян -17-20225 минут чтения

Когда речь заходит о параллелизме и параллельности, может показаться, что они обозначают одни и те же понятия при выполнении компьютерных программ в многопоточной среде. Посмотрев их определения в Оксфордском словаре, вы, возможно, будете склонны так думать. Однако если углубиться в эти понятия применительно к

Когда речь заходит о параллелизме и параллельности, может показаться, что они обозначают одни и те же понятия при выполнении компьютерных программ в многопоточной среде. Посмотрев их определения в Оксфордском словаре, вы, возможно, будете склонны так думать. Однако, углубившись в эти понятия применительно к тому, как процессор выполняет программные инструкции, вы заметите, что параллелизм и параллельность - это два разных понятия. 

В этой статье мы подробнее рассмотрим параллелизм и параллельность, их различия и совместную работу для повышения производительности выполнения программы. Наконец, мы обсудим, какие две стратегии лучше всего подходят для веб-скреппинга. Итак, давайте приступим.

Что такое параллельное выполнение?

Для начала, чтобы упростить ситуацию, мы начнем с параллелизма в одном приложении, выполняемом на одном процессоре. Dictionary.com определяет параллелизм как объединение действий или усилий и одновременное возникновение событий. Однако то же самое можно сказать и о параллельном выполнении, так как выполнение совпадает, и поэтому в мире компьютерного программирования это определение несколько вводит в заблуждение.

В повседневной жизни вы можете одновременно работать на компьютере. Например, вы можете читать статью в блоге в браузере, одновременно слушая музыку в проигрывателе Windows Media. Может быть запущен еще один процесс: загрузка PDF-файла с другой веб-страницы - все эти примеры являются отдельными процессами.

До изобретения параллельно выполняющихся приложений центральные процессоры выполняли программы последовательно. Это означало, что инструкции одной программы должны быть завершены, прежде чем процессор перейдет к следующей.

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

В однопроцессорной многопоточной среде одна программа выполняется, когда другая заблокирована для ввода пользователем. Теперь вы можете спросить, что такое многопоточная среда. Это набор потоков, которые выполняются независимо друг от друга - подробнее о потоках в следующем разделе.

Параллельность не следует путать с параллельным выполнением

Теперь проще спутать параллелизм с параллельностью. В приведенных выше примерах под параллелизмом мы подразумевали то, что процессы не выполняются параллельно. 

Допустим, одному процессу требуется завершить операцию ввода-вывода, тогда операционная система выделит процессор другому процессу, пока он завершает свою операцию ввода-вывода. Эта процедура будет продолжаться до тех пор, пока все процессы не завершат свое выполнение.

Однако, поскольку переключение задач операционной системой происходит в течение нано- или микросекунды, пользователю может показаться, что процессы выполняются параллельно, 

Что такое нить?

В отличие от последовательного выполнения, в современных архитектурах центральный процессор может не выполнять весь процесс/программу сразу. Вместо этого большинство компьютеров могут разделить весь процесс на несколько легких компонентов, которые выполняются независимо друг от друга в произвольном порядке. Именно эти легкие компоненты и называются потоками.

Например, в Google Docs может быть несколько потоков, которые работают одновременно. Пока один поток автоматически сохраняет вашу работу, другой может работать в фоновом режиме, проверяя орфографию и грамматику.  

Операционная система определяет порядок и приоритет потоков, что зависит от системы.

Что такое параллельное выполнение?

Теперь вы знаете, что компьютерные программы выполняются в среде с одним процессором. В отличие от этого, современные компьютеры выполняют множество процессов одновременно в нескольких центральных процессорах, что называется параллельным выполнением. Большинство современных архитектур имеют несколько центральных процессоров.

Как видно на диаграмме ниже, процессор выполняет каждый поток, принадлежащий процессу, параллельно друг другу.  

При параллельном выполнении операционная система переключает потоки на центральный процессор и обратно за доли макро- или микросекунды, в зависимости от архитектуры системы. Для достижения параллельного выполнения операционной системы компьютерные программисты используют концепцию, известную как параллельное программирование. При параллельном программировании программисты разрабатывают код, чтобы наилучшим образом использовать несколько центральных процессоров. 

Как параллелизм может ускорить веб-скрептинг

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

В следующем разделе описаны некоторые причины, по которым веб-скраппинг работает медленно.

Существенные причины, по которым веб-скреппинг работает медленно?

Для начала скреперу необходимо перейти на целевой веб-сайт. Затем он должен извлечь сущности из HTML-тегов, которые вы хотите соскрести. Наконец, в большинстве случаев данные сохраняются во внешний файл, например, в формате CSV.  

Как видите, большинство из перечисленных задач требуют выполнения тяжелых операций ввода-вывода, таких как извлечение данных с веб-сайтов и последующее сохранение их во внешних файлах. Переход на целевые веб-сайты часто зависит от внешних факторов, таких как скорость сети или ожидание, пока сеть станет доступной.

Как видно на рисунке ниже, такое крайне медленное потребление времени может еще больше затруднить процесс соскабливания, если вам нужно соскоблить три или более веб-сайтов. При этом предполагается, что вы выполняете операцию соскабливания последовательно.

Поэтому, так или иначе, вам придется применить параллелизм или параллельность к операциям скрапинга. В следующем разделе мы сначала рассмотрим параллелизм.

Параллелизм в веб-скреппинге с помощью Python

Я уверен, что вы уже знакомы с понятиями параллелизма и параллельности. В этом разделе мы рассмотрим параллелизм в веб-скреппинге на примере простого кодирования на Python.

Простой пример, демонстрирующий отсутствие параллельного выполнения

В этом примере мы возьмем из Википедии URL стран со списком столиц, основанных на численности населения. Программа сохранит ссылки, а затем перейдет на каждую из 240 страниц и сохранит HTML этих страниц локально.

 Чтобы продемонстрировать эффекты параллелизма, мы покажем две программы - одну с последовательным выполнением, а другую - с одновременным многопоточным.

Вот код:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import time

def get_countries():
    countries = 'https://en.wikipedia.org/wiki/List_of_national_capitals_by_population'
    all_countries = []
    response = requests.get(countries)
    soup = BeautifulSoup(response.text, "html.parser")
    countries_pl = soup.select('th .flagicon+ a')
    for link_pl in countries_pl:
        link = link_pl.get("href")
        link = urljoin(countries, link)
        
        all_countries.append(link)
    return all_countries
  
def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)
  

        
def main():
    clinks = get_countries()
    print(f"Total pages: {len(clinks)}")
    start_time = time.time()
    for link in clinks:
        fetch(link)
 
    duration = time.time() - start_time
    print(f"Downloaded {len(links)} links in {duration} seconds")
main()

Объяснение кода

Во-первых, мы импортируем библиотеки, включая BeautifulSoap, для извлечения HTML-данных. Другие библиотеки включают request для доступа к веб-сайту, urllib для объединения URL, как вы узнаете, и библиотеку time для определения общего времени выполнения программы.

импортировать запросы
from bs4 import BeautifulSoup
из urllib.parse import urljoin
импортировать время

Программа начинается с главного модуля, который вызывает функцию get_countries(). Затем функция получает доступ к URL-адресу Википедии, указанному в переменной countries, через экземпляр BeautifulSoup посредством HTML-парсера.

Затем он ищет URL-адрес для списка стран в таблице, извлекая значение в атрибуте href тега якоря.

Полученные ссылки являются относительными. Функция urljoin преобразует их в абсолютные ссылки. Затем эти ссылки добавляются в массив all_countries, который возвращается в основную функцию 

Затем функция fetch сохраняет HTML-содержимое каждой ссылки в виде HTML-файла. Вот что делают эти части кода:

def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)

Наконец, главная функция выводит время, затраченное на сохранение файлов в формате HTML. На нашем компьютере это заняло 131,22 секунды.

Это время, конечно, можно ускорить. Мы узнаем об этом в следующем разделе, где та же программа будет выполняться в несколько потоков.

Та же программа с параллелизмом

В многопоточной версии нам придется внести небольшие изменения, чтобы программа выполнялась быстрее.

Помните, что параллелизм - это создание нескольких потоков и выполнение программы. Существует два способа создания потоков - вручную и с помощью класса ThreadPoolExecutor. 

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

В этой программе мы будем выполнять код с помощью класса ThreadPoolExecutor, который является частью модуля concurrent.futures. Поэтому прежде всего необходимо вставить в приведенную выше программу следующую строку. 

из concurrent.futures import ThreadPoolExecutor

После этого вы можете изменить цикл for, сохраняющий содержимое HTML в формате HTML, следующим образом:

  с ThreadPoolExecutor(max_workers=32) в качестве executor:
           executor.map(fetch, clinks)

Приведенный выше код создает пул потоков с максимальным числом потоков 32. Для каждого процессора параметр max_workers отличается, и вам нужно поэкспериментировать с разными значениями. Это не обязательно означает, что чем больше потоков, тем быстрее время выполнения.

Таким образом, наш компьютер выдал результат за 15,14 секунды, что гораздо лучше, чем при последовательном выполнении.

Итак, прежде чем мы перейдем к следующему разделу, вот окончательный код программы с параллельным выполнением:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from concurrent.futures import ThreadPoolExecutor
import time

def get_countries():
    countries = 'https://en.wikipedia.org/wiki/List_of_national_capitals_by_population'
    all_countries = []
    response = requests.get(countries)
    soup = BeautifulSoup(response.text, "html.parser")
    countries_pl = soup.select('th .flagicon+ a')
    for link_pl in countries_pl:
        link = link_pl.get("href")
        link = urljoin(countries, link)
        
        all_countries.append(link)
    return all_countries
  
def fetch(link):
    res = requests.get(link)
    with open(link.split("/")[-1]+".html", "wb") as f:
        f.write(res.content)


def main():
  clinks = get_countries()
  print(f"Total pages: {len(clinks)}")
  start_time = time.time()
  

  with ThreadPoolExecutor(max_workers=32) as executor:
           executor.map(fetch, clinks)
        
 
  duration = time.time()-start_time
  print(f"Downloaded {len(clinks)} links in {duration} seconds")
main()

Как параллелизм может ускорить веб-скрептинг

Теперь мы надеемся, что вы получили представление о параллельном выполнении. Чтобы помочь вам лучше анализировать, давайте посмотрим, как работает одна и та же программа в многопроцессорной среде с процессами, выполняющимися параллельно на нескольких CPU.

Для начала необходимо импортировать нужный модуль:

from multiprocessing import Pool,cpu_count

В Python есть метод cpu_count(), который подсчитывает количество процессоров на вашей машине. Это, несомненно, помогает определить точное количество задач, которые могут выполняться параллельно.

Теперь вам нужно заменить код с циклом for в последовательном выполнении на этот код:

с Pool (cpu_count()) как p:
 
   p.map(fetch,clinks)

После выполнения этого кода общее время выполнения составило 20,10 секунды, что относительно быстрее, чем последовательное выполнение в первой программе.

Заключение

На данный момент мы надеемся, что у вас есть полное представление о параллельном и последовательном программировании - выбор в пользу одного или другого зависит в первую очередь от конкретного сценария, с которым вы столкнулись.

Для сценария веб-скреппинга мы рекомендуем начать с параллельного выполнения, а затем перейти к параллельному решению. Мы надеемся, что вам понравилось читать эту статью. Не забудьте также прочитать другие статьи о веб-скреппинге, подобные этой, в нашем блоге.