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

Я сломал себе голову в поисках того, как спарсить карточки товаров в Амазоне. Проблема в том, что у Амазона используется разные варианты дизайна под различную выдачу, в частности – если необходимо спарсить карточки по поисковому запросу «bags» - карточки будут расположены вертикально, как мне и нужно, а вот если взять, к примеру «t-shirts» - тут уже карточки расположены горизонтально, и с таким расположение скрипт выпадает в ошибку, он отрабатывает открытие страницы, но не хочет скроллить.

Более, того, начитавшись различных статей, где юзеры ломают голову, как обойти капчу на Амазон, я апгрейдил скрипт и теперь он может обойти капчу, если она встретится (работает при помощи 2капча), скрипт проверяет наличие капчи на странице, после каждой загрузки новой страницы и если капча встречается – отправляет запрос на сервер 2капча, а после получения решения, подставляет его и продолжает работу.

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

Ниже я подробно распишу что в себя включает скрипт, продемонстрирую его работу, а если вы сможете помочь в решении задачи, что добавить (поменять) в скрипте, чтобы он срабатывал на горизонтальные карточки – буду благодарен.

Ну, а кому-то скрипт поможет хотя бы в своем ограниченном функционале.

Итак, давайте разберем скрипт по кусочкам!

Подготовка

Сперва скрипт импортирует необходимые для выполнения задачи модули

from selenium import webdriver
 from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
import os
from time import sleep
import requests

Разберем по частям:

from selenium import webdriver

Импортирует класс webdriver, который позволяет управлять браузером (в моем случае Firefox) через скрипт

from selenium.webdriver.common.by import By

Импортирует класс By, с помощью которого скрипт будет искать элементы для парсинга по XPath (он может искать и другие атрибуты, но в данном случае будет использоваться XPath)

from selenium.webdriver.common.keys import Keys

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

from selenium.webdriver.common.action_chains import ActionChains

Импортирует класс ActionChains, для создания сложных последовательных действий, в нашем случае – нажатие на кнопку PAGE_DOWN и ожидание загрузки всех элементов на странице (так как в Амазоне карточки загружаются по мере скролла)

from selenium.webdriver.support.ui import WebDriverWait

Импортирует класс WebDriverWait который ждет, пока искомая нам информация прогрузилась, например описание товара, которое мы будем выискивать по XPath

from selenium.webdriver.support import expected_conditions as EC

Импортирует класс expected_conditions (сокращенно EC) который работает в связке с предыдущим классом и  указывает WebDriverWait, какое конкретное условие ему необходимо ждать. Повышает надежность работы скрипта, чтобы он не начал взаимодействовать с еще незагруженным содержимым.

import csv

Импортирует модуль csv, для работы с csv файлами.

import os

Импортирует модуль os, для работы с операционной системой (создание директорий, проверка наличие файлов и т.п.).

from time import sleep

Импортируем функцию sleep – эта та самая функция, которая будет приостанавливать скрипт на конкретное время (в моем случае 2 сек, но можно поставить и больше) чтобы элементы при скролле прогрузились.

import requests

Импортирует библиотеку requests, для отправки HTTP-запросов, для взаимодействия с сервисом распознавания капчи 2капча.

Настройка

После того, как все импортировано, скрипт приступает к настройке браузера для работы, в частности:

Установка ключа АПИ для обращения к сервису 2капча

# API key for 2Captcha
API_KEY = "Your API Key"

В скрипте прописан user-agent (его естественно, можно менять), который устанавливается для браузера. После этого происходит запуск браузера с указанными настройками.

user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"

options = webdriver.FirefoxOptions()
options.add_argument(f"user-agent={user_agent}")

driver = webdriver.Firefox(options=options)

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

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

