Скадрирование вкладки Google Shopping с помощью Python

  • Что будет соскоблено
  • Полный код
  • Предварительные условия
  • Объяснение кода
    • Получить исходные изображения
    • Загрузить оригинальные изображения
    • Получение данных предлагаемого поиска
  • Использование API Google Shopping Results
  • Ссылки

Что будет соскоблено

📌Примечание: Если некоторые результаты не извлекаются, значит, некоторые селекторы были изменены Google.

Полный код

import requests, json, re, os
from parsel import Selector
from serpapi import GoogleSearch

# https://docs.python-requests.org/en/master/user/quickstart/#passing-parameters-in-urls
params = {
    "q": "shoes",
    "hl": "en",     # language
    "gl": "us",     # country of the search, US -> USA
    "tbm": "shop"   # google search shopping
}

# https://docs.python-requests.org/en/master/user/quickstart/#custom-headers
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}

html = requests.get("https://www.google.com/search", params=params, headers=headers, timeout=30)
selector = Selector(html.text)


def get_original_images():
    all_script_tags = "".join(
        [
            script.replace("</script>", "</script>n")
            for script in selector.css("script").getall()
        ]
    )

    image_urls = []

    for result in selector.css(".Qlx7of .sh-dgr__grid-result"):
        # https://regex101.com/r/udjFUq/1
        url_with_unicode = re.findall(rf"vars?_u='(.*?)';vars?_i='{result.attrib['data-pck']}';", all_script_tags)

        if url_with_unicode:
            url_decode = bytes(url_with_unicode[0], 'ascii').decode('unicode-escape')
            image_urls.append(url_decode)

    # download_original_images(image_urls)  

    return image_urls


def download_original_images(image_urls):
    for index, image_url in enumerate(image_urls, start=1):
        image = requests.get(image_url, headers=headers, timeout=30, stream=True)

        if image.status_code == 200:
            print(f"Downloading {index} image...")
            with open(f"images/image_{index}.jpeg", "wb") as file:
                file.write(image.content)


def get_suggested_search_data():
    google_shopping_data = []

    for result, thumbnail in zip(selector.css(".Qlx7of .i0X6df"), get_original_images()):
        title = result.css(".Xjkr3b::text").get()       
        product_link = "https://www.google.com" + result.css(".Lq5OHe::attr(href)").get()   
        product_rating = result.css(".NzUzee .Rsc7Yb::text").get()      
        product_reviews = result.css(".NzUzee > div::text").get()       
        price = result.css(".a8Pemb::text").get()       
        store = result.css(".aULzUe::text").get()       
        store_link = "https://www.google.com" + result.css(".eaGTj div a::attr(href)").get()        
        delivery = result.css(".vEjMR::text").get()

        store_rating_value = result.css(".zLPF4b .XEeQ2 .QIrs8::text").get()
        # https://regex101.com/r/kAr8I5/1
        store_rating = re.search(r"^S+", store_rating_value).group() if store_rating_value else store_rating_value

        store_reviews_value = result.css(".zLPF4b .XEeQ2 .ugFiYb::text").get()
        # https://regex101.com/r/axCQAX/1
        store_reviews = re.search(r"^(?(S+)", store_reviews_value).group() if store_reviews_value else store_reviews_value

        store_reviews_link_value = result.css(".zLPF4b .XEeQ2 .QhE5Fb::attr(href)").get()
        store_reviews_link = "https://www.google.com" + store_reviews_link_value if store_reviews_link_value else store_reviews_link_value

        compare_prices_link_value = result.css(".Ldx8hd .iXEZD::attr(href)").get()      
        compare_prices_link = "https://www.google.com" + compare_prices_link_value if compare_prices_link_value else compare_prices_link_value

        google_shopping_data.append({
            "title": title,
            "product_link": product_link,
            "product_rating": product_rating,
            "product_reviews": product_reviews,
            "price": price,
            "store": store,
            "thumbnail": thumbnail,
            "store_link": store_link,
            "delivery": delivery,
            "store_rating": store_rating,
            "store_reviews": store_reviews,
            "store_reviews_link": store_reviews_link,
            "compare_prices_link": compare_prices_link,
        })

    print(json.dumps(google_shopping_data, indent=2, ensure_ascii=False))
