Очистите вкладку Google Shopping с помощью Python

Что будет очищено

📌Примечание. Если некоторые результаты не извлекаются, 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"var\s?_u='(.*?)';var\s?_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 — это пакет API SerpApi, который будет показан в конце как альтернативное решение.

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

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

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

Код Пояснение

Импортировать библиотеки:

import requests, json, re, os 
from parsel import Selector 
from serpapi import GoogleSearch
  • Селекторный парсер XML/HTML с полной поддержкой селекторов XPath и CSS.
  • запросы сделать запрос на сайт.
  • 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"
}

Сделать запрос, передать созданные параметры запроса и заголовки. Запрос вернул 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, который будет заполнителем изображения 1x1. Не особенно полезное разрешение изображения.

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

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

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

Цикл for перебирает все изображения на странице (кроме рекламы или предложений магазина) и находит идентификатор текущего изображения в списке 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"var\s?_u='(.*?)';var\s?_i='{result.attrib['data-pck']}';", all_script_tags)

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

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

url_with_unicode = re.findall(rf"var\s?_u='(.*?)';var\s?_i='{result.attrib['data-pck']}';", all_script_tags)

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

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"var\s?_u='(.*?)';var\s?_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(). Эта функция добавляет счетчик к итерируемому объекту и возвращает его. Счетчик нужен для присвоения уникального номера каждому изображению на этапе сохранения (если нужно их сохранить).

В каждой итерации цикла вы должны перейти по текущей ссылке и загрузить изображение. Вы можете использовать with 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(), которая позволяет вам перебирать две итерации одновременно, первая итерация — это селектор всех списков продуктов, вторая — это list URL-адресов эскизов, возвращаемых функцией get_original_images(). .

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

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

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))

Распечатать возвращенные данные:

[
  {
    "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

Основное отличие в том, что это более быстрый подход. 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 
]

Ссылки

Первоначально опубликовано на SerpApi: https://serpapi.com/blog/scrape-google-finance-markets-in-python/

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

Добавьте Запрос функции💫 или Ошибку🐞