How to Download WeChat Article Comments and Replies via the Comment API

The wechat-article-exporter downloads WeChat article comments and their threaded replies by routing authenticated requests through a Nitro server proxy, paginating through the appmsg_comment API responses, and persisting the data to IndexedDB using separate Dexie stores for comments and replies.

The wechat-article/wechat-article-exporter repository implements a complete workflow to extract user interactions from WeChat articles via the official comment API. This guide breaks down the technical pipeline—from credential forwarding and pagination handling to local storage architectur—using the actual source files and method signatures found in the codebase.

Proxying the WeChat Comment API Through Nitro

Direct browser requests to WeChat's comment endpoints fail due to CORS restrictions and strict authentication requirements. The exporter solves this by implementing a server-side proxy endpoint that forwards client requests with valid session credentials.

In server/api/web/misc/comment.get.ts, the Nitro endpoint receives query parameters—including __biz, uin, key, and pass_ticket—and forwards them to https://mp.weixin.qq.com/mp/appmsg_comment using the proxyMpRequest utility. This returns the raw JSON payload from WeChat's appmsg_comment API to the client without modification.

The proxy handles the sensitive cookie-based authentication that WeChat requires, allowing the client-side downloader to operate without exposing credentials to the browser context directly.

Downloading Top-Level Comments

The Downloader class in utils/download/Downloader.ts orchestrates the comment retrieval process. When downloader.startDownload('comments') is invoked, the system iterates over each target article URL and executes fetchComments() (lines 71-84).

This method constructs the getcomment URL, injects the user's credential headers, routes the request through the private proxy managed by ProxyManager, and parses the JSON response. The downloader continues calling the API until continue_flag returns false, indicating all pages have been retrieved.

Each CommentResponse is temporarily held in memory before being committed to storage via updateCommentCache(). The method also checks the reply_new.reply_total_cnt field to determine if a comment has replies that need separate fetching.

Fetching Comment Replies

WeChat stores replies separately from parent comments. After saving top-level comments, the downloader filters for entries where reply_new.reply_total_cnt exceeds the length of the already-fetched reply_list. For each match, it invokes fetchCommentReply() (lines 19-28 in utils/download/Downloader.ts).

This method calls the getcommentreply endpoint with the comment's content_id and the last known max_reply_id parameter. The API returns up to 100 new replies per call. The method iteratively fetches until all replies are retrieved, persisting each ReplyResponse via updateCommentReplyCache().

Persisting Data to IndexedDB

The exporter uses Dexie (an IndexedDB wrapper) to maintain two distinct object stores for comment data:

  • comment – Stores top-level comment JSON blobs, keyed by article URL. Defined in store/v2/comment.ts.
  • comment_reply – Stores reply JSON blobs, keyed by both article URL and comment content_id. Defined in store/v2/comment_reply.ts.

These wrappers provide async methods like getCommentCache() and updateCommentReplyCache() that abstract the IndexedDB transactions. The separation allows the UI to query comments independently from their reply threads, improving render performance for articles with thousands of interactions.

Integrating with the Vue Frontend

The useDownloader composable in composables/useDownloader.ts (lines 49-68) bridges the download engine with the user interface. It instantiates the Downloader class, registers listeners for download:progress and download:finish events, and exposes a download() method that accepts an onComment callback.

When comments are successfully saved, the composable triggers UI updates such as success toasts or progress bars. The downloader also respects a configurable maxRetries parameter defined in utils/download/constants.ts, automatically selecting alternative proxies via ProxyManager when requests fail.

Practical Implementation Examples

Triggering a Comment Download from a Vue Component

import useDownloader from '~/composables/useDownloader'

const { download } = useDownloader({
  onComment: (url) => console.log('Comments saved for', url)
})

// selectedUrls is an array of article URLs chosen in the UI
download('comment', selectedUrls)

Manually Calling the Server Proxy

import { $fetch } from 'ofetch'

async function getComments(biz: string, commentId: string) {
  const resp = await $fetch('/api/web/misc/comment.get', {
    query: {
      __biz: biz,
      comment_id: commentId,
      key: 'xxx',
      uin: 'yyy',
      pass_ticket: 'zzz'
    }
  })
  return resp
}

Inspecting Cached Comment Data

import { getCommentCache } from '~/store/v2/comment'
import { getCommentReplyCache } from '~/store/v2/comment_reply'

async function dumpComments(url: string, contentId: string) {
  console.log('Top-level:', await getCommentCache(url))
  console.log('Replies:', await getCommentReplyCache(url, contentId))
}

Summary

  • Server Proxy Required: The Nitro endpoint at server/api/web/misc/comment.get.ts forwards authenticated requests to WeChat's appmsg_comment API to bypass CORS and handle session cookies.
  • Two-Stage Download: Downloader.fetchComments() retrieves paginated parent comments while fetchCommentReply() handles nested reply threads separately using content_id and max_reply_id.
  • Pagination Handling: The downloader monitors continue_flag in API responses to iterate through all comment pages automatically until exhaustion.
  • Separate Storage: Comments and replies are stored in distinct IndexedDB tables (comment and comment_reply) via Dexie wrappers for efficient querying and lazy-loading.
  • Resilient Architecture: The ProxyManager and configurable retry logic in utils/download/constants.ts ensure reliable downloading even when individual proxies fail.

Frequently Asked Questions

Why does the exporter need a server proxy to download comments?

WeChat's comment API endpoints require valid session cookies and specific credential parameters (pass_ticket, uin, key) that are bound to a logged-in user. Direct browser requests are blocked by CORS policies. The Nitro server acts as a proxy that forwards these authenticated requests server-side and returns clean JSON to the client.

How does the system handle articles with thousands of comments?

The Downloader class implements pagination logic that checks the continue_flag field in each API response. When continue_flag equals true, fetchComments() automatically requests the next page until all comments are retrieved. For replies, it uses the max_reply_id parameter to paginate through threads that exceed 100 replies.

What is the difference between the comment and comment_reply stores?

The comment store (defined in store/v2/comment.ts) persists the top-level CommentResponse objects containing the original user messages. The comment_reply store (defined in store/v2/comment_reply.ts) holds the nested reply objects fetched via the getcommentreply endpoint. This separation allows the UI to lazy-load replies only when users expand a specific comment thread.

How are failed comment download requests handled?

Each API call is wrapped in a try/catch block within the Downloader methods. The system uses a maxRetries configuration from utils/download/constants.ts and leverages ProxyManager.ts to track proxy health. When a request fails, the downloader automatically selects the next available proxy and retries the operation before surfacing a permanent error to the UI.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →