

发布于 2026-06-04 11:25475浏览做网页爬虫时绝大多数人的惯性思维:遇到动态渲染空白页面→立刻安装Playwright/Selenium启动浏览器加载JS。但踩过无数坑后才发现:大量主流站点在SSR渲染时,已把全量业务数据预埋进初始HTML源码,只用requests+正则就能抓完全部内容,零浏览器、零环境依赖,语雀知识库全文档爬取就是典型落地案例。
想要批量抓取公开语雀知识库所有正文文档链接,新手起步标准代码如下:
import requests
from bs4 import BeautifulSoup
headers = {"User-Agent":"Mozilla/5.0 Chrome"}
res = requests.get("知识库地址",headers=headers)
soup = BeautifulSoup(res.text,"html.parser")
all_a = soup.find_all("a")执行结果:页面内所有文章跳转链接全部缺失。
根源在于语雀采用SPA+SSR架构,浏览器初次HTTP请求拿到的HTML只是DOM空骨架,目录、文章、详情等核心数据由前端JS异步请求接口后动态渲染进页面,静态HTML里看不到渲染后的DOM节点,直接解析标签必然一无所获。
绝大多数教程给出的标准答案:启用自动化浏览器框架,启动Chromium内核等待页面资源加载完毕、JS执行渲染完成再解析DOM。
但浏览器方案痛点很明显:环境部署繁琐、启动耗资源、爬取效率低、容易被站点风控识别浏览器特征、容器/云服务器部署还会面临无头驱动兼容难题。
排查动态页面优先打开浏览器开发者工具-元素面板,是爬虫提速的核心习惯。在语雀页面<head>头部script标签中,能发现一行固定格式代码:
window.appData = JSON.parse(decodeURIComponent("%7B%22me%22%3A......"));平台做服务端渲染优化时,在后端拼接完整知识库数据,经过URL编码后嵌入页面源码,页面不用额外请求后端接口,前端JS直接读取window.appData变量就能渲染整站目录。
这份编码后的JSON,就是全站原始数据源,第一次HTTP请求就能完整获取,不需要等待任何JS、不用模拟浏览器访问接口。
整体解析流程分三步,无需复杂依赖库:
正则截取编码字符串:通过正则匹配decodeURIComponent("内容"),提取双引号内URL编码字符串;
解码还原标准JSON:使用urllib解码URL特殊字符,再通过json模块转为Python字典;
筛选目录数据拼接链接:字典内book.toc存储完整知识库树形目录,区分DOC(正文文档)与TITLE(分类标题),过滤正文后拼接域名生成最终访问链接。
精简核心代码:
import re,json,urllib.parse
# 正则捕获编码内容
raw_encode = re.search(r'decodeURIComponent\("([^"]+)"\)',html).group(1)
# URL解码+JSON序列化
data = json.loads(urllib.parse.unquote(raw_encode))
# 筛选文档类型
doc_list = [item for item in data["book"]["toc"] if item["type"]=="DOC"]基于上述原理封装完整爬虫脚本,配置知识库地址即可一键运行,自动导出TXT目录清单与JSON结构化数据,适配Windows本地存储路径:
import re
import json
import urllib.parse
from typing import List, Dict
import requests
# 配置区:替换为目标语雀知识库首页地址
BOOK_URL = "https://www.yuque.com/pengzhiqiang999/xiaokenai"
BASE_DOMAIN = "https://www.yuque.com"
# 知识库空间路径,跟随链接自定义
SPACE_PATH = "pengzhiqiang999/xiaokenai"
def get_page_html(url: str) -> str:
"""模拟浏览器请求,获取页面原始HTML源码"""
req_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
"Accept-Language": "zh-CN,zh;q=0.9",
"Referer": "https://www.yuque.com/"
}
resp = requests.get(url, headers=req_headers, timeout=15)
resp.raise_for_status()
return resp.text
def parse_app_data(html_content: str) -> dict:
"""正则提取并解码window.appData全局数据"""
reg_rule = r'decodeURIComponent\("([^"]+)"\)'
match_res = re.search(reg_rule, html_content)
if not match_res:
raise Exception("页面数据格式变更,未匹配到appData编码字段")
encode_str = match_res.group(1)
json_str = urllib.parse.unquote(encode_str)
return json.loads(json_str)
def filter_article_list(raw_data: dict) -> List[Dict]:
"""从原始数据过滤正文文档,剔除分类标题"""
# 兼容语雀多版本数据结构
toc_data = raw_data.get("book",{}).get("toc") or raw_data.get("toc",[])
article_arr = []
for node in toc_data:
# DOC代表正文文章,TITLE为目录分组
if node.get("type") == "DOC" and node.get("url"):
article_arr.append({
"title": node.get("title","无标题文档"),
"slug": node["url"],
"full_url": f"{BASE_DOMAIN}/{SPACE_PATH}/{node['url']}"
})
return article_arr
def main():
print(f"开始爬取知识库:{BOOK_URL}\n")
html = get_page_html(BOOK_URL)
source_data = parse_app_data(html)
# 保存原始全量JSON,方便后续调试字段变更
with open("app_data_raw.json","w",encoding="utf-8") as f:
json.dump(source_data,f,ensure_ascii=False,indent=2)
article_info = filter_article_list(source_data)
if not article_info:
print("未抓取到文章,查看app_data_raw.json核对toc字段路径")
return
# 控制台打印结果
print(f"共成功抓取{len(article_info)}篇正文文档:")
print(f"{'序号':<4}{'文档标题':<42}{'访问链接'}")
print("-"*110)
for idx,art in enumerate(article_info,1):
print(f"{idx:<4}{art['title']:<42}{art['full_url']}")
# 落地存储:TXT文档清单 + JSON结构化文件
save_txt_path = r"D:\桌面\公司产品\article_links.txt"
save_json_path = r"D:\桌面\公司产品\article_links.json"
with open(save_txt_path,"w",encoding="utf-8") as f:
for art in article_info:
f.write(f"{art['title']}\t{art['full_url']}\n")
with open(save_json_path,"w",encoding="utf-8") as f:
json.dump(article_info,f,ensure_ascii=False,indent=2)
print(f"\n✅ 数据落地完成:\n文本清单:{save_txt_path}\n结构化JSON:{save_json_path}")
if __name__ == "__main__":
main()以后遇到SPA动态渲染页面,遵循先查预埋数据→再查接口→最后启用浏览器的排查顺序,大幅降低爬虫开发成本:
检索全局预埋变量:页面源码/开发者工具搜索关键词:__NEXT_DATA__(NextJS站点)、window.__INITIAL_STATE__(Vue/React站点)、appData、window.__PRELOADED_STATE__,小某书、知乎、掘金、语雀等主流平台普遍采用该方案;
抓接口请求:若无预埋数据,打开F12-网络面板筛选XHR/Fetch,找到后端数据接口,直接调用API拿JSON;
兜底方案浏览器渲染:前两种方式失效后,再使用Playwright、Selenium启动浏览器渲染页面。
依托站点SSR预埋数据爬取,代码体量小、运行速度快、资源占用极低,是动态页面爬虫性价比最高的实现方案。