Project portfolio Browse selected work

Shopify Plus Upgrade Monthly Fee Reduction + Up to $4800 Development Fee Credit - Exclusive WesWoo Offer

Article

Shopify 商品查询错搜排查:筛选式诊断与更新时间范围验收

Topics: ,

商品查询返回了数据,却包含范围外的商品,先检查搜索式是否被按预期解析。本文针对 GraphQL Admin API 的 products 查询,完成一次“诊断搜索式、修正 UTC 边界、验收小量结果”的只读排查。输入是时间范围和实际请求;输出是可解释的诊断记录与样本结果。

本文按 2026 年 10 月 4 日核对的 2026-10 文档编写。前提是现有应用已获商家授权,并具有所需的 read_products 权限;使用在线访问方式时还要考虑操作用户的权限。先确认现有授权,再执行下面的读取。参见 Product 权限要求和访问范围与商家授权说明。

先把时间范围写成可核对的搜索式

假设要检查 UTC 的 2026 年 10 月 1 日,约定范围为下界包含、上界不包含,即从 2026-10-01T00:00:00Z 到 2026-10-02T00:00:00Z。这是演示输入,不代表某个商店的实际数据。带时区的输入应先转换为 UTC,不能把本地零点直接加上 Z。

products 的搜索字段是 updated_at,响应里的商品字段则是 updatedAt。搜索式中的日期值要加引号,两个比较条件以空格连接。排序采用与筛选字段相配的 UPDATED_AT;默认排序键是 ID。这些名称分别见 products 的 updated_at 与 sortKey 定义、ProductSortKeys和日期比较语法。

updated_at:>='2026-10-01T00:00:00Z' updated_at:<'2026-10-02T00:00:00Z'

在实际请求上开启搜索诊断

在现有授权请求层向 /admin/api/2026-10/graphql.json 发送以下查询和变量,并加入请求头 Shopify-Search-Query-Debug: 1。这个头用于取得搜索诊断;要检查实际发送的头和变量,而不是仅检查客户端的配置对象。官方示例见搜索诊断请求头与响应。

query ProductWindow($filter: String!, $after: String) {
  products(first: 5, after: $after, query: $filter,
           sortKey: UPDATED_AT, reverse: false) {
    nodes { id updatedAt }
    pageInfo { hasNextPage endCursor }
  }
}
{
  "filter": "updated_at:>='2026-10-01T00:00:00Z' updated_at:<'2026-10-02T00:00:00Z'",
  "after": null
}

首次请求的 after 为 null,first: 5 只用于小量检查。记录响应的 X-Shopify-API-Version,确认实际处理版本与预期一致;Shopify 对不再支持的请求版本可能回退到受支持版本,不能只看 URL。参见版本响应头说明。

先看 warnings,再逐项核对 parsed

在响应的 extensions.search 中找到对应 products 路径的记录,并核对其 query 与本次发送的筛选式。随后检查 warnings 和 parsed。官方诊断示例包含 path、query、parsed、warnings;无效搜索字段会被忽略,可能造成结果范围扩大。字段是否可用应查当前资源定义,不能将其他资源的筛选字段直接搬来。参见搜索解析与无效字段警告。

  1. 出现无效字段等警告时,保存原请求和警告,修正搜索字段后重新请求。此时返回了商品也不能证明筛选生效。
  2. 没有诊断记录时,检查实际请求是否携带 debug 头、响应扩展是否被中间层丢弃,以及请求路径和变量是否对应本次查询。
  3. 有 parsed 且没有返回警告后,仍需阅读解析结果:字段应是 updated_at,下界应包含、上界应排除,两个条件应共同约束该范围。

本文查询没有别名,因此下面的辅助函数只匹配路径 ["products"]。若查询改用别名,应根据该请求的实际诊断路径调整匹配规则。不要把多个路径的诊断合并成一份成功结论。

用小函数生成边界并保留人工验收关口

