CoderCastrov logo
CoderCastrov
Питон

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

Как освоить парсинг сложного веб-сайта с использованием Scrapy и Selenium на Python
просмотров
7 мин чтение
#Питон

Введение

Парсинг веб-сайтов - это навык программирования, целью которого является извлечение данных с веб-сайта. Это навык, который тесно связан с вводом данных, но он программно собирает данные с веб-сайта. После процесса парсинга мы сохраняем данные в локальный файл на компьютере или в формате базы данных, таком как CSV или Excel. Мы будем использовать Python с библиотеками BeautifulSoup4 и Selenium для парсинга товаров на сайте NIKE Indonesia.

NIKE Indonesia - это веб-сайт с большим количеством JavaScript, что означает, что иногда его будет сложно спарсить. На веб-сайте NIKE Indonesia используется "бесконечная прокрутка", что означает, что веб-сайт будет непрерывно загружать новые данные о товарах при прокрутке вниз по странице.

Как работает парсинг

Я предлагаю, что процесс парсинга включает в себя 3 основных шага:

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

2. Получение данных

Поскольку Nike Indonesia сильно полагается на JavaScript, мы будем использовать Selenium, чтобы сохранить полностью прокрученные данные в локальный HTML-файл для последующего парсинга с помощью Scrapy.

3. Извлечение данных

Получив локальный HTML-файл с помощью Selenium, мы будем использовать Scrapy для парсинга, разбора и извлечения нужной информации из локальных HTML-файлов. Собранные данные будут сохранены в желаемом формате файла, от JSON до CSV, здесь мы сохраним данные в формате CSV.

Какие данные мы будем парсить

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

Catalogues

Теперь, если мы посмотрим на страницу одного из товаров:

Product Page

Мы будем парсить название товара, категорию, описание, цвет, стиль, цену, изображение и URL товара.

Выполнение

Нам понадобится Selenium для работы с бесконечной прокруткой на NIKE Indonesia и сохранения полностью прокрученного веб-сайта в локальный HTML-файл.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time, os
from bs4 import BeautifulSoup

class Scroller:
    def __init__(self):
        options = Options()
        options.add_argument("--start-maximized")
        self.driver = webdriver.Chrome(options=options)
      
    def scroll(self, url):
        target_url = url
        self.driver.get(target_url)
        print(self.driver.execute_script("return navigator.userAgent"))
        time.sleep(5)
        last_height = self.driver.execute_script("return document.body.scrollHeight")

        while True:
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(5) # or adjust to a higher value if the page needs longer to load

            new_height = self.driver.execute_script("return document.body.scrollHeight")
            if new_height == last_height:
                break
            last_height = new_height

    def save_html(self, webname):
        time.sleep(10)
        root_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../"))
        folder_path = os.path.join(root_folder, ".temp","html_output")
        os.makedirs(folder_path, exist_ok=True)
        format_file = os.path.join(folder_path, f"{webname}_NIKE.html")
        with open(format_file, 'w', encoding="utf-8") as f:
            soup = BeautifulSoup(self.driver.page_source, 'html.parser')
            f.writelines(soup.prettify())

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

urls = {
    'men_shoes'                   : 'https://www.nike.com/id/w/mens-shoes-nik1zy7ok', #v
    'men_clothing'                : 'https://www.nike.com/id/w/mens-clothing-6ymx6znik1', #v
    'men_accessories_equipment'   : 'https://www.nike.com/id/w/mens-accessories-equipment-awwpwznik1', #v
    'women_shoes'                 : 'https://www.nike.com/id/w/womens-shoes-5e1x6zy7ok', #?? 260 out of 420
    'women_clothing'              : 'https://www.nike.com/id/w/womens-clothing-5e1x6z6ymx6',
    'women_accessories_equipment' : 'https://www.nike.com/id/w/womens-accessories-equipment-5e1x6zawwpw',
    'kids_boys_clothing'          : "https://www.nike.com/id/w/boys-clothing-4413nz6ymx6",
    'kids_boys_shoes'             : "https://www.nike.com/id/w/boys-shoes-4413nzy7ok",
    "kids_girls_clothing"         : "https://www.nike.com/id/w/girls-clothing-6bnmbz6ymx6",
    "kids_girls_shoes"            : "https://www.nike.com/id/w/girls-shoes-6bnmbzy7ok",
    'kids_accessories_equipment'  : 'https://www.nike.com/id/w/kids-accessories-equipment-awwpwzv4dh'
}

if __name__ == "__main__":  
    scroller = Scroller()   
    for webname, url in urls.items():    
        scroller.scroll(url=url)
        scroller.save_html(webname)  # replace 'page.html' with your desired file name     
        print(f"success accessing NIKE {webname} at URL : {url}")
    scroller.quit()
    #scroller.close()

Причина, по которой мы используем options.add_argument("--start-maximized"), заключается в том, что по какой-то причине NIKE Indonesia не будет загружать бесконечную прокрутку, если мы не находимся в режиме максимального окна для нашего веб-драйвера Chrome.

Теперь мы можем проверить бесконечную загрузку страниц с помощью

last_height = self.driver.execute_script("return document.body.scrollHeight")

Этот код сохраняет высоту веб-страницы в переменной last_height, а затем мы используем цикл while True, чтобы проверить, полностью ли прокручена страница.