Вход в полноэкранный режим Выход из полноэкранного режима

Необходимые условия

Установите библиотеки:

pip install requests lxml parsel google-search-results
Войти в полноэкранный режим Выйти из полноэкранного режима

google-search-results — это пакет SerpApi API, который будет показан в конце в качестве альтернативного решения.

Уменьшите вероятность блокировки

Убедитесь, что вы используете заголовки запроса user-agent, чтобы действовать как «реальный» пользовательский визит. Поскольку по умолчанию requests user-agent — это python-requests, и веб-сайты понимают, что, скорее всего, запрос посылает скрипт. Проверьте, какой у вас user-agent.

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

Объяснение кода

Импортируйте библиотеки:

import requests, json, re, os
from parsel import Selector
from serpapi import GoogleSearch
Войти в полноэкранный режим Выход из полноэкранного режима
Библиотека Назначение
Selector Парсер XML/HTML с полной поддержкой XPath и селекторов CSS.
requests для выполнения запроса к веб-сайту.
lxml для быстрой обработки XML/HTML документов.
json для преобразования извлеченных данных в объект JSON.
re извлекать части данных с помощью регулярного выражения.
os возвращать значение переменной окружения (ключ SerpApi API).

Создавать параметры URL и заголовки запроса:

# https://docs.python-requests.org/en/master/user/quickstart/#passing-parameters-in-urls
params = {
    "q": "shoes",
    "hl": "en",     # language
    "gl": "us",     # country of the search, US -> USA
    "tbm": "shop"   # google search shopping
}

# https://docs.python-requests.org/en/master/user/quickstart/#custom-headers
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Объяснение
params более красивый способ передачи параметров URL в запрос.
user-agent действовать как «настоящий» пользовательский запрос от браузера, передавая его в заголовки запроса. По умолчанию requests user-agent является python-reqeusts, поэтому сайты могут понять, что это бот или скрипт и заблокировать запрос к сайту. Проверьте, какой у вас user-agent.

Сделайте запрос, передайте созданные параметры запроса и заголовки. Запросите возвращаемый HTML в Selector:

html = requests.get("https://www.google.com/search", params=params, headers=headers, timeout=30)
selector = Selector(html.text)
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
timeout=30 прекратить ожидание ответа через 30 секунд.
Selector() где возвращенные HTML-данные будут обработаны parsel.

Получение исходных изображений

Чтобы найти подходящее разрешение миниатюр, нам нужно открыть исходный код страницы (CTRL+U) и найти теги <script>, которые содержат идентификаторы изображений и URL-адреса изображений в закодированном состоянии. Нам также нужен идентификатор изображения, чтобы извлечь правильное изображение листинга, а не какое-то случайное.

Вы можете напрямую разобрать данные из тега img и атрибута src, но вы получите URL в кодировке base64, который будет представлять собой изображение размером 1×1. Не слишком полезное разрешение изображения.

Прежде всего, вам нужно выделить все теги <script>. Чтобы найти их, вы можете использовать метод css() parsel. Этот метод вернет список всех совпавших тегов <script>.

all_script_tags = "".join(
    [
        script.replace("</script>", "</script>n")
        for script in selector.css("script").getall()
    ]
)
Вход в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
"".join() конкатенировать список в строку.
replace() заменить все вхождения старой подстроки на новую, чтобы регулярное выражение выполнялось правильно.
getall() вернуть список со всеми результатами.

Далее наша задача — найти теги скрипта, содержащие информацию об URL изображения и его ID. Для этого мы можем использовать цикл for и итерировать список найденных элементов.

Цикл for итерирует все изображения на странице (кроме рекламы или предложений магазина) и находит ID текущего изображения в списке all_script_tags с помощью регулярного выражения, чтобы извлечь конкретное изображение, а не случайное, проверяя его ID var _i:

