Python实现知乎图片爬虫(无需登录)

那天朋友问我:“有没有能自动合成数字人说话视频的工具?”
我回了句:“可以有。”
然后就有了——但今天不讲那个。

我们来点“复古”的:用Python写一个完全免登录的知乎图片爬虫。

你是不是也经常刷知乎时,看到某个高赞回答里的配图特别惊艳?可能是旅行摄影、艺术插画,或是干货满满的示意图。想保存下来,却一张张手动点开太麻烦?更别说还要防着反爬、处理登录态、应对验证码……

其实,根本不用那么复杂。

只要策略得当,不需要账号、不模拟登录、不碰Cookie、甚至不用Selenium,也能稳定抓取知乎公开页面中的高清图片。本文要做的,就是一个轻量、干净、高效的采集脚本:输入关键词 → 自动搜索 → 遍历问题页 → 提取所有答案中的原图 → 批量下载到本地。

整个过程基于HTTP请求和HTML解析完成,代码不到200行,依赖极少,跑起来飞快。


核心思路:绕过登录,直取公开内容

知乎虽然对未登录用户做了部分限制,比如部分内容折叠、弹出登录框等,但它的搜索结果页和绝大多数问答页仍然是可公开访问的。尤其是通过搜索引擎跳转过来的内容,基本都能正常加载。

我们的突破口就在这里:

  1. 构造知乎搜索URL(https://www.zhihu.com/search?q=xxx),获取返回的HTML;
  2. 用BeautifulSoup从中提取所有 /question/{id} 类型的链接;
  3. 逐个请求这些页面,解析其中的答案区 <img> 标签;
  4. 过滤缩略图,保留高清源地址(如 _hd.jpg, _r.jpg);
  5. 下载并编号保存。

全程走纯静态页面路线,避开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)

注意两个细节:

  1. 使用正则 /question/\d+ 精准匹配问题ID;
  2. 去重用 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 ❤️

至于开头提到的那个“数字人生成系统”?下次再聊。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