Tutorial

Migrating from AkShare to ashareapi

AkShare wins on breadth; we win on not having to maintain it yourself. If you already use AkShare and are tired of "upstream redesign → broken function", migration is three changes: code format, call style, field names. Below is the mapping and runnable code — and the parts that do not migrate.

First, be clear about why you are migrating

AkShare is a free, MIT-licensed Python library whose data coverage is much wider than ours. The usual reason to migrate is not "more data" but these three:

  • You do not want to maintain it: AkShare scrapes public pages, so an upstream redesign breaks a function until you upgrade or patch the code
  • You want stability and caching: every one of our endpoints has multiple sources (auto-failover), caching and health checks
  • You need it for AI Agents or outside Python: we speak HTTP and MCP, so any language works without installing 1,300+ dependencies

If your budget is zero, you are already in Python and you are doing exploratory research (not production jobs), AkShare is a great choice — this page is for people who want to maintain a little less, not to talk anyone out of it. The two also work fine together.

Step 1: Convert the code format from bare digits to prefixed

This is the easiest trap in the migration. AkShare takes a bare six-digit `symbol` (`"600667"`); ours is prefixed (`sh600667`). Every endpoint takes this format.

Python
# AkShare                              # ashareapi
# "600667"   (Shanghai A)  →   sh600667
# "000001"   (Shenzhen A)  →   sz000001
# "830799"   (Beijing)     →   bj830799
# "00700"    (Hong Kong)   →   hk00700
# "AAPL"     (US)          →   usAAPL

# Infer the market from the leading digits (A-share rules) — good enough, not exhaustive
def to_code(symbol: str) -> str:
    """AkShare bare code -> ashareapi prefixed code"""
    if not symbol.isdigit():
        return "us" + symbol.upper()          # non-numeric -> US
    if symbol.startswith(("60", "68")):       # Shanghai main / STAR
        return "sh" + symbol
    if symbol.startswith(("00", "30")):       # Shenzhen main / ChiNext
        return "sz" + symbol
    if symbol.startswith(("83", "87", "43")): # Beijing
        return "bj" + symbol
    return "sh" + symbol                        # fallback (always verify)
  • Safest route: call `/v1/search?q=` first — it returns the correct prefixed code and disambiguates automatically
  • The prefix also removes ambiguity like "is 000001 Ping An Bank or the SSE index"

Step 2: Swap the call style (function → HTTP request)

AkShare is `import akshare as ak` then call a function and get a DataFrame; ours is one GET request returning a uniform JSON envelope. Neither is hard, but the shape differs.

Python (runnable; the K-line part needs no key)
# ── Before (AkShare) ─────────────────────────
# import akshare as ak
# df = ak.stock_zh_a_hist(symbol="600667", period="daily",
#                         start_date="20260101", end_date="20260925",
#                         adjust="")
# print(df[["日期", "收盘", "成交量"]])

# ── After (ashareapi) ────────────────────────
import requests

BASE = "https://api.ashareapi.com/v1"
H = {}          # free endpoints need no key; add {"Authorization": "Bearer ct-your-key"} for paid

def get(path, **params):
    r = requests.get(f"{BASE}/{path}", params=params, headers=H, timeout=30)
    if r.status_code == 429:                  # rate limited: see section 4
        raise RuntimeError("rate limited")
    r.raise_for_status()
    body = r.json()
    if not body.get("ok"):
        raise RuntimeError(f"upstream failed (not counted): {body}")
    return body["data"]

# Historical K-line (free) — equivalent to stock_zh_a_hist
# (no date-range args: take the last N bars, filter locally)
bars = get("kline", code="sh600667", period="day", count=30)
for r in bars:
    print(r["date"], r["last"], r["volume"])   # 收盘 -> last

