如何使用Python抓取亚马逊网页
Web 抓取 Amazon 对于电子商务企业来说至关重要,因为了解有关您的竞争对手和最新趋势的信息至关重要。
什么是亚马逊抓取?
网页抓取是从网站收集数据的活动,亚马逊是最大的在线购物平台。那么,您如何利用网络抓取来获得优于竞争对手的优势呢?
本实用教程将展示如何使用 Python 从亚马逊抓取产品信息!
亚马逊是否允许网页抓取?
确实!但有一个警告:亚马逊使用速率限制,如果您使网站负担过重,它可以阻止您的 IP 地址。他们还会检查 HTTP 标头,如果您的活动看起来可疑,他们会阻止您。
如果您尝试同时抓取多个页面,则可能会在没有代理轮换的情况下被阻止。此外,Amazon 的网页具有不同的结构。甚至不同的产品页面也有不同的 HTML 结构。构建一个健壮的网络爬虫应用程序是很困难的。
然而,抓取产品价格、评论和列表是合法的。
为什么要从亚马逊抓取网页?
尤其是在电子商务中,拥有尽可能多的信息至关重要。如果您可以将提取过程自动化,您就可以更专注于您的竞争对手和您的业务。
这些是一些好处:
监控产品
您可以查看您所在类别中最畅销的产品。这将使您有机会确定市场的当前趋势。因此,您可以根据更好的基础来决定您的产品。您还可以发现失去最高销售位置的产品。
监控竞争对手
赢得比赛的唯一方法是监视竞争对手的行动。例如,抓取亚马逊产品价格将有助于发现价格趋势。这将使您确定更好的定价策略。这样您就可以跟踪变化并进行竞争对手的产品分析。
如何从亚马逊抓取数据?
ZenRows API 处理自动高级代理轮换。它还具有自动解析支持,可自动提取页面上可用的所有内容!它支持 Amazon、eBay、Youtube、Indeed 等。了解有关可用平台的更多信息!
我们可以使用它的Amazon scraper自动从产品页面中提取数据。让我们从安装 ZenRows SDK 开始:
pip install zenrows
可以添加额外的配置来进行自定义的 API 调用。您可以从API 文档中了解更多信息。现在,让我们自动从产品中抓取数据!
from zenrows import ZenRowsClient client = ZenRowsClient(API_KEY) url = "https://www.amazon.com/Crockpot-Electric-Portable-20-Ounce-Licorice/dp/B09BDGFSWS" # It's possible to specify javascript rendering, premium proxies and geolocation. # You can check the API documentation for further customization. params = {"premium_proxy":"true","proxy_country":"us","autoparse":"true"} response = client.get(url, params=params) print(response.json())
结果:
{ "avg_rating": "4.7 out of 5 stars", "category": "Home & Kitche › Kitchen & Dining › Storage & Organizatio › Travel & To-Go Food Containers › Lunch Boxes", "description": "Take your favorite meals with you wherever you go! The Crockpot Lunch Crock Food Warmer is a convenient, easy-to-carry, electric lunch box. Plus, with its modern-vintage aesthetic and elegant Black Licorice color, it's stylish, too. It is perfectly sized for one person, and is ideal for carrying and warming meals while you’re on the go. With its 20-ounce capacity, this heated lunch box is perfect whether you're in the office, working from home, or on a road trip. Take your leftovers, soup, oatmeal, and more with you, then enjoy it at the perfect temperature! This portable food warmer features a tight-closing outer lid to help reduce spills, as well as an easy-carry handle, soft-touch coating, and detachable cord. The container is removable for effortless filling, carrying, and storage. Cleanup is easy, too: the inner container and lid are dishwasher-safe.", "out_of_stock": false, "price": "$29.99", "price_without_discount": "$44.99", "review_count": "14,963 global ratings", "title": "Crockpot Electric Lunch Box, Portable Food Warmer for On-the-Go, 20-Ounce, Black Licorice", "features": [ { "Package Dimensions": "13.11 x 10.71 x 8.58 inches" }, { "Item Weight": "2.03 pounds" }, { "Manufacturer": "Crockpot" }, { "ASIN": "B09BDGFSWS" }, { "Country of Origin": "China" }, { "Item model number": "2143869" }, { "Best Sellers Rank": "#17 in Kitchen & Dining (See Top 100 in Kitchen & Dining) #1 in Lunch Boxes" }, { "Date First Available": "November 24, 2021" } ], "ratings": [ { "5 star": "82%" }, { "4 star": "10%" }, { "3 star": "4%" }, { "2 star": "1%" }, { "1 star": "2%" } ] }
使用 BeautifulSoup 进行网页抓取
本文不需要高级 Python 技能,但如果您刚刚开始学习网络抓取,请查看我们的使用 Python 进行网络抓取指南!
对于 Python 中的网络抓取,我们有几个注意事项要评论:
可以只构建基于请求的脚本。我们还可以使用诸如 Selenium 之类的浏览器自动化工具来抓取 Amazon 的网页。但是亚马逊很容易检测到无头浏览器。特别是当您不自定义它们时。
使用像 Selenium 这样的框架也是低效的,因为如果您扩展应用程序,它们很容易使系统过载。
因此,我们将使用Python 中的requests
和BeautifulSoup
模块来抓取亚马逊产品的数据。
让我们从安装必要的 Python 包开始吧!我们将使用requests
和BeautifulSoup
模块。
您可以使用pip
安装这些:
pip install requests beautifulsoup4
避免在网页抓取时被阻止
正如我们之前提到的,亚马逊确实允许网络抓取。但如果执行了很多请求,它会阻止网络抓取工具。他们正在检查 HTTP 标头并应用速率限制来阻止恶意机器人。
从 HTTP 标头检测客户端是否是机器人相当容易,因此如果您不自定义固定的浏览器设置,则使用浏览器自动化工具(例如 Selenium)将无法工作。
我们将使用真正的浏览器User-Agent
,因为我们不会发送太频繁的请求,所以我们不会受到速率限制。如果你想大规模地抓取数据,你应该使用代理服务器。
从亚马逊抓取产品数据
在这篇文章中,我们将从这个午餐盒产品中抓取数据:
我们将提取以下内容:
- 产品名称。
- 评分。
- 价格。
- 折扣(如果有的话)。
- 全价(如果有折扣,
price
将是折扣价)。 - 库存状态(是否缺货)。
- 产品描述。
- 相关项目。
如前所述,Amazon 检查固定设置,因此使用标准设置发送 HTTP 请求将不起作用。您可以通过使用 Python 发送一个简单的请求来检查这一点:
import requests response = requests.get("https://www.amazon.com/Sabrent-4-Port-Individual-Switches-HB-UM43/dp/B00JX1ZS5O") print(response.status_code) # prints 503
响应的状态代码为 503 服务不可用。响应的 HTML 中也有这条消息:
To discuss automated access to Amazon data please contact [email protected]. For information about migrating to our APIs refer to our Marketplace APIs at https://developer.amazonservices.com/ref=rm_5_sv, or our Product Advertising API at https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html/ref=rm_5_ac for advertising use cases.
那么,我们可以做些什么来绕过亚马逊的保护系统呢?
在任何网络抓取项目中,模仿真实用户都是至关重要的。因此,我们可以使用我们的标准 HTTP 标头并删除不必要的标头:
我们只需要accept
, accept-encoding
,accept-language
和user-agent
标题。Amazon 检查标头以User-Agent
阻止可疑客户。
他们还使用速率限制,因此如果您频繁发送请求,您的 IP 可能会被阻止。您可以从我们的速率限制绕过指南中了解有关速率限制的更多信息!
首先,我们将为我们的爬虫定义一个类:
from bs4 import BeautifulSoup from requests import Session class Amazon: def __init__(self): self.sess = Session() self.headers = { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding": "gzip, deflate, br", "accept-language": "en", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36", } self.sess.headers = self.headers
Amazon
类定义了一个Session
对象,它跟踪我们的连接记录。我们还可以看到自定义user-agent
和其他标头用作 的Session
标头。由于我们需要获取网页,因此让我们定义一个get
方法:
class Amazon: #.... def get(self, url): response = self.sess.get(url) assert response.status_code == 200, f"Response status code: f{response.status_code}" splitted_url = url.split("/") self.id = splitted_url[-1].split("?")[0] self.product_page = BeautifulSoup(response.text, "html.parser") return response
此方法使用上述方法连接到网站Session
。如果返回的状态码不是 200,那意味着有错误,所以我们提出一个AssertionError
.
如果请求成功,那么我们拆分请求 URL 并提取产品 ID。该BeautifulSoup
对象是为产品页面定义的。它会让我们搜索 HTML 元素并从中提取数据。
现在,我们将定义该data_from_product_page
方法。这将从产品页面中提取相关信息。我们可以使用该find
方法并指定元素属性来搜索 HTML 元素。
但是,我们怎么知道要搜索什么?
通过使用开发者控制台!我们可以使用开发者控制台找出我们需要哪些元素和属性。
我们可以右键单击并检查元素:
正如我们所看到的,产品标题写在一个span
元素中,该元素productTitle
在id
属性中有一个值。让我们开始吧!
# As the search result returns another BeautifulSoup object, we use "text" to # extract data inside of the element. # we will also use strip to remove whitespace at the start and end of the words title = self.product_page.find("span", attrs={"id": "productTitle"}).text.strip()
要提取评分,我们可以使用相同的方法!当您检查平均评分部分时,您i
首先会看到一个元素。相反,我们将使用span
上面一点的元素(由绿色矩形表示)。因为它的title
属性已经有了平均评分文本和唯一class
值。
我们将搜索span
具有该类的元素reviewCountTextLinkedHistogram
。让我们使用属性,而不是提取元素内部的文本title
:
让我们将代码添加到我们的脚本中:
# The elements attributes are read as a dictionary, so we can get the title by passing its key # of course, we will also use strip here rating = self.product_page.find("span", attrs={"class": "reviewCountTextLinkedHistogram"})["title"].strip()
和类对于折扣元素是唯一的,因此我们将使用它们来reinventPriceSavingsPercentageMargin
搜索savingsPercentage
折扣。打折的时候还会在挂牌价下方写上总价:
a-price
我在和类中找不到任何其他元素a-text-price
。因此,让我们使用它们来查找实际的上市价格。当然,由于不确定是否有折扣,我们只在有折扣时提取该价格:
# get the element, "find" returns None if the element could not be found discount = self.product_page.find("span", attrs={"class": "reinventPriceSavingsPercentageMargin savingsPercentage"}) # if the discount is found, extract the total price # else, just set the total price as the price found above and set discount = False if discount: discount = discount.text.strip() price_without_discount = self.product_page.find("span", attrs={"class": "a-price a-text-price"}).text.strip() else: price_without_discount = price discount = False
为了检查产品是否缺货,我们可以检查矩形中的部分是否在页面上。
让我们检查一下盒子:
我们需要检查一个div
元素是否id=outOfStock
存在。让我们将这个片段添加到我们的脚本中:
# simply check if the item is out of stock or not out_of_stock = self.product_page.find("div", {"id": "outOfStock"}) if out_of_stock: out_of_stock=True else: out_of_stock=False
好的!我们做到了这一点,唯一剩下的就是产品描述,然后我们就完成了!
产品描述存储在div
元素中,该元素具有id="productDescription"
:
我们可以很容易地提取描述:
description = self.product_page.find("div", {"id": "productDescription"}).text
大多数时候,您必须浏览相关项目。您的爬虫还应该收集有关其他产品的数据,因为您需要进行适当的分析。您可以存储相关项目的链接并在以后从中提取信息。
正如您在下面看到的,相关项目存储在 carousel slider 中div
。看来我们可以a-carousel-viewport
使用div
. 我们将获取li
元素,然后收集 ASIN 值。ASIN 值是亚马逊的标准标识号,我们可以用它们来构建产品 URL。
现在,将代码添加到我们的脚本中:
# extract the related items carousel's first page carousel = self.product_page.find("div", {"class": "a-carousel-viewport"}) related_items = carousel.find_all("li") related_item_asins = [item.find("div")["data-asin"] for item in related_items] # of course, we need to get item links related_item_links = [] for asin in related_item_asins: link = "www.amazon.com/dp/" + asin related_item_links.append(link)
现在让我们完成我们到目前为止准备的脚本:
from bs4 import BeautifulSoup from requests import Session class Amazon: def __init__(self): self.sess = Session() self.headers = { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding": "gzip, deflate, br", "accept-language": "en", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36", } self.sess.headers = self.headers def get(self, url): response = self.sess.get(url) assert response.status_code == 200, f"Response status code: f{response.status_code}" splitted_url = url.split("/") self.product_page = BeautifulSoup(response.text, "html.parser") self.id = splitted_url[-1].split("?")[0] return response def data_from_product_page(self): # As the search result returns another BeautifulSoup object, we use "text" to # extract data inside of the element. # we will also use strip to remove whitespace at the start and end of the words title = self.product_page.find("span", attrs={"id": "productTitle"}).text.strip() # The elements attributes are read as a dictionary, so we can get the title by passing its key # of course, we will also use strip here rating = self.product_page.find("span", attrs={"class": "reviewCountTextLinkedHistogram"})["title"].strip() # First, find the element by specifiyng a selector with two options in this case price_span = self.product_page.select_one("span.a-price.reinventPricePriceToPayMargin.priceToPay, span.a-price.apexPriceToPay") # Then, extract the pricing from the span inside of it price = price_span.find("span", {"class": "a-offscreen"}).text.strip() # Get the element, "find" returns None if the element could not be found discount = self.product_page.find("span", attrs={"class": "reinventPriceSavingsPercentageMargin savingsPercentage"}) # If the discount is found, extract the total price # Else, just set the total price as the price found above and set discount = False if discount: discount = discount.text.strip() price_without_discount = self.product_page.find("span", attrs={"class": "a-price a-text-price"}).text.strip() else: price_without_discount = price discount = False # Simply check if the item is out of stock or not out_of_stock = self.product_page.find("div", {"id": "outOfStock"}) if out_of_stock: out_of_stock=True else: out_of_stock=False # Get the description description = self.product_page.find("div", {"id": "productDescription"}).text # Extract the related items carousel's first page carousel = self.product_page.find("div", {"class": "a-carousel-viewport"}) related_items = carousel.find_all("li") related_item_asins = [item.find("div")["data-asin"] for item in related_items] # Of course, we need to return the product URLs # So let's construct them! related_item_links = [] for asin in related_item_asins: link = "www.amazon.com/dp/" + asin related_item_links.append(link) extracted_data = { "title": title, "rating": rating, "price": price, "discount": discount, "price without discount": price_without_discount, "out of stock": out_of_stock, "description": description, "related items": related_item_links } return extracted_data
让我们使用产品的链接来尝试一下:
scraper = Amazon() scraper.get("https://www.amazon.com/Crockpot-Electric-Portable-20-Ounce-Licorice/dp/B09BDGFSWS") data = scraper.data_from_product_page() for k,v in data.items(): print(f"{k}:{v}")
最终输出:
title:Crockpot Electric Lunch Box, Portable Food Warmer for On-the-Go, 20-Ounce, Black Licorice rating:4.7 out of 5 stars price:$29.99 discount:False price without discount:$29.99 out of stock:False description: Take your favorite meals with you wherever you go! The Crockpot Lunch Crock Food Warmer is a convenient, easy-to-carry, electric lunch box. Plus, with its modern-vintage aesthetic and elegant Black Licorice color, it's stylish, too. It is perfectly sized for one person, and is ideal for carrying and warming meals while you’re on the go. With its 20-ounce capacity, this heated lunch box is perfect whether you're in the office, working from home, or on a road trip. Take your leftovers, soup, oatmeal, and more with you, then enjoy it at the perfect temperature! This portable food warmer features a tight-closing outer lid to help reduce spills, as well as an easy-carry handle, soft-touch coating, and detachable cord. The container is removable for effortless filling, carrying, and storage. Cleanup is easy, too: the inner container and lid are dishwasher-safe. related items:['www.amazon.com/dp/B09YXSNCTM', 'www.amazon.com/dp/B099PNWYRH', 'www.amazon.com/dp/B09B14821T', 'www.amazon.com/dp/B074TZKCCV', 'www.amazon.com/dp/B0937KMYT8', 'www.amazon.com/dp/B07T7F5GHX', 'www.amazon.com/dp/B07YJFB8GY']
当然,亚马逊的页面结构比较复杂,不同的商品页面可能会有不同的结构。因此,应该一遍又一遍地重复这个过程来构建一个可靠的网络抓取工具。
恭喜!如果你跟着做,你就建立了自己的亚马逊产品抓取工具!
这很麻烦,好吧,使用完全为网络抓取设计的服务怎么样?
结论
亚马逊的网页结构复杂,您可能会看到不同的结构化页面。您应该找出差异并随之更新您的应用程序。
您还需要在扩展时随机化 HTTP 标头并使用高级代理服务器。否则,你很容易被发现。
在本文中,您已了解
- 从亚马逊抓取数据的重要性。
- 亚马逊用来阻止网络抓取工具的方法。
- 从亚马逊抓取产品价格、描述和更多数据。
- ZenRows 如何帮助您使用其自动解析功能。
最后,扩展和构建高效的网络抓取工具是困难的。当页面结构发生变化时,即使我们准备的脚本也会失败。