Collect macroeconomic releases, events and holidays through the official browser SDK with resumable history from 2025. Preserve evidence, revisions and observation times, and merge verified series with official fields taking priority. Add category filters, field provenance, coverage and connection status. Verify calendar persistence and deployment recovery in isolated acceptance checks.
79 lines
4.1 KiB
Python
79 lines
4.1 KiB
Python
"""Use Jin10's own browser SDK rather than reproducing its encrypted transport."""
|
|
import asyncio
|
|
from datetime import date
|
|
|
|
from playwright.async_api import Error as BrowserError, async_playwright
|
|
|
|
from alphapulse.calendar.jin10 import KINDS, day_url, normalize
|
|
from alphapulse.calendar.jin10_auth import Jin10Error, uc_request
|
|
|
|
METHODS = {"data": "getCalendarData", "event": "getCalendarEvent", "holiday": "getCalendarHoliday"}
|
|
VALIDATION_DAY = date(2025, 1, 8)
|
|
|
|
|
|
def validate_access(responses: dict) -> None:
|
|
# This archived day has verified economic releases and events. An authenticated
|
|
# account alone does not prove that the SDK can read the economic calendar.
|
|
normalized = {kind: normalize(responses[kind], kind, VALIDATION_DAY) for kind in KINDS}
|
|
if not normalized["data"] or not normalized["event"]:
|
|
raise Jin10Error("金十历史日历读取权限未通过验证,请重新连接后检查账号权限")
|
|
|
|
|
|
class Jin10Browser:
|
|
def __init__(self) -> None:
|
|
self.playwright = self.browser = self.context = self.page = None
|
|
self.credential = None
|
|
self.lock = asyncio.Lock()
|
|
|
|
async def close(self) -> None:
|
|
if self.browser:
|
|
await self.browser.close()
|
|
if self.playwright:
|
|
await self.playwright.stop()
|
|
self.playwright = self.browser = self.context = self.page = None
|
|
self.credential = None
|
|
|
|
async def connect(self, credential: str) -> None:
|
|
await self.close()
|
|
info = await uc_request("/userinfo", token=credential)
|
|
if not isinstance(info.get("data"), dict) or not info["data"]:
|
|
raise Jin10Error("金十登录态已失效,请重新扫码")
|
|
try:
|
|
self.playwright = await async_playwright().start()
|
|
self.browser = await self.playwright.chromium.launch(headless=True, args=["--no-sandbox", "--disable-dev-shm-usage"])
|
|
self.context = await self.browser.new_context()
|
|
await self.context.add_cookies([{"name": "x-token", "value": credential, "domain": ".jin10.com",
|
|
"path": "/", "secure": True, "sameSite": "Lax"}])
|
|
self.page = await self.context.new_page()
|
|
# Keep the host page outside the collection window so its initial requests do not compete.
|
|
await self.page.goto(day_url(date(2024, 1, 1)), wait_until="domcontentloaded", timeout=30000)
|
|
await self.page.wait_for_function(
|
|
"window.Jin10FlashInstance && window.Jin10FlashInstance.socket && "
|
|
"window.Jin10FlashInstance.socket.readyState === 1", timeout=30000)
|
|
await self.page.wait_for_timeout(2000)
|
|
self.credential = credential
|
|
validate_access(await self.fetch_day(VALIDATION_DAY))
|
|
except BrowserError:
|
|
await self.close()
|
|
raise Jin10Error("金十浏览器连接失败,请检查网络与浏览器运行环境") from None
|
|
|
|
async def fetch_day(self, day: date) -> dict[str, dict]:
|
|
async with self.lock:
|
|
if self.page is None:
|
|
raise Jin10Error("金十浏览器尚未连接")
|
|
try:
|
|
result = {}
|
|
for kind in KINDS:
|
|
result[kind] = await self.page.evaluate(
|
|
"async ({method, day}) => {"
|
|
"if (!window.Jin10FlashInstance || window.Jin10FlashInstance.socket.readyState !== 1) throw Error('disconnected');"
|
|
"return await window.Jin10FlashInstance[method](day, {timeout: 15000});}",
|
|
{"method": METHODS[kind], "day": day.isoformat()})
|
|
info = await uc_request("/userinfo", token=self.credential)
|
|
if not isinstance(info.get("data"), dict) or not info["data"]:
|
|
raise Jin10Error("金十登录态已失效,未将空列表记为完整日历")
|
|
return result
|
|
except BrowserError:
|
|
# Browser exceptions may include URLs or script contents; never forward them to logs.
|
|
raise Jin10Error("金十日历请求失败或超时,未将本次响应记为成功") from None
|