npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pang3fan-creator/analytics-kit

v0.7.0

Published

Typed clients for querying Google Search Console, Google Analytics 4, Microsoft Clarity, Bing Webmaster Tools, and Ahrefs. Runtime-agnostic (Node.js / Cloudflare Workers / edge).

Readme

analytics-kit

类型安全的网站分析数据查询客户端,用于在 Node.js / Cloudflare Workers / Deno / Bun 中查询五个平台的数据:

  • Google Search Console(搜索表现:点击/展示/排名/CTR)
  • Google Analytics 4(runReport 报表)
  • Microsoft Clarity(仪表盘指标 + 实验性会话列表)
  • Bing Webmaster Tools(排名与流量 / 关键词 / 页面)
  • Ahrefs(免费公开端点:域名权重 DR 查询)

零 Node 专有依赖(仅 jose),基于全局 fetch,可在 Node.js 18+ / Cloudflare Workers / Deno / Bun 中运行。刻意不使用 googleapis 等官方包,因为它们依赖的 google-auth-library/gaxios 栈无法可靠运行在 Workers 等 edge 运行时。

平台与入口

| 平台 | 客户端 | 主要 API | 必需配置 | |---|---|---|---| | Google Search Console | createGscClient | searchAnalytics / searchAnalyticsAll / inspectUrl / inspectUrls | GSC_SITE_URL + Google 服务账号 | | Google Analytics 4 | createGa4Client | runReport / runReportAll / toRowObjects | GA4_PROPERTY_ID + Google 服务账号 | | Bing Webmaster Tools | createBingClient | getRankAndTrafficStats / getQueryStats / getPageStats | BING_API_KEY + BING_SITE_URL | | Ahrefs | createAhrefsClient | getDomainRating / getLimitsAndUsage | AHREFS_API_KEY | | Microsoft Clarity | createClarityClient | getLiveInsights / getRawLiveInsights / listSessions | CLARITY_API_TOKEN |

目录

安装

# 从 npm 安装(推荐)
pnpm add @pang3fan-creator/analytics-kit

# 需要跟仓库里尚未发布的改动时:
pnpm add git+ssh://[email protected]/pang3fan-creator/analytics-kit.git

前置配置

Google(GSC + GA4 共用一套服务账号)

  1. 在 Google Cloud Console 创建项目,启用两个 API:
    • Search Console API
    • Google Analytics Data API
  2. 创建服务账号(IAM → Service Accounts),生成 JSON 密钥并下载。密钥里的 client_email 对应 GOOGLE_SERVICE_ACCOUNT_EMAIL,private_key 对应 GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY。
  3. 授权访问数据(最常见踩坑点,漏掉必 403):
    • GSC:设置 → 用户和权限 → 添加服务账号邮箱为用户。仅用 searchAnalytics 时 Restricted 即可;使用 inspectUrl(URL Inspection API)必须是 Owner 或 Full,否则会 403
    • GA4:管理 → 属性访问管理 → 添加服务账号邮箱为 Viewer

Microsoft Clarity

  • Clarity 项目 → Settings → Data Export → Generate new API Token(仅项目管理员可操作)
  • Token 与单个项目绑定;每个项目需要各自的 token