for result in selector.css(".Qlx7of .sh-dgr__grid-result"):
    # https://regex101.com/r/udjFUq/1
    url_with_unicode = re.findall(rf"vars?_u='(.*?)';vars?_i='{result.attrib['data-pck']}';", all_script_tags)
Вход в полноэкранный режим Выйти из полноэкранного режима

Вот пример регулярного выражения для извлечения URL изображения и его ID из тегов <script>:

Вместо d+ нужно подставить атрибут data-pck, который хранит id изображения. Для этого мы будем использовать форматированную строку:

url_with_unicode = re.findall(rf"vars?_u='(.*?)';vars?_i='{result.attrib['data-pck']}';", all_script_tags)
Вход в полноэкранный режим Выйти из полноэкранного режима

Регулярное выражение возвращает URL изображения в закодированном состоянии. В этом случае необходимо декодировать сущности Unicode. После проделанных операций добавьте адрес в image_urls list:

url_decode = bytes(url_with_unicode[0], 'ascii').decode('unicode-escape')
image_urls.append(url_decode)
Войти в полноэкранный режим Выйти из полноэкранного режима

Функция выглядит следующим образом:

def get_original_images():
    all_script_tags = "".join(
        [
            script.replace("</script>", "</script>n")
            for script in selector.css("script").getall()
        ]
    )

    image_urls = []

    for result in selector.css(".Qlx7of .sh-dgr__grid-result"):
        # https://regex101.com/r/udjFUq/1
        url_with_unicode = re.findall(rf"vars?_u='(.*?)';vars?_i='{result.attrib['data-pck']}';", all_script_tags)

        if url_with_unicode:
            url_decode = bytes(url_with_unicode[0], 'ascii').decode('unicode-escape')
            image_urls.append(url_decode)

    # download_original_images(image_urls)  

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

Печать возвращенных данных:

[
    "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcS_YSNcYQrumsokg4AXKHXCM4kSdA1gWxmFUOZeyqRnf7nR5m8CWJ_-tCNIaNjiZzTYD3ERR0iDXenl0Q_Lswu44VHqIQPpIaxrwS0kVV08NnmLyK1lOphA&usqp=CAE",
    "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcTshSE9GfoLG_mbwZLwqx_yqnGsjR7tvuSPqmOnM8Z6uLToZ_p4DeHXa0obu5sh8QSp3vfIHuaLn5uiLIQHFVXsFb6lX0QwbSbLwS1R7nBiJLPesluLfR2T&usqp=CAE",
    "https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcTRbSGCkFzgXlPrVK3EdoBg8oqGZq4mpyLreYFGsRplcXwBBD1tUMzUEU_yiFNyo8sOimNHpaVMGgxeCk3EDXjhOu879Jb3D8JzP2sv2iP0h7vJywzMXazx&usqp=CAE",
    "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcQRPOrOELZafWAURXGD2weyznXQn-RKKsX7M9l7JoAcecF-eFwyTU-IDIzorSt4CYtTQwfh3Mr3gb7xgNW15ekBvzGUS2QGuP5FgufKAhB3rMr6saJy-dgxveNR&usqp=CAE",
    "https://encrypted-tbn0.gstatic.com/shopping?q=tbn:ANd9GcS_dJ1xHzSAdvHdqnSUV8WoYXPI7boeoqQuhF71iG8-F4gKCrFdevOWILnjO1ePA-wDYtiRuFO4K2pFcEJ3DNO0qhtN6OrL2RLlauy3EkHqdvKSx8wrAH7NHg&usqp=CAE",
    "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcRNPyBYHEhBzyKUlZs8nxb25rfeBmAhleNK0B1ClLbCVamO-IYlGgfZbKPYp9cDydlBvxbkaylGXmr2IakZnnsqGBo-OynpBs2yrgNIGqH_vu7lLOkkLuWI5A&usqp=CAE",
    "https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcR2sF3ADV112NjwYLlS7HOWRgEYHqx8FCMjXaYwQaCcXNKwhQqkPfQTKVr0FURG4lDt0OsNKXCZI3eiKsDRrLGD_lsM9MyhUjkf6l-C5Mb_pk6bJyqypcxq&usqp=CAE",
    "https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcSp8b4L3ckI6Xqv-HGv8b_a_62OOXHn4I3mC4M2DycS9CqoeIj5uClX24vL_Pwzx3ZtLbUtArVo1pqmIythL-gucrx-z6DwRsJPF4Swp2rB9JEbjeG6GokLMw&usqp=CAE",
    "https://encrypted-tbn1.gstatic.com/shopping?q=tbn:ANd9GcQO3t4RziWLeRDDGzY7ZAbmUS7oH0RfNEZ_tGJLJwcijroa1zE2qnA8vG6XCaGd99lbMp3e2O2-GFCnz9SWBf7g7zSJG4DnYTyB5Ib6InMOAOfU5oebGNGx&usqp=CAE",
    "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcS9mRGTiqUkVAZHjVnpEIiVIyW9W6haxbitsHXiMgR5Ibf4wC4aqvFclwUe6VIU75Qg_Z84dEiEhCIYc1V8uOEOIZaGESfmQwrW-3-2LUbCQEhqEHlyP_x7&usqp=CAE",
    ... other results
]
Войти в полноэкранный режим Выйти из полноэкранного режима

