python爬虫实战:爬取知乎盐选专栏文章,批量保存为 PDF
本文介绍了一种使用Python实现知乎盐选专栏文章批量爬取并转换为PDF的方法。通过Selenium模拟登录获取访问权限,结合BeautifulSoup解析网页内容提取文章信息,最后利用pdfkit将HTML转换为PDF格式。项目提供了完整的代码实现,包括多线程优化和断点续爬功能,能够高效地保存文章内容到本地。文章详细说明了技术实现流程、常见问题解决方案以及功能扩展方向,为需要离线阅读或收藏知乎盐

目录
前言
知乎盐选专栏汇聚了大量优质的付费内容,涵盖职场、心理学、历史、情感等多个领域。对于希望离线阅读或归档收藏的用户来说,将这些文章批量保存为 PDF 是一个实用的需求。本项目将介绍如何使用 Python 爬虫技术获取知乎盐选专栏文章,并通过编程方式批量转换为 PDF 格式,方便用户离线阅读和长期保存。
摘要
本文详细阐述了爬取知乎盐选专栏文章并批量保存为 PDF 的完整实现过程。通过分析知乎网页结构,使用 Selenium 模拟浏览器操作获取登录状态,结合 BeautifulSoup 解析网页内容提取文章信息,最终利用 pdfkit 工具将 HTML 内容转换为 PDF 格式。文章提供了完整的代码实现、详细的步骤说明和常见问题解决方案,读者可以直接复现整个过程,实现知乎盐选专栏文章的批量下载与保存。
一、项目准备
1.1 爬取目标
爬取知乎盐选专栏的文章内容,具体包括:
- 文章标题
- 发布时间
- 作者信息
- 文章正文
- 文章配图
爬取链接示例:知乎盐选专栏(需登录后访问)
1.2 所需工具与库
本项目需要使用以下工具和 Python 库:
| 工具 / 库名称 | 用途 |
|---|---|
| Python 3.x | 项目开发语言 |
| Selenium | 模拟浏览器操作,处理登录状态 |
| BeautifulSoup | 解析 HTML 页面,提取文章内容 |
| pdfkit | 将 HTML 内容转换为 PDF 格式 |
| wkhtmltopdf | pdfkit 的依赖工具,用于 HTML 转 PDF |
| requests | 发送 HTTP 请求 |
| lxml | HTML 解析器 |
| time | 控制操作间隔 |
| os | 文件和目录操作 |
安装命令:
bash
pip install selenium beautifulsoup4 pdfkit requests lxml
wkhtmltopdf 安装:
- Windows:从wkhtmltopdf 官网下载安装包,安装后需将程序路径添加到系统环境变量
- macOS:使用 Homebrew 安装
brew install wkhtmltopdf - Linux:使用包管理器安装
sudo apt-get install wkhtmltopdf
二、爬虫实现
2.1 分析登录流程与网页结构
知乎盐选专栏属于付费内容,需要登录账号才能访问。我们将使用 Selenium 模拟人工登录过程,获取登录状态后的 Cookie,用于后续的文章爬取。
盐选专栏文章列表和内容页采用动态加载方式,每篇文章的内容包含在特定的 HTML 标签中,我们需要定位这些标签提取所需信息。
2.2 编写登录与爬虫代码
下面是完整的登录和爬虫代码,用于获取知乎盐选专栏文章:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import pdfkit
import requests
import time
import os
import json
from datetime import datetime
# 配置Chrome浏览器
chrome_options = Options()
# 无头模式,不显示浏览器窗口
# chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
# 初始化WebDriver
driver_path = "chromedriver.exe" # 请替换为你的chromedriver路径
service = Service(driver_path)
driver = webdriver.Chrome(service=service, options=chrome_options)
wait = WebDriverWait(driver, 10)
# 创建保存文件的目录
save_dir = "zhihu_salt_articles"
if not os.path.exists(save_dir):
os.makedirs(save_dir)
def login_zhihu():
"""登录知乎账号"""
driver.get("https://www.zhihu.com/signin")
try:
# 选择密码登录
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".SignFlow-tab")))
password_login = driver.find_elements(By.CSS_SELECTOR, ".SignFlow-tab")[1]
password_login.click()
# 输入账号密码(请替换为你的知乎账号密码)
username = input("请输入知乎账号:")
password = input("请输入知乎密码:")
wait.until(EC.presence_of_element_located((By.NAME, "username"))).send_keys(username)
driver.find_element(By.NAME, "password").send_keys(password)
# 点击登录按钮
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".SignFlow-submit")))
login_button.click()
# 等待登录完成(可能需要手动验证)
time.sleep(10)
# 检查是否登录成功
if "zhihu.com/feed" in driver.current_url:
print("登录成功!")
return True
else:
print("登录失败,请检查账号密码或手动完成验证")
return False
except Exception as e:
print(f"登录过程出错:{e}")
return False
def get_column_articles(column_url, max_articles=10):
"""获取专栏文章列表"""
driver.get(column_url)
time.sleep(3)
articles = []
article_count = 0
# 滚动加载更多文章
while article_count < max_articles:
# 获取当前页面的文章链接
soup = BeautifulSoup(driver.page_source, 'lxml')
article_links = soup.select(".ColumnPostItem-title a")
# 提取新的文章链接
for link in article_links:
article_url = link.get('href')
if article_url.startswith('/p/'):
article_url = f"https://www.zhihu.com{article_url}"
if article_url not in [a['url'] for a in articles]:
articles.append({
'title': link.text.strip(),
'url': article_url
})
article_count += 1
print(f"发现文章:{link.text.strip()}")
if article_count >= max_articles:
break
if article_count >= max_articles:
break
# 滚动到页面底部加载更多
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
# 检查是否有更多文章
try:
load_more = driver.find_element(By.CSS_SELECTOR, ".ColumnPagination-next")
if load_more.is_enabled():
load_more.click()
time.sleep(3)
else:
break
except:
break
print(f"共发现 {len(articles)} 篇文章")
return articles[:max_articles]
def save_article_as_pdf(article):
"""将单篇文章保存为PDF"""
try:
# 访问文章页面
driver.get(article['url'])
time.sleep(3)
# 等待文章内容加载完成
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".Post-RichTextContainer")))
# 提取文章信息
soup = BeautifulSoup(driver.page_source, 'lxml')
# 获取标题
title = soup.select_one(".Post-Title").text.strip() if soup.select_one(".Post-Title") else article['title']
# 获取作者
author = soup.select_one(".AuthorInfo-name").text.strip() if soup.select_one(".AuthorInfo-name") else "未知作者"
# 获取发布时间
publish_time = soup.select_one(".ContentItem-time").text.strip() if soup.select_one(".ContentItem-time") else datetime.now().strftime("%Y-%m-%d %H:%M")
# 获取文章内容
content = soup.select_one(".Post-RichTextContainer")
# 处理图片
for img in content.select('img'):
if 'src' in img.attrs:
img_url = img['src']
# 下载图片
img_filename = f"img_{hash(img_url)}.png"
img_path = os.path.join(save_dir, img_filename)
try:
response = requests.get(img_url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})
with open(img_path, 'wb') as f:
f.write(response.content)
# 替换为本地图片路径
img['src'] = img_path
except Exception as e:
print(f"下载图片失败:{e}")
# 构建HTML内容
html_content = f"""
<html>
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>
body {{ font-family: SimHei, Arial, sans-serif; line-height: 1.6; padding: 20px; max-width: 800px; margin: 0 auto; }}
.title {{ font-size: 24px; font-weight: bold; margin-bottom: 10px; }}
.meta {{ color: #666; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; }}
.content {{ font-size: 16px; }}
.content img {{ max-width: 100%; height: auto; margin: 10px 0; }}
.content p {{ margin-bottom: 15px; }}
.content h2 {{ font-size: 20px; margin: 20px 0 10px; }}
</style>
</head>
<body>
<h1 class="title">{title}</h1>
<div class="meta">作者:{author} | 发布时间:{publish_time}</div>
<div class="content">{str(content)}</div>
</body>
</html>
"""
# 保存HTML临时文件
html_filename = f"{hash(title)}.html"
html_path = os.path.join(save_dir, html_filename)
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
# 转换为PDF
pdf_filename = f"{title.replace(':', '-').replace('?', '')}.pdf"
pdf_path = os.path.join(save_dir, pdf_filename)
# 配置pdfkit
config = pdfkit.configuration(wkhtmltopdf=r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe') # 请替换为你的wkhtmltopdf路径
# 转换HTML到PDF
pdfkit.from_file(html_path, pdf_path, configuration=config, options={
'page-size': 'Letter',
'encoding': "UTF-8",
'custom-header': [
('Accept-Encoding', 'gzip')
]
})
# 删除临时HTML文件
os.remove(html_path)
print(f"已保存为PDF:{pdf_filename}")
return True
except Exception as e:
print(f"保存文章失败:{e}")
return False
def main():
# 登录知乎
if not login_zhihu():
print("登录失败,程序退出")
driver.quit()
return
# 专栏URL(可替换为其他盐选专栏URL)
column_url = "https://www.zhihu.com/column/salt-selected"
# 获取文章列表(最多爬取20篇)
max_articles = 20
print(f"开始获取专栏文章列表,最多获取{max_articles}篇...")
articles = get_column_articles(column_url, max_articles)
if not articles:
print("未获取到任何文章,程序退出")
driver.quit()
return
# 保存文章为PDF
print(f"开始保存文章为PDF...")
for i, article in enumerate(articles, 1):
print(f"正在处理第{i}/{len(articles)}篇:{article['title']}")
save_article_as_pdf(article)
time.sleep(2) # 控制速度,避免被反爬
print("所有文章处理完成!")
driver.quit()
if __name__ == "__main__":
main()
2.3 代码解释
-
浏览器配置:使用 Selenium 的 ChromeOptions 配置浏览器参数,包括窗口大小、用户代理等,模拟真实浏览器环境。
-
登录功能:
- 自动打开知乎登录页面
- 切换到密码登录模式
- 接收用户输入的账号密码并提交
- 等待登录完成(可能需要手动完成验证码)
-
文章列表获取:
- 访问指定的盐选专栏页面
- 通过滚动页面和点击 "加载更多" 按钮获取更多文章
- 提取文章标题和链接
-
文章内容爬取与保存:
- 访问单篇文章页面
- 提取标题、作者、发布时间和正文内容
- 下载文章中的图片并替换为本地路径
- 构建完整的 HTML 内容
- 使用 pdfkit 将 HTML 转换为 PDF 格式
- 清理临时文件
-
反爬措施:
- 模拟真实浏览器行为
- 在操作之间加入时间间隔
- 避免短时间内发送过多请求
2.4 输出结果
运行代码后,会在当前目录下创建zhihu_salt_articles文件夹,其中包含爬取到的所有文章的 PDF 文件。同时,控制台会输出以下信息:
plaintext
请输入知乎账号:your_username
请输入知乎密码:your_password
登录成功!
开始获取专栏文章列表,最多获取20篇...
发现文章:职场中,有哪些常见的“伪能力”,却被很多人当成宝?
发现文章:为什么说真正的高手,都懂得“藏锋”?
...
共发现 20 篇文章
开始保存文章为PDF...
正在处理第1/20篇:职场中,有哪些常见的“伪能力”,却被很多人当成宝?
已保存为PDF:职场中,有哪些常见的“伪能力”,却被很多人当成宝?.pdf
正在处理第2/20篇:为什么说真正的高手,都懂得“藏锋”?
已保存为PDF:为什么说真正的高手,都懂得“藏锋”?.pdf
...
所有文章处理完成!
三、功能扩展与优化
3.1 多线程爬取
为了提高爬取效率,可以使用多线程同时处理多篇文章的下载和转换:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
import pdfkit
import requests
import time
import os
import json
from datetime import datetime
import threading
from queue import Queue
# 配置项(与之前相同)
chrome_options = Options()
# chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
driver_path = "chromedriver.exe"
save_dir = "zhihu_salt_articles"
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 全局变量存储登录状态的cookies
global_cookies = None
def login_zhihu():
"""登录知乎并获取cookies"""
global global_cookies
driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
driver.get("https://www.zhihu.com/signin")
try:
# 选择密码登录
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".SignFlow-tab")))
password_login = driver.find_elements(By.CSS_SELECTOR, ".SignFlow-tab")[1]
password_login.click()
# 输入账号密码
username = input("请输入知乎账号:")
password = input("请输入知乎密码:")
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.NAME, "username"))).send_keys(username)
driver.find_element(By.NAME, "password").send_keys(password)
# 点击登录按钮
login_button = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".SignFlow-submit")))
login_button.click()
# 等待登录完成
time.sleep(10)
# 检查是否登录成功
if "zhihu.com/feed" in driver.current_url:
print("登录成功!")
# 获取cookies
global_cookies = driver.get_cookies()
driver.quit()
return True
else:
print("登录失败,请检查账号密码或手动完成验证")
driver.quit()
return False
except Exception as e:
print(f"登录过程出错:{e}")
driver.quit()
return False
def get_column_articles(column_url, max_articles=10):
"""获取专栏文章列表"""
driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
driver.get("https://www.zhihu.com")
# 设置cookies
global global_cookies
if global_cookies:
for cookie in global_cookies:
driver.add_cookie(cookie)
driver.get(column_url)
time.sleep(3)
articles = []
article_count = 0
# 滚动加载更多文章(与之前相同)
while article_count < max_articles:
soup = BeautifulSoup(driver.page_source, 'lxml')
article_links = soup.select(".ColumnPostItem-title a")
for link in article_links:
article_url = link.get('href')
if article_url.startswith('/p/'):
article_url = f"https://www.zhihu.com{article_url}"
if article_url not in [a['url'] for a in articles]:
articles.append({
'title': link.text.strip(),
'url': article_url
})
article_count += 1
print(f"发现文章:{link.text.strip()}")
if article_count >= max_articles:
break
if article_count >= max_articles:
break
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
try:
load_more = driver.find_element(By.CSS_SELECTOR, ".ColumnPagination-next")
if load_more.is_enabled():
load_more.click()
time.sleep(3)
else:
break
except:
break
print(f"共发现 {len(articles)} 篇文章")
driver.quit()
return articles[:max_articles]
def article_worker(queue):
"""处理文章的工作线程"""
# 为每个线程创建独立的浏览器实例
driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
# 设置cookies
global global_cookies
if global_cookies:
driver.get("https://www.zhihu.com")
for cookie in global_cookies:
driver.add_cookie(cookie)
time.sleep(2)
while not queue.empty():
article, index, total = queue.get()
try:
print(f"线程 {threading.current_thread().name} 正在处理第{index}/{total}篇:{article['title']}")
save_article_as_pdf(article, driver)
except Exception as e:
print(f"处理文章 {article['title']} 时出错:{e}")
finally:
queue.task_done()
time.sleep(1)
driver.quit()
def save_article_as_pdf(article, driver):
"""将单篇文章保存为PDF(使用传入的driver)"""
try:
driver.get(article['url'])
time.sleep(3)
# 等待文章内容加载完成
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".Post-RichTextContainer")))
# 提取文章信息(与之前相同)
soup = BeautifulSoup(driver.page_source, 'lxml')
title = soup.select_one(".Post-Title").text.strip() if soup.select_one(".Post-Title") else article['title']
author = soup.select_one(".AuthorInfo-name").text.strip() if soup.select_one(".AuthorInfo-name") else "未知作者"
publish_time = soup.select_one(".ContentItem-time").text.strip() if soup.select_one(".ContentItem-time") else datetime.now().strftime("%Y-%m-%d %H:%M")
content = soup.select_one(".Post-RichTextContainer")
# 处理图片
for img in content.select('img'):
if 'src' in img.attrs:
img_url = img['src']
img_filename = f"img_{hash(img_url)}.png"
img_path = os.path.join(save_dir, img_filename)
try:
response = requests.get(img_url, headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
})
with open(img_path, 'wb') as f:
f.write(response.content)
img['src'] = img_path
except Exception as e:
print(f"下载图片失败:{e}")
# 构建HTML内容
html_content = f"""
<html>
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>
body {{ font-family: SimHei, Arial, sans-serif; line-height: 1.6; padding: 20px; max-width: 800px; margin: 0 auto; }}
.title {{ font-size: 24px; font-weight: bold; margin-bottom: 10px; }}
.meta {{ color: #666; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; }}
.content {{ font-size: 16px; }}
.content img {{ max-width: 100%; height: auto; margin: 10px 0; }}
.content p {{ margin-bottom: 15px; }}
.content h2 {{ font-size: 20px; margin: 20px 0 10px; }}
</style>
</head>
<body>
<h1 class="title">{title}</h1>
<div class="meta">作者:{author} | 发布时间:{publish_time}</div>
<div class="content">{str(content)}</div>
</body>
</html>
"""
# 保存HTML临时文件并转换为PDF
html_filename = f"{hash(title)}.html"
html_path = os.path.join(save_dir, html_filename)
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
pdf_filename = f"{title.replace(':', '-').replace('?', '')}.pdf"
pdf_path = os.path.join(save_dir, pdf_filename)
config = pdfkit.configuration(wkhtmltopdf=r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe')
pdfkit.from_file(html_path, pdf_path, configuration=config, options={
'page-size': 'Letter',
'encoding': "UTF-8"
})
os.remove(html_path)
print(f"已保存为PDF:{pdf_filename}")
return True
except Exception as e:
print(f"保存文章失败:{e}")
return False
def main():
# 登录知乎
if not login_zhihu():
print("登录失败,程序退出")
return
# 专栏URL
column_url = "https://www.zhihu.com/column/salt-selected"
# 获取文章列表
max_articles = 20
print(f"开始获取专栏文章列表,最多获取{max_articles}篇...")
articles = get_column_articles(column_url, max_articles)
if not articles:
print("未获取到任何文章,程序退出")
return
# 使用多线程保存文章
print(f"开始使用多线程保存文章为PDF...")
num_threads = 3 # 设置线程数量
queue = Queue()
# 填充队列
for i, article in enumerate(articles, 1):
queue.put((article, i, len(articles)))
# 创建并启动线程
threads = []
for i in range(num_threads):
thread = threading.Thread(target=article_worker, args=(queue,), name=f"Thread-{i+1}")
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
print("所有文章处理完成!")
if __name__ == "__main__":
main()
优化说明:
-
多线程处理:使用队列和多线程技术,同时处理多篇文章的下载和转换,提高爬取效率。
-
共享登录状态:通过保存和复用登录 Cookies,避免每个线程都需要重新登录。
-
线程安全:每个线程使用独立的浏览器实例,避免资源竞争和冲突。
-
任务队列:使用队列管理待处理的文章,实现任务的均衡分配。
3.2 断点续爬功能
添加断点续爬功能,避免因程序中断需要重新爬取所有内容:
import json
import os
# 其他导入与之前相同
# 在原有代码基础上添加以下函数
def load_progress():
"""加载已完成的任务进度"""
progress_file = os.path.join(save_dir, "progress.json")
if os.path.exists(progress_file):
with open(progress_file, 'r', encoding='utf-8') as f:
return json.load(f)
return []
def save_progress(completed_urls):
"""保存任务进度"""
progress_file = os.path.join(save_dir, "progress.json")
with open(progress_file, 'w', encoding='utf-8') as f:
json.dump(completed_urls, f, ensure_ascii=False, indent=2)
# 修改main函数,添加断点续爬逻辑
def main():
# 登录知乎(与之前相同)
if not login_zhihu():
print("登录失败,程序退出")
return
# 加载已完成的任务
completed_urls = load_progress()
print(f"已完成 {len(completed_urls)} 篇文章的爬取")
# 专栏URL
column_url = "https://www.zhihu.com/column/salt-selected"
# 获取文章列表
max_articles = 20
print(f"开始获取专栏文章列表,最多获取{max_articles}篇...")
articles = get_column_articles(column_url, max_articles)
if not articles:
print("未获取到任何文章,程序退出")
return
# 过滤已完成的文章
remaining_articles = [a for a in articles if a['url'] not in completed_urls]
print(f"还需处理 {len(remaining_articles)} 篇文章")
if not remaining_articles:
print("所有文章都已处理完成!")
return
# 使用多线程保存文章
print(f"开始使用多线程保存文章为PDF...")
num_threads = 3
queue = Queue()
# 填充队列
for i, article in enumerate(remaining_articles, 1):
queue.put((article, i, len(remaining_articles)))
# 创建并启动线程
threads = []
for i in range(num_threads):
thread = threading.Thread(target=article_worker, args=(queue, completed_urls), name=f"Thread-{i+1}")
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
# 保存最终进度
save_progress(completed_urls)
print("所有文章处理完成!")
# 修改article_worker函数,接收completed_urls参数并更新
def article_worker(queue, completed_urls):
"""处理文章的工作线程"""
driver = webdriver.Chrome(service=Service(driver_path), options=chrome_options)
# 设置cookies(与之前相同)
global global_cookies
if global_cookies:
driver.get("https://www.zhihu.com")
for cookie in global_cookies:
driver.add_cookie(cookie)
time.sleep(2)
while not queue.empty():
article, index, total = queue.get()
try:
print(f"线程 {threading.current_thread().name} 正在处理第{index}/{total}篇:{article['title']}")
if save_article_as_pdf(article, driver):
# 保存成功,添加到已完成列表
completed_urls.append(article['url'])
# 定期保存进度
if len(completed_urls) % 5 == 0:
save_progress(completed_urls)
except Exception as e:
print(f"处理文章 {article['title']} 时出错:{e}")
finally:
queue.task_done()
time.sleep(1)
driver.quit()
功能说明:
-
进度保存:使用 JSON 文件记录已成功爬取的文章 URL,避免重复爬取。
-
断点续爬:程序启动时检查进度文件,只处理未完成的文章。
-
定期保存:每完成 5 篇文章的爬取,自动保存一次进度,降低因意外中断导致的进度丢失风险。
四、项目总结
本项目实现了知乎盐选专栏文章的批量爬取和 PDF 转换功能,主要特点和成果如下:
-
完整的登录机制:使用 Selenium 模拟浏览器登录,处理知乎的登录验证,获取访问盐选专栏的权限。
-
高效的内容提取:通过 BeautifulSoup 解析网页结构,准确提取文章标题、作者、发布时间和正文内容。
-
PDF 转换功能:利用 pdfkit 和 wkhtmltopdf 工具,将 HTML 内容转换为格式良好的 PDF 文件,保留文章的排版和图片。
-
性能优化:实现多线程爬取,提高处理效率;添加断点续爬功能,增强程序的健壮性。
-
用户友好:代码结构清晰,注释完善,用户只需修改少量配置即可运行,适合不同技术水平的用户使用。
通过本项目,读者可以学习到动态网页爬取、模拟登录、文件格式转换等实用技术,这些技术不仅适用于知乎爬取,还可以推广应用到其他类似的网站和场景。
五、常见问题解答
-
运行代码时提示找不到 chromedriver 怎么办?
这是因为没有正确配置 chromedriver 路径。解决方法:
- 从ChromeDriver 官网下载与你的 Chrome 浏览器版本匹配的 chromedriver
- 在代码中正确设置 driver_path 变量,指向 chromedriver 的位置
- 确保 chromedriver 具有可执行权限(Linux/macOS 系统)
-
登录时需要验证码怎么办?
代码中预留了 10 秒的手动验证时间,当出现验证码时,你可以在浏览器窗口中手动完成验证,程序会继续执行。
-
转换 PDF 时出现中文乱码如何解决?
这通常是由于缺少中文字体导致的。解决方法:
- 确保系统中安装了常用的中文字体(如宋体、黑体等)
- 在 HTML 的 style 中指定中文字体,如代码中已设置的 "SimHei, Arial, sans-serif"
- 检查 wkhtmltopdf 是否支持中文字体,必要时重新安装最新版本
-
爬取过程中出现 "403 Forbidden" 错误怎么办?
这是网站的反爬机制导致的。解决方法:
- 增加请求间隔时间,降低爬取速度
- 更换 User-Agent,模拟不同的浏览器
- 尝试使用代理 IP
- 避免短时间内大量爬取,分时段进行
-
如何爬取特定的盐选专栏?
只需将代码中的 column_url 变量替换为目标专栏的 URL 即可。例如,要爬取 "盐选推荐" 专栏,使用 "https://www.zhihu.com/column/salt-selected";要爬取其他专栏,只需替换为相应的 URL。
-
为什么有些文章无法正确转换为 PDF?
这可能是由于文章内容格式复杂或包含特殊元素导致的。可以尝试:
- 简化 HTML 内容,去除复杂的 CSS 样式
- 更新 wkhtmltopdf 到最新版本
- 调整 pdfkit 的转换参数,如增加 --no-stop-slow-scripts 选项
通过以上解答,希望能帮助读者顺利解决使用过程中遇到的问题,充分发挥本项目的实用价值。

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)