in

如何爬取美国时尚品牌Nordstrom的产品数据

如何爬取美国时尚品牌Nordstrom的产品数据

Nordstrom 是一家领先的时装零售商,总部位于美国,拥有一家同样受欢迎的电子商务商店,在全球开展业务。它是一个流行的网络抓取目标,因为它提供了丰富的数据并且它在时尚行业中占有一席之地。

在本指南中,我们将了解使用 Python 进行网络抓取 Nordstrom。我们将涵盖:

  • Nordstrom 产品数据抓取。
  • 产品发现和搜索。

为此,我们将在 Python工具httpxparsel. 为了解析数据,我们将使用隐藏的网络数据方法。

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 框架来支持他们的网站,我们可以直接从页面源中提取数据集:

页面源中提取数据集

Nordstrom 产品的隐藏网络数据
nordstrom 产品的隐藏网络数据

如果我们打开页面源和 ctrl+f 以获取唯一的产品标识符文本(如描述或标题),我们可以看到有一个隐藏的 JSON 数据集。在网络抓取中,这称为隐藏的网络数据抓取,让我们看看如何在 Python 中抓取它。

我们的抓取过程看起来像这样:

  1. 使用 检索产品的 HTML 页面httpx
  2. 使用XPath从<script>标记中找到隐藏的 JSON 数据集。parsel
  3. 使用加载 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

搜索页面

我们可以看到每个搜索(或类别)页面都是由几个页面组成的。所以,我们也需要抓取分页。

为了抓取这个,我们将使用与抓取产品页面非常相似的方法:

  1. 抓取第一个搜索/类别页面 HTML。
  2. 使用parsel和 XPath 查找隐藏的 Web 数据。
  3. 使用从隐藏数据集中提取产品预览数据和分页信息nested-lookup
  4. 计算页面总数并抓取它们。

让我们看看它在 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 适合网络爬行,因为它在整个网站上都有许多产品参考。请注意,与我们在本教程中介绍的直接网页抓取相比,抓取占用的资源要多得多,因此不推荐这样做。相关:Web Scraping 和 Crawling 有什么区别?

Nordstrom 抓取摘要

在本网络抓取指南中,我们了解了如何抓取 Nordstrom——一家流行的时尚电子商务商店。

为此,我们使用 Pythonhttpxparsel隐藏的nested-lookup网络jmespath数据抓取方法。我们收集了 HTML 页面并提取了隐藏的 React 框架数据,只需几行 Python 代码即可找到产品数据字段。

Nordstrom?

Nordstrom 是一家颇受欢迎的时装零售商,拥有庞大的产品目录。由于它提供的丰富数据,它是网络抓取的一个很好的目标。它的受欢迎程度和数据集大小是了解时尚电子商务市场的好方法。此数据可用于业务分析、市场分析和竞争情报。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。