news 2026/2/8 10:35:19

语雀文档抓取工具

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
语雀文档抓取工具

语雀文档抓取工具

可以保存任意用户整个语雀知识库为Markdown格式 (包含完整目录结构和索引)

importsysimportosimportreimportjsonimportuuidimporttimeimporturllib.parsefrompathlibimportPathfromtypingimportDict,Tuple,ListimportrequestsfromrequestsimportResponse,exceptionsasreq_exc HEADERS={"User-Agent":("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/125.0.0.0 Safari/537.36")}TIMEOUT=10# 单次请求超时RETRY=3# 图片下载最大重试次数SLEEP_BETWEEN_RETRY=1# 重试间隔(秒)def_retry_get(url:str,max_retry:int=RETRY,**kwargs)->Tuple[bool,Response]:"""带重试的 GET 请求。"""forattemptinrange(1,max_retry+1):try:resp=requests.get(url,headers=HEADERS,timeout=TIMEOUT,stream=True)ifresp.status_code==200:returnTrue,respelse:print(f"[WARN] 下载失败(HTTP{resp.status_code}),第{attempt}/{max_retry}次重试:{url}")exceptreq_exc.RequestExceptionase:print(f"[WARN] 下载异常{e},第{attempt}/{max_retry}次重试:{url}")time.sleep(SLEEP_BETWEEN_RETRY)returnFalse,Nonedef_ensure_dir(path:Path)->None:"""确保目录存在。"""ifnotpath.exists():path.mkdir(parents=True,exist_ok=True)def_save_binary(resp:Response,dest:Path)->None:"""保存二进制文件。"""withdest.open("wb")asf:forchunkinresp.iter_content(chunk_size=8192):ifchunk:f.write(chunk)def_extract_images(md:str)->List[Tuple[str,str]]:"""提取 markdown 中的图片。"""pattern_md=re.compile(r'!\[([^\]]*)\]\((https?[^)]+)\)',re.IGNORECASE)pattern_html=re.compile(r'<img[^>]*?src=["\'](https?[^"\']+)["\']',re.IGNORECASE)images=pattern_md.findall(md)images+=[('',m)forminpattern_html.findall(md)]returnimagesdef_local_filename(url:str)->str:"""生成本地文件名。"""suffix=Path(urllib.parse.urlparse(url).path).suffix.lower()ifnotsuffixorlen(suffix)>6:suffix=".png"returnf"{uuid.uuid4().hex}{suffix}"defsave_page(book_id:str,slug:str,md_path:str)->None:""" 增加断点续传逻辑,文件存在则跳过。 """# 断点续传】如果文件已存在,直接跳过ifos.path.exists(md_path):print(f"[INFO] 文件已存在,跳过:{md_path}")returnurl=f"https://www.yuque.com/api/docs/{slug}?book_id={book_id}&merge_dynamic_data=false&mode=markdown"try:resp=requests.get(url,headers=HEADERS,timeout=TIMEOUT)ifresp.status_code!=200:print(f"[ERROR] 文档下载失败(状态码{resp.status_code}),可能已删除:{book_id}{slug}")returndoc_json=resp.json()md_content=doc_json["data"]["sourcecode"]except(req_exc.RequestException,KeyError,json.JSONDecodeError)ase:print(f"[ERROR] 文档解析失败:{e}|{book_id}{slug}")return# 处理图片images_dir=Path(md_path).parent/"images"images_map:Dict[str,str]={}foralt,img_urlin_extract_images(md_content):ifimg_urlinimages_map:continuelocal_name=_local_filename(img_url)local_rel_path=f"images/{local_name}"local_abs_path=images_dir/local_name# 只有确实需要下载图片时才创建目录_ensure_dir(images_dir)# 检查图片是否已存在(可选,防止重复下载图片)iflocal_abs_path.exists():images_map[img_url]=local_rel_pathcontinuesuccess,img_resp=_retry_get(img_url,RETRY)ifsuccess:try:_save_binary(img_resp,local_abs_path)images_map[img_url]=local_rel_path# print(f"[INFO] 图片已保存:{local_rel_path}") # 减少日志刷屏exceptExceptionase:print(f"[WARN] 图片保存失败{e},回退远程链接:{img_url}")images_map[img_url]=img_urlelse:images_map[img_url]=img_urlforremote,localinimages_map.items():md_content=md_content.replace(remote,local)# 写入文件try:Path(md_path).parent.mkdir(parents=True,exist_ok=True)withopen(md_path,"w",encoding="utf-8")asf_md:f_md.write(md_content)print(f"[INFO] 文档已保存:{md_path}")exceptOSErrorase:print(f"[ERROR] 保存文件失败(可能是文件名非法):{md_path}|{e}")# 在这更换网址defget_book(book_url:str="")->None:""" 抓取整本语雀知识库。 增强了文件名清洗逻辑,去除首尾空格。 """try:resp=requests.get(book_url,headers=HEADERS,timeout=TIMEOUT)resp.raise_for_status()exceptreq_exc.RequestExceptionase:print(f"[ERROR] 获取知识库失败:{e}")returnmatches=re.findall(r'decodeURIComponent\(\"(.+)\"\)\);',resp.text)ifnotmatches:print("[ERROR] 未找到知识库数据")returntry:docs_json=json.loads(urllib.parse.unquote(matches[0]))exceptjson.JSONDecodeErrorase:print(f"[ERROR] 解析知识库 JSON 失败:{e}")returnbook_id=str(docs_json["book"]["id"])book_root=Path("download")/book_id _ensure_dir(book_root)toc=docs_json["book"]["toc"]uuid_title_parent:Dict[str,Tuple[str,str]]={d["uuid"]:(d["title"],d["parent_uuid"])fordintoc}resolved_paths:Dict[str,str]={}# 构造层级路径,增加非法字符过滤,Windows下 ? 也是非法的trans_table=str.maketrans('\/:*?"<>|'+"\n\r","___________")defresolve_path(u:str)->str:ifuinresolved_paths:returnresolved_paths[u]# 处理可能缺失的情况ifunotinuuid_title_parent:return"Unknown"title,parent=uuid_title_parent[u]# 加上 .strip() 去除首尾空格!safe_title=title.translate(trans_table).strip()# 再次兜底,防止 strip 后为空ifnotsafe_title:safe_title="Untitled"ifnotparent:path_=safe_titleelse:path_=f"{resolve_path(parent)}/{safe_title}"resolved_paths[u]=path_returnpath_ summary_lines:List[str]=[]foritemintoc:path_rel=resolve_path(item["uuid"])abs_dir=book_root/path_rel is_dir=item["type"]=="TITLE"oritem.get("child_uuid")!=""ifis_dir:# 这里可能会因为路径太深或名字问题报错,加个trytry:_ensure_dir(abs_dir)exceptOSError:passheader_level=path_rel.count("/")+2summary_lines.append("#"*header_level+f"{path_rel.split('/')[-1]}")ifitem.get("url"):md_filename=f"{path_rel}.md"summary_indent=" "*path_rel.count("/")summary_lines.append(f"{summary_indent}* [{item['title']}]({urllib.parse.quote(md_filename)})")# 调用下载save_page(book_id,item["url"],str(book_root/f"{md_filename}"))withopen(book_root/"SUMMARY.md","w",encoding="utf-8")asf_sum:f_sum.write("\n".join(summary_lines))print(f"[INFO] SUMMARY.md 已生成:{book_root/'SUMMARY.md'}")if__name__=="__main__":iflen(sys.argv)>1:get_book(sys.argv[1])else:get_book("")