Загрузить исходные изображения

Теперь у нас есть список со всеми правильными URL-адресами. Мы используем тот же цикл for со встроенной функцией enumerate(). Эта функция добавляет счетчик к итерабельной таблице и возвращает его. Счетчик необходим для присвоения уникального номера каждому изображению на этапе сохранения (если вам нужно их сохранить).

В каждой итерации цикла необходимо перейти по текущей ссылке и загрузить изображение. Для локального сохранения изображения можно использовать контекстный менеджер с open().

def download_original_images(image_urls):
    for index, image_url in enumerate(image_urls, start=1):
        image = requests.get(image_url, headers=headers, timeout=30, stream=True)

        if image.status_code == 200:
            print(f"Downloading {index} image...")

            with open(f"images/image_{index}.jpeg", "wb") as file:
                file.write(image.content)
Вход в полноэкранный режим Выход из полноэкранного режима

На рисунке ниже я демонстрирую, как работает эта функция:

📌Примечание: Вы можете заметить, что изображения загружаются не в том порядке, в котором они появляются на сайте. Это происходит потому, что Google отображает страницы для каждого пользователя уникальным образом, основываясь на его местоположении, истории поиска и т.д. Вы можете узнать больше, прочитав эту статью: Причины резкого изменения результатов поиска Google.

Получение данных предлагаемого поиска

В этой функции я использую встроенную функцию zip(), которая позволяет выполнять итерацию сразу по двум итерациям, первая итерация — это селектор всех списков товаров, вторая — список URL миниатюр, возвращенных функцией get_original_images().

Вы можете заметить, что некоторые данные могут отсутствовать в некоторых карточках товаров:

Эта функция использует библиотеку Parsel, чтобы избежать использования exceptions. Если вы попытаетесь разобрать несуществующие данные, значение будет автоматически записано в None и программа не остановится. В результате вся информация о товаре добавляется в google_shopping_data list:

