python爬虫:将知乎专栏文章转为pdf
欢迎关注本人的知乎主页~
实现思路
-
用户输入专栏ID:
- 代码首先提示用户输入一个知乎专栏的ID,默认值为
'c_1747690982282477569'。 - 输入的ID用于构建API请求的URL。
- 代码首先提示用户输入一个知乎专栏的ID,默认值为
-
发送HTTP请求:
- 使用
requests.get()向知乎API发送GET请求,获取指定专栏的文章列表。 - 检查响应的状态码,确认请求是否成功。
- 使用
-
如果请求成功,解析返回的JSON数据,并将其保存到本地文件
zhihu.json中。 -
定义处理HTML内容的函数
process_content:- 这个函数用于处理文章的HTML内容,具体操作包括:
- 移除
data-pid属性。 - 替换特殊的字符
\u003C和\u003E为<和>。 - 添加段落的缩进和底部边距。
- 移除包含
<img>的<figure>标签。 - 移除
class="ztext-empty-paragraph"的<p>标签。 - 去除多余的
<br>标签。 - 确保每个段落都在
<p>和</p>之间。
- 移除
- 这个函数用于处理文章的HTML内容,具体操作包括:
-
从之前保存的
zhihu.json文件中读取JSON数据,并解析为Python字典。 -
从解析后的数据中提取文章的具体内容和标题。
-
创建一个名为
articles的目录来保存生成的HTML文件。 -
使用 Jinja2 模板引擎初始化模板环境,并加载预定义的HTML模板
template.html。 -
遍历文章数据并生成HTML文件:
- 对每篇文章的内容进行处理,并使用Jinja2模板渲染为完整的HTML页面。
- 将渲染后的HTML内容保存到
articles目录下的.html文件中。
-
转换HTML文件为PDF文件:
- 创建一个名为
pdfs的目录来保存生成的PDF文件。 - 遍历
articles目录中的所有HTML文件,并使用pdfkit将其转换为PDF格式。 - 在转换过程中,禁止加载远程资源,并忽略加载错误。
- 创建一个名为
-
输出结果信息,告知用户所有文章已保存为HTML文件,并且所有HTML文件已转换为PDF文件。
完整代码
import json
import os
import re
from jinja2 import Environment, FileSystemLoader
import requests
import pdfkit# 用户输入专栏名称,默认为c_1747690982282477569
column_id = input("请输入知乎专栏ID(默认为 c_1747690982282477569):") or 'c_1747690982282477569'
url = f'https://www.zhihu.com/api/v4/columns/{column_id}/articles'# 发送请求获取专栏文章列表
response = requests.get(url)# 检查请求是否成功
if response.status_code == 200:# 解析 JSON 数据data = response.json()# 保存到本地文件output_file = 'zhihu.json'with open(output_file, 'w', encoding='utf-8') as file:json.dump(data, file, ensure_ascii=False, indent=4)print(f"数据已保存到 {output_file}")
else:print(f"请求失败,状态码:{response.status_code}")def process_content(content):"""处理HTML内容,移除不需要的标签和属性,调整样式等。"""# 移除标识符号# 匹配 data-pid 属性,并允许属性值使用普通双引号或转义的双引号,以及可能存在的空白字符content = re.sub(r'data-pid\s*=\s*(?:"|\")(.+?)(?:"|\")', '', content)# 替换特殊字符content = content.replace('\u003C', '<').replace('\u003E', '>')# 处理<p>标签,添加缩进和底部边距content = content.replace('<p ', '<p style="text-indent: 2em; margin-bottom: 1em;">')# 处理</p>标签content = content.replace('</p>', '</p>')# 移除包含 <img> 的 <figure> 标签content = re.sub(r'<figure.*?>.*?</figure>', '', content, flags=re.DOTALL)# 移除 class="ztext-empty-paragraph"content = re.sub(r'<p[^>]*class\s*=\s*["\']ztext-empty-paragraph["\'][^>]*>', '</p>', content)# 去除多余的<br>content = re.sub(r'</p><br>', '</p>', content)# 最后一个段落不应该有额外的换行if content.endswith('<p style="text-indent: 2em; margin-bottom: 1em;">'):content = content[:-len('<p style="text-indent: 2em; margin-bottom: 1em;">')]content += '</p>'# 确保每段文本都包裹在<p>和</p>之间paragraphs = re.split(r'(<p[^>]*>)', content)cleaned_paragraphs = []for i in range(0, len(paragraphs), 2):if i + 1 < len(paragraphs): # 如果有对应的<p>标签cleaned_paragraphs.append(paragraphs[i])cleaned_paragraphs.append(paragraphs[i + 1].strip())else:cleaned_paragraphs.append(paragraphs[i].strip())content = ''.join(cleaned_paragraphs)return content# 定义输入文件名
input_file = 'zhihu.json'# 从文件中读取JSON数据
with open(input_file, 'r', encoding='utf-8') as file:json_data = file.read()# 解析JSON数据
data = json.loads(json_data)# 提取"data"数组中的内容
articles_data = data['data']# 创建一个目录来保存HTML和PDF文件
output_dir = 'articles'
os.makedirs(output_dir, exist_ok=True)# 初始化Jinja2环境
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('template.html')# 遍历每一篇文章的数据
for article in articles_data:# 获取文章内容和标题article_id = str(article['id'])content = article['content']processed_content = process_content(content)title = article['title']# 渲染HTML模板html_content = template.render(title=title, content=processed_content)# 移除连续的 '>>',只保留一个 '>'html_content = re.sub(r'(>)>', r'\1', html_content)# 将内容写入HTML文件html_file_path = os.path.join(output_dir, f'{article_id}.html')with open(html_file_path, 'w', encoding='utf-8') as file:file.write(html_content)print("所有文章已保存为HTML文件")# 指定输入文件夹
input_dir = 'articles'# 创建一个目录来保存 PDF 文件
output_dir = 'pdfs'
os.makedirs(output_dir, exist_ok=True)# 遍历文件夹中的所有 HTML 文件
for filename in os.listdir(input_dir):if filename.endswith('.html'):# 获取 HTML 文件的完整路径html_file_path = os.path.join(input_dir, filename)# 构造 PDF 文件的名称pdf_filename = os.path.splitext(filename)[0] + '.pdf'pdf_file_path = os.path.join(output_dir, pdf_filename)# 读取 HTML 文件内容with open(html_file_path, 'r', encoding='utf-8') as file:html_content = file.read()# 将 HTML 文件转换为 PDF 文件try:# 使用 options 禁止加载远程资源options = {'disable-local-file-access': None,'load-error-handling': 'ignore',}# 注意html文件名不能含有中文pdfkit.from_string(html_content, pdf_file_path, options=options)print(f"{filename} 已转换为 {pdf_filename}")except Exception as e:print(f"转换 {filename} 时发生错误:{e}")print("所有 HTML 文件已转换为 PDF 文件。")
运行结果