Bing Webmaster Tools

  • Bing Webmaster → 右上角 Settings → API Access → 接受条款 → API Key → Generate API Key
  • API Key 按账号生成(不是站点),一个 Key 可用于账号下所有已验证站点;删除后必须更新所有引用它的项目
  • 站点标识为 URL-prefix 形式(如 https://example.com/),Bing 不支持 Google 的 sc-domain: 网域资源语法

Ahrefs(当前仅免费公开端点)

  • Ahrefs → Account settings → API keys → 生成 APIv3 Key(无需付费订阅;公共端点免费、消耗 0 units)
  • 注意:site-audit/*、keywords-explorer/* 等订阅端点在公共 Key 下会 401(返 ["Error","Unauthorized"]),仅 subscription-info/limits-and-usage 在付费计划下可用;本库目前只实现免费能力,付费端点待升级 Lite+ 后按需扩展
  • 域名权重数据公开显示需标注 "Domain Rating by Ahrefs"(许可条款)

环境变量

| 变量 | 说明 | |---|---| | GOOGLE_SERVICE_ACCOUNT_EMAIL | 服务账号邮箱(client_email) | | GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY | 服务账号私钥(private_key),单行 \n 转义自动还原 | | GSC_SITE_URL | GSC 属性:https://example.com/ 或 sc-domain:example.com | | GA4_PROPERTY_ID | GA4 纯数字 Property ID(不是 G- 开头的数据流 ID) | | CLARITY_API_TOKEN | Clarity 项目 API Token | | BING_API_KEY | Bing Webmaster API Key(设置 → API Access → API Key,账号级) | | BING_SITE_URL | Bing 站点标识,URL-prefix 形式,如 https://example.com/ | | AHREFS_API_KEY | Ahrefs APIv3 Key(Account settings → API keys;免费公共端点无需订阅) |

所有变量都可以不设——改为在创建客户端时显式传参(Cloudflare Workers 无 process.env 时用这种方式)。

快速开始

以 GSC 为例(其他平台替换工厂函数即可):

import { createGscClient } from '@pang3fan-creator/analytics-kit'

const gsc = createGscClient() // 从 process.env 读取 GSC_SITE_URL + Google 服务账号
const result = await gsc.searchAnalytics({
  startDate: '2026-08-01',
  endDate: '2026-08-21',
  dimensions: ['query', 'page'],
  rowLimit: 1_000,
})

所有客户端都支持显式传参(Workers 等无 process.env 的运行时必须这样用),也支持 timeoutMs 和 fetchImpl:

// Cloudflare Workers 等运行时把 bindings 作为 env 传进来
const gsc = createGscClient({
  siteUrl: env.GSC_SITE_URL,
  clientEmail: env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
  privateKey: env.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY,
  timeoutMs: 30_000,
})

timeoutMs 不传则不加超时;超时以 AnalyticsKitError 抛出,kind: 'timeout'。

显式传参速查

| 客户端 | 主要参数 | |---|---| | createGscClient | siteUrl / clientEmail / privateKey | | createGa4Client | propertyId / clientEmail / privateKey | | createBingClient | apiKey / siteUrl | | createAhrefsClient | apiKey | | createClarityClient | apiToken |

所有客户端还都接受 env(自定义环境变量记录)、fetchImpl(自定义 fetch)和 timeoutMs。GSC / GA4 还接受 auth(自定义 GoogleTokenProvider,例如复用业务侧已有的 access token)。

平台用法

Google Search Console

import { createGscClient } from '@pang3fan-creator/analytics-kit'

const gsc = createGscClient() // 自动读环境变量

const result = await gsc.searchAnalytics({
  startDate: '2026-08-01',
  endDate: '2026-08-21',
  dimensions: ['query', 'page'],
  rowLimit: 1000,
})

// 自动分页拉全量(内部循环 startRow)
const allRows = await gsc.searchAnalyticsAll({
  startDate: '2026-08-01',
  endDate: '2026-08-21',
  dimensions: ['query'],
}, { maxRows: 50_000 })

结果固定按 clicks 降序;keys[] 的顺序与请求的 dimensions 一致。

URL 检查(单个 URL 的索引裁决)

searchAnalytics 之外,GSC 客户端还封装了 URL Inspection API,用于查询单个 URL 在 Google 索引中的状态:

const inspection = await gsc.inspectUrl({
  inspectionUrl: 'https://example.com/blog/post',
  siteUrl: 'sc-domain:example.com', // 可选,默认取客户端的 GSC_SITE_URL
  languageCode: 'en-US',            // 可选,影响 coverageState 的文案语言
})

const status = inspection.indexStatusResult
status.verdict          // 'PASS' | 'NEUTRAL' | 'FAIL' | 'PARTIAL' | 'VERDICT_UNSPECIFIED'
status.coverageState    // 如 'Crawled - currently not indexed'(本地化文案;缺失时字段不存在)
status.googleCanonical  // Google 选定的 canonical(未收录时可能缺省)
status.userCanonical    // 页面声明的 canonical(未声明时缺省)
status.lastCrawlTime    // 最后一次成功抓取时间(从未抓取时缺省)
status.robotsTxtState   // 'ALLOWED' | 'DISALLOWED' | ...
status.indexingState    // 'INDEXING_ALLOWED' | 'BLOCKED_BY_META_TAG' | ...
status.pageFetchState   // 'SUCCESSFUL' | 'SOFT_404' | 'NOT_FOUND' | ...
status.referringUrls    // 指向该 URL 的链接(缺省为 [])
status.sitemap          // 包含该 URL 的 sitemap(缺省为 [])

inspection.raw          // 完整 inspectionResult,含可选的 ampResult / richResultsResult / mobileUsabilityResult

// 批量:先校验全部 URL,再顺序请求;任一失败立即抛 AnalyticsKitError
const results = await gsc.inspectUrls(
  [url1, url2, url3, url4],
  { siteUrl: 'sc-domain:example.com', languageCode: 'en-US' },
)

本客户端只归一化 indexStatusResult;AMP、Rich Results、Mobile Usability 等分析结果不丢弃,原样放在 inspection.raw 里,需要时直接取。

使用注意:

  • 返回的是 Google 索引中的版本,不是实时抓取;lastCrawlTime 可能落后于当前页面。
  • coverageState 是本地化展示文案,不适合做精确字符串匹配;判断是否收录请用 verdict,要稳定文案请固定 languageCode: 'en-US'。
  • inspectUrls 会先校验批量里的每一个 URL 再发第一个请求,坏 URL 不会白白消耗配额;请求顺序执行,任一失败立即抛错,已成功的前序结果不会返回。需要部分成功请自行循环 inspectUrl 并捕获错误。
  • 配额为 2,000 次/天、600 次/分钟(按资源);服务账号在该资源上必须是 Owner 或 Full,Restricted 会 403。
  • 这个 API 只能逐个 URL 查询,不能列出所有未收录 URL,也拿不到 GSC「网页索引编制」报告的桶计数——那些只能在 GSC 界面导出。

Google Analytics 4

import { createGa4Client, toRowObjects } from '@pang3fan-creator/analytics-kit'

const ga4 = createGa4Client()

const report = await ga4.runReport({
  dateRanges: [{ startDate: '29daysAgo', endDate: 'today' }],
  dimensions: [{ name: 'country' }],
  metrics: [{ name: 'sessions' }, { name: 'activeUsers' }],
  returnPropertyQuota: true,
})

// 行转对象:{ country: 'China', sessions: '123', ... }
const rows = toRowObjects(report)

// 自动分页(按 rowCount 循环 offset)
const all = await ga4.runReportAll({
  dateRanges: [{ startDate: '90daysAgo', endDate: 'today' }],
  dimensions: [{ name: 'pagePath' }],
  metrics: [{ name: 'screenPageViews' }],
})

GA4 与 GSC 共享同一服务账号凭据时,access token 自动复用缓存:两个客户端默认都以合并后的只读 scopes(webmasters.readonly + analytics.readonly)请求 token,同凭据每小时只换一次。

Bing Webmaster Tools

import { createBingClient } from '@pang3fan-creator/analytics-kit'

const bing = createBingClient() // 自动读环境变量

// 排名与流量(按天,近一个月)
const traffic = await bing.getRankAndTrafficStats()
// [{ date: '2026-08-21', clicks: 16, impressions: 202 }, ...]

// 关键词明细(官方扁平结构:每行一个查询词)
const queries = await bing.getQueryStats()
// [{ query: 'schedule maker', clicks: 15, impressions: 100, avgClickPosition: 18, avgImpressionPosition: 17, date: '2011-09-16' }, ...]

// 页面流量(官方 Query 字段承载页面 URL)
const pages = await bing.getPageStats()

Bing 走官方 JSON/HTTP 协议(其 SOAP/POX 协议已于 2026-08-31 退役,本库只使用 JSON 端点)。单个 API Key 可查询账号下所有已验证站点,按 BING_SITE_URL 区分目标站。注意:

  • 位置字段粒度:avgClickPosition / avgImpressionPosition 与 GSC 的 position 不同,是 Bing 原始刻度(如 18 对应 UI 显示的 1.8),展示前请自行 ÷10
  • 日期格式:微软序列化为 /Date(毫秒数±时区)/,本库已按带时区的本地日历日期归一化为 YYYY-MM-DD(不会因 UTC 截断出现跨日偏移)
  • 日期范围:GetQueryStats / GetPageStats 不支持服务端日期参数,返回全量数据,需要窗口时请客户端过滤
  • 误传 sc-domain: 形式的站点标识会抛出带说明的 AnalyticsKitConfigError

Ahrefs

import { createAhrefsClient } from '@pang3fan-creator/analytics-kit'

const ahrefs = createAhrefsClient() // 自动读环境变量

// 任意域名/URL 的 Domain Rating(0 units,免费)
const dr = await ahrefs.getDomainRating('tanship.app')
console.log(dr.domainRating) // 例如 22
console.log(dr.license)      // 署名条款 URL(公开显示时需标注 "Domain Rating by Ahrefs")

// 查看当前 Key 的计划/额度(0 units;公共 Key 会 401,需 Lite+ 计划)
const limits = await ahrefs.getLimitsAndUsage()
console.log(limits.subscription, limits.unitsUsageApiKey, limits.unitsLimitApiKey, limits.usageResetDate)

当前覆盖官方免费公开端点;site-audit / keywords-explorer 等订阅端点消耗 API units(50/次起),需要升级到 Lite 及以上后才可用(届时再按需扩展方法)。

Microsoft Clarity

import { createClarityClient } from '@pang3fan-creator/analytics-kit'

const clarity = createClarityClient()

// 官方 Export API:仅最近 1-3 天,10 次/项目/天
const insights = await clarity.getLiveInsights({
  numOfDays: 3,
  dimensions: ['Device', 'Country/Region'], // 最多 3 个
})

for (const metric of insights) {
  console.log(metric.metricName)          // 如 "Traffic"、"RageClickCount"
  for (const row of metric.rows) {
    console.log(row.dimensions)           // { Device: 'Mobile', ... }
    console.log(row.values)               // 计数字符串已转为数字
  }
}

归一化处理了官方响应的已知陷阱:

  • 计数字段是 JSON 字符串 → 已转为 number
  • 维度回显键名大小写不稳定(如 URL 回显为 Url)→ 统一映射回请求时的维度名
  • 注意:totalSessionCount 包含 bot 会话(totalBotSessionCount),人类会话需自行相减;摩擦类指标没有原始计数,只有发生率与 subTotal/sessionsCount
  • 需要原始结构时改用 getRawLiveInsights()

实验性:会话列表(走未文档化的 clarity.microsoft.com/mcp/recordings/sample 端点,即官方 MCP 服务器所用接口;无 SLA,可能随官方变更失效):

const sessions = await clarity.listSessions({
  start: new Date('2026-08-20T00:00:00Z'),
  end: new Date('2026-08-21T00:00:00Z'),
  sortBy: 'SessionDuration_DESC', // 可选,默认 SessionStart_DESC
  count: 100,                     // 上限 250
  filters: {
    deviceType: ['Mobile'],
    rageClickPresent: true,
    deadClickPresent: true,
    javascriptErrors: [''],
  },
})

响应结构未文档化,返回解析后的 JSON 原样值。

Cloudflare Workers 中使用

Workers 没有 process.env,创建客户端时显式传配置即可:

export default {
  async fetch(request: Request, env: Env) {
    const gsc = createGscClient({
      siteUrl: env.GSC_SITE_URL,
      clientEmail: env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
      privateKey: env.GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY,
    })
    // ...
  },
}

错误处理

所有失败都抛出统一的 AnalyticsKitError——HTTP 错误、网络失败、超时、外部中断和响应解析失败都不会漏网,调用方只需 instanceof 一种判断:

import { AnalyticsKitError } from '@pang3fan-creator/analytics-kit'

try {
  await gsc.searchAnalytics({ ... })
} catch (error) {
  if (error instanceof AnalyticsKitError) {
    error.platform  // 'gsc' | 'ga4' | 'clarity' | 'bing' | 'ahrefs' | 'google-auth'
    error.kind      // 'http' | 'network' | 'timeout' | 'abort' | 'parse'
    error.status    // HTTP 状态码(仅 kind === 'http' 时存在)
    error.body      // 服务端错误体(仅 HTTP 错误)
    error.url       // 请求地址(Bing 这类把 apikey 放在 query 上的平台已脱敏)
    error.cause     // 原始错误(仅非 HTTP 错误)
  }
}

已知边界:本库不做自动重试/退避(由调用方决定策略)、不对响应做运行时 schema 校验(类型安全覆盖输入与文档化契约);配置类问题抛 AnalyticsKitConfigError。

常见 HTTP 状态:

| status | 常见原因与处理 | |---|---| | 401 | 凭据无效或已过期;重新生成 token / 检查服务账号密钥 | | 403 | 服务账号未被加入对应资源;GSC 的 inspectUrl 需要 Owner 或 Full;也可能是对应的 Google API 未启用 | | 404 | 资源不存在(GSC_SITE_URL / GA4_PROPERTY_ID / Bing 站点);GSC 待查 URL 不属于资源时更常见 403 | | 429 | 触发限速或日额度用尽 |

error.body 保留服务端原始错误体;排查时优先看 body.error.message。

平台限制速查

| 限制 | GSC | GA4 | Clarity | Bing | Ahrefs | |---|---|---|---|---|---| | 单请求行数上限 | 25,000 | 250,000 | 1,000(不可分页) | 近月按天聚合,无分行参数 | 单域名 DR 一次查询 | | 数据窗口 | 16 个月 | 视属性配置 | 仅最近 3 天 | 约一个月(按天) | 实时 | | 频率限制 | 1,200 QPM/站点(searchAnalytics);URL Inspection 2,000 QPD / 600 QPM | 按 token 配额(标准属性约 200k tokens/天) | 10 次/天/项目 | 官方未公开限额(宽松) | 免费公开端点 0 units,无公布限额 | | 数据延迟 | 2–3 天 | 数小时–1 天 | 数小时 | 4–5 天 | 实时 |

GA4 注意:单请求最多返回 250,000 行;runReportAll 通过 offset 深翻页拉取更大结果集时,官方服务端在大 offset 下可能出现不稳定或超时,建议用 maxRows 控制总量或缩小维度基数。

Clarity 官方 API 只覆盖近 72 小时且每天仅 10 次调用——若需历史留存,请在业务侧定时调用并将结果持久化到自己的存储。

开发

pnpm test                                # vitest 单测(全部 mock fetch,不发真实网络请求)
pnpm typecheck                           # tsc --noEmit
pnpm build                               # tsdown 构建 dist/
pnpm exec vitest run tests/gsc.test.ts   # 只跑单个平台的测试

真实凭据冒烟:node scripts/smoke.mjs 会依次打五个平台的真实接口(GSC 另含一条 URL Inspection 用例;需先 pnpm build,且根目录 .env 里是真值)。所有 Google 端点(GSC / GA4 的 token 交换与查询、GSC URL Inspection)需要代理:

NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://127.0.0.1:8118 node scripts/smoke.mjs

只跑其中一条(避免消耗其他平台额度;SMOKE_ONLY 按用例名精确匹配,多个用逗号分隔):

SMOKE_ONLY='GSC URL 检查' NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://127.0.0.1:8118 node scripts/smoke.mjs

NODE_USE_ENV_PROXY=1 需要 Node ≥ 24;Clarity / Bing / Ahrefs 可直连。该脚本不打进发布包(files: ["dist"])。

发布:npm publish(需先登录 npm 账号 pang3fan-creator;prepublishOnly 钩子会自动跑 typecheck + test + build)。