Get Product Data from Bing Shopping with Python and SerpApi

This blog post is a step-by-step tutorial for scraping Bing Shopping using SerpApi and Python.

Β·

10 min read

What will be scraped

wwbs-bing-shopping

πŸ“ŒNote: Inline shopping results may not always be present.

Full Code

If you don't need an explanation, have a look at the full code example in the online IDE.

from selectolax.lexbor import LexborHTMLParser
import requests, json, re


def get_inline_shopping_results(parser: LexborHTMLParser, product_counter: int) -> list:
    data = []

    for position, product in enumerate(parser.root.css('.br-gOffCard'), start=product_counter):
        raw_title = product.css_first('.br-offTtl span')
        raw_shipping = product.css_first('.br-offDec')
        raw_rating = product.css_first('.tags > span')

        title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-offTtl').text().strip()
        product_link = product.css_first('.br-offLink').attributes.get('href', '')
        brand = product.css_first('.br-offSecLbl').text().strip() if product.css_first('.br-offSecLbl') else None
        seller = product.css_first('.br-offSlr').text().strip()
        price = product.css_first('.br-price').text().strip()
        # https://regex101.com/r/lap8lr/1
        extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
        old_price = product.css_first('.br-standardPriceDemoted').text().strip() if product.css_first('.br-standardPriceDemoted') else None
        shipping_text = raw_shipping.text().strip() if raw_shipping else ''
        shipping = shipping_text if 'shipping' in shipping_text else None
        rating_text = raw_rating.attributes.get('aria-label', '') if raw_rating else None
        # https://regex101.com/r/D2YTRI/1
        rating = float(re.search(r'[\d.\d|\d]+', rating_text).group(0)) if rating_text else None
        reviews_text = raw_rating.text().strip() if raw_rating else None
        reviews = reviews_text.replace('(', '').replace(')', '') if reviews_text else None
        thumbnail = product.css_first('.cico img').attributes.get('src', None) if product.css_first('.cico img') else None

        data.append({
            'position': position,
            'title': title,
            'product_link': product_link,
            'brand': brand,
            'seller': seller,
            'price': price,
            'extracted_price': extracted_price,
            'old_price': old_price,
            'shipping': shipping,
            'rating': rating,
            'reviews': reviews,
            'thumbnail': thumbnail
        })

    return data


def get_shopping_results(parser: LexborHTMLParser, product_counter: int) -> list:
    data = []

    for position, product in enumerate(parser.root.css('.br-fullCard'), start=product_counter):
        raw_title = product.css_first('.br-title span')

        title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-title').text().strip()
        product_link = f"https://www.bing.com{product.css_first('.br-titlelink').attributes.get('href', '')}"
        seller = product.css_first('.br-seller').text().strip()
        price = product.css_first('.pd-price').text().strip()
        # https://regex101.com/r/lap8lr/1
        extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
        sale = True if product.css_first('.br-saletag') else False
        old_price = product.css_first('.br-pdOfferPrice').text().strip() if product.css_first('.br-pdOfferPrice') else None
        shipping = product.css_first('.br-decoSlot').text().strip() if product.css_first('.br-decoSlot') else None
        thumbnail = product.css_first('.br-pdMainImg img').attributes.get('src', None) if product.css_first('.br-pdMainImg img') else None

        data.append({
            'position': position,
            'title': title,
            'product_link': product_link,
            'seller': seller,
            'price': price,
            'extracted_price': extracted_price,
            'sale': sale,
            'old_price': old_price,
            'shipping': shipping,
            'thumbnail': thumbnail
        })

    return data