待完善的功能
- 本项目没有保存知乎文章中的图片,因为图片大小较难以控制;
- 知乎文章中的引用和脚注没能很好地处理。
以上问题有待解决。另外,这篇文章介绍了直接保存知乎网页文章的方法,值得参考。
相关文章:
python爬虫:将知乎专栏文章转为pdf
欢迎关注本人的知乎主页~ 实现思路 用户输入专栏ID: 代码首先提示用户输入一个知乎专栏的ID,默认值为 c_1747690982282477569。输入的ID用于构建API请求的URL。 发送HTTP请求: 使用 requests.get() 向知乎API发送GET请求,获取指定…...
嵌入式笔记(入门系列2)
目录 宏函数 预处理器#include 内存泄漏 内存对齐 堆与栈 Malloc 和 New Inline 宏函数 宏函数,宏函数,实际上就是让宏像函数一样被使用。宏函数以函数形式的方式进行入参,但是返回结果是通过表达式求值得到。话说的抽象,我…...
并发编程多线程
1.线程和进程的区别? 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间线程更轻量,线程上下文切换成本一般上要比进程上下文…...
【十八】MySQL 8.0 新特性
MySQL 8.0 新特性 目录 MySQL 8.0 新特性 概述 简述 1、数据字典 2、原子数据定义语句 3、升级过程 4、会话重用 5、安全和账户管理 6、资源管理 7、表加密管理 8、InnoDB增强功能 9、字符集支持 10、增强JSON功能 11、数据类型的支持 12、查询的优化 13、公用…...
巨潮股票爬虫逆向
目标网站 aHR0cDovL3dlYmFwaS5jbmluZm8uY29tLmNuLyMvSVBPTGlzdD9tYXJrZXQ9c3o 一、抓包分析 请求头参数加密 二、逆向分析 下xhr断点 参数生成位置 发现是AES加密,不过是混淆的,但并不影响咱们扣代码 文章仅提供技术交流学习,不可对目标服…...
传知代码-从零开始构建你的第一个神经网络
代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 从零开始构建你的第一个神经网络 在本教程中,我们将使用PyTorch框架从零开始构建一个简单的卷积神经网络(CNN),用于图片二分类任务。CNN 是一种深度学习模型&#…...
大厂面试真题:SpringBoot的核心注解
其实理解一个注解就行了@SpringBootApplication,我们的启动类其实就加了这一个 但是这么答也不行,因为面试官要的答案肯定不止这一个 我们打开SpringBootApplication的源码,会发现上面加了一堆的注解 相对而言比较重要是下面三个…...
Java设计模式—面向对象设计原则(五) ----->迪米特法则(DP) (完整详解,附有代码+案例)
文章目录 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.5 迪米特法则(DP) 迪米特法则:Demeter Principle,简称DP 3.5.1 概述 只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to stranger…...
docker多阶段镜像制作,比如nginx镜像,编译+制作
镜像制作, nginx的源码包 把nginx源码拷贝到容器内 编译要用到gcc make , 以及扩展工具 pcre openssl # "pcre" perl compatibal regulaer expression 刚开始,可以两个终端, 一个手工操作(编译安装、拷贝、环境变量等)…...
大语言模型量化方法GPTQ、GGUF、AWQ详细原理
大语言模型量化的目的是减少模型的计算资源需求和存储占用,同时尽量保持模型的性能。以下是几种常见的量化方法的原理; 1. GPTQ (Gradient-based Post-training Quantization) GPTQ 是一种基于梯度的后训练量化方法,主要目的是在减少浮点计…...
《 C++ 修炼全景指南:十 》自平衡的艺术:深入了解 AVL 树的核心原理与实现
摘要 本文深入探讨了 AVL 树(自平衡二叉搜索树)的概念、特点以及实现细节。我们首先介绍了 AVL 树的基本原理,并详细分析了其四种旋转操作,包括左旋、右旋、左右双旋和右左双旋,阐述了它们在保持树平衡中的重要作用。…...
SAP 特别总账标识[SGL]
1. 特别总账标识(SGL)概述 1.1 定义与目的 特别总账标识(Special General Ledger, SGL)在SAP系统中用于区分客户或供应商的不同业务类型,以便将特定的业务交易记录到非标准的总账科目中。 定义:SGL是一个用于标记特殊业务类型的…...
认知杂谈77《简单:通往高手的技巧》
内容摘要: 在信息爆炸、关系复杂的时代,简单是复杂背后的真谛。简单如“112”,是智慧的朴素呈现。简单有强大力量,像清泉般纯净,如“我爱你”简单却有力,基础财务知识也体现其在理财中的作…...
《SmartX ELF 虚拟化核心功能集》发布,详解 80+ 功能特性和 6 例金融实践
《SmartX ELF 虚拟化核心功能集》电子书现已发布!本书详细介绍了 SmartX ELF 虚拟化及云平台核心功能,包含虚机服务、容器服务、网络服务、存储服务、运维管理、工具服务、数据保护等各个方面。 即刻下载电子书,了解如何利用基于 SmartX ELF …...
9月23日
思维导图 作业 统计家目录下.c文件的个数 #!/bin/bashnum0for file in ~/*.c; doif [ -f "$file" ]; then((num))fi doneecho "家目录下.c文件的个数: $num"...
如何使用Jinja定义dbt宏
dbt宏在dbt框架内的工作方式与传统编程中的函数类似。它允许用户将特定的、通常是重复的SQL逻辑封装到可调用的命名单元中,就像在其他编程语言中用函数来避免重复代码一样;dbt宏定义特定业务的SQL逻辑,然后在dbt项目中需要的地方调用该宏函数…...
深入理解 JavaScript 三大作用域:全局作用域、函数作用域、块级作用域
一. 作用域 对于多数编程语言,最基本的功能就是能够存储变量当中的值、并且允许我们对这个变量的值进行访问和修改。那么有了变量之后,应该把它放在哪里、程序如何找到它们?是否需要提前约定好一套存储变量、访问变量的规则?答案…...
【门牌制作 / A】
题目 代码 #include <bits/stdc.h> using namespace std; int main() {int cnt 0;for (int i 1; i < 2020; i){string s;s to_string(i);cnt count(s.begin(), s.end(), 2);}cout << cnt; }...
Git+Jenkins 基本使用(Basic Usage of Git+Jenkins)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…...
智谱清言:智能语音交互的引领者,解锁高效沟通新体验
哪个编程工具让你的工作效率翻倍? 在日益繁忙的工作环境中,选择合适的编程工具已成为提升开发者工作效率的关键。不同的工具能够帮助我们简化代码编写、自动化任务、提升调试速度,甚至让团队协作更加顺畅。那么,哪款编程工具让你…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