执行python3 main.py 语雀文档地址
项目地址https://github.com/burpheart/yuque-crawl

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/5 0:14:46

理解LoadRunner,基于此工具进行后端性能测试的详细过程(上)

1、LoadRunner 的基本原理 后端性能测试工具通过虚拟用户脚本生成器生成基于协议的虚拟用户脚本&#xff0c;然后根据性能测试场景设计的要求&#xff0c;通过压力控制器控制协调各个压力产生器以并发的方式执行虚拟用户脚本&#xff0c;并且在测试执行过程中&#xff0c;通过系…

作者头像 李华
网站建设 2026/1/28 14:37:16

AI大模型+Agent终极指南!从入门到落地,三大行业案例让你一篇看透!

本文将从AI Agent和大模型的发展背景切入&#xff0c;结合51Talk、哈啰出行以及B站三个各具特色的行业案例&#xff0c;带你一窥事件驱动架构、RAG技术、人机协作流程&#xff0c;以及一整套行之有效的实操方法。具体包含内容有&#xff1a;51Talk如何让智能客服“主动进攻”&a…

作者头像 李华
网站建设 2026/1/30 16:04:25

基于深度学习的智能停车场系统设计与实现

摘要&#xff1a;近年来&#xff0c;随着城市化进程的加快和人民生活水平的提高&#xff0c;车辆的增多导致停车难问题日益严重&#xff0c;传统的停车场管理方式已经无法满足现代城市的需求。该系统通过摄像头实时采集停车场信息&#xff0c;并结合基于卷积神经网络(CNN)的深度…

作者头像 李华
网站建设 2026/2/7 0:20:13

栈桢中引用对象是如何进行的?

要理解栈帧中引用对象的过程&#xff0c;首先需明确核心原则&#xff1a;对象实例存储在堆中&#xff0c;栈帧仅存储指向堆对象的 “引用”&#xff08;地址 / 句柄&#xff09;&#xff0c;栈帧通过这个引用间接操作堆中的对象。以下从栈帧结构、引用关联过程、访问逻辑、生命…

作者头像 李华
网站建设 2026/2/4 21:15:29

EcoVadis 评级划分

EcoVadis 评级按 0 - 100 分总分划分为铂金、金、银、铜、无等级五个等级。2024 年后等级对应标准为&#xff1a;铂金&#xff08;前 1%&#xff0c;81 - 100 分&#xff09;金牌&#xff08;前 5%&#xff0c;73 - 80 分&#xff09;银牌&#xff08;前 15%&#xff0c;66 - 7…

作者头像 李华