← back to FashionCrawler
fashioncrawler/scraper/grailed_scraper.py
194 lines
"""
Grailed Scraper Module
======================
This module provides functionality for scraping data from the Grailed website.
Dependencies:
- sys: Standard library for system-specific parameters and functions.
- selenium.common.exceptions: Module providing exceptions for the Selenium WebDriver.
- selenium.webdriver: Module providing classes for accessing web browsers.
- selenium.webdriver.common.by: Module providing mechanisms for locating elements by various strategies.
- selenium.webdriver.support: Module providing classes for waiting for conditions before performing actions.
- selenium.webdriver.support.expected_conditions: Module providing predefined conditions for use with WebDriverWait.
- selenium.webdriver.support.wait: Module providing WebDriverWait class for waiting until a condition is true or a timeout occurs.
- .base_scraper: Module providing the BaseScraper class for web scraping.
Classes:
- GrailedScraper: Subclass of BaseScraper for scraping data from the Grailed website.
Attributes:
- LOGIN_POPUP_SELECTOR (str): CSS selector for the login popup.
- SEARCH_BAR_CSS_SELECTOR (str): CSS selector for the search bar.
- SEARCH_BAR_SUBMIT_CSS_SELECTOR (str): CSS selector for the search bar submit button.
- COOKIES_CSS_SELECTOR (str): CSS selector for the cookies acceptance button.
- BASE_URL (str): Base URL of the Grailed website.
- ITEM_CLASS_NAME (str): CSS class name for identifying items on the page.
- MIN_COUNT (int): Minimum count of items to wait for during page load.
Methods:
- __init__(self, config): Initializes the Grailed scraper with the base scraper object.
- run_scraper(self, search_query) -> None: Runs the Grailed scraper to search for items based on the provided search query.
- _navigate_and_search(self, search_query: str) -> None: Navigates to the search bar and performs a search based on the provided query.
- get_to_search_bar_to_search(self, search_bar_css_selector: str, timeout=2) -> None: Navigate to the search bar and interact with it to initiate a search.
- _dismiss_login_popup(self, timeout: int) -> None: Dismisses the login popup within a specified timeout period.
"""
import sys
from selenium.common.exceptions import (
NoSuchElementException,
StaleElementReferenceException,
TimeoutException,
)
from selenium.webdriver import ActionChains, Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from .base_scraper import BaseScraper
class GrailedScraper(BaseScraper):
"""
Subclass of BaseScraper for scraping data from the Grailed website.
Attributes:
- LOGIN_POPUP_SELECTOR (str): CSS selector for the login popup.
- SEARCH_BAR_CSS_SELECTOR (str): CSS selector for the search bar.
- SEARCH_BAR_SUBMIT_CSS_SELECTOR (str): CSS selector for the search bar submit button.
- COOKIES_CSS_SELECTOR (str): CSS selector for the cookies acceptance button.
- BASE_URL (str): Base URL of the Grailed website.
- ITEM_CLASS_NAME (str): CSS class name for identifying items on the page.
- MIN_COUNT (int): Minimum count of items to wait for during page load.
Methods:
- __init__(self, base_scraper): Initializes the Grailed scraper with the base scraper object.
- run_scraper(self, search_query) -> None: Runs the Grailed scraper to search for items based on the provided search query.
- _navigate_and_search(self, search_query: str) -> None: Navigates to the search bar and performs a search based on the provided query.
- get_to_search_bar_to_search(self, search_bar_css_selector: str, timeout=2) -> None: Navigate to the search bar and interact with it to initiate a search.
- _dismiss_login_popup(self, timeout: int) -> None: Dismisses the login popup within a specified timeout period.
"""
# Selectors for various elements
LOGIN_POPUP_SELECTOR = ".ReactModal__Content.ReactModal__Content--after-open.modal.Modal-module__authenticationModal___g7Ufu._hasHeader"
SEARCH_BAR_CSS_SELECTOR = "#header_search-input"
SEARCH_BAR_SUBMIT_CSS_SELECTOR = ".Button-module__button___fE9iu.Button-module__small___uF0cg.Button-module__secondary___gYP5i.Form-module__searchButton___WDphC"
COOKIES_CSS_SELECTOR = "#onetrust-accept-btn-handler"
BASE_URL = "https://grailed.com"
# Item related constants
ITEM_CLASS_NAME = "feed-item"
def __init__(self, config):
super().__init__(config=config)
def run_scraper(self, search_query) -> None:
"""
Runs the Grailed scraper to search for items based on the provided search query.
Args:
- search_query (str): The search query to be used for searching items.
Returns:
- None
"""
self._navigate_and_search(search_query)
super().wait_for_page_load(self.ITEM_CLASS_NAME)
self.logger.debug("Running Grailed Scraper.")
def _navigate_and_search(self, search_query: str) -> None:
"""
Navigates to the search bar and performs a search based on the provided query.
Args:
- search_query (str): The search query to be used for searching items.
Returns:
- None
"""
super().navigate_to_search_bar(self.BASE_URL, self.SEARCH_BAR_CSS_SELECTOR)
super().search_for_query(
search_query,
self.SEARCH_BAR_CSS_SELECTOR,
self.SEARCH_BAR_SUBMIT_CSS_SELECTOR,
)
def get_to_search_bar_to_search(
self, search_bar_css_selector: str, timeout=2
) -> None:
"""
Navigate to the search bar and interact with it to initiate a search.
Args:
- search_bar_css_selector: The CSS selector for the search bar.
- timeout: The maximum time to wait for elements to be interactable.
Returns:
- None
"""
try:
self.accept_cookies(self.COOKIES_CSS_SELECTOR)
search_bar = WebDriverWait(self.driver, timeout).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, search_bar_css_selector))
)
search_bar.click()
for _ in range(3):
try:
self._dismiss_login_popup(timeout=2)
break
except TimeoutException:
pass
except (
NoSuchElementException,
StaleElementReferenceException,
TimeoutException,
) as e:
self.logger.error(
f"Error interacting with Grailed search bar: {e}", exc_info=True
)
sys.exit(1)
def _dismiss_login_popup(self, timeout: int) -> None:
"""
Dismisses the login popup within a specified timeout period.
Args:
- timeout (int): The maximum time to wait for the login popup.
Returns:
- None
"""
try:
login_popup = WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(
(
By.CSS_SELECTOR,
self.LOGIN_POPUP_SELECTOR,
)
)
)
ActionChains(self.driver).move_to_element(login_popup).pause(
1
).move_by_offset(250, 0).pause(1).click()
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
WebDriverWait(self.driver, 10).until_not(
EC.presence_of_element_located(
(
By.CSS_SELECTOR,
self.LOGIN_POPUP_SELECTOR,
)
)
)
except TimeoutException:
self.logger.info("Login popup did not appear within the timeout.")