# 2025 Guide to Scraping eBay Listings, Reviews, Search Results, Product Variants > Source: https://scrape.do/blog/ebay-scraping/ Published: 2025-07-31 · Updated: 2025-07-31 · Authors: Serhat Kurtulus · Categories: Scraping Use Cases Although the recent Temu and Amazon boom slowed down some of the traffic for eBay, they remain to be a solid player for [e-commerce scraping](https://scrape.do/blog/how-can-data-scraping-benefit-my-e-commerce-business/) with millions of products selling out still. This guide will help you **scrape product listings (with variants)**, **search results & categories**, and of course, **product reviews from eBay**. We'll use basic Python libraries like Requests to scrape the website, and level-up our scraper with Scrape.do to bypass any blocks. [Find all working code in this GitHub repository ⚙](https://github.com/scrape-do/scrapedo-scrapers/tree/main/ebay-scraper) Let's get to it! > **To stay ethical and for** [**maximum web scraping legality**](https://scrape.do/blog/web-scraping-legality/)**\:** > > - Avoid scraping pages behind login > - Don't flood servers with high request volumes > - Don’t resell their content or present it as your own ## Scraping Product Data from an eBay Listing (Simple Page) At its core, every e-commerce site comes down to product pages, and scraping product names and prices the first task for scrapers. So, we'll start by scraping a single eBay product page. I've been looking for a good mouse for my PC recently, [so I'm picking this one](https://www.ebay.com/itm/125575167955). We want to extract: - **Product Name** - **Price** - **ePID (eBay Product ID)** - **Product Image URL** Let’s take it step by step. ### 1\. Sending the First Request We’ll begin by sending a GET request through [Scrape.do](https://scrape.do) to ensure we can access the product page without getting blocked. Don't forget to grab your Scrape.do API token from here, for free: ![](/uploads/blog/scrape-do-token_hu9c2649c4b36428f89688ab9d0acb0a55_138939_1200x0_resize_q80_h2_box_3.webp) ```python import requests import urllib.parse # Scrape.do API token TOKEN = "" # Updated eBay product URL target_url = "https://www.ebay.com/itm/125575167955" encoded_url = urllib.parse.quote_plus(target_url) # Scrape.do API endpoint api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}&geocode=us" response = requests.get(api_url) print(response.status_code) ``` A successful response should print: ```bash 200 ``` If you're blocked or see status code `403`, try these adjustments: - Append `&render=true` to enable JavaScript rendering - Check that `super=true` is included to enable premium proxies - Make sure your token is valid and hasn’t hit the limit ### 2\. Parsing and Extracting Product Data To extract data from an eBay product page, you first need to understand the structure of the page. Open the product in your browser and use the DevTools panel to inspect the elements you’re after. Right-click on any field (like the product name or price), and select **“Inspect”**. This will highlight the exact HTML tag and attributes you can use to locate it in your script. In this section, we’ll extract the following fields: - Product Name - Price - Product Image URL - Product ID (we won’t scrape this since it’s already in the URL) #### 2\.1 Parse the Page with BeautifulSoup Once you’ve made a successful request and received the page’s HTML, you can parse it using BeautifulSoup to make it easier to navigate: ```python from bs4 import BeautifulSoup soup = BeautifulSoup(response.text, "html.parser") ``` This gives you access to all the HTML elements in a tree-like structure, which you can search by tag names, class names, and attributes. #### 2\.2 Get Product ID from the URL In this case, we’re scraping a specific product, and the item ID is already part of the URL. So we don’t need to extract it from the page. ```python product_id = "125575167955" ``` > For [large-scale scraping](https://scrape.do/blog/large-scale-web-scraping/), you’ll likely have a list of URLs that already include the product IDs and storing them ahead of time keeps your extraction logic simpler. #### 2\.3 Extract Product Name Let’s inspect the product title first. In the browser, locate the title using DevTools. You should see it inside an `