# Realtime quote (free) — the single-name version of stock_zh_a_spot_em
q = get("quote", code="sh600667")
print(q[0]["last"], q[0]["turnover"])          # last price / turnover
  • No `adjust` (price-adjustment) parameter — the basis is fixed at forward-adjusted; ⚠️ do not re-adjust (double adjustment); unadjusted / back-adjusted series are not provided
  • No `start_date` / `end_date` — use `count` for the last N bars and filter locally (this also avoids "no data in range" being mistaken for an API error)
  • The return is a `list[dict]` (numeric fields are strings), not a DataFrame; convert with `import pandas as pd; pd.DataFrame(bars)`

Step 3: Rename the fields

Only a few common field names differ; the rest are the same. Note our numeric fields are strings (`"19.41"`, not `19.41`) — cast before comparing or computing.

Date
日期
date
Ours includes dashes (2026-09-24)
Close / last
收盘
last
Different name, same meaning
Open
开盘
open
Same
High / low
最高 / 最低
high / low
Same
Volume (lots)
成交量
volume
Both in lots
Turnover (CNY)
成交额
amount
CNY
Turnover rate (%)
换手率
turnover
Renamed from `exchange` on 2026-09-26 (the old name read like "exchange" the venue) · same as `snapshot`
Code
代码 / symbol
code
Prefixed: sh600667

Mapping table (AkShare function → our endpoint)

The left column lists common AkShare functions from its official docs; the right column is the equivalent (or nearest) endpoint. Where there is no equivalent, we do not force one — see the notes.

stock_zh_a_spot_em (whole-market snapshot)
GET /v1/quote (single) · /v1/hot (ranking)
We do not bulk-export a whole-market snapshot; only per-code quotes
stock_zh_a_hist (history)
GET /v1/kline
period=day|week|month, count for last N bars (no date range)
stock_zh_a_hist_min_em (minute)
❌
We do not do minute-level data (an explicit boundary)
stock_individual_info_em
GET /v1/profile · /v1/snapshot
profile = listing date / business / industry; snapshot = valuation / market cap / shares / limit price
stock_bid_ask_em (order book)
GET /v1/orderbook
Buy 1–5 / sell 1–5 prices and sizes
stock_financial_abstract
GET /v1/finance
Three statements, multi-period (num controls periods); ROE / margin included
stock_profit_sheet_by_report_em
GET /v1/finance
All three statements in one call
stock_individual_fund_flow
GET /v1/fund
One endpoint covers everything: money flow + top-trader boards + block trades + margin
stock_market_fund_flow (market-wide flow)
❌
No market-wide intraday flow; see /v1/changedist (breadth, free)
stock_lhb_detail_em
GET /v1/lhb
type=institution / hotmoney / activeseat
stock_margin_detail_sse / _szse
GET /v1/margin-trade
Balance / buy / repay / short; code supports batches
stock_dzjy_mrmx (block trades)
GET /v1/block-trade
Price / premium / volume / both sides
stock_dividend_cninfo
GET /v1/dividend
years controls the window (default 3)
stock_gdfx_free_top_10_em
GET /v1/shareholder
Top holders + holder count + institutional holdings
stock_zh_a_gdhs (holder count)
GET /v1/shareholder
Holder count (ownership concentration)
bond_zh_cov
GET /v1/bond
Terms-focused: conversion price / call trigger / double-low / premium; no bond daily bars
fund_etf_spot_em
GET /v1/etf
Quote / size / premium-discount / flows (code like sh510300)
stock_new_gh_detail_em (IPOs)
GET /v1/ipo
Issue / subscription / allotment / listing calendar (days)
stock_board_industry_name_em
GET /v1/sector · /v1/sector-valuation
Sector ranking + valuation (incl. historical percentile)
stock_board_concept_name_em
GET /v1/sector · /v1/industry-chain
sector includes concept boards; industry-chain gives supply-chain position and relevance
stock_zh_index_daily
❌
/v1/market-overview (breadth / style rotation / valuation percentile) is not an index K-line
stock_news_em / filings / full reports
❌
No news or full filings; research reports are digest only (/v1/dehydrated)
Stock lists / whole-market export
GET /v1/search (by keyword)
No bulk list export

Step 4: Rate limits and error handling

AkShare has no rate limit (it runs locally); over HTTP you must handle 429 and upstream failures. That is exactly the trade-off — and the point — of the stability you get.

