How MediaCrawler Handles Platform-Specific API Signatures and JavaScript Injection
MediaCrawler uses dedicated signature helper modules per platform plus stealth JavaScript injection to bypass anti-bot detection and generate valid request signatures for Zhihu, Douyin, Xiaohongshu, and other platforms.
Each social media platform in the MediaCrawler project—Zhihu, Douyin, Xiaohongshu (XHS), Bilibili, Kuaishou, Tieba, and Weibo—protects its APIs with proprietary signing algorithms implemented in JavaScript. This article examines exactly how the codebase handles platform-specific API signatures and JavaScript injection to emulate legitimate browser sessions.
Platform-Specific Signature Architecture
MediaCrawler isolates signing logic into dedicated help.py modules or client-level implementations. This modular design lets each platform evolve independently without breaking others.
Zhihu: JavaScript Engine Delegation
In media_platform/zhihu/help.py, the sign(url, cookies) function forwards parameters to a bundled JavaScript engine (ZHIHU_SGIN_JS) and extracts two critical headers:
x-zst-81– timestamp-based tokenx-zse-96– request signature
# media_platform/zhihu/help.py (conceptual flow)
def sign(url: str, cookies: dict) -> dict:
# ZHIHU_SGIN_JS contains the obfuscated signing algorithm
result = js_engine.eval(ZHIHU_SGIN_JS, url, cookies)
return {
"x-zst-81": result["zst81"],
"x-zse-96": result["zse96"]
}
The core module in media_platform/zhihu/core.py injects these headers before every authenticated request.
Douyin: execjs with External JS Library
Douyin's implementation in media_platform/douyin/help.py uses the execjs library to load libs/douyin.js. This handwritten JavaScript file exposes functions like sign_datail and sign_reply that generate the a_bogus parameter required by Douyin's API:
from media_platform.douyin.client import DouyinClient
client = DouyinClient()
params = {"aweme_id": "1234567890", "device_platform": "android"}
# Internally:
# 1. execjs loads libs/douyin.js
# 2. Calls sign_datail(params, user_agent) → returns a_bogus
resp = await client.get("/aweme/v1/web/aweme/detail/", params=params)
The a_bogus parameter is platform-specific to Douyin and changes frequently, requiring the external JS file to stay synchronized with upstream changes.
Xiaohongshu: Dual Implementation Strategy
Xiaohongshu employs two complementary approaches in media_platform/xhs/:
| Implementation | File | Method | Use Case |
|---|---|---|---|
| Pure Python | xhs_sign.py |
Algorithmic reproduction | Fast, no browser overhead |
| Playwright | playwright_sign.py |
Delegates to xhshow library |
When Python implementation breaks |
# media_platform/xhs/xhs_sign.py — Python-native signing
from media_platform.xhs.xhs_sign import sign_request
headers = sign_request(endpoint, params) # No JS runtime needed
# media_platform/xhs/playwright_sign.py — Browser-based fallback
from media_platform.xhs.playwright_sign import PlaywrightSigner
signer = PlaywrightSigner()
headers = await signer.sign_with_xhshow(endpoint, params) # Uses Playwright page
This redundancy ensures crawler resilience when XHS rotates their signing algorithm.
Bilibili: WBI Specification Compliance
Bilibili follows a documented standard. In media_platform/bilibili/help.py, the BiliWbiSigner class implements the WBI (Web Binary Interface) specification:
from media_platform.bilibili.help import BiliWbiSigner
signer = BiliWbiSigner()
query = {"mid": "12345", "platform": "web"}
signed_query = signer.sign(query)
# signed_query now includes: {"w_rid": "md5_hash_of_query_plus_salt", ...}
The algorithm concatenates sorted query parameters with a dynamic salt (rotated periodically by Bilibili), then computes an MD5 hash to produce w_rid.
Kuaishou: Full Playwright Evaluation
Unlike other platforms, Kuaishou's signing logic in media_platform/kuaishou/help.py runs entirely within a headless browser. The get_ks_sign_from_playwright function executes the platform's native JavaScript in a controlled Chromium context:
# media_platform/kuaishou/help.py
async def get_ks_sign_from_playwright(url: str) -> dict:
page = await browser_context.new_page()
# Evaluate KS's own signing functions in their runtime environment
signature = await page.evaluate("""() => {
return window._getSign(JSON.parse(arguments[0]));
}""", json.dumps(parse_url(url)))
return signature
This approach sacrifices speed for accuracy when the signing algorithm resists static analysis.
Tieba: Deterministic MD5 with Secret
Baidu Tieba uses a simpler but effective scheme in media_platform/tieba/client.py. The client builds an MD5 hash over alphabetically sorted parameters concatenated with PC_SIGN_SECRET:
# media_platform/tieba/client.py
def _generate_sign(params: dict) -> str:
sorted_params = "&".join(f"{k}={v}" for k, v in sorted(params.items()))
return md5(f"{sorted_params}{PC_SIGN_SECRET}".encode()).hexdigest()
No JavaScript injection is required—Tieba's signing is fully implemented in Python.
Weibo: Core-Integrated Signing
Weibo handles signatures directly in media_platform/weibo/core.py, adding headers based on the platform's login flow and session state without a separate helper module.
JavaScript Injection for Stealth and Anti-Detection
Generating valid signatures is necessary but insufficient. Modern platforms detect automation through JavaScript environment fingerprinting. MediaCrawler neutralizes these checks via systematic stealth script injection.
The Stealth Script: libs/stealth.min.js
MediaCrawler bundles libs/stealth.min.js, a minified anti-fingerprinting script derived from the puppeteer-extra-plugin-stealth ecosystem. This script:
- Deletes
navigator.webdriver - Spoofs
navigator.plugins,navigator.languages, and screen properties - Overrides
WebDriverproperty getters to returnundefined - Patches
chrome.runtimeand other extension APIs to appear absent
Per-Platform Injection Pattern
Every Playwright-based core module injects the stealth script before any navigation:
# media_platform/zhihu/core.py (representative of all platforms)
class ZhihuCrawler:
async def initialize(self):
self.browser = await chromium.launch()
self.browser_context = await self.browser.new_context()
# Critical: inject before any page creation
await self.browser_context.add_init_script(path="libs/stealth.min.js")
self.page = await self.browser_context.new_page()
The same pattern appears in:
CDP Browser Manager
For advanced use cases, tools/cdp_browser.py provides CDPBrowserManager with a dedicated add_stealth_script() method:
from tools.cdp_browser import CDPBrowserManager
manager = CDPBrowserManager()
await manager.add_stealth_script() # Injects libs/stealth.min.js via CDP
This enables stealth injection when connecting to existing Chrome instances via the Chrome DevTools Protocol (CDP), bypassing Playwright's standard launch flow.
Signature Generation Flow: Complete Example
Here's how a Zhihu request flows from caller to signed HTTP request:
from media_platform.zhihu.client import ZhihuClient
async def fetch_answers():
client = ZhihuClient()
url = "https://www.zhihu.com/api/v4/questions/123456/answers"
# Step 1: core.py initializes browser with stealth script injected
await client.initialize() # add_init_script("libs/stealth.min.js")
# Step 2: client.get() calls help.sign() with URL and cookies
# Step 3: help.py evaluates ZHIHU_SGIN_JS, extracts x-zst-81 / x-zse-96
# Step 4: headers applied, request executed in stealth context
response = await client.get(url)
return response.json()
The JavaScript signing algorithm runs in the stealth-injected context, producing signatures indistinguishable from genuine browser sessions.
Implementation Comparison by Platform
| Platform | Signature Source | Injection Method | Stealth Required |
|---|---|---|---|
| Zhihu | ZHIHU_SGIN_JS via help.py |
add_init_script |
Yes |
| Douyin | libs/douyin.js via execjs |
add_init_script |
Yes |
| Xiaohongshu | xhs_sign.py or playwright_sign.py |
add_init_script |
Yes |
| Bilibili | Pure Python WBI in help.py |
None | Optional |
| Kuaishou | Native JS via Playwright evaluation | add_init_script |
Yes |
| Tieba | Pure Python MD5 in client.py |
None | No |
Integrated in core.py |
add_init_script |
Yes |
Summary
- Modular signature helpers (
help.pymodules) isolate platform-specific signing logic per service - Dual strategies (Python-native vs. JavaScript evaluation) balance speed and accuracy across platforms
- Stealth script injection via
add_init_script(path="libs/stealth.min.js")masks automation fingerprints before any page loads - CDP support through
tools/cdp_browser.pyextends stealth capabilities to externally managed browser instances - No one-size-fits-all: Each platform requires tailored analysis—Bilibili uses documented specs while Kuaishou demands full browser evaluation
Frequently Asked Questions
How does MediaCrawler avoid detection when signing requests?
MediaCrawler injects libs/stealth.min.js into every Playwright context before navigation. This script patches navigator.webdriver and other automation indicators, ensuring the JavaScript signing environment matches a genuine browser. The injection happens in media_platform/{platform}/core.py via browser_context.add_init_script().
Why does Xiaohongshu need two different signing implementations?
Xiaohongshu's signing algorithm changes periodically. The pure-Python xhs_sign.py offers speed when functional, while playwright_sign.py delegates to the official xhshow library as a fallback. This dual approach minimizes downtime when the platform rotates its anti-scraping measures.
What is the a_bogus parameter in Douyin requests?
a_bogus is Douyin's request signature parameter. MediaCrawler generates it by loading libs/douyin.js through execjs and calling platform-specific functions (sign_datail, sign_reply) with request parameters and user-agent strings. The JavaScript file requires manual updates when Douyin changes their algorithm.
When should I use the CDP browser manager instead of standard Playwright?
Use tools/cdp_browser.py when connecting to existing Chrome instances rather than launching new browsers. The add_stealth_script() method ensures stealth injection works via CDP, useful for distributed crawling or reusing already-warmed browser sessions with established platform cookies.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →