Nordstrom 是一家领先的时装零售商,总部位于美国,拥有一家同样受欢迎的电子商务商店,在全球开展业务。它是一个流行的网络抓取目标,因为它提供了丰富的数据并且它在时尚行业中占有一席之地。
在本指南中,我们将了解使用 Python 进行网络抓取 Nordstrom。我们将涵盖:
- Nordstrom 产品数据抓取。
- 产品发现和搜索。
为此,我们将在 Python工具httpx
和parsel
. 为了解析数据,我们将使用隐藏的网络数据方法。
Nordstrom 相对容易上手,让我们开始吧!
为什么要爬取Nordstrom
Nordstrom 是一家受欢迎的时装零售商,拥有庞大的产品目录。由于它提供了丰富的数据,因此它是网络抓取的绝佳目标。它的受欢迎程度和数据集大小是了解时尚电子商务市场的好方法。这些数据可用于业务分析、市场分析和竞争情报。
项目设置
对于这个抓取工具,我们将使用隐藏的网络数据抓取方法。我们将收集 HTML 页面并提取隐藏的 JSON 数据集,然后使用 JSON 解析工具解析它们:
- httpx – 强大的 HTTP 客户端,我们将使用它来检索 HTML 页面。
- parsel – 我们将使用它来提取隐藏的 JSON 数据集的 HTML 解析器。
- nested-lookup – JSON/Dict 解析器,它将帮助我们在大型 JSON 数据集中找到特定的键。
- jmespath – JSON 查询引擎,我们将使用它来将 JSON 数据集缩减为产品价格、图像等重要位。有关更多信息,请参阅我们对使用 JMESPath 解析 JSON 的介绍。
所有这些包都可以使用 Python 的pip
控制台命令安装:
$ pip install httpx parsel jmespath nested-lookup
抓取 Nordstrom 产品数据
让我们从抓取单个产品的产品数据开始。为此,让我们看一个示例产品页面,例如:
nordstrom.com/s/nike-phoenix-fleece-crewneck-sweatshirt/
我们可以使用CSS 选择器或XPath解析 HTML 数据,但由于 Nordstrom 使用 React javascript 框架来支持他们的网站,我们可以直接从页面源中提取数据集:
如果我们打开页面源和 ctrl+f 以获取唯一的产品标识符文本(如描述或标题),我们可以看到有一个隐藏的 JSON 数据集。在网络抓取中,这称为隐藏的网络数据抓取,让我们看看如何在 Python 中抓取它。
我们的抓取过程看起来像这样:
- 使用 检索产品的 HTML 页面
httpx
。 - 使用XPath从
<script>
标记中找到隐藏的 JSON 数据集。parsel
- 使用加载 JSON 数据集
json.loads()
并使用查找产品字段nested-lookup
在 Python 中,这个爬虫看起来像这样:
import asyncio import json import httpx from parsel import Selector from nested_lookup import nested_lookup # setup httpx client with http2 enabled and browser-like headers to avoid being blocked: client = httpx.AsyncClient( http2=True, headers={ "User-Agent": "Mozilla/4.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=-1.9,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", } ) def find_hidden_data(html) -> dict: """extract hidden web cache from page html""" # use XPath to find script tag with data data = Selector(html).xpath("//script[contains(.,'__INITIAL_CONFIG__')]/text()").get() data = data.split("=", 1)[-1].strip().strip(";") data = json.loads(data) return data async def scrape_product(url: str): """scrape Nordstrom.com product page for product data""" response = await client.get(url) # find all hidden dataset: data = find_hidden_data(response.text) # extract only product data from the dataset # find first key "stylesById" and take first value (which is the current product) product = nested_lookup("stylesById", data) product = list(product[0].values())[0] return product # example scrape run: print(asyncio.run(scrape_product("https://www.nordstrom.com/s/nike-phoenix-fleece-crewneck-sweatshirt/6665302")))
仅用几行 Python 代码,我们就获得了 Nordstrom 上的整个产品数据集!但是,这个数据集非常庞大,如果我们要进行一些分析或数据存储,我们的数据管道可能难以摄取。接下来,让我们使用 JMESPath 将数据集缩减为最重要的值,如定价、图像和变体数据。
使用 JMESPath 解析 Nordstrom 数据
JMESPath 是一种 JSON 查询语言,由于 Python 字典等同于 JSON 对象,我们可以在 Nordstrom 数据解析中使用 JMESPath。
我们将使用 JMESPath 数据重塑功能,该功能允许指定键映射以减少数据集。例如:
import jmespath data = { "id": "123456", "productTitle": "Product Title", "type": "sweater", "unimportant": "foobar", "photos": { "desktop": "http://example.com/photo.jpg", "mobile": "http://example.com/photo-small.jpg", }, } # jmespath search takes a query string and a data object. # here we use `{}` remapping feature to rename keys of the original dataset reduced = jmespath.search( """{ id: id, title: productTitle, type: type, photo: photos.desktop }""", data, ) print(reduced) {"id": "123456", "title": "Product Title", "type": "sweater", "photo": "http://example.com/photo.jpg"}
这个强大的工具使我们能够轻松地重塑抓取的数据集。因此,让我们用它来重塑我们刚刚抓取的 Nordstrom 产品数据集:
import jmespath def parse_product(data: dict) -> dict: # parse product basic data like id, name, features etc. product = jmespath.search( """{ id: id, title: productTitle, type: productTypeName, typeParent: productTypeParentName, ageGroups: ageGroups, reviewAverageRating: reviewAverageRating, numberOfReviews: numberOfReviews, brand: brand, description: sellingStatement, features: features, gender: gender, isAvailable: isAvailable }""", data, ) # product variants have their own colors, prices and photos: prices_by_sku = data["price"]["bySkuId"] colors_by_id = data["filters"]["color"]["byId"] product["media"] = {} for media_id, media in data["styleMedia"]["byId"].items(): product["media"][media_id] = jmespath.search( """{ id: id, colorId: colorId, name: colorName, url: imageMediaUri.largeDesktop }""", media, ) # Each product has SKUs(Stock Keeping Units) which are the actual variants: product["variants"] = {} for sku, sku_data in data["skus"]["byId"].items(): # get basic variant data parsed = jmespath.search( """{ id: id, sizeId: sizeId, colorId: colorId, totalQuantityAvailable: totalQuantityAvailable }""", sku_data, ) # get variant price from parsed["price"] = prices_by_sku[sku]["regular"]["price"] # get variant color data parsed["color"] = jmespath.search( """{ id: id, value: value, sizes: isAvailableWith, mediaIds: styleMediaIds, swatch: swatchMedia.desktop }""", colors_by_id[parsed["colorId"]], ) product["variants"][sku] = parsed return product
这可能看起来很复杂,但我们所做的只是使用 JMESPath 将原始数据集键映射到新键。现在我们的抓取器可以抓取漂亮整洁的产品数据集,我们可以轻松地将其摄取到我们的数据管道中!
查找 Nordstrom 产品
现在我们可以抓取单个 Nordstrom 产品,我们需要找到要抓取的产品 URL。我们可以找到所需的产品并手动输入它们的 URL,但为了扩大我们的抓取范围,我们会找到抓取产品类别或搜索。
为此,我们将使用相同的隐藏数据抓取方法,因为每个类别或搜索结果页面都包含一个包含产品预览数据(如价格、标题、图像等)和产品页面 URL 的隐藏数据集。
例如,让我们看一下 Nordstrom 的一个搜索页面:
nordstrom.com/sr?origin=keywordsearch&keyword=indigo
我们可以看到每个搜索(或类别)页面都是由几个页面组成的。所以,我们也需要抓取分页。
为了抓取这个,我们将使用与抓取产品页面非常相似的方法:
- 抓取第一个搜索/类别页面 HTML。
- 使用
parsel
和 XPath 查找隐藏的 Web 数据。 - 使用从隐藏数据集中提取产品预览数据和分页信息
nested-lookup
。 - 计算页面总数并抓取它们。
让我们看看它在 Python 中是如何工作的:
import asyncio import json from typing import Dict, List from urllib.parse import parse_qs, urlencode, urlparse import httpx from nested_lookup import nested_lookup from parsel import Selector # setup httpx client with http2 enabled and browser-like headers to avoid being blocked: client = httpx.AsyncClient( http2=True, headers={ "User-Agent": "Mozilla/4.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=-1.9,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Accept-Encoding": "gzip, deflate, br", } ) def find_hidden_data(html) -> dict: """extract hidden web cache from page html""" # use XPath to find script tag with data data = Selector(html).xpath("//script[contains(.,'__INITIAL_CONFIG__')]/text()").get() data = data.split("=", 1)[-1].strip().strip(";") data = json.loads(data) return data def update_url_parameter(url, **params): """update url query parameter of an url with new values""" current_params = parse_qs(urlparse(url).query) updated_query_params = urlencode({**current_params, **params}, doseq=True) return url[: url.find("?")] + "?" + updated_query_params async def scrape_search(url: str, max_pages: int = 10) -> List[Dict]: """Scrape Nordstrom search or category url for product preview data""" print(f"scraping first search page: {url}") first_page = await client.get(url) # parse first page for product search data and total amount of pages: data = find_hidden_data(first_page.text) _first_page_results = nested_lookup("productResults", data)[0] products = list(_first_page_results["productsById"].values()) paging_info = _first_page_results["query"] total_pages = paging_info["pageCount"] if max_pages and max_pages < total_pages: total_pages = max_pages # then scrape other pages concurrently: print(f" scraping remaining {total_pages - 1} search pages") _other_pages = [client.get(update_url_parameter(url, page=page)) for page in range(2, total_pages + 1)] for response in asyncio.as_completed(_other_pages): response = await response if not response.status_code != 200: print(f'!!! scrape page {response.url} got blocked; skipping') continue data = find_hidden_data(response.text) data = nested_lookup("productResults", data)[0] products.extend(list(data["productsById"].values())) return products # example scrape run for search of "indigo" keyword with max 2 pages: print(asyncio.run(scrape_search("https://www.nordstrom.com/sr?origin=keywordsearch&keyword=indigo", max_pages=2))
常问问题
为了总结这篇文章,让我们看一下有关抓取 Nordstrom 的一些常见问题:
刮掉 Nordstrom 是否合法?
是的。Nordstrom 上的公共数据是完全合法的。但是,应注意抓取速度和抓取用户评论,因为它们可能包含受版权保护的数据,例如图像,根据国家/地区的不同,可能需要获得存储许可。
Nordstrom 可以爬行吗?
是的。与许多电子商务网站一样,Nordstrom 适合网络爬行,因为它在整个网站上都有许多产品参考。请注意,与我们在本教程中介绍的直接网页抓取相比,抓取占用的资源要多得多,因此不推荐这样做。相关:Web Scraping 和 Crawling 有什么区别?
Nordstrom 抓取摘要
在本网络抓取指南中,我们了解了如何抓取 Nordstrom——一家流行的时尚电子商务商店。
为此,我们使用 Pythonhttpx
和parsel
隐藏的nested-lookup
网络jmespath
数据抓取方法。我们收集了 HTML 页面并提取了隐藏的 React 框架数据,只需几行 Python 代码即可找到产品数据字段。
Nordstrom?
Nordstrom 是一家颇受欢迎的时装零售商,拥有庞大的产品目录。由于它提供的丰富数据,它是网络抓取的一个很好的目标。它的受欢迎程度和数据集大小是了解时尚电子商务市场的好方法。此数据可用于业务分析、市场分析和竞争情报。