Описание изображения

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

лол-кот.gif

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


Необходимые модули 🔌 :

  • Python: Настройка Python, ссылка на сайт
  • Selenium: Настройка селена Установка селена с помощью pip
pip install selenium

Войти в полноэкранный режим

Выйти из полноэкранного режима

  • Webdriver-manager: для получения драйверов безголового браузера и управления ими Установка Webdriver-manager с помощью pip
pip install webdriver-manager

Войти в полноэкранный режим

Выйти из полноэкранного режима


Базовая настройка 🗂:

Давайте создадим каталог с именем web-scraper и вложенную папку с именем scraper. В папку скребка мы поместим наши вспомогательные функции

Для создания каталога мы можем использовать следующую команду.

mkdir -p web-scraper/scraper

Войти в полноэкранный режим

Выйти из полноэкранного режима

После создания папок нам нужно создать несколько файлов, используя приведенные ниже команды. Чтобы написать наш код, scrapeQuotes.py содержит наш основной код парсера.

cd web-scraper && touch scrapeQuotes.py

Войти в полноэкранный режим

Выйти из полноэкранного режима

тогда как scraper.py содержит наши вспомогательные функции.

cd scraper && touch scraper.py __init__.py

Войти в полноэкранный режим

Выйти из полноэкранного режима

После этого наша структура папок будет выглядеть так, откройте ее в любом текстовом редакторе по вашему выбору.

Скриншот от 20220821194111.png


Редактирование scraper.py в папке со скребком:

Теперь давайте напишем несколько вспомогательных функций для нашего бота.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager

def get_driver():
    options = webdriver.ChromeOptions()
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    return webdriver.Chrome(
        service=ChromeService(ChromeDriverManager().install()),
        options=options,
    )

def persist_data(data, filename):
    # this function writes data in the file
    try:
        file = open(filename, "a")
        file.writelines([f"{line}\n" for line in data])
    except Exception as e:
        return e
    finally:
        file.close()
    return True

Войти в полноэкранный режим

Выйти из полноэкранного режима


Редактирование scrapeQuotes.py 📝 :

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

Скриншот от 202208212239261.png

Для получения всех элементов мы будем использовать Автор.CLASS_NAME идентификатор

 def scrape_quotes(self):
        quotes_list = []
        quotes = WebDriverWait(self.driver, 20).until(
            EC.visibility_of_all_elements_located((By.CLASS_NAME, "quote"))
        )
        for quote in quotes:
            quotes_list.append(self.clean_output(quote))
        self.close_driver()
        return quotes_list

Войти в полноэкранный режим

Выйти из полноэкранного режима

Полный скрипт с еще несколькими функциями:

  • страница_загрузки: load_page — это функция, которая принимает URL-адрес и загружает веб-страницу.
  • скреб_кавычки: scrape_quotes извлекает все котировки, используя имя класса
  • close_driver: эта функция закрывает сеанс селена после того, как мы получим наши котировки
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from scraper.scraper import get_driver, persist_data

class ScrapeQuotes:
    def __init__ (self, url):
        self.url = url
        self.driver = get_driver()

    def load_page(self):
        self.driver.get(self.url)

    def scrape_quotes(self):
        quotes_list = []
        quotes = WebDriverWait(self.driver, 20).until(
            EC.visibility_of_all_elements_located((By.CLASS_NAME, "quote"))
        )
        for quote in quotes:
            quotes_list.append(self.clean_output(quote))
        self.close_driver()
        return quotes_list

    def clean_output(self, quote):
        raw_quote = quote.text.strip().split("\n")
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[1] = raw_quote[1].replace("by", "")
        raw_quote[1] = raw_quote[1].replace("(about)", "")
        raw_quote[2] = raw_quote[2].replace("Tags: ", "")
        return ",".join(raw_quote)

    def close_driver(self):
        self.driver.close()

def main(tag):
    scrape = ScrapeQuotes(" + tag)
    scrape.load_page()
    quotes = scrape.scrape_quotes()
    persist_data(quotes, "quotes.csv")

if __name__ == " __main__":
    tags = ["love", "truth", "books", "life", "inspirational"]
    for tag in tags:
        main(tag)

Войти в полноэкранный режим

Выйти из полноэкранного режима


Время расчета:

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

time -p /usr/bin/python3 scrapeQuotes.py

Войти в полноэкранный режим

Выйти из полноэкранного режима

Скриншот от 20220821202338-660x115.png

Здесь мы видим, что наш скрипт занял 22,84 секунды.


Настроить многопоточность:

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

    # list to store the threads
    threadList = []

    # initialize the thread pool
    with ThreadPoolExecutor() as executor:
        for tag in tags:
            threadList.append(executor.submit(main, tag))

    # wait for all the threads to complete
    wait(threadList)

Войти в полноэкранный режим

Выйти из полноэкранного режима

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


Редактирование scrapeQuotes.py для использования многопоточности 📝:

from concurrent.futures import ThreadPoolExecutor, process, wait

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from scraper.scraper import get_driver, persist_data

class ScrapeQuotes:
    def __init__ (self, url):
        self.url = url
        self.driver = get_driver()

    def load_page(self):
        self.driver.get(self.url)

    def scrape_quotes(self):
        quotes_list = []
        quotes = WebDriverWait(self.driver, 20).until(
            EC.visibility_of_all_elements_located((By.CLASS_NAME, "quote"))
        )
        for quote in quotes:
            quotes_list.append(self.clean_output(quote))
        self.close_driver()
        return quotes_list

    def clean_output(self, quote):
        raw_quote = quote.text.strip().split("\n")
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[1] = raw_quote[1].replace("by", "")
        raw_quote[1] = raw_quote[1].replace("(about)", "")
        raw_quote[2] = raw_quote[2].replace("Tags: ", "")
        return ",".join(raw_quote)

    def close_driver(self):
        self.driver.close()

def main(tag):
    scrape = ScrapeQuotes(" + tag)
    scrape.load_page()
    quotes = scrape.scrape_quotes()
    persist_data(quotes, "quotes.csv")

if __name__ == " __main__":
    tags = ["love", "truth", "books", "life", "inspirational"]

    # list to store the threads
    threadList = []

    # initialize the thread pool
    with ThreadPoolExecutor() as executor:
        for tag in tags:
            threadList.append(executor.submit(main, tag))

    # wait for all the threads to complete
    wait(threadList)

Войти в полноэкранный режим

Выйти из полноэкранного режима


Время расчета:

Рассчитаем время, необходимое для работы с использованием многопоточности в этом скрипте.

time -p /usr/bin/python3 scrapeQuotes.py

Войти в полноэкранный режим

Выйти из полноэкранного режима

Скриншот от 20220821221935-660x101.png

Здесь мы видим, что наш скрипт занял 9,27 секунды.


Настроить многопроцессорность:

Мы можем легко конвертировать многопоточные скрипты в многопроцессорные скрипты, так как в python многопроцессорность реализуется с помощью ProcessPoolExecutor который имеет тот же интерфейс, что и ThreadPoolExecutor. Многопроцессорность известна параллелизмом задач, тогда как многопоточность известна параллелизмом в задачах.

    # list to store the processes
    processList = []

    # initialize the mutiprocess interface
    with ProcessPoolExecutor() as executor:
        for tag in tags:
            processList.append(executor.submit(main, tag))

    # wait for all the threads to complete
    wait(processList)

Войти в полноэкранный режим

Выйти из полноэкранного режима


Редактирование scrapeQuotes.py для использования многопроцессорности 📝:

from concurrent.futures import ProcessPoolExecutor, wait

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from scraper.scraper import get_driver, persist_data

class ScrapeQuotes:
    def __init__ (self, url):
        self.url = url
        self.driver = get_driver()

    def load_page(self):
        self.driver.get(self.url)

    def scrape_quotes(self):
        quotes_list = []
        quotes = WebDriverWait(self.driver, 20).until(
            EC.visibility_of_all_elements_located((By.CLASS_NAME, "quote"))
        )
        for quote in quotes:
            quotes_list.append(self.clean_output(quote))
        self.close_driver()
        return quotes_list

    def clean_output(self, quote):
        raw_quote = quote.text.strip().split("\n")
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[0] = raw_quote[0].replace("", '"')
        raw_quote[1] = raw_quote[1].replace("by", "")
        raw_quote[1] = raw_quote[1].replace("(about)", "")
        raw_quote[2] = raw_quote[2].replace("Tags: ", "")
        return ",".join(raw_quote)

    def close_driver(self):
        self.driver.close()

def main(tag):
    scrape = ScrapeQuotes(" + tag)
    scrape.load_page()
    quotes = scrape.scrape_quotes()
    persist_data(quotes, "quotes.csv")

if __name__ == " __main__":
    tags = ["love", "truth", "books", "life", "inspirational"]

    # list to store the processes
    processList = []

    # initialize the mutiprocess interface
    with ProcessPoolExecutor() as executor:
        for tag in tags:
            processList.append(executor.submit(main, tag))

    # wait for all the threads to complete
    wait(processList)

Войти в полноэкранный режим

Выйти из полноэкранного режима


Время расчета:

Рассчитаем время, необходимое для работы с использованием Multiprocessing в этом скрипте.

time -p /usr/bin/python3 scrapeQuotes.py

Войти в полноэкранный режим

Выйти из полноэкранного режима

Скриншот от 20220821234155.png

Здесь мы видим, что наш многопроцессорный скрипт занял 8,23 секунды.