while True:
  self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
  time.sleep(5) # or adjust to a higher value if the page needs longer to load

  new_height = self.driver.execute_script("return document.body.scrollHeight")
  if new_height == last_height:
    break
  last_height = new_height

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

Для сохранения HTML-файла нам понадобится beautifulsoup4 для форматирования строк кода внутри HTML-файла.

Теперь, с помощью Scrapy, мы можем попытаться открыть эти HTML-файлы с такой конфигурацией

import scrapy
import os
import subprocess
import logging

subprocess.run(['python', 'browser.py'])

def get_html_folder_files():
    root_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../"))
    folder_path = os.path.join(root_folder, ".temp","html_output")
    return folder_path

def get_files_in_folder(folder_path):
    file_list = []

    # List all files and directories in the given folder path
    entries = os.listdir(folder_path)

    for entry in entries:
        # Check if the entry is a file (not a directory)
        if os.path.isfile(os.path.join(folder_path, entry)):
            full_path = os.path.join(folder_path, entry)
            file_list.append("file:\\" + full_path)

    return file_list


class NikespiderSpider(scrapy.Spider):
    name = "nikespider"
    allowed_domains = ["www.nike.com"]
    start_urls = get_files_in_folder(folder_path=get_html_folder_files())

Цель этих строк кода - вернуть список папок HTML в scrapy, чтобы он открывал их последовательно с помощью Scrapy Spider.

Теперь, давайте вернемся к одному из товаров и попробуем спарсить данные с помощью Scrapy.

Product Page

После настройки Scrapy мы можем получить URL товара, используя терминал Python:

scrapy shell
fetch('https://www.nike.com/id/w/mens-shoes-nik1zy7ok')

Если успешно, это вернет данные вот так:

Response

Если мы посмотрим на название товара и его категорию, они будут находиться в теге h1 с классом headline-2 и теге h2 с классом headline-5 соответственно.

Product Name and Category

Чтобы спарсить их, мы можем использовать response.css('h1.headline-2 ::text').get() для названия товара и response.css('h2.headline-5 ::text').get() для категории товара.

Price

Если мы посмотрим на цену, используя инструменты разработчика Chrome, элемент будет показан в теге div с классом product-price.

Price Element

Теперь мы можем попробовать проверить цену товара с помощью response.css('div.product-price::text').get(), но это вернет 'Rp\xa01,549,000'. Чтобы исправить это, мы можем использовать функцию .replace, чтобы избавиться от 'Rp\xa0' и запятой ',' и использовать int, чтобы преобразовать строку в целое число.

Colour Colour Element

Далее мы попытаемся спарсить информацию о цвете, как видно, она находится в теге li с классом description-preview__color-description.

С помощью Scrapy мы можем проверить это снова с помощью response.css.

Colour Response

Как видно, если мы сделаем это так, как есть, он вернет часть строки 'Цвет показан: '. Чтобы избавиться от этого, мы можем сохранить данные в переменную и использовать функцию .replace, чтобы удалить 'Цвет показан' из окна.

Теперь мы попытаемся спарсить ссылку на изображение. Если мы посмотрим здесь:

Image Element

Данные изображения находятся в теге img, но с использованием атрибута src. Чтобы справиться с этим, мы можем использовать response.css совместно с xpath из Scrapy.

Image Response

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

ID ID Element

Далее мы попытаемся спарсить данные его ID, чтобы каждый товар имел свой уникальный ID после парсинга. После парсинга мы можем использовать response.css для получения стиля продукта ID.

ID Response

Как видно, стиль находится в теге li с классом .description-preview__style-color

С помощью Scrapy мы можем разобрать это с использованием response.css, чтобы избавиться от остатка 'Стиль: ', мы можем использовать функцию .replace на Python снова.

URL

Чтобы спарсить ссылку на URL, мы можем легко использовать response.url из Scrapy.

Parsing Output

Теперь парсинг одного товара завершен, следующий шаг - определить все URL каждого товара из HTML-файлов, которые были сохранены с помощью Selenium.

Теперь мы рассмотрим обувь для мужчин. Если мы проанализируем это, мы увидим, что каталог каждого товара находится в теге div с классом product-card.

Теперь с помощью Scrapy, если мы попытаемся получить к нему доступ с помощью response.css div product card, он вернет список вот так:

Product Cards

Оказывается, что ссылка на href каждого товара находится внутри div.product-card. Точнее, внутри тега 'a' с классом product-card__link-overlay. Мы можем спарсить каждую ссылку с помощью response.follow, а затем нам понадобится обратная функция обработки страницы товара.

def parse(self, response):
  products = response.css('div.product-card')
  for product in products:
    yield response.follow(relative_url, callback=self.parse_product_page)

Внутри self.parse_product_page это должно выглядеть так для окончательного парсинга:

def parse_product_page(self, response):
    output = {
        'id'          : self.parse_id(response=response),
        'title'       : response.css('h1.headline-2 ::text').get(),
        'category'    : response.css('h2.headline-5 ::text').get(),
        'price (RP)'  : self.parse_price(response=response),
        'description' : response.css('div.description-preview ::text').get(),
        'colour'      : self.parse_colour(response=response),
        'url'         : response.url, 
        'img_url'     : self.parse_img_url(response=response)
       }                        
        yield output

Теперь, если мы запустим программу и сохраним вывод в CSV, он будет выглядеть примерно так:

scrapy crawl nikespider -O output.csv
CSV Output

Вывод

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