def solve_captcha(driver):
    try:
        captcha_element = driver.find_element(By.CLASS_NAME, 'g-recaptcha')
        if captcha_element:
            print("Captcha detected. Solving...")
            site_key = captcha_element.get_attribute('data-sitekey')
            current_url = driver.current_url
            
            # Запрос решения капчи к 2Captcha
            captcha_id = requests.post(
                'http://2captcha.com/in.php', 
                data={
                    'key': API_KEY, 
                    'method': 'userrecaptcha', 
                    'googlekey': site_key, 
                    'pageurl': current_url
                }
            ).text.split('|')[1]

            # Ожидание решения капчи
            recaptcha_answer = ''
            while True:
                sleep(5)
                response = requests.get(f"http://2captcha.com/res.php?key={API_KEY}&action=get&id={captcha_id}")
                if response.text == 'CAPCHA_NOT_READY':
                    continue
                if 'OK|' in response.text:
                    recaptcha_answer = response.text.split('|')[1]
                    break
            
            # Ввод решения капчи на странице
            driver.execute_script(f'document.getElementById("g-recaptcha-response").innerHTML = "{recaptcha_answer}";')
            driver.find_element(By.ID, 'submit').click()
            sleep(5)
            print("Captcha solved.")
    except Exception as e:
        print("No captcha found or error occurred:", e)

Парсинг

Далее идет участок кода, который отвечает за перебор страниц, их загрузку и прокрутку

try:
    base_url = "https://www.amazon.in/s?k=bags"

    for page_number in range(1, 10): 
        page_url = f"{base_url}&page={page_number}"

        driver.get(page_url)
        driver.implicitly_wait(10)

        solve_captcha(driver)

        WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//span[@class="a-size-medium a-color-base a-text-normal"]')))

        for _ in range(5):  
            ActionChains(driver).send_keys(Keys.PAGE_DOWN).perform()
            sleep(2)

Следующий кусок – сбор данных о товарах. Самый важный участок. В этой части скрипт изучает загруженную страницу и забирает оттуда данные, которые указаны, в нашем случае это – название продукта, количество отзывов, цена, URL, оценка товара.

        product_name_elements = driver.find_elements(By.XPATH, '//span[@class="a-size-medium a-color-base a-text-normal"]')
        rating_number_elements = driver.find_elements(By.XPATH, '//span[@class="a-size-base s-underline-text"]')
        star_rating_elements = driver.find_elements(By.XPATH, '//span[@class="a-icon-alt"]')
        price_elements = driver.find_elements(By.XPATH, '//span[@class="a-price-whole"]')
        product_urls = driver.find_elements(By.XPATH, '//a[@class="a-link-normal s-underline-text s-underline-link-text s-link-style a-text-normal"]')
        
        product_names = [element.text for element in product_name_elements]
        rating_numbers = [element.text for element in rating_number_elements]
        star_ratings = [element.get_attribute('innerHTML') for element in star_rating_elements]
        prices = [element.text for element in price_elements]
        urls = [element.get_attribute('href') for element in product_urls]

Далее происходит выгрузка указанных данных в папку (для каждой страницы создается свой файл csv, который сохраняется в папку output files), если папка отсутствует, скрипт ее создает.

        output_directory = "output files"
        if not os.path.exists(output_directory):
            os.makedirs(output_directory)
        
        with open(os.path.join(output_directory, f'product_details_page_{page_number}.csv'), 'w', newline='', encoding='utf-8') as csvfile:
            csv_writer = csv.writer(csvfile)
            csv_writer.writerow(['Product Urls', 'Product Name', 'Product Price', 'Rating', 'Number of Reviews'])
            for url, name, price, star_rating, num_ratings in zip(urls, product_names, prices, star_ratings, rating_numbers):
                csv_writer.writerow([url, name, price, star_rating, num_ratings])

И завершающий этап – завершение работы и высвобождение ресурсов.

finally:
    driver.quit()

Полный скрипт

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
import os
from time import sleep
import requests

# API key for 2Captcha
API_KEY = "Your API Key"

# Set a custom user agent to mimic a real browser
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"

options = webdriver.FirefoxOptions()
options.add_argument(f"user-agent={user_agent}")

driver = webdriver.Firefox(options=options)

