Ported from Shirokami-API scraper/stalk/*.js: - /stalk/mobile-legends (gempaytopup CSRF+POST) - /stalk/free-fire (freefirecommunity API + field transform) - /stalk/genshin-impact (enka.network __data.json) - /stalk/twitter (fxtwitter API) - /stalk/tiktok (rehydration JSON parse) - /stalk/instagram (mollygram HTML scrape) Upstream status: twitter/genshin verified working live (elonmusk 241M followers). github+youtube verified earlier. tiktok WAF-blocked, instagram mollygram-captcha blocked, mobile-legends gempaytopup changed POST->GET+JS (all upstream dead for VPS IP; code faithful to source).
64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""Extract TikTok user-detail JSON (__UNIVERSAL_DATA_FOR_REHYDRATION__) via Playwright.
|
|
|
|
The direct HTTP path gets a SlardarWAF challenge page; a real browser with JS
|
|
executes the challenge and exposes the __UNIVERSAL_DATA_FOR_REHYDRATION__ script.
|
|
Usage: python3 stalk_tiktok.py <username>
|
|
"""
|
|
import sys
|
|
import asyncio
|
|
import json
|
|
import os
|
|
|
|
os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "/usr/local/share/ms-playwright")
|
|
|
|
from playwright.async_api import async_playwright
|
|
|
|
|
|
async def main(username):
|
|
url = f"https://www.tiktok.com/@{username}?_t=ZS-8tHANz7ieoS&_r=1"
|
|
async with async_playwright() as p:
|
|
browser = await p.chromium.launch(
|
|
headless=True,
|
|
args=["--no-sandbox", "--disable-bypass", "--disable-blink-features=AutomationControlled"],
|
|
)
|
|
context = await browser.new_context(
|
|
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
viewport={"width": 1366, "height": 768},
|
|
java_script_enabled=True,
|
|
locale="en-US",
|
|
)
|
|
page = await context.new_page()
|
|
try:
|
|
await page.goto(url, timeout=60000, wait_until="domcontentloaded")
|
|
except Exception as e:
|
|
await browser.close()
|
|
print(json.dumps({"success": False, "error": f"Navigation: {e}"}))
|
|
return
|
|
|
|
# Wait for the rehydration script to appear (WAF challenge may take a few seconds)
|
|
found = None
|
|
for _ in range(30):
|
|
try:
|
|
found = await page.evaluate(
|
|
"""() => {
|
|
const s = document.querySelector('script#__UNIVERSAL_DATA_FOR_REHYDRATION__');
|
|
return s ? s.textContent : null;
|
|
}"""
|
|
)
|
|
if found:
|
|
break
|
|
except Exception:
|
|
pass
|
|
await page.wait_for_timeout(1000)
|
|
|
|
await browser.close()
|
|
|
|
if not found:
|
|
print(json.dumps({"success": False, "error": "Rehydration data not found (WAF challenge)"}))
|
|
return
|
|
|
|
print(json.dumps({"success": True, "data": found}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main(sys.argv[1])) |