def main():
    # 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/111.0.0.0 Safari/537.36'
    }

    url = 'https://www.bing.com/shop?q=ps4 controller'

    inline_shopping_results_counter: int = 1
    shopping_results_counter: int = 1

    bing_shopping_results = {
        'inline_shopping_results': [],
        'shopping_results': []
    }

    while True:
        html = requests.get(url, headers=headers)
        parser = LexborHTMLParser(html.text)

        inline_shopping_page_results = get_inline_shopping_results(parser, inline_shopping_results_counter)
        bing_shopping_results['inline_shopping_results'].extend(inline_shopping_page_results)

        shopping_page_results = get_shopping_results(parser, shopping_results_counter)
        bing_shopping_results['shopping_results'].extend(shopping_page_results)

        next_page_button = parser.css_first('.sb_pagN_bp')

        if next_page_button:
            url = f"https://www.bing.com{next_page_button.attributes.get('href', '')}"
            inline_shopping_results_counter += len(inline_shopping_page_results)
            shopping_results_counter += len(shopping_page_results)
        else:
            break

    print(json.dumps(bing_shopping_results, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

Preparation

Install libraries

pip install requests selectolax

Reduce the chance of being blocked

Make sure you're using request headers user-agent to act as a "real" user visit. Because default requests user-agent is python-requests and websites understand that it's most likely a script that sends a request. Check what's your user-agent.

There's a how to reduce the chance of being blocked while web scraping blog post that can get you familiar with basic and more advanced approaches.

Code Explanation

Import libraries:

from selectolax.lexbor import LexborHTMLParser
import requests, json, re
LibraryPurpose
LexborHTMLParsera fast HTML5 parser with CSS selectors using Lexbor engine.
requeststo make a request to the website.
jsonto convert extracted data to a JSON object.
reto extract parts of the data via regular expression.

The next part of the code is divided into functions. Each function is described in the corresponding heading below.

Top-level code environment

The request headers are generated:

# 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/111.0.0.0 Safari/537.36'
}

The url variable contains a link to the Bing Shopping page:

url = 'https://www.bing.com/shop?q=ps4 controller'

Variables are created to determine the position of the current product, taking into account pagination. For example, if on the first page the position of the last product is 36, then on the second page the position of the first product will be 37 (not 1):

inline_shopping_results_counter: int = 1
shopping_results_counter: int = 1

The bing_shopping_results dictionary is formed, where data on the corresponding keys will subsequently be added:

bing_shopping_results = {
    'inline_shopping_results': [],
    'shopping_results': []
}

Requests are sent in a loop for each page:

while True:
    # pagination

Make a request, pass url and headers. The data extraction itself is done with selectolax because it has Lexbor parser which is incredibly fast, like 186% faster compared to bs4 with lxml backend when parsing data with 3000 iterations 5 times:

html = requests.get(url, headers=headers)
parser = LexborHTMLParser(html.text)

By calling the appropriate functions, all the necessary data is retrieved and written to the bing_shopping_results dictionary. These functions are detailed in the sections below.

inline_shopping_page_results = get_inline_shopping_results(parser, inline_shopping_results_counter)
bing_shopping_results['inline_shopping_results'].extend(inline_shopping_page_results)

shopping_page_results = get_shopping_results(parser, shopping_results_counter)
bing_shopping_results['shopping_results'].extend(shopping_page_results)

At the end of the iteration, it checks whether the "Next" button is present. If such a button is present, then the url and counters are updated. Otherwise, the loop is stopped.

next_page_button = parser.css_first('.sb_pagN_bp')

if next_page_button:
    url = f"https://www.bing.com{next_page_button.attributes.get('href', '')}"
    inline_shopping_results_counter += len(inline_shopping_page_results)
    shopping_results_counter += len(shopping_page_results)
else:
    break

After receiving the data from all pages, they are output in JSON format:

print(json.dumps(bing_shopping_results, indent=2, ensure_ascii=False))

This code uses boilerplate __name__ == "__main__" construct that protects users from accidentally invoking the script when they didn't intend to. This indicates that the code is a runnable script:

def main():
    # 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/111.0.0.0 Safari/537.36'
    }

    url = 'https://www.bing.com/shop?q=ps4 controller'

    inline_shopping_results_counter: int = 1
    shopping_results_counter: int = 1

    bing_shopping_results = {
        'inline_shopping_results': [],
        'shopping_results': []
    }

    while True:
        html = requests.get(url, headers=headers)
        parser = LexborHTMLParser(html.text)

        inline_shopping_page_results = get_inline_shopping_results(parser, inline_shopping_results_counter)
        bing_shopping_results['inline_shopping_results'].extend(inline_shopping_page_results)

        shopping_page_results = get_shopping_results(parser, shopping_results_counter)
        bing_shopping_results['shopping_results'].extend(shopping_page_results)

        next_page_button = parser.css_first('.sb_pagN_bp')

        if next_page_button:
            url = f"https://www.bing.com{next_page_button.attributes.get('href', '')}"
            inline_shopping_results_counter += len(inline_shopping_page_results)
            shopping_results_counter += len(shopping_page_results)
        else:
            break

    print(json.dumps(bing_shopping_results, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()

This check will only be performed if the user has run this file. If the user imports this file into another, then the check will not work.

You can watch the video Python Tutorial: if name == 'main' for more details.

Get inline shopping results

The function takes a parser and a product_counter. Returns a list with the retrieved data.

The data list is declared to which the extracted data will be added:

data = []

To retrieve all products from a page, you need to use the css() method and pass the .br-gOffCard selector there.

After that, you need to iterate over the resulting list of products using the for loop with the built-in enumerate() function. This function adds a counter to the iterable and returns it. The counter is needed to assign a position to each product:

for position, product in enumerate(parser.root.css('.br-gOffCard'), start=product_counter):
    # data extraction

Retrieving the required data is done as follows: we find the required selector for current product using the css_first() method, after this we extract the text value or attribute value:

product_link = product.css_first('.br-offLink').attributes.get('href', '')
seller = product.css_first('.br-offSlr').text().strip()
price = product.css_first('.br-price').text().strip()

There are data that may not be present in some products. In this case, checks are added:

raw_title = product.css_first('.br-offTtl span')
raw_shipping = product.css_first('.br-offDec')
raw_rating = product.css_first('.tags > span')

title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-offTtl').text().strip()
brand = product.css_first('.br-offSecLbl').text().strip() if product.css_first('.br-offSecLbl') else None
old_price = product.css_first('.br-standardPriceDemoted').text().strip() if product.css_first('.br-standardPriceDemoted') else None
shipping_text = raw_shipping.text().strip() if raw_shipping else ''
shipping = shipping_text if 'shipping' in shipping_text else None
rating_text = raw_rating.attributes.get('aria-label', '') if raw_rating else None
reviews_text = raw_rating.text().strip() if raw_rating else None
reviews = reviews_text.replace('(', '').replace(')', '') if reviews_text else None
thumbnail = product.css_first('.cico img').attributes.get('src', None) if product.css_first('.cico img') else None

If you need to get a specific value, then you can use regular expression to extract them:

# https://regex101.com/r/lap8lr/1
extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
# https://regex101.com/r/D2YTRI/1
rating = float(re.search(r'[\d.\d|\d]+', rating_text).group(0)) if rating_text else None

At the end of each iteration, product data is added to the data list:

data.append({
    'position': position,
    'title': title,
    'product_link': product_link,
    'brand': brand,
    'seller': seller,
    'price': price,
    'extracted_price': extracted_price,
    'old_price': old_price,
    'shipping': shipping,
    'rating': rating,
    'reviews': reviews,
    'thumbnail': thumbnail
})

After all the operations are done, return the data list:

return data

The function looks like this:

def get_inline_shopping_results(parser: LexborHTMLParser, product_counter: int) -> list:
    data = []

    for position, product in enumerate(parser.root.css('.br-gOffCard'), start=product_counter):
        raw_title = product.css_first('.br-offTtl span')
        raw_shipping = product.css_first('.br-offDec')
        raw_rating = product.css_first('.tags > span')

        title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-offTtl').text().strip()
        product_link = product.css_first('.br-offLink').attributes.get('href', '')
        brand = product.css_first('.br-offSecLbl').text().strip() if product.css_first('.br-offSecLbl') else None
        seller = product.css_first('.br-offSlr').text().strip()
        price = product.css_first('.br-price').text().strip()
        # https://regex101.com/r/lap8lr/1
        extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
        old_price = product.css_first('.br-standardPriceDemoted').text().strip() if product.css_first('.br-standardPriceDemoted') else None
        shipping_text = raw_shipping.text().strip() if raw_shipping else ''
        shipping = shipping_text if 'shipping' in shipping_text else None
        rating_text = raw_rating.attributes.get('aria-label', '') if raw_rating else None
        # https://regex101.com/r/D2YTRI/1
        rating = float(re.search(r'[\d.\d|\d]+', rating_text).group(0)) if rating_text else None
        reviews_text = raw_rating.text().strip() if raw_rating else None
        reviews = reviews_text.replace('(', '').replace(')', '') if reviews_text else None
        thumbnail = product.css_first('.cico img').attributes.get('src', None) if product.css_first('.cico img') else None

        data.append({
            'position': position,
            'title': title,
            'product_link': product_link,
            'brand': brand,
            'seller': seller,
            'price': price,
            'extracted_price': extracted_price,
            'old_price': old_price,
            'shipping': shipping,
            'rating': rating,
            'reviews': reviews,
            'thumbnail': thumbnail
        })

    return data

Get shopping results

The function takes a parser and a product_counter. Returns a list with the retrieved data.

The data list is declared to which the extracted data will be added:

data = []

In the previous function, the data extraction process was described in detail. In this function, the process is very similar except for different data and correspondingly different selectors:

for position, product in enumerate(parser.root.css('.br-fullCard'), start=product_counter):
    raw_title = product.css_first('.br-title span')

    title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-title').text().strip()
    product_link = f"https://www.bing.com{product.css_first('.br-titlelink').attributes.get('href', '')}"
    seller = product.css_first('.br-seller').text().strip()
    price = product.css_first('.pd-price').text().strip()
    # https://regex101.com/r/lap8lr/1
    extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
    sale = True if product.css_first('.br-saletag') else False
    old_price = product.css_first('.br-pdOfferPrice').text().strip() if product.css_first('.br-pdOfferPrice') else None
    shipping = product.css_first('.br-decoSlot').text().strip() if product.css_first('.br-decoSlot') else None
    thumbnail = product.css_first('.br-pdMainImg img').attributes.get('src', None) if product.css_first('.br-pdMainImg img') else None

At the end of each iteration, product data is added to the data list:

data.append({
    'position': position,
    'title': title,
    'product_link': product_link,
    'seller': seller,
    'price': price,
    'extracted_price': extracted_price,
    'sale': sale,
    'old_price': old_price,
    'shipping': shipping,
    'thumbnail': thumbnail
})

After all the operations are done, return the data list:

return data

The function looks like this:

def get_shopping_results(parser: LexborHTMLParser, product_counter: int) -> list:
    data = []

    for position, product in enumerate(parser.root.css('.br-fullCard'), start=product_counter):
        raw_title = product.css_first('.br-title span')

        title = raw_title.attributes.get('title', '') if raw_title else product.css_first('.br-title').text().strip()
        product_link = f"https://www.bing.com{product.css_first('.br-titlelink').attributes.get('href', '')}"
        seller = product.css_first('.br-seller').text().strip()
        price = product.css_first('.pd-price').text().strip()
        # https://regex101.com/r/lap8lr/1
        extracted_price = float(re.search(r'[\d|,|.]+', price).group().replace(',', ''))
        sale = True if product.css_first('.br-saletag') else False
        old_price = product.css_first('.br-pdOfferPrice').text().strip() if product.css_first('.br-pdOfferPrice') else None
        shipping = product.css_first('.br-decoSlot').text().strip() if product.css_first('.br-decoSlot') else None
        thumbnail = product.css_first('.br-pdMainImg img').attributes.get('src', None) if product.css_first('.br-pdMainImg img') else None

        data.append({
            'position': position,
            'title': title,
            'product_link': product_link,
            'seller': seller,
            'price': price,
            'extracted_price': extracted_price,
            'sale': sale,
            'old_price': old_price,
            'shipping': shipping,
            'thumbnail': thumbnail
        })

    return data

Output

{
  "inline_shopping_results": [
    {
      "position": 1,
      "title": "Sony Playstation 4 Dual Shock 4 Controller",
      "product_link": "https://www.bing.com/aclick?ld=e8y8UnkNEA5yLnmFwFXVJsIDVUCUxGDjCRfBB7hezInXrQF_5sOcYbnlwQ7FroV_Zn5FefQcj7dQqTSlvA3lj2I21y0MviXMAVnyW-3FkoUoi16_LPCXsLblfhhQ2D_DBXPc7yCF56HaNeXUDrxLymGBGLDEWL241igNH5h1ZNrEBK3Hy1&u=aHR0cHMlM2ElMmYlMmZ3d3cuYW1hem9uLmNvbSUyZlNvbnktUGxheVN0YXRpb24tRHVhbHNob2NrLVdpcmVsZXNzLUNvbnRyb2xsZXItQmxhY2slMmZkcCUyZkIwMEQ4Mlo0WU8lMmZyZWYlM2Rhc2NfZGZfQjAwRDgyWjRZTyUzZnRhZyUzZGJpbmdzaG9wcGluZ2EtMjAlMjZsaW5rQ29kZSUzZGRmMCUyNmh2YWRpZCUzZDc5OTg5NTk3MTQ0NTE1JTI2aHZuZXR3JTNkbyUyNmh2cW10JTNkZSUyNmh2Ym10JTNkYmUlMjZodmRldiUzZGMlMjZodmxvY2ludCUzZCUyNmh2bG9jcGh5JTNkJTI2aHZ0YXJnaWQlM2RwbGEtNDU4MzU4OTExODI4NTM1MCUyNnBzYyUzZDE&rlid=044e0ea8d1d91e49a33a6a95b021d2b3",
      "brand": "580+ viewed",
      "seller": "Amazon.com",
      "price": "$56.00",
      "extracted_price": 56.0,
      "old_price": null,
      "shipping": null,
      "rating": 4.5,
      "reviews": null,
      "thumbnail": "https://th.bing.com/th?id=OP.javWvhvskVjF3A474C474&w=140&h=140&pid=21.1"
    },
    {
      "position": 2,
      "title": "Wireless Controller For PS4/Slim/Pro, Berry Blue",
      "product_link": "https://www.bing.com/aclick?ld=e8K5Qjv1rx7b91q1TGu2kIHDVUCUzljX6PIJ1_Ag71Iy5QZiGWnbe49xj4OnlDYYwXIZtyHeZHN66fGgfhttXqkZjtRPPAG3TZF7rGH0z6XerddgQBQx_dfYoJlYMoPPnC1ujLoNsFoUL6VTMyu4Ln14u3EoPqCJV8wSvMZ7FHfLWcEayH&u=aHR0cHMlM2ElMmYlMmZjbGlja3NlcnZlLmRhcnRzZWFyY2gubmV0JTJmbGluayUyZmNsaWNrJTNmJTI2JTI2ZHNfZV9hZGlkJTNkNzUzMTY2OTg4MTk5MTglMjZkc19lX3RhcmdldF9pZCUzZHBsYS00NTc4OTE2MjM5MDc0Njc5JTI2ZHNfZV9wcm9kdWN0X2dyb3VwX2lkJTNkNDU3ODkxNjIzOTA3NDY3OSUyNmRzX2VfcHJvZHVjdF9pZCUzZDEzOTY2MDA3OV8xMDAwMTEzNTk4OCUyNmRzX2VfcHJvZHVjdF9jb3VudHJ5JTNkVVMlMjZkc19lX3Byb2R1Y3RfbGFuZ3VhZ2UlM2RFTiUyNmRzX2VfcHJvZHVjdF9jaGFubmVsJTNkT25saW5lJTI2ZHNfdXJsX3YlM2QyJTI2ZHNfZGVzdF91cmwlM2RodHRwcyUzYSUyZiUyZnd3dy53YWxtYXJ0LmNvbSUyZmlwJTJmV2lyZWxlc3MtQ29udHJvbGxlci1mb3ItUFM0LVNsaW0tUHJvLUJlcnJ5LUJsdWUlMmYxMzk2NjAwNzklM2Z3bWxzcGFydG5lciUzZHdscGElMjZzZWxlY3RlZFNlbGxlcklkJTNkMTAxMTE2NjI2JTI2YWRpZCUzZDIyMjIyMjIyMjIyNTc2ODA1MzgwJTI2d21sc3BhcnRuZXIlM2R3bXRsYWJzJTI2d2wwJTNkZSUyNndsMSUzZG8lMjZ3bDIlM2RjJTI2d2wzJTNkNzUzMTY2OTg4MTk5MTglMjZ3bDQlM2RwbGEtNDU3ODkxNjIzOTA3NDY3OSUyNndsNSUzZCUyNndsNiUzZCUyNndsNyUzZCUyNndsMTAlM2RXYWxtYXJ0JTI2d2wxMSUzZE9ubGluZSUyNndsMTIlM2QxMzk2NjAwNzlfMTAwMDExMzU5ODglMjZ3bDE0JTNkcHM0JTI1MjBjb250cm9sbGVyJTI2dmVoJTNkc2VtJTI2Z2NsaWQlM2Q1NjkxZjc3M2I3ODQxMTQ2ZTVhYjkzNGVkNjdjNTQ0OSUyNmdjbHNyYyUzZDNwLmRzJTI2bXNjbGtpZCUzZDU2OTFmNzczYjc4NDExNDZlNWFiOTM0ZWQ2N2M1NDQ5&rlid=5691f773b7841146e5ab934ed67c5449",
      "brand": "Brand: SPBPQY",
      "seller": "Walmart",
      "price": "$19.99",
      "extracted_price": 19.99,
      "old_price": null,
      "shipping": "Free shipping",
      "rating": null,
      "reviews": null,
      "thumbnail": "https://th.bing.com/th?id=OP.AjeAqUx49FCBaQ474C474&w=140&h=140&pid=21.1"
    },
    {
      "position": 3,
      "title": "YCCTEAM Wireless Ps4 Controller ,Wireless Game Controller Compatible With Playstation 4/Slim/Pro, Built-In 1000Mah Battery With Turbo/Dual Vibration",
      "product_link": "https://www.bing.com/aclick?ld=e8XOuCV1B-F1vb3cgxnxYFWjVUCUxeNZ_G0jSncEe8IzWF8UzLDZihTUvXFtS_U7DFDfJNV_L4aBCyLLwDxIW3jnYGUaW0Q0C4MhhYByxkPdaXmHEexgmFgmfKA1EITFmoVR4_Yla_FXAV-Nmx6yJiQmPLtRwNLSr6eWXTZE07O-ZjlAPX&u=aHR0cHMlM2ElMmYlMmZ3d3cuYW1hem9uLmNvbSUyZkNvbnRyb2xsZXItQ2hhcmdpbmctWUNDVEVBTS1JbmRpY2F0b3ItSm95c3RpY2tzJTJmZHAlMmZCMDdXN0gyMVZWJTJmcmVmJTNkYXNjX2RmX0IwN1c3SDIxVlYlM2Z0YWclM2RiaW5nc2hvcHBpbmdhLTIwJTI2bGlua0NvZGUlM2RkZjAlMjZodmFkaWQlM2Q3OTg1MjEyNDE1MTUzNSUyNmh2bmV0dyUzZG8lMjZodnFtdCUzZGUlMjZodmJtdCUzZGJlJTI2aHZkZXYlM2RjJTI2aHZsb2NpbnQlM2QlMjZodmxvY3BoeSUzZCUyNmh2dGFyZ2lkJTNkcGxhLTQ1ODM0NTE2NjkzOTg1NzklMjZwc2MlM2Qx&rlid=15ad8be3c1af10f584ffefdea057bbd8",
      "brand": null,
      "seller": "Amazon.com",
      "price": "$19.99",
      "extracted_price": 19.99,
      "old_price": null,
      "shipping": null,
      "rating": null,
      "reviews": null,
      "thumbnail": "https://th.bing.com/th?id=OP.NsyWoG%2bGBv4G3A474C474&w=140&h=140&pid=21.1"
    },
    ... other inline shopping results
  ],
  "shopping_results": [
    {
      "position": 1,
      "title": "Sony PS4 Dualshock 4 Wireless Controller - Midnight Blue",
      "product_link": "https://www.bing.com/shop/productpage?q=ps4+controller&filters=scenario%3a%2217%22+gType%3a%223%22+gId%3a%22IKQSlWYtjMJCH45YKbCW1INZT5%22+gIdHash%3a%221034604334%22+gGlobalOfferIds%3a%2297596786905%22+AucContextGuid%3a%220%22+GroupEntityId%3a%22IKQSlWYtjMJCH45YKbCW1INZT5%22+NonSponsoredOffer%3a%22True%22&productpage=true&FORM=SHPPDP&browse=true",
      "seller": "Walmart",
      "price": "$64.00",
      "extracted_price": 64.0,
      "sale": false,
      "old_price": null,
      "shipping": "Free shipping",
      "thumbnail": "https://th.bing.com/th?id=OP.5KY3rYxghNq1ng474C474&w=272&h=272&o=5&pid=21.1"
    },
    {
      "position": 2,
      "title": "Sony Dual Shock 4 Bluetooth Controller For PS4 - Black",
      "product_link": "https://www.bing.com/shop/productpage?q=ps4+controller&filters=scenario%3a%2217%22+gType%3a%223%22+gId%3a%22IjzPcGXIjh5kEfHzF4DdKkdm7O%22+gIdHash%3a%22503285226%22+gGlobalOfferIds%3a%2213378540660%22+AucContextGuid%3a%220%22+GroupEntityId%3a%22IjzPcGXIjh5kEfHzF4DdKkdm7O%22+NonSponsoredOffer%3a%22True%22&productpage=true&FORM=SHPPDP&browse=true",
      "seller": "Tech Import World",
      "price": "$51.92",
      "extracted_price": 51.92,
      "sale": false,
      "old_price": null,
      "shipping": null,
      "thumbnail": "https://th.bing.com/th?id=OP.2zrQISY7yRggYA474C474&w=272&h=272&o=5&pid=21.1"
    },
    {
      "position": 3,
      "title": "Sony PS4 Dualshock 4 Wireless Controller - Green Camouflage",
      "product_link": "https://www.bing.com/shop/productpage?q=ps4+controller&filters=scenario%3a%2217%22+gType%3a%223%22+gId%3a%22IsUwpe7StYMy9zWBEooypnccb6%22+gIdHash%3a%221451490157%22+gGlobalOfferIds%3a%2285554586355%22+AucContextGuid%3a%220%22+GroupEntityId%3a%22IsUwpe7StYMy9zWBEooypnccb6%22+NonSponsoredOffer%3a%22True%22&productpage=true&FORM=SHPPDP&browse=true",
      "seller": "Walmart",
      "price": "$64.00",
      "extracted_price": 64.0,
      "sale": false,
      "old_price": null,
      "shipping": "Free shipping",
      "thumbnail": "https://th.bing.com/th?id=OP.bmIHvaU977kYvw474C474&w=272&h=272&o=5&pid=21.1"
    },
    ... other shopping results
  ]
}

Join us on Twitter | YouTube

Add a Feature RequestπŸ’« or a Bug🐞

Β