feat(scraper): 微信消息翻页取完整历史(paginate_chats hasLast/end游标+200页上限,移除临时skip;全量回填3478对话+95361条至2025-10)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-18 09:15:04 +08:00
parent aeb8a89b03
commit f9ed7dd821
2 changed files with 180 additions and 66 deletions
@@ -0,0 +1,89 @@
"""Unit tests for the hasLast/end cursor pagination helper in
:mod:`scripts.scrape_via_page` (WeChat msg/chats deep-history paging).
No network/browser — the fetch callable is faked; these verify the
cursor protocol: page N+1 uses ``data.end - 1`` as ``end`` until
``hasLast`` is falsy, with guards for malformed ``end`` and a page cap.
"""
from __future__ import annotations
from scripts.scrape_via_page import paginate_chats
def _page(messages, has_last, end=1000):
return {"data": {"messages": messages, "hasLast": has_last, "end": end}}
async def _noop_sleep(_seconds: float) -> None:
return None
async def test_multi_page_uses_end_minus_one_cursor():
calls: list[int] = []
async def fetch(end_ms: int):
calls.append(end_ms)
if len(calls) == 1:
return _page([{"id": 1}], has_last=True, end=1000)
return _page([{"id": 2}], has_last=False, end=500)
datas = [d async for d in paginate_chats(fetch, end_ms=2000, sleep=_noop_sleep)]
assert [len(d["messages"]) for d in datas] == [1, 1]
assert calls == [2000, 999] # 下一页游标 = end - 1
async def test_stops_when_has_last_falsy():
async def fetch(end_ms: int):
return _page([{"id": 1}], has_last=False)
datas = [d async for d in paginate_chats(fetch, end_ms=2000, sleep=_noop_sleep)]
assert len(datas) == 1
async def test_stops_on_non_dict_data():
calls = []
async def fetch(end_ms: int):
calls.append(end_ms)
return {"message": "调用成功", "data": None}
datas = [d async for d in paginate_chats(fetch, end_ms=2000, sleep=_noop_sleep)]
assert datas == []
assert len(calls) == 1
async def test_stops_when_end_not_numeric():
async def fetch(end_ms: int):
return _page([{"id": 1}], has_last=True, end=None)
datas = [d async for d in paginate_chats(fetch, end_ms=2000, sleep=_noop_sleep)]
assert len(datas) == 1 # hasLast 真但游标缺失 → 不再无限请求
async def test_page_cap_prevents_infinite_loop():
calls = []
async def fetch(end_ms: int):
calls.append(end_ms)
return _page([{"id": len(calls)}], has_last=True, end=end_ms - 1)
datas = [d async for d in paginate_chats(fetch, end_ms=2000, max_pages=5, sleep=_noop_sleep)]
assert len(datas) == 5
assert len(calls) == 5
async def test_empty_messages_list_still_pages():
async def fetch(end_ms: int):
if end_ms > 1500:
return _page(None, has_last=True, end=1500)
return _page([{"id": 1}], has_last=False)
datas = [d async for d in paginate_chats(fetch, end_ms=2000, sleep=_noop_sleep)]
assert len(datas) == 2