refactor(favsort): send bilibili requests through httpx
Reuse a pooled httpx client for favsort GET and POST requests, share the raw-request-derived browser headers, and add folder-specific referers for ids, infos and sort. Add transport tests for header parity and anonymous-to-authenticated fallback.
This commit is contained in:
1 parent
5b22ed1c5d
commit
8a87ebfb7c
5 files changed
+160
-22
No files matched your search
+64
-20
@@ -28,7 +28,8 @@ import random
|
||||
import sys
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
import httpx
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
|
||||
@@ -95,41 +96,67 @@ def load_config(require_auth=True):
|
||||
|
||||
# ---------------------------------------------------------------- http
|
||||
|
||||
def _get_json(cfg, url, auth):
|
||||
req = urllib.request.Request(url, headers=make_headers(cfg, auth=auth))
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.loads(r.read().decode("utf-8", "replace"))
|
||||
_HTTP_CLIENT: httpx.Client | None = None
|
||||
|
||||
|
||||
def get_json(cfg, url, auth=False):
|
||||
def _http_client() -> httpx.Client:
|
||||
global _HTTP_CLIENT
|
||||
if _HTTP_CLIENT is None:
|
||||
_HTTP_CLIENT = httpx.Client(
|
||||
timeout=httpx.Timeout(30, connect=10),
|
||||
limits=httpx.Limits(
|
||||
max_keepalive_connections=5,
|
||||
keepalive_expiry=30,
|
||||
),
|
||||
follow_redirects=False,
|
||||
trust_env=True,
|
||||
)
|
||||
return _HTTP_CLIENT
|
||||
|
||||
|
||||
def _get_json(cfg, url, auth, referer=None):
|
||||
response = _http_client().get(
|
||||
url,
|
||||
headers=make_headers(cfg, auth=auth, referer=referer),
|
||||
timeout=30,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def get_json(cfg, url, auth=False, referer=None):
|
||||
"""只读请求:默认用去掉账号身份的 cookie。
|
||||
|
||||
万一匿名请求被拒(HTTP 错误,或 code != 0),自动带完整 cookie 重试一次,
|
||||
保证功能不会因为剥离身份而回退。
|
||||
"""
|
||||
if auth:
|
||||
return _get_json(cfg, url, auth=True)
|
||||
return _get_json(cfg, url, auth=True, referer=referer)
|
||||
try:
|
||||
r = _get_json(cfg, url, auth=False)
|
||||
r = _get_json(cfg, url, auth=False, referer=referer)
|
||||
if r.get("code") == 0:
|
||||
return r
|
||||
except Exception: # noqa: BLE001 网络错误 / 412 风控都走回退
|
||||
pass
|
||||
return _get_json(cfg, url, auth=True)
|
||||
return _get_json(cfg, url, auth=True, referer=referer)
|
||||
|
||||
|
||||
def post_form(cfg, url, data):
|
||||
headers = make_headers(cfg, auth=True)
|
||||
def post_form(cfg, url, data, referer=None):
|
||||
headers = make_headers(cfg, auth=True, referer=referer)
|
||||
headers["content-type"] = "application/x-www-form-urlencoded"
|
||||
req = urllib.request.Request(
|
||||
url, data=urllib.parse.urlencode(data).encode(), headers=headers, method="POST"
|
||||
response = _http_client().post(
|
||||
url, headers=headers, data=data, timeout=30
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.loads(r.read().decode("utf-8", "replace"))
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def fetch_ids(cfg, media_id):
|
||||
r = get_json(cfg, API["ids"].format(base=BASE, mid=media_id))
|
||||
referer = (
|
||||
f"https://space.bilibili.com/{cfg['mid']}/favlist"
|
||||
f"?fid={media_id}&ftype=create"
|
||||
)
|
||||
r = get_json(cfg, API["ids"].format(base=BASE, mid=media_id), referer=referer)
|
||||
if r.get("code") != 0:
|
||||
raise SystemExit(f"ids 失败: code={r.get('code')} msg={r.get('message')}")
|
||||
return r.get("data") or []
|
||||
@@ -137,13 +164,17 @@ def fetch_ids(cfg, media_id):
|
||||
|
||||
def fetch_infos(cfg, media_id, folder_mid, id_list):
|
||||
items, calls = [], 0
|
||||
referer = (
|
||||
f"https://space.bilibili.com/{folder_mid}/favlist"
|
||||
f"?fid={media_id}&ftype=create"
|
||||
)
|
||||
for i in range(0, len(id_list), CHUNK):
|
||||
chunk = id_list[i:i + CHUNK]
|
||||
res = ",".join(f"{x['id']}:{x['type']}" for x in chunk)
|
||||
url = API["infos"].format(
|
||||
base=BASE, res=urllib.parse.quote(res), fmid=folder_mid, mid=media_id
|
||||
)
|
||||
r = get_json(cfg, url)
|
||||
r = get_json(cfg, url, referer=referer)
|
||||
calls += 1
|
||||
if r.get("code") != 0:
|
||||
raise SystemExit(f"infos 失败 @{i}: code={r.get('code')} msg={r.get('message')}")
|
||||
@@ -310,7 +341,12 @@ def load_plan(media_id):
|
||||
def cmd_folders(cfg, args):
|
||||
# 注意:list-all 是"列出我自己的收藏夹",匿名调用只会返回公开收藏夹(实测 11 -> 3),
|
||||
# 因此这里必须带完整 cookie(仍然只是 GET,不会改动任何数据)。
|
||||
r = get_json(cfg, API["folders"].format(base=BASE, mid=cfg["mid"]), auth=True)
|
||||
r = get_json(
|
||||
cfg,
|
||||
API["folders"].format(base=BASE, mid=cfg["mid"]),
|
||||
auth=True,
|
||||
referer=f"https://space.bilibili.com/{cfg['mid']}/favlist",
|
||||
)
|
||||
if r.get("code") != 0:
|
||||
raise SystemExit(f"获取收藏夹列表失败: {r.get('code')} {r.get('message')}")
|
||||
lst = (r.get("data") or {}).get("list") or []
|
||||
@@ -431,6 +467,10 @@ def cmd_run(cfg, args):
|
||||
work = list(actual)
|
||||
total = len(moves)
|
||||
long_after = random.randint(sc["long_every_min"], sc["long_every_max"])
|
||||
sort_referer = (
|
||||
f"https://space.bilibili.com/{cfg['mid']}/favlist"
|
||||
f"?fid={media_id}&ftype=create"
|
||||
)
|
||||
|
||||
for i, m in enumerate(moves, 1):
|
||||
pred_type = types.get(m["pred"], 2)
|
||||
@@ -442,14 +482,18 @@ def cmd_run(cfg, args):
|
||||
}
|
||||
print(f"[run] {i}/{total} {titles[str(m['moved'])]['title'][:30]} "
|
||||
f"-> after {m['pred']}", end=" ", flush=True)
|
||||
resp = post_form(cfg, API["sort"].format(base=BASE), body)
|
||||
resp = post_form(
|
||||
cfg, API["sort"].format(base=BASE), body, referer=sort_referer
|
||||
)
|
||||
code = resp.get("code")
|
||||
print(f"code={code}")
|
||||
if code != 0:
|
||||
wait = sc["retry_wait"]
|
||||
print(f"[run] 返回非 0,冷却 {wait}s 后重试一次")
|
||||
time.sleep(wait)
|
||||
resp = post_form(cfg, API["sort"].format(base=BASE), body)
|
||||
resp = post_form(
|
||||
cfg, API["sort"].format(base=BASE), body, referer=sort_referer
|
||||
)
|
||||
code = resp.get("code")
|
||||
print(f"[run] retry code={code}")
|
||||
if code != 0:
|
||||
|
||||
Reference in new issue
Block a user