` tag with a class like `x-item-title__mainTitle`. ![product title from ebay](/uploads/blog/title-inspect_hu445d7093d62c8fe3d787519c9633f00e_20962_1200x0_resize_q80_h2_box_3.webp) This tag contains only the title text, so it’s safe to extract it directly: ```python try: title = soup.find("h1", class_="x-item-title__mainTitle").get_text(strip=True) except Exception: title = None ``` We use a `try-except` block here to prevent the script from crashing if the element is missing or renamed which is useful when scraping hundreds of items. #### 2\.4 Extract Price Now let’s do the same for the price. Open DevTools and inspect the price text. ![scrape ebay price](/uploads/blog/price-inspect_hu31e86bd0daecc5aee8feea4786d6826c_12280_1200x0_resize_q80_h2_box_3.webp) You’ll usually find it inside a `
` tag with the class `x-price-primary`. ```python try: price = soup.find("div", class_="x-price-primary").get_text(strip=True) except Exception: price = None ``` This will return the full price string as shown on the page (e.g., `US $44.99`). #### 2\.5 Extract Product Image URL Product images on eBay are displayed in a carousel. When the page loads, the first image in the carousel is usually marked with the class `active`. ![scrape ebay images](/uploads/blog/image-inspect_huf7f4d7af398c70209ce326084f83cad0_223383_1200x0_resize_q80_h2_box_3.webp) Each image is wrapped inside a `
` with the class `ux-image-carousel-item`, and the visible one will also have the `active` class. The actual image is inside an `` tag, so we’ll target that: ```python try: image_url = soup.select_one('.ux-image-carousel-item.active').find("img")["src"] except Exception: image_url = None ``` This grabs the `src` attribute of the active image, which is a direct link to the image file. To test the full setup, print the results: ```python print(title, "---", price, "---", product_id, "---", image_url) ``` Final code should look like this: ```python import requests import urllib.parse from bs4 import BeautifulSoup # Scrape.do API token TOKEN = "" # eBay product page target_url = "https://www.ebay.com/itm/125575167955" encoded_url = urllib.parse.quote_plus(target_url) # Scrape.do API endpoint api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}&geocode=us" response = requests.get(api_url) product_id = "125575167955" soup = BeautifulSoup(response.text, "html.parser") # Product Name try: title = soup.find("h1", class_="x-item-title__mainTitle").get_text(strip=True) except Exception: title = None # Price try: price = soup.find("div", class_="x-price-primary").get_text(strip=True) except Exception: price = None # Product Image try: image_url = soup.select_one('.ux-image-carousel-item.active').find("img")["src"] except Exception: image_url = None print(title, "---", price, "---", product_id, "---", image_url) ``` And this will be your output: ![](/uploads/blog/single-product-scrape_hu6cfcd89fd3b0d0e8913e841dfcb771b2_12799_1200x0_resize_q80_h2_box_3.webp) ### 3\. Exporting the Data Finally, let’s save the output into a CSV file. We’ll create a simple CSV file with the following columns: - Product ID - Product Name - Price - Image URL Import Python’s built-in `csv` module: ```python import csv ``` We’ll open a file named `ebay_product.csv`, write the headers, and then insert a single row with our data: ```python with open("ebay_product.csv", mode="w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["product_id", "title", "price", "image_url"]) # header row writer.writerow([product_id, title, price, image_url]) # data row ``` The `newline=""` ensures the output isn’t spaced out weirdly on Windows, and `encoding="utf-8"` avoids issues with special characters in product names. Now run the final code and you should see a file named `ebay_product.csv` in your project directory. Open it with any spreadsheet app or text editor and check that all fields are correctly placed. ![scrape ebay listing](/uploads/blog/csv-export-single_hu96762e3811e0f929233fce94201d7339_6790_1200x0_resize_q80_h2_box_3.webp) You now have a working data extraction and export flow for a simple eBay product listing. ## Scraping Product Data from an eBay Listing with Dropdown Variants Some eBay product pages look deceptively simple like the one we just scraped. Until you notice multiple dropdowns that create numerous variations for the same product. Take for example [this amazing St. Patrick's Day t-shirt](https://www.ebay.com/itm/176212861437). ![ebay listing scraping](/uploads/blog/ebay-listing-variant-scraping_huef14610d851c0cda16830fb50e602c56_339657_1200x0_resize_q80_h2_box_3.webp) Unlike the product we scraped earlier, this one has multiple variations (size and color), and stock or price data of these variations are _not_ visible unless you click on the dropdowns. We could very well use [the playWithBrowser feature of Scrape.do](https://scrape.do/documentation/#play-with-browser) to click and select these dropdowns to find prices and stock info for each variation, but there's an easier way. eBay hides info for every variation inside a large JavaScript object called **`MSKU`**, which contains a complete nested map of all product variants, stock status, and pricing info. **That's what we'll scrape**. In this section, we’ll: - Get the fully rendered HTML of an eBay product page - Extract and parse the `MSKU` JSON blob - Reconstruct all combinations of variations with price and availability - Export everything into a clean CSV ### 1\. Send Request and Extract HTML You're familiar with everything below from the previous step. Let's first import required libraries, send a request, and get a clean HTML: ```python import json import re import csv import requests import urllib.parse # Your Scrape.do API token TOKEN = "" # The eBay product page URL TARGET_URL = "https://www.ebay.com/itm/176212861437" # Construct the Scrape.do URL encoded_url = urllib.parse.quote_plus(TARGET_URL) API_URL = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}&super=true" # Send request through Scrape.do response = requests.get(API_URL) html_content = response.text ``` At this point, `html_content` contains the full HTML of the product page including the JavaScript variable that holds the variation data we need. ### 2\. Extract and Parse MSKU Data On eBay, products with dropdowns (like size or color) store all their variation logic inside a hidden JavaScript object called **`MSKU`**. **`MSKU`** is unique to eBay, and means multi-variation SKU (SKU is pretty common, though). This `MSKU` object includes: - All variation combinations (e.g., Small + Red, Medium + Blue) - Pricing and stock for each variation - The structure and labels of dropdown menus - Display names and internal IDs You won’t find this data in the DOM, **it’s buried inside a JavaScript variable** somewhere in the page source, and looks like this (simplified): ```json "MSKU": { "variationsMap": { ... }, "menuItemMap": { ... }, "variationCombinations": { ... }, "selectMenus": [ ... ] } ``` To extract it, we’ll use a regex pattern that **finds** the `"MSKU": { ... }` block, **stops** cleanly before the next `"QUANTITY"` field, and **avoids** overreaching (non-greedy matching). Here’s the code that does that: ```python msku_data = json.loads(re.search(r'"MSKU":({.+?}),"QUANTITY"', html_content).group(1)) ``` > Why we use `.+?` instead of `.+`:
Greedy matching (`.+`) would capture _everything_ until the **last** `"QUANTITY"` in the file. Non-greedy (`.+?`) stops at the **first** one, giving us a clean JSON chunk. ### 3\. Extract Core Data Structures Now that we’ve parsed the full `MSKU` object, we can extract the key substructures inside it: - `variationsMap`: links variation IDs to their price and stock - `menuItemMap`: maps menu item IDs to display names - `variationCombinations`: maps menu selections (like “0_1”) to variation IDs These are what we’ll use to reconstruct every possible product combination. Here’s how we load them: ```python variations = msku_data.get('variationsMap', {}) menu_items = msku_data.get('menuItemMap', {}) variation_combinations = msku_data.get('variationCombinations', {}) ``` We also grab the selection menu labels (like “Size” or “Color”) to use as CSV headers: ```python headers = [menu['displayLabel'] for menu in msku_data.get('selectMenus', [])] + ['Price', 'OutOfStock'] ``` This prepares us to loop over each variation and extract the actual data in the next step. ### 4\. Parse Each Variation Now that we have the raw mappings and headers, we can walk through each possible variation combination. Every entry in `variationCombinations` represents a unique combo of dropdown selections (like “Small + Red”) and links to a specific variation ID. From there, we: - Map menu item IDs to their display names - Extract the price from `binModel` - Check availability via the `outOfStock` flag > ⚠ You'll need to dig deep into the JSON blob and understand exactly how it is structured to learn how we're deconstructing variant data and then re-constructing it for the right variations. Here’s the full loop: ```python rows = [] for combo_key, combo_data_id in variation_combinations.items(): variation_data = variations.get(str(combo_data_id)) if not variation_data: continue row = {} menu_ids = [int(i) for i in combo_key.split('_')] for menu_id in menu_ids: menu_item = menu_items.get(str(menu_id)) if menu_item: for menu in msku_data.get('selectMenus', []): if menu_item['valueId'] in menu['menuItemValueIds']: row[menu['displayLabel']] = menu_item['displayName'] break price_spans = variation_data.get('binModel', {}).get('price', {}).get('textSpans', []) row['Price'] = price_spans[0].get('text') if price_spans else None row['OutOfStock'] = variation_data.get('quantity', {}).get('outOfStock', False) rows.append(row) ``` At this point, `rows` contains every possible variation, neatly structured with dropdown labels, price, and stock info. All that’s left is writing it to a CSV. ### 5\. Export to CSV Once all product variations are parsed into `rows`, we can export everything into a structured CSV file for easy analysis. Here's the full code with export step added at the end: ```python import json import re import csv import requests import urllib.parse # Configuration TOKEN = "" TARGET_URL = "https://www.ebay.com/itm/176212861437" # Scrape the page using scrape.do response = requests.get(f"https://api.scrape.do/?url={urllib.parse.quote_plus(TARGET_URL)}&token={TOKEN}&super=true") html_content = response.text # Extract and parse MSKU data from HTML msku_data = json.loads(re.search(r'"MSKU":({.+?}),"QUANTITY"', html_content).group(1)) # Get core data structures variations = msku_data.get('variationsMap', {}) menu_items = msku_data.get('menuItemMap', {}) variation_combinations = msku_data.get('variationCombinations', {}) # Create CSV headers headers = [menu['displayLabel'] for menu in msku_data.get('selectMenus', [])] + ['Price', 'OutOfStock'] # Process each variation combination rows = [] for combo_key, combo_data_id in variation_combinations.items(): variation_data = variations.get(str(combo_data_id)) if not variation_data: continue row = {} menu_ids = [int(i) for i in combo_key.split('_')] # Extract menu values (size, color, etc.) for menu_id in menu_ids: menu_item = menu_items.get(str(menu_id)) if menu_item: for menu in msku_data.get('selectMenus', []): if menu_item['valueId'] in menu['menuItemValueIds']: row[menu['displayLabel']] = menu_item['displayName'] break # Extract price and stock status price_spans = variation_data.get('binModel', {}).get('price', {}).get('textSpans', []) row['Price'] = price_spans[0].get('text') if price_spans else None row['OutOfStock'] = variation_data.get('quantity', {}).get('outOfStock', False) rows.append(row) # Write to CSV with open('product_data.csv', 'w', newline='', encoding='utf-8') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=headers) writer.writeheader() writer.writerows(rows) print("Written to product_data.csv") ``` Once you run the script, it’ll create a clean CSV file like this: ![scrape ebay products](/uploads/blog/ebay-listing-variant-scrape-api_hu7be3e0cc77e1728d9840966df0c54327_21378_1200x0_resize_q80_h2_box_3.webp) Bit complex, yes, but works! ## Scraping eBay Reviews for a Product User reviews are one of the most valuable data points on eBay. They offer real customer sentiment, highlight recurring issues, and can reveal details that aren't obvious from product descriptions. Rather than scraping reviews embedded in the product page, we’ll target eBay’s **dedicated review pages**, which are easier to parse and support full pagination. Our goal is to collect every review, not just the first few. For this section, I'm picking [a premium water bottle which has tens of reviews](https://www.ebay.com/urw/Hydro-Flask-FXM334-Straw-Lid-Black/product-reviews/26031753680?_itm=204914180000). No idea why I picked this product, I was probably thirsty :) We'll: - Load all paginated review pages - Extract structured data (reviewer name, rating, title, date, and comment) - Export everything into a CSV for further analysis ### 1\. Send a Request to the Review Page We start from the dedicated review page instead of the listing page. It loads directly, doesn't require JavaScript interaction, and returns all visible review data. We’ll use Scrape.do to fetch the full HTML: ```python import requests import urllib.parse from bs4 import BeautifulSoup TOKEN = "" start_url = "https://www.ebay.com/urw/Hydro-Flask-FXM334-Straw-Lid-Black/product-reviews/26031753680?_itm=204914180000" encoded_url = urllib.parse.quote_plus(start_url) api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}&super=true&geocode=us" response = requests.get(api_url) soup = BeautifulSoup(response.text, "html.parser") ``` ### 2\. Extract Review Fields Each review is contained within a `.ebay-review-section` block. Inside it, we’ll find reviewer's name, rating, and you guessed it, review. The star rating is stored in the `data-stars` attribute like `data-stars="5-0"`, so we split and extract just the number. ![scrape ebay reviews](/uploads/blog/review-html-inspect_hu608a84564bac56ef39f666c6ada9f58d_87894_1200x0_resize_q80_h2_box_3.webp) Here's the extraction logic: ```python def extract_reviews(soup): reviews = soup.select(".ebay-review-section") data = [] for r in reviews: try: reviewer = r.select_one(".review-item-author").get_text(strip=True) except: reviewer = None try: rating = r.select_one("div.star-rating")["data-stars"].split("-")[0] except: rating = None try: title = r.select_one(".review-item-title").get_text(strip=True) except: title = None try: date = r.select_one(".review-item-date").get_text(strip=True) except: date = None try: comment = r.select_one(".review-item-content").get_text(strip=True) except: comment = None data.append({ "Reviewer": reviewer, "Rating": rating, "Title": title, "Date": date, "Comment": comment }) return data ``` ### 3\. Detect and Follow Pagination Each review page includes a “Next” button that links to the next set of reviews. The easiest way to detect it is by looking for a link with `rel="next"`. The link is already absolute, so we can use it directly: ```python def get_next_page(soup): try: return soup.select_one('a[rel="next"]')["href"] except: return None ``` ### 4\. Scrape All Pages in a Loop With both review extraction and pagination detection in place, we can loop through all available pages until we reach the end. This loop will keep scraping as long as there’s a valid `next` link on the page: ```python all_reviews = [] current_url = start_url while current_url: encoded = urllib.parse.quote_plus(current_url) api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded}&super=true&geocode=us" response = requests.get(api_url) soup = BeautifulSoup(response.text, "html.parser") all_reviews.extend(extract_reviews(soup)) current_url = get_next_page(soup) ``` ### 5\. Export Reviews to CSV Once all pages are scraped, we’ll write the results to a CSV. Here’s the full code with CSV exporting added: ```python import requests import urllib.parse from bs4 import BeautifulSoup import csv TOKEN = "" start_url = "https://www.ebay.com/urw/Hydro-Flask-FXM334-Straw-Lid-Black/product-reviews/26031753680?_itm=204914180000" def extract_reviews(soup): reviews = soup.select(".ebay-review-section") data = [] for r in reviews: try: reviewer = r.select_one(".review-item-author").get_text(strip=True) except: reviewer = None try: rating = r.select_one("div.star-rating")["data-stars"].split("-")[0] except: rating = None try: title = r.select_one(".review-item-title").get_text(strip=True) except: title = None try: date = r.select_one(".review-item-date").get_text(strip=True) except: date = None try: comment = r.select_one(".review-item-content").get_text(strip=True) except: comment = None data.append({ "Reviewer": reviewer, "Rating": rating, "Title": title, "Date": date, "Comment": comment }) return data def get_next_page(soup): try: return soup.select_one('a[rel="next"]')["href"] except: return None all_reviews = [] current_url = start_url while current_url: encoded = urllib.parse.quote_plus(current_url) api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded}&super=true&geocode=us" response = requests.get(api_url) soup = BeautifulSoup(response.text, "html.parser") all_reviews.extend(extract_reviews(soup)) current_url = get_next_page(soup) with open("ebay_paginated_reviews.csv", mode="w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["Reviewer", "Rating", "Title", "Date", "Comment"]) writer.writeheader() for row in all_reviews: writer.writerow(row) print("Saved all reviews to ebay_paginated_reviews.csv") ``` And here's the CSV it created: ![scrape ebay reviews api](/uploads/blog/review-export-preview_hudefa263a33084cd02b2e84e72eaa6f56_110126_1200x0_resize_q80_h2_box_3.webp) ## Scraping eBay Search Results _Scraping reviews and listing variants has not been a big of a deal so far._ But what if you want to scrape a full **search results page**? That’s where things get interesting, you’ll need to extract a list of products, each with its own: - Name - Price - Image - Link to the product page Let's imagine that St. Patrick's Day is right around the corner and I want to scrape [all related t-shirt designs](https://www.ebay.com/sch/i.html?_nkw=saint+patricks+tshirt) from eBay for competitive insights. Here's how I would do it: ### 1\. Sending the First Request Let’s start by making a request to the eBay search results page using Scrape.do. ```python import requests import urllib.parse from bs4 import BeautifulSoup # Your Scrape.do token TOKEN = "" query_url = "https://www.ebay.com/sch/i.html?_nkw=saint+patricks+tshirt" encoded_url = urllib.parse.quote_plus(query_url) # Scrape.do endpoint api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}" response = requests.get(api_url) soup = BeautifulSoup(response.text, "html.parser") ``` ### 2\. Extracting Product Listings **eBay uses multiple layouts for search results, which makes our job slightly more difficult.** While some pages follow the older `.s-item` structure, others use a newer `.s-card` layout. Instead of hardcoding for one or the other, we’ll detect which layout is present and use the correct selectors accordingly. ![scrape ebay categories](/uploads/blog/search-list-items-inspect_hu451c034c4046ace525b2feb98c835416_211218_1200x0_resize_q80_h2_box_3.webp) From each listing we'll extract name, price, image URL, and link to the actual product. Here’s the layout-aware extraction logic: ```python if soup.select("li.s-card"): items = soup.select("li.s-card") layout = "s-card" elif soup.select("li.s-item"): items = soup.select("li.s-item") layout = "s-item" else: items = [] layout = None for item in items: try: title = item.select_one(".s-card__title" if layout == "s-card" else ".s-item__title").get_text(strip=True) except: title = None try: price_spans = item.select(".s-card__price" if layout == "s-card" else ".s-item__price") if len(price_spans) > 1: price = " ".join([span.get_text(strip=True) for span in price_spans]) elif price_spans: price = price_spans[0].get_text(strip=True) else: price = None except: price = None try: image_url = item.select_one(".s-card__image" if layout == "s-card" else ".s-item__image-img")["src"] except: image_url = None try: link = item.select_one("a.su-link" if layout == "s-card" else "a.s-item__link")["href"] except: link = None results.append({ "title": title, "price": price, "image_url": image_url, "link": link }) ``` ### 3\. Navigating Pagination To scrape beyond the first page, we need to follow the “Next” button and repeat the extraction process. eBay includes pagination links at the bottom of the page, and the “Next” button is usually marked with the class `pagination__next`. We’ll loop through pages by following that link until no more pages are available. The scraper also adapts to layout changes between pages. Before showing you the full code, let's make the final addition: ### 4\. Exporting the Data We also need to export this data to CSV, so we'll use the same method as before. With pagination and CSV writing added, here's the full working code: ```python import requests import urllib.parse from bs4 import BeautifulSoup import csv # Your Scrape.do token TOKEN = "" results = [] current_url = "https://www.ebay.com/sch/i.html?_nkw=saint+patricks+tshirt" while current_url: encoded_url = urllib.parse.quote_plus(current_url) api_url = f"https://api.scrape.do/?token={TOKEN}&url={encoded_url}&geocode=us&super=true&render=true" response = requests.get(api_url) soup = BeautifulSoup(response.text, "html.parser") # Detect which layout is being used if soup.select("li.s-card"): items = soup.select("li.s-card") layout = "s-card" elif soup.select("li.s-item"): items = soup.select("li.s-item") layout = "s-item" else: break for item in items: try: title = item.select_one(".s-card__title" if layout == "s-card" else ".s-item__title").get_text(strip=True) except: title = None try: price_spans = item.select(".s-card__price" if layout == "s-card" else ".s-item__price") if len(price_spans) > 1: price = " ".join([span.get_text(strip=True) for span in price_spans]) elif price_spans: price = price_spans[0].get_text(strip=True) else: price = None except: price = None try: image_url = item.select_one(".s-card__image" if layout == "s-card" else ".s-item__image-img")["src"] except: image_url = None try: link = item.select_one("a.su-link" if layout == "s-card" else "a.s-item__link")["href"] except: link = None results.append({ "title": title, "price": price, "image_url": image_url, "link": link }) try: next_link = soup.select_one("a.pagination__next")["href"] current_url = next_link except: current_url = None # Export to CSV with open("ebay_search_results.csv", mode="w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["title", "price", "image_url", "link"]) writer.writeheader() for row in results: writer.writerow(row) print("Scraping completed. Results saved to ebay_search_results.csv.") ``` And this is going to be our large CSV file: ![ebay search results csv scrape](/uploads/blog/search-results-csv_huee330863c4011c26bc9b7eb958da716d_124597_1200x0_resize_q80_h2_box_3.webp) **You're now able to scrape any and all data from eBay.** ## Conclusion You're now able to scrape the entirety of eBay, including: - Static product pages using simple HTML parsing - Search results that span multiple layouts and pages - Variant-based listings by extracting hidden **MSKU** data from the HTML source - All reviews of any product To make this process reliable, we used [Scrape.do](https://scrape.do), which handled anti-bot detection and proxy management for us, enabling us to bypass geo-restrictions and blocks. If you want to skip the hard parts of scraping, especially on JavaScript-heavy sites like eBay, Scrape.do is built for it. [👉 **Try Scrape.do today** and start scraping faster, cleaner, and without the constant breakage.](https://dashboard.scrape.do/sign-up)