def solve_captcha(driver):
    # Check for the presence of a captcha on the page
    try:
        captcha_element = driver.find_element(By.CLASS_NAME, 'g-recaptcha')
        if captcha_element:
            print("Captcha detected. Solving...")
            site_key = captcha_element.get_attribute('data-sitekey')
            current_url = driver.current_url
            
            # Send captcha request to 2Captcha
            captcha_id = requests.post(
                'http://2captcha.com/in.php', 
                data={
                    'key': API_KEY, 
                    'method': 'userrecaptcha', 
                    'googlekey': site_key, 
                    'pageurl': current_url
                }
            ).text.split('|')[1]

            # Wait for the captcha to be solved
            recaptcha_answer = ''
            while True:
                sleep(5)
                response = requests.get(f"http://2captcha.com/res.php?key={API_KEY}&action=get&id={captcha_id}")
                if response.text == 'CAPCHA_NOT_READY':
                    continue
                if 'OK|' in response.text:
                    recaptcha_answer = response.text.split('|')[1]
                    break
            
            # Inject the captcha answer into the page
            driver.execute_script(f'document.getElementById("g-recaptcha-response").innerHTML = "{recaptcha_answer}";')
            driver.find_element(By.ID, 'submit').click()
            sleep(5)
            print("Captcha solved.")
    except Exception as e:
        print("No captcha found or error occurred:", e)

try:
    # Starting page URL
    base_url = "https://www.amazon.in/s?k=bags"

    for page_number in range(1, 2): 
        page_url = f"{base_url}&page={page_number}"

        driver.get(page_url)
        driver.implicitly_wait(10)

        # Attempt to solve captcha if detected
        solve_captcha(driver)

        # Explicit Wait
        WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, '//span[@class="a-size-medium a-color-base a-text-normal"]')))

        for _ in range(5):  
            ActionChains(driver).send_keys(Keys.PAGE_DOWN).perform()
            sleep(2)

        product_name_elements = driver.find_elements(By.XPATH, '//span[@class="a-size-medium a-color-base a-text-normal"]')
        rating_number_elements = driver.find_elements(By.XPATH, '//span[@class="a-size-base s-underline-text"]')
        star_rating_elements = driver.find_elements(By.XPATH, '//span[@class="a-icon-alt"]')
        price_elements = driver.find_elements(By.XPATH, '//span[@class="a-price-whole"]')
        product_urls = driver.find_elements(By.XPATH, '//a[@class="a-link-normal s-underline-text s-underline-link-text s-link-style a-text-normal"]')
        
        # Extract and print the text content of each product name, number of ratings, and star rating, urls
        product_names = [element.text for element in product_name_elements]
        rating_numbers = [element.text for element in rating_number_elements]
        star_ratings = [element.get_attribute('innerHTML') for element in star_rating_elements]
        prices = [element.text for element in price_elements]
        urls = [element.get_attribute('href') for element in product_urls]
        
        sleep(5)        
        output_directory = "output files"
        if not os.path.exists(output_directory):
            os.makedirs(output_directory)
        
        with open(os.path.join(output_directory, f'product_details_page_{page_number}.csv'), 'w', newline='', encoding='utf-8') as csvfile:
            csv_writer = csv.writer(csvfile)
            csv_writer.writerow(['Product Urls', 'Product Name', 'Product Price', 'Rating', 'Number of Reviews'])
            for url, name, price, star_rating, num_ratings in zip(urls, product_names, prices, star_ratings, rating_numbers):
                csv_writer.writerow([url, name, price, star_rating, num_ratings])

finally:
    driver.quit()

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

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

Комментарии (5)


  1. HyperWin
    27.08.2024 19:44
    +2

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


  1. BasiC2k
    27.08.2024 19:44
    +1

    Надеюсь Вы дойдёте до уровня rainforestapi.com


    1. kentavr009
      27.08.2024 19:44

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


  1. barraqud
    27.08.2024 19:44

    Насколько помню, там вообще не нужен драйвер, хватит и одного requests(читай scrappy/aiohttp/httpx), разве что allow_redirects возможно выключить надо. Главное взять asin с поисков, а остальное собрать со страниц карточек(есть паттерн, есть asin, убираете query параметры и в путь, вроде там все нужное просто в аттрибутах лежит. Ну и естественно в асинхронку, только robots.txt не игнорируем:)))). Кстати небольшая хитрость для автора, на том же apify(или аналогах) есть возможность взять пробный пакет, а asin бывают списками в открытых доступах. Так вот если запустить и глянуть логи, то там частенько видно весь процесс сборки. просто повторив можно много времени выиграть и код там часто актуальный:)


  1. sergeseme
    27.08.2024 19:44

    Для эпизодических парсингов пользуюсь расширением браузера Instant Data Scraper - оно отлично справляется с разными расположениями карточек.