Python
import time, requests

BASE = "https://api.ashareapi.com/v1"
H = {}

def get_with_retry(path, tries=3, **params):
    for i in range(tries):
        r = requests.get(f"{BASE}/{path}", params=params, headers=H, timeout=30)
        if r.status_code == 429:              # rate limited
            time.sleep(2 ** i)                # back off (do not hammer)
            continue
        r.raise_for_status()
        body = r.json()
        if not body.get("ok"):                # upstream failed -> not counted, retryable
            time.sleep(1)
            continue
        return body["data"]
    raise RuntimeError(f"{path}: failed after {tries} tries")
  • Free endpoints: anonymous normal quota is 5/min; solve one PoW challenge (`GET /v1/challenge`) to reach 60/min
  • Note: rapid consecutive calls are temporarily tightened (we measured "anonymous limit 2/min" after six back-to-back calls) — it recovers after back-off, it is not a ban
  • Paid keys are tiered (trial 30 · standard 120 · pro 300 · unlimited 600 per minute)
  • Failed upstream calls are not counted (`ok:false`), so retrying is safe; only 429 needs a back-off

What moves, what does not (stated up front)

  • Moves: realtime quotes · daily / weekly / monthly K-line · three statements · money flow · top-trader boards · margin · block trades · dividends · shareholders · convertible-bond terms · ETFs · IPOs · sectors · supply chain
  • Does not move: minute bars · news / filings / full reports · index daily bars · whole-market list export · unadjusted / back-adjusted price series (our K-line is fixed to forward-adjusted)
  • Stance: for those, keep using AkShare — it is stronger there, and using both side by side is the pragmatic combination

Common mistakes

  • Passing `"600667"` → 400 / empty; must be `sh600667` (or confirm via `/v1/search`)
  • Treating strings as numbers → `"19.41" * 2` is not 38.82; cast with `float(...)` first
  • Looking for `adjust` / `start_date` / `end_date` → the adjustment basis is fixed (forward-adjusted) and there are no range queries; use `count` and filter locally
  • Turnover rate missing → the field is `turnover` (`quote` / `kline` / `snapshot` — same name and value; it was `exchange` before 2026-09-26)
  • "Bad data" that is really undisclosed → financials / holders / margin update on disclosure cadence; before the disclosure date there is simply no data (not a failure)

FAQ

AkShare is free — why migrate to you?

Not for more data (AkShare covers more than we do) but for maintenance: AkShare scrapes public pages, so you patch it when upstream changes, whereas we run multi-source failover, caching and health checks. If your budget is zero and you accept the upkeep, AkShare is a great choice — do not migrate just to migrate.

Can I use both together?

Yes, and it is common. Typical split: niche or very broad data (news, filings, minute bars, indices) from AkShare, and core data (realtime quotes, money flow, top-trader boards, sector valuation) from us, where we carry the stability and maintenance cost.

Can I skip the conversion and pass bare digits?

No. Every endpoint requires the prefixed form (`sh600667`). The laziest correct way is to call `/v1/search?q=Ping+An+Bank` first — it returns the right code and disambiguates same-name / same-number cases.

Do you return DataFrames?

The HTTP endpoints return a `list[dict]` (values are mostly strings). One line converts it: `import pandas as pd; df = pd.DataFrame(bars)`. The official Python SDK (`pip install ashareapi`) returns DataFrames directly with the `[pandas]` extra.

My function is not in this page — what now?

Start with the endpoint list (32 endpoints by category) or search a keyword via `/v1/search`. For what is genuinely absent (minute bars, news / filings, index daily bars, unadjusted / back-adjusted series, whole-market lists) keep using AkShare — that is our boundary, and we say so here so you do not migrate for nothing.

Last updated: 2026-09-25

AkShare function names are taken from its official docs (akshare.akfamily.xyz); our endpoint count and tiers come from the official endpoint list endpoints.json (32 = 7 free + 25 paid, generated 2026-09-24); fields and shapes come from live responses (quote / kline measured 2026-09-25). Both sides may change — their official pages govern.