def get_suggested_search_data():
    google_shopping_data = []

    for result, thumbnail in zip(selector.css(".Qlx7of .i0X6df"), get_original_images()):
        title = result.css(".Xjkr3b::text").get()       
        product_link = "https://www.google.com" + result.css(".Lq5OHe::attr(href)").get()   
        product_rating = result.css(".NzUzee .Rsc7Yb::text").get()      
        product_reviews = result.css(".NzUzee > div::text").get()       
        price = result.css(".a8Pemb::text").get()       
        store = result.css(".aULzUe::text").get()       
        store_link = "https://www.google.com" + result.css(".eaGTj div a::attr(href)").get()        
        delivery = result.css(".vEjMR::text").get()

        store_rating_value = result.css(".zLPF4b .XEeQ2 .QIrs8::text").get()
        # https://regex101.com/r/kAr8I5/1
        store_rating = re.search(r"^S+", store_rating_value).group() if store_rating_value else store_rating_value

        store_reviews_value = result.css(".zLPF4b .XEeQ2 .ugFiYb::text").get()
        # https://regex101.com/r/axCQAX/1
        store_reviews = re.search(r"^(?(S+)", store_reviews_value).group() if store_reviews_value else store_reviews_value

        store_reviews_link_value = result.css(".zLPF4b .XEeQ2 .QhE5Fb::attr(href)").get()
        store_reviews_link = "https://www.google.com" + store_reviews_link_value if store_reviews_link_value else store_reviews_link_value

        compare_prices_link_value = result.css(".Ldx8hd .iXEZD::attr(href)").get()      
        compare_prices_link = "https://www.google.com" + compare_prices_link_value if compare_prices_link_value else compare_prices_link_value

        google_shopping_data.append({
            "title": title,
            "product_link": product_link,
            "product_rating": product_rating,
            "product_reviews": product_reviews,
            "price": price,
            "store": store,
            "thumbnail": thumbnail,
            "store_link": store_link,
            "delivery": delivery,
            "store_rating": store_rating,
            "store_reviews": store_reviews,
            "store_reviews_link": store_reviews_link,
            "compare_prices_link": compare_prices_link,
        })

    print(json.dumps(google_shopping_data, indent=2, ensure_ascii=False))
Вход в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
google_shopping_data временный список, куда извлеченные данные будут добавлены в конце функции.
zip() для парралельного перебора нескольких итераций. Помните, что zip используется специально. zip() завершается самым коротким итератором, а zip_longest() выполняет итерацию до длины самого длинного итератора.
css() для доступа к элементам по переданному селектору.
::text или ::attr(<attribute>) для извлечения текстовых или атрибутивных данных из узла.
get() для фактического извлечения текстовых данных.
search() для поиска шаблона в строке и возврата соответствующего объекта match.
group() извлечь найденный элемент из объекта соответствия.
google_shopping_data.append({}) применить извлеченные данные изображений к списку в виде словаря.

Вывести возвращаемые данные:

[
  {
    "title": "Jordan Boys AJ 1 Mid - Basketball Shoes Black/Dark Iris/White Size 11.0",
    "product_link": "https://www.google.com/shopping/product/2446415938651229617?q=shoes&hl=en&gl=us&prds=eto:8229454466840606844_0,pid:299671923759156329,rsk:PC_2261195288052060612&sa=X&ved=0ahUKEwi5qJ-i1pH5AhU1j2oFHYH0A1EQ8wIIkhQ",
    "product_rating": "5.0",
    "product_reviews": "2",
    "price": "$70.00",
    "store": "Nike",
    "thumbnail": "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcS_YSNcYQrumsokg4AXKHXCM4kSdA1gWxmFUOZeyqRnf7nR5m8CWJ_-tCNIaNjiZzTYD3ERR0iDXenl0Q_Lswu44VHqIQPpIaxrwS0kVV08NnmLyK1lOphA&usqp=CAE",
    "store_link": "https://www.google.com/url?url=https://www.nike.com/t/jordan-1-mid-little-kids-shoes-Rt7WmQ/640734-095%3Fnikemt%3Dtrue&rct=j&q=&esrc=s&sa=U&ved=0ahUKEwi5qJ-i1pH5AhU1j2oFHYH0A1EQguUECJQU&usg=AOvVaw33rVk6a7KZ7tSuibaJiP8L",
    "delivery": "Delivery by Thu, Aug 11",
    "store_rating": "4.6",
    "store_reviews": "2.6K",
    "store_reviews_link": "https://www.google.com/url?url=https://www.google.com/shopping/ratings/account/metrics%3Fq%3Dnike.com%26c%3DUS%26v%3D18%26hl%3Den&rct=j&q=&esrc=s&sa=U&ved=0ahUKEwi5qJ-i1pH5AhU1j2oFHYH0A1EQ9-wCCJsU&usg=AOvVaw3pWu7Rw-rfT2lXuzldJ4f-",
    "compare_prices_link": "https://www.google.com/shopping/product/2446415938651229617/offers?q=shoes&hl=en&gl=us&prds=eto:8229454466840606844_0,pid:299671923759156329,rsk:PC_2261195288052060612&sa=X&ved=0ahUKEwi5qJ-i1pH5AhU1j2oFHYH0A1EQ3q4ECJwU"
  },
  ... other results
]
Войти в полноэкранный режим Выйти из полноэкранного режима

