Как освоить парсинг сложного веб-сайта с использованием Scrapy и Selenium на Python
Table Of Content
Введение
Парсинг веб-сайтов - это навык программирования, целью которого является извлечение данных с веб-сайта. Это навык, который тесно связан с вводом данных, но он программно собирает данные с веб-сайта. После процесса парсинга мы сохраняем данные в локальный файл на компьютере или в формате базы данных, таком как 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.
Какие данные мы будем парсить
Если мы посмотрим на главную страницу, то увидим их список каталогов, уже разделенных на мужской, женский и детский. Здесь мы будем парсить каждый каталог их обуви, одежды, аксессуаров и оборудования.
Теперь, если мы посмотрим на страницу одного из товаров:
Мы будем парсить название товара, категорию, описание, цвет, стиль, цену, изображение и 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.
После настройки Scrapy мы можем получить URL товара, используя терминал Python:
scrapy shell
fetch('https://www.nike.com/id/w/mens-shoes-nik1zy7ok')
Если успешно, это вернет данные вот так:
Если мы посмотрим на название товара и его категорию, они будут находиться в теге h1 с классом headline-2 и теге h2 с классом headline-5 соответственно.
Чтобы спарсить их, мы можем использовать response.css('h1.headline-2 ::text').get()
для названия товара и response.css('h2.headline-5 ::text').get()
для категории товара.
Если мы посмотрим на цену, используя инструменты разработчика Chrome, элемент будет показан в теге div с классом product-price.
Теперь мы можем попробовать проверить цену товара с помощью response.css('div.product-price::text').get()
, но это вернет 'Rp\xa01,549,000'. Чтобы исправить это, мы можем использовать функцию .replace
, чтобы избавиться от 'Rp\xa0' и запятой ',' и использовать int
, чтобы преобразовать строку в целое число.
Далее мы попытаемся спарсить информацию о цвете, как видно, она находится в теге li с классом description-preview__color-description.
С помощью Scrapy мы можем проверить это снова с помощью response.css
.
Как видно, если мы сделаем это так, как есть, он вернет часть строки 'Цвет показан: '. Чтобы избавиться от этого, мы можем сохранить данные в переменную и использовать функцию .replace
, чтобы удалить 'Цвет показан' из окна.
Теперь мы попытаемся спарсить ссылку на изображение. Если мы посмотрим здесь:
Данные изображения находятся в теге img, но с использованием атрибута src. Чтобы справиться с этим, мы можем использовать response.css
совместно с xpath из Scrapy.
К сожалению, у нас возникла проблема, потому что данные, которые возвращаются, являются списком ссылок, текущее решение - проверить каждую ссылку и найти лучшее качество изображения. Мы обнаружили, что индекс списка 4 имеет лучшее качество изображения, поэтому мы будем парсить его.
Далее мы попытаемся спарсить данные его ID, чтобы каждый товар имел свой уникальный ID после парсинга. После парсинга мы можем использовать response.css
для получения стиля продукта ID.
Как видно, стиль находится в теге li с классом .description-preview__style-color
С помощью Scrapy мы можем разобрать это с использованием response.css
, чтобы избавиться от остатка 'Стиль: ', мы можем использовать функцию .replace
на Python снова.
Чтобы спарсить ссылку на URL, мы можем легко использовать response.url
из Scrapy.
Теперь парсинг одного товара завершен, следующий шаг - определить все URL каждого товара из HTML-файлов, которые были сохранены с помощью Selenium.
Теперь мы рассмотрим обувь для мужчин. Если мы проанализируем это, мы увидим, что каталог каждого товара находится в теге div с классом product-card.
Теперь с помощью Scrapy, если мы попытаемся получить к нему доступ с помощью response.css div product card
, он вернет список вот так:
Оказывается, что ссылка на 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
Вывод
Наконец, если мы правильно настроим Scrapy, вывод программы будет выглядеть как выше, содержащий название, категорию, цену, цвет, описание, ссылку на изображение и URL товара. Надеюсь, этот статья поможет другим, которые занимаются парсингом веб-страниц с использованием Python.