Python实现知乎图片爬虫(无需登录)
Python实现知乎图片爬虫(无需登录)
那天朋友问我:“有没有能自动合成数字人说话视频的工具?”
我回了句:“可以有。”
然后就有了——但今天不讲那个。
我们来点“复古”的:用Python写一个完全免登录的知乎图片爬虫。
你是不是也经常刷知乎时,看到某个高赞回答里的配图特别惊艳?可能是旅行摄影、艺术插画,或是干货满满的示意图。想保存下来,却一张张手动点开太麻烦?更别说还要防着反爬、处理登录态、应对验证码……
其实,根本不用那么复杂。
只要策略得当,不需要账号、不模拟登录、不碰Cookie、甚至不用Selenium,也能稳定抓取知乎公开页面中的高清图片。本文要做的,就是一个轻量、干净、高效的采集脚本:输入关键词 → 自动搜索 → 遍历问题页 → 提取所有答案中的原图 → 批量下载到本地。
整个过程基于HTTP请求和HTML解析完成,代码不到200行,依赖极少,跑起来飞快。
核心思路:绕过登录,直取公开内容
知乎虽然对未登录用户做了部分限制,比如部分内容折叠、弹出登录框等,但它的搜索结果页和绝大多数问答页仍然是可公开访问的。尤其是通过搜索引擎跳转过来的内容,基本都能正常加载。
我们的突破口就在这里:
- 构造知乎搜索URL(
https://www.zhihu.com/search?q=xxx),获取返回的HTML; - 用BeautifulSoup从中提取所有
/question/{id}类型的链接; - 逐个请求这些页面,解析其中的答案区
<img>标签; - 过滤缩略图,保留高清源地址(如
_hd.jpg,_r.jpg); - 下载并编号保存。
全程走纯静态页面路线,避开JavaScript渲染、Ajax异步加载、Token验证等一系列“重武器”,反而更轻便可靠。
小技巧:知乎图片CDN域名通常是
pic[1-9].zhimg.com,路径中常带有_m(中等)、_b(大图)、_hd(高清)等标识。我们优先抓_hd和_r,再替换掉_m/_b 为无后缀版本,往往就能拿到原始分辨率。
技术栈选择:极简主义路线
pip install requests beautifulsoup4 lxml
| 库名 | 作用 |
|---|---|
requests | 发起HTTP请求,接口简洁,异常处理友好 |
BeautifulSoup4 | 解析HTML结构,定位目标标签 |
lxml | 作为底层解析器,速度远超内置html.parser |
为什么不选Selenium或Playwright?
因为没必要。这类工具适合动态渲染强、严重依赖JS的站点(比如微博、小红书)。而知乎的搜索页和问题页在未登录状态下仍会返回完整HTML,直接解析即可,何必启动整个浏览器?
而且,轻量意味着更高的并发能力和更低的资源消耗。你可以轻松把它集成进定时任务、GUI工具或Web服务里。
关键代码拆解
下面这个类封装了全部逻辑。我们一步步来看它是怎么工作的。
import os
import re
import sys
import requests
from bs4 import BeautifulSoup
class ZhihuImageSpider:
def __init__(self, keyword, save_path):
self.keyword = keyword
self.save_path = save_path.strip()
self.image_urls = []
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Referer': f'https://www.zhihu.com/search?q={requests.utils.quote(keyword)}',
'Origin': 'https://www.zhihu.com',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
请求头伪装很关键
别小看这几个Header字段:
User-Agent模拟主流Chrome浏览器;Referer设置成搜索页来源,让服务器觉得你是“自然浏览”而非脚本调用;Origin和Accept-Language增加真实性。
这几个组合起来,能有效降低被识别为爬虫的概率。
获取搜索页内容
def get_search_page(self):
url = f"https://www.zhihu.com/search?q={requests.utils.quote(self.keyword)}"
print(f"正在获取搜索页: {url}")
try:
response = requests.get(url, headers=self.headers, timeout=10)
if response.status_code == 200:
return response.text
else:
print(f"搜索页请求失败,状态码: {response.status_code}")
return None
except Exception as e:
print(f"网络请求异常: {str(e)}")
return None
这里用了 requests.utils.quote() 对关键词做URL编码,防止中文乱码。同时设置了10秒超时,避免卡死。
提取问题链接
def parse_question_links(self, html):
soup = BeautifulSoup(html, 'lxml')
links = set()
for a in soup.find_all('a', href=re.compile(r'/question/\d+')):
href = a['href']
if not href.startswith('http'):
href = 'https://www.zhihu.com' + href
links.add(href)
print(f"共找到 {len(links)} 个问题链接")
return list(links)
注意两个细节:
- 使用正则
/question/\d+精准匹配问题ID; - 去重用
set(),避免重复抓取同一问题。
抓取单个问题页并提取图片
def extract_images_from_page(self, html):
soup = BeautifulSoup(html, 'lxml')
img_elements = soup.find_all('img', src=re.compile(r'pic\d\.zhimg\.com'))
urls = []
pattern = re.compile(r'src="(https?://[^"]+?\.jpg)"')
for img in img_elements:
src = str(img)
match = pattern.search(src)
if match:
full_url = match.group(1)
if '_hd' in full_url or '_r' in full_url or 'v2-' in full_url:
urls.append(full_url.replace('_m', '').replace('_b', ''))
return urls
这里的过滤逻辑是重点:
- 只提取包含
pic[数字].zhimg.com的图片(排除头像、图标等无关资源); - 匹配
_hd(高清)、_r(可能是raw)、v2-(新版图床)等关键词; - 主动替换
_m和_b后缀,尝试获取更高清版本。
实测表明,很多所谓的“中等图”去掉 _m 后依然可访问,且清晰度提升明显。
下载图片与主流程控制
def download_image(self, url, filename):
try:
response = requests.get(url, headers=self.headers, stream=True, timeout=10)
if response.status_code == 200:
filepath = os.path.join(self.save_path, filename)
with open(filepath, 'wb') as f:
for chunk in response.iter_content(1024):
f.write(chunk)
print(f"✅ 已保存: {filename}")
return True
else:
print(f"❌ 下载失败: {url}")
return False
except Exception as e:
print(f"下载出错: {url}, 原因: {str(e)}")
return False
使用 stream=True 是为了支持大文件分块下载,避免内存溢出。
最后的 run() 方法串联整个流程:
def run(self):
if not self.create_save_dir():
print("⛔ 目录创建失败,终止运行")
return False
search_html = self.get_search_page()
if not search_html:
return False
question_urls = self.parse_question_links(search_html)
if not question_urls:
print("⚠️ 未找到任何问题链接,请检查关键词是否有效")
return False
for q_url in question_urls:
page_html = self.get_question_page(q_url)
if not page_html:
continue
imgs = self.extract_images_from_page(page_html)
self.image_urls.extend(imgs)
self.image_urls = list(set(self.image_urls))
print(f"🔍 共提取到 {len(self.image_urls)} 张唯一图片")
failed_count = 0
for idx, img_url in enumerate(self.image_urls, start=1):
ext = os.path.splitext(img_url)[1]
filename = f"{idx:04d}{ext}"
success = self.download_image(img_url, filename)
if not success:
failed_count += 1
print(f"🎉 爬虫完成!共下载 {len(self.image_urls) - failed_count} 张图片")
print(f"📁 图片已保存至: {self.save_path}")
return True
加上交互入口:
if __name__ == '__main__':
keyword = input('请输入要搜索的关键词(例如:风景、美食、摄影): ').strip()
while not keyword:
keyword = input('关键词不能为空,请重新输入: ').strip()
save_path = input('请输入图片保存路径(直接回车使用默认路径): ').strip()
if not save_path:
if sys.platform.startswith('win'):
save_path = os.path.expanduser(r'~\Documents\ZhihuImages\\')
else:
save_path = os.path.expanduser(f'~/ZhihuImages/{keyword}/')
spider = ZhihuImageSpider(keyword=keyword, save_path=save_path)
spider.run()
注:原文使用
raw_input,那是Python 2 的语法。已改为兼容 Python 3 的input。
实际运行效果
python zhihu_image_spider.py
输入关键词:
请输入要搜索的关键词:春日樱花
输出节选:
正在获取搜索页: https://www.zhihu.com/search?q=%E6%98%A5%E6%97%A5%E6%A8%B1%E8%8A%B1
共找到 12 个问题链接
正在抓取问题页: https://www.zhihu.com/question/123456789
✅ 已保存: 0001.jpg
✅ 已保存: 0002.jpg
...
🔍 共提取到 287 张唯一图片
🎉 爬虫完成!共下载 279 张图片
📁 图片已保存至: /home/user/ZhihuImages/春日樱花/
平均每个问题页耗时1~2秒,一次能捞几百张图,效率相当可观。
稳定性优化建议
当然,实际运行中可能会遇到一些小波折。以下是几个实用改进点:
1. 加入随机延时,避免频率过高
import time
import random
# 在每次请求前加一句:
time.sleep(random.uniform(1, 2))
既能缓解服务器压力,又能显著降低被限流风险。
2. 使用 Session 复用TCP连接
def __init__(self, ...):
self.session = requests.Session()
self.session.headers.update(self.headers)
# 后续请求改用:
response = self.session.get(url, timeout=10)
减少握手开销,提升整体速度。
3. 忽略SSL警告(仅开发环境)
import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()
某些代理或网络环境下可能出现InsecureRequestWarning,关闭即可。
4. 添加重试机制
对于关键请求(如搜索页),可封装一个带重试的函数:
def fetch_with_retry(self, url, max_retries=3):
for i in range(max_retries):
try:
return self.session.get(url, timeout=10)
except:
if i == max_retries - 1:
raise
time.sleep(1.5 ** i) # 指数退避
写在最后:技术的边界感
这个爬虫确实简单高效,但它也提醒我们一件事:数据的便利性永远伴随着责任。
知乎的内容生态建立在创作者辛勤输出的基础上。我们编写这样的工具,初衷应是个人学习、资料整理、研究分析,而不是批量盗图、商用牟利、侵犯版权。
所以请记住:
✅ 可以用来收集灵感素材、建立私人图库、辅助AI训练(非公开发布);
❌ 不可用于大规模镜像站建设、图片售卖、未经授权的内容搬运。
合理使用,方能长久。
如果你喜欢这个项目,欢迎前往 GitHub 查看完整代码:
GitHub 示例项目地址: github.com/dev-z/zhihu-image-spider
欢迎 Star & Fork ❤️
至于开头提到的那个“数字人生成系统”?下次再聊。
更多推荐
所有评论(0)