以下 Python 代码只处理带时区日期与诊断记录,不发送 HTTP。utc_filter 生成同一 UTC 范围;inspect_search 拒绝缺失、错配、有警告或有顶层错误的输入。返回 REVIEW_PARSED_SCOPE 的含义是“可以开始人工核对 parsed”,并非自动确认 Shopify 已正确执行所有搜索条件。

"""Offline helpers for one bounded products search. No HTTP client."""
from datetime import datetime, timezone

def utc_filter(lower, upper):
    def parse(value):
        stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
        if "T" not in value or stamp.tzinfo is None:
            raise ValueError("timezone-bearing datetime required")
        return stamp.astimezone(timezone.utc)
    start, end = parse(lower), parse(upper)
    if start >= end:
        raise ValueError("lower boundary must precede upper boundary")
    fmt = lambda value: value.isoformat().replace("+00:00", "Z")
    return f"updated_at:>='{fmt(start)}' updated_at:<'{fmt(end)}'"

def inspect_search(payload, expected_filter):
    if not isinstance(payload, dict) or payload.get("errors"):
        return "CHECK_RESPONSE_ERRORS", None
    ext = payload.get("extensions")
    diagnostics = ext.get("search") if isinstance(ext, dict) else None
    if not isinstance(diagnostics, list):
        return "NO_SEARCH_DIAGNOSTICS", None
    matches = [entry for entry in diagnostics if isinstance(entry, dict)
               and entry.get("path") == ["products"]]
    if len(matches) != 1:
        return "AMBIGUOUS_OR_MISSING_PATH", None
    entry = matches[0]
    if entry.get("query") != expected_filter:
        return "FILTER_MISMATCH", None
    warnings = entry.get("warnings", [])
    if not isinstance(warnings, list):
        return "UNCONFIRMED_WARNING_SHAPE", None
    if warnings:
        return "SEARCH_WARNINGS", warnings
    if not isinstance(entry.get("parsed"), dict) or not entry["parsed"]:
        return "NO_PARSED_DIAGNOSTIC", None
    return "REVIEW_PARSED_SCOPE", entry["parsed"]

调用 inspect_search(payload, filter_value) 前,应由现有请求层检查 HTTP 响应、JSON 解码与实际 API 版本。这里没有实现 Shopify 搜索解析器,也不根据一个固定的自造 AST 形状判断线上语义。若还需检查单个商品的字段类型、空值和错误响应,可参照商品响应契约与字段验收。

用小量只读结果完成这次排查

搜索诊断通过人工核对后,逐个检查返回的 id 与 updatedAt,将时间转换为 UTC,再比较是否落在所约定的范围内。如果有已知更新时间的测试商品,可在受控环境中核对边界附近的对象;本例没有在商家店铺执行该测试。

  • 把最终查询、变量、实际版本、诊断记录与样本 ID 保存在同一次排查记录中,避免拿旧响应解释新筛选式。
  • hasNextPage 为 true 时,五条样本不能当作完整范围。需要继续读取时,维持查询条件与排序,把 endCursor 用作下一页的 after;修改筛选式后从首页面重新检查。连续读取与断点持久化另见分页和安全断点示例。
  • 若出现范围外对象,保留该对象时间、原始诊断和请求变量,先确认 UTC 转换与实际解析范围,再决定下一步排查。

本轮配套的离线合成用例已检查带偏移日期转 UTC、无时区输入拒绝、反向范围拒绝、诊断缺失、查询错配与警告拦截。诊断测试使用人工构造的数据,不代表 Shopify 实际返回的 parsed 树;没有向商家 API 发请求。样本验收用于判断这一次搜索式排查能否结束,不能保证持续变化的商品集合已形成一致快照。

Random Image 608
Random Image 608

WESWOO — Shopify Storefront Development

We help Chinese brands expand internationally with storefront development and Shopify Plus integration services, supporting their cross-border commerce operations.

  • Shopify storefront branding
  • International UI design
  • Multichannel social media marketing

Start your indie brand at sea

Independent station brand from 0 to 1 steps: product development - VI tone - website customization - operation automation - customer maintenance

  • 24-hour technical team support

  • All-around brand seafaring escort

Browse the WESWOO project portfolio