Использование API Google Shopping Results

Основное отличие заключается в более быстром подходе. Google Shopping Results API будет обходить блоки от поисковых систем, и вам не придется создавать парсер с нуля и поддерживать его.

Пример кода для интеграции:

from serpapi import GoogleSearch
import requests, lxml, os, json

params = {
    "q": "shoes",                       # search query
    "tbm": "shop",                      # shop results
    "location": "Dallas",               # location from where search comes from
    "hl": "en",                         # language of the search
    "gl": "us",                         # country of the search
    # https://docs.python.org/3/library/os.html#os.getenv
    "api_key": os.getenv("API_KEY"),    # your serpapi api
}

# https://docs.python-requests.org/en/master/user/quickstart/#custom-headers
headers = {
    "User-Agent":
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}

search = GoogleSearch(params)           # where data extraction happens on the SerpApi backend
results = search.get_dict()             # JSON -> Python dict


def download_google_shopping_images():
    for index, result in enumerate(results["shopping_results"], start=1):
        image = requests.get(result['thumbnail'], headers=headers, timeout=30, stream=True)

        if image.status_code == 200:
            with open(f"images/image_{index}.jpeg", 'wb') as file:
                file.write(image.content)


def serpapi_get_google_shopping_data():
    google_shopping_data = results["shopping_results"]

    # download_google_shopping_images()

    print(json.dumps(google_shopping_data, indent=2, ensure_ascii=False)) 
Вход в полноэкранный режим Выход из полноэкранного режима

Выходные данные:

[
  {
    "position": 1,
    "title": "Jordan (PS) Jordan 1 Mid Black/Dark Iris-White",
    "link": "https://www.google.com/url?url=https://www.nike.com/t/jordan-1-mid-little-kids-shoes-Rt7WmQ/640734-095%3Fnikemt%3Dtrue&rct=j&q=&esrc=s&sa=U&ved=0ahUKEwiS3MaljJX5AhURILkGHTBYCSUQguUECIQW&usg=AOvVaw1GOVGa9RfptVnLwtJxW13f",
    "product_link": "https://www.google.com/shopping/product/2446415938651229617",
    "product_id": "2446415938651229617",
    "serpapi_product_api": "https://serpapi.com/search.json?device=desktop&engine=google_product&gl=us&google_domain=google.com&hl=en&location=Dallas&product_id=2446415938651229617",
    "source": "Nike",
    "price": "$70.00",
    "extracted_price": 70.0,
    "rating": 5.0,
    "reviews": 2,
    "thumbnail": "https://encrypted-tbn2.gstatic.com/shopping?q=tbn:ANd9GcS_YSNcYQrumsokg4AXKHXCM4kSdA1gWxmFUOZeyqRnf7nR5m8CWJ_-tCNIaNjiZzTYD3ERR0iDXenl0Q_Lswu44VHqIQPpIaxrwS0kVV08NnmLyK1lOphA&usqp=CAE",
    "delivery": "Delivery by Sun, Aug 14"
  },
  ... other results
]
Войти в полноэкранный режим Выход из полноэкранного режима

Ссылки

  • Код в онлайн IDE
  • Google Shopping Results API

Первоначально опубликовано на SerpApi: https://serpapi.com/blog/web-scraping-google-shopping-tab-in-python/

Присоединяйтесь к нам на Twitter | YouTube

Добавить запрос на улучшение💫 или ошибку🐞

Оцените статью
devanswers.ru
Добавить комментарий