生成公众号语文试题与答案 PDF
把微信公众号上的语文高考试卷文章,转成排版保真的打印版 PDF + HTML,自动拆成(试题版)和(答案版)两份。
首选路径:用户直接给链接
用户甩 mp.weixin.qq.com/s/... 链接时,直接抓,不要先去搜狗搜。 实测直连一次 200、3–4MB 完整正文,无验证码、不限流;而搜狗搜索端点一天可能连续多轮限流。
mkdir -p <TMP>
curl -sL -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
"<URL>" -o <TMP>/page.html -w "size=%{size_download}\n"
抓完先核对元信息(确认是哪个公众号、哪天的文章,转载源极易混淆):
python -c "
import re,html as H,time
raw=open('page.html',encoding='utf-8',errors='ignore').read()
for p,l in [(r'var biz = \"([^\"]+)\"','biz'),(r'id=\"js_name\"[^>]*>\s*([^<]*)','公众号'),
(r'property=\"og:title\" content=\"([^\"]*)\"','标题')]:
m=re.search(p,raw)
if m: print(f'{l}: {H.unescape(m.group(1)).strip()[:100]}')
m=re.search(r'var oriCreateTime = [\'\"](\d+)',raw)
if m: print('发布时间:',time.strftime('%Y-%m-%d %H:%M',time.localtime(int(m.group(1)))))
"
biz是公众号唯一标识,比js_name(昵称)可靠;发布以oriCreateTime为准。- 正文是否真被截断,看
js_content是否存在。别把"正文里出现『验证』二字"当成验证码页——那是误报;真判据是wappoc_appmsgcaptcha/环境异常/请输入验证码。
没有链接时:从搜狗发现文章
> 本节整段是"发现层",可用性远不如上面的"直抓层"。搜狗 IP 级限流是常态,命中就转到 WebSearch 收尾,不要死磕。
搜狗抓取链路
- 搜索页:
curl -s -c <T>/sogou.jar -A "<桌面 Chrome UA>" "https://weixin.sogou.com/weixin?type=2&query=<URL编码关键词>" -o <T>/sg.html
- 结果块是<li id="sogou_vr_11002601_box_N">,块内/link?url=...是跳转链接。
- 公众号名在<span class="all-time-y2">里(account_name属性取不到,会误判成"查不到来源")。
- 日期在块内timeConvert('1791313342')时间戳里,必须换算后比对——搜狗排序不按时间,标题里的月份不代表是新文。
- 不要加&tsn=1(按时间筛选直接触发反爬,返回 63 字节空页)。
- 关键词里绝不要带公众号名(搜狗对账号名权重低,带了反而压制结果甚至触发反爬)。正确做法:搜标题样式词(如2026-2027学年高三上学期10月语文试题及参考答案),再按all-time-y2事后过滤归属。
- 不要搜超长完整标题:常返回 11–12KB 正常页但 0 结果(关键词过偏),看起来像限流实则是空结果。用短核心词,再用省市名做标题内过滤。
- 连续请求间隔 3.5s,20 组以内,跑完即停。 - 跳转页:同一 jar +
-H "Referer: https://weixin.sogou.com/"抓/link?url=...,返回是分片 JS:
url += 'https://mp.'; url += 'weixin.qq.c'; ...
按顺序拼接全部url += '...'片段并replace("@",""),得真实mp.weixin.qq.com/s?...地址。query=后可能含空格,必须替换成%20,否则 curl 报 Malformed input、Python 报 URL can't contain control characters。 - 正文页:curl 真实地址存
page.html(普通文章 1–5 MB)。 /link?url=token 有时效,拿到后尽快 resolve + 抓正文;隔久再 resolve 会 302 到/antispider/,必须重新搜索拿新链接。
止损红线(命中即停,别硬撑)
搜索页 size < 1KB 即为 IP 级限流(正常空结果是 11–12KB)。以下手段全部实测无效,不要做:
- ❌ 换 cookie jar / 重新 warm 首页(首页仍是 6694 字节正常,说明只限了搜索端点)
- ❌ 逐级退避重试(20s→100s→240s→300s,累计 9 分钟全失败)
- ❌ 后台再挂一条长退避任务(纯烧时间)
- ❌ 换 UA、换 IP 变体
命中后正确做法:立刻转 WebSearch 做 4–6 次交叉验证(带 freshness 分档、限定站点、加 mp.weixin.qq.com 定向),仍无命中就输出「检索受限 + 已知候选清单 + 请用户直接甩链接」。一个轮次上限 15 分钟。
搜狗索引滞后,必须主动补漏
「当天搜不到」≠「当天没发」。实测:某日 06:52 发的文章在当日 08:45 那轮搜不到,次日才被索引。所以每轮除了搜当天,还要扫候选列表按日期倒序,主动发现前两天漏掉的文章。
查到的含"某公众号"水印的转载源不等于当天新文,务必核对 timeConvert / oriCreateTime 时间戳。
拆分:先打块结构,别盲猜
文章结构多变(专练/套卷/汇编/逐套穿插),先打印块结构确认拆分点:
# PY 指向任意可用的 Python 3(需标准库即可,无第三方依赖)
PY=<你的 Python 3 可执行文件路径>
SK=<技能目录>/scripts
"$PY" -c "
import sys; sys.path.insert(0,r'$SK')
from rich_split import read_blocks, block_text
for i,b in enumerate(read_blocks('page.html')):
t=block_text(b)
if t:
tag=' [ANS]' if '参考答案' in t else (' [AD]' if '本号VIP导航' in t else '')
print(f'{i:3d}{tag} | {t[:68]}')
"
选择模式:
- 默认模式——文末有单个「参考答案」块(专练/套卷类,绝大多数)。脚本自动切。
- --mode interleaved——汇编/合集类,答案逐套穿插(【答案】【解析】【翻译】【赏析】、独立的"参考答案"标题、"故选X。"单行)。用"答案组"状态机切:遇答案起始块进答案态,遇新题起始块退出。
一步产出
PY=<你的 Python 3 可执行文件路径>
SK=<技能目录>/scripts/rich_split.py
"$PY" "$SK" <TMP>/page.html --out "<输出目录>" --pdf
- 不要传
--title,让它自动从页面元数据取原文完整标题(og:title → twitter:title → msg_title → activity-name)。手动传容易把完整标题缩成简写,违反命名铁律。 --split-word答案区首块文本(默认"参考答案");--ad-word广告区起点(默认"本号VIP导航"),其后整块丢弃。--mode interleaved汇编/合集类专用(见上一节)。--watermark "<公众号名>"换一家公众号的水印字面值时必传;异形水印(字与字间带空格)由内置正则兜底。--no-title-heading可关闭"页首补原文标题"(默认会补——微信标题在js_content之外,正文里没有,不补打印版纸上没标题)。--tmp <目录>Edge 中转目录,建议指到系统 temp 下,别留在技能包内(会积十几 MB profile 垃圾)。- 脚本会打印块数、拆分位置、标题,并自动检查:水印残留 / img / a / script 计数 / 答案泄漏 / 预填选项括号。边界异常会报错退出。
铁律
1. 命名必须保留原文完整标题
文件名 = <原文完整标题>(试题版).html / (答案版),页面 <title> 同。不要自拟简写(把「江西吉安市2027届高三上学期联考模拟预测语文试题及参考答案」缩成「高三语文」是错的)。
只做两件事:去掉 Windows 非法字符 \/:*?"<>|、把连续空白并成一个。同批 HTML/PDF 命名保持一致。
2. 答案版必须含全部解析
答案版 = 参考答案 + 全部解析,二者缺一不可。拆分时答案区整段落入,不得只留答案丢解析。
判断"有没有解析"至少同时看 7 个标志:
考查学生 / 本题考查 / 故选 / 评分建议 / 参考译文 / 答案示例 / 括号内选项分析(如 B(A项…错误))
只看「解析」「考查学生」两个会把内联式解析误判成"源无解析"——实证:某篇文章这两个 grep 均为 0,但解析完整。
已实测的四种解析形态,都按原样保留、不要套同一种判读:
| 形态 | 标志 |
|---|---|
| 专练典型 | 「本题考查学生…」逐题成段 |
| 括号内联 | B(A项…错误) 直接跟在题号后 |
| 评分细则式 | 【评分细则】+【参考译文】+【写作指导】 |
| 逐题标注式 | B【解析】 紧跟题号 |
源确实只有干答案(无任何解析性文字)才在交付说明注明"源无解析",不得编造。
3. 清洗隐形白字水印
公众号常把自家公众号名做成 color: rgb(255,255,255) 白字缝在长段落末尾,页面不可见,复制/打印才现形。
必须双管齐下:replace("<完整水印字面值>", "") 去掉标准形态,再用容忍空白的正则(如 超\s*然\s*客\s*公?\s*众?\s*号)兜住异形形态——异形水印常在字与字之间插空格,只用字面串一定抓不到。最后 grep 公众号名 = 0 才算过关。
换公众号时用 --watermark "<该号完整名>" 传字面值;异形兜底正则需同步改 scripts/rich_split.py 的 WATERMARK_RE。
4. 其余清洗
<script>、<o:page>(Word 残留)、<a>(留文字)、<img>、<iframe> 全清。广告区从"本号VIP导航"起整块删(含导航图、二维码、"完整电子版下载回复"、"校编声明")。正文文字逐字保留,不改写、不概括、不增删。
5. 试题版不得残留答案
grep 参考答案 /【答案】/【解析】/【翻译】/【赏析】/故选 / 正确答案,以及 ([A-D]) 形式的预填括号。
两处已知误报,别当 bug 修:
- 「参考答案」命中的多半是 <title> 和页首补的原文标题行——原文标题本身含"及参考答案",按命名铁律保留完整标题必然命中。判正文要先剥掉 <head> 和标题 <p> 再 grep。
- (A)(B)(C)(D) 可能是剧本题目的补写位置标记(如"在剧本A、B、C、D各处补写舞台说明"),必须保留。文言断句题的 [A][B][C] 同理——那是断句点,不是答案。
交付前校验
- 逐行完整性 diff:按拆分区间分别比对(试题区行 vs 试题版、答案区行 vs 答案版),别拿全量行去比对单版,否则满屏误报。diff 前要剥
<head>/<style>、把&nbsp;换成空格、表格单元格按</td>拆行会产生表观差异(产物保留了原表格结构,以"无实质差异"为准,不要为消 diff 去改产物)。目标 0 字差异。 - 水印 grep = 0;广告词 grep = 0。
- 答案版含解析 grep 上面 7 个标志,至少应命中若干。
- Edge 截图目检(必做):
cp "<中文名.html>" <TMP>/q.html # file:// URL 不接受中文文件名,先复制成 ASCII 名
EDGE="/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
"$EDGE" --headless --disable-gpu --hide-scrollbars --user-data-dir="<TMP>/prof" \
--screenshot="<TMP>/q.png" --window-size=900,1900 "file:///<TMP>/q.html"
Read 截图确认标题居中、楷体诗句/材料居中、段落缩进正常。--user-data-dir 必须每次用独立目录,否则并行任务互锁。
坑
- WebFetch 抓长文必截断(3 万字以上),别反复重试,直接 curl + 解析
js_content。 - Edge 本机是 x86 路径:
/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe。 - PDF 输出:先写 ASCII 临时名再移成中文名。临时目录在 C 盘、产物在 D 盘时
os.replace会抛 WinError 17(不能跨盘移动),必须用shutil.move——脚本已内置兜底。 - 输出目录在百度同步盘:读写可能瞬时失败/短暂换位,重试即可别当丢文件。同步盘会锁死旧文件,覆盖同名 PDF 若报
WinError 5:把新版 PDF 落到同名子文件夹避开占用,旧文件留用户手动删,不要硬删。 - 重名时加"(二)"。
- 临时目录别放进技能包内——Edge 的
--user-data-dir会往里写十几 MB profile 垃圾,把技能包撑到几十 MB。放系统 temp 下。
本机环境限制(别再试)
- 题库站全部直连失败:
21cnjy/51jiaoxi/book118/zxxk/jintiankanshacurl 一律http=000。"从题库站兜底抓原文"这条路在本机不可执行,只有 WebSearch 摘要能间接拿到内容。 - RSS 类服务全不可用:RSSHub wechat 路由常年 404、WeRSS 官网打不开、wechat2rss 免费版只有安全/科技类且搜索需登录。GitHub / npm registry / Docker Hub 全部 000 不通(本机无 Docker 守护进程,无法自建)。若用户提 RSS,先说明这些硬伤再动手。
- 想直接在微信里查公众号:做不到。三层硬限制:① 没微信客户端登不了;②
mp/profile_ext强制客户端鉴权,返回"请在微信客户端打开链接",appmsg/publish_time计数为 0;③ 本地微信缓存只有朋友圈图片,零个 HTML 正文。
备选后端:ReadGZH MCP(仅"已知链接 → 取正文")
搜狗限流但已知 mp 地址时,可用 ReadGZH 远程 MCP 读正文:
- 端点
POST https://api.readgzh.site/mcp-server(JSON-RPC 2.0,SSE 响应)。 - 必须 curl + 桌面浏览器 UA +
Origin: https://readgzh.com+Referer;Pythonurllib不带 UA 会被 Cloudflare 1010 Access denied 挡掉,curl 带 UA 一次 200。 - 工具:
readgzh.read(url) /list/search/list_by_account/get(slug)。 - ⚠️ 硬限制:
search和list_by_account只搜它自己缓存过的文章,不是搜索引擎,发现不了新文章。只能当"已知链接 → 取正文"的后端。
实战记录
2026-10-03(一批 7 篇):路线 A 产出试题版/答案版 HTML,文字 0 差异、0 答案泄漏、清 10 处隐形水印,用户评价"完成得很好"。
2026-10-03(古诗汇编四篇):《高考十年(2017—2026)古诗阅读汇编》一~四均为答案逐套穿插结构,必须 --mode interleaved。踩坑:旧同名 PDF 被同步盘锁死无法原地覆盖,新版 PDF 落到子文件夹,根目录保留 HTML。
2026-10-03(外国小说专练二):默认模式,388 块(试题 0–227 / 答案 228–378 / 广告 379 起)。新文件名与旧完整版不同名,不撞同步盘锁,PDF 直接落根目录。
2026-10-10(一轮三篇):某公众号三篇(高二 10 月检测 ×2、高三二诊 ×1)。搜狗 IP 级限流全轮不可用,改用 ReadGZH MCP 读正文。三篇答案区解析形态完全不同——专练典型 / 括号内联 / 评分细则式,催生了上面"7 个标志 + 四种形态"的判读法。
2026-10-10(单篇补做):226 块(试题 0–160 / 答案 161–215 / 广告 216 起),默认模式,试题版 12 页 / 答案版 5 页。逐行 diff 双版 100%、0 字差异;水印 9 处全清。再次印证用户甩链接是最可靠通道。