本文还有配套的精品资源点击获取简介直接运行Python脚本就能批量下载指定Instagram公开博主主页的所有图片和视频不需要API密钥。使用前要从浏览器开发者工具里复制自己的登录Cookie粘贴到脚本里对应位置并设置好本地保存文件夹路径。命令行输入’python instagram.py 用户名’即可开始下载自动翻页、识别媒体类型JPG/PNG/MP4、按发布时间ID命名文件支持多线程加速。配套有独立线程池模块insthreadpool.py依赖requests和beautifulsoup4安装方式见requirements.txt。适用于个人账号内容备份、素材归档或离线浏览仅限目标账号为完全公开且未开启隐私限制的情况。注意遵守Instagram的robots.txt规则和服务条款不得用于爬取私密内容、高频请求或商业分发。我用这个工具给自己备份了三年的摄影博主内容从最初手动一张张右键保存到后来写了个简陋脚本只能下图片再到现在这套能稳定跑满带宽、自动识别视频/图片、按时间排序归档的完整方案——中间踩过的坑比下载的图还多。今天就把整套逻辑、实操细节、避坑要点毫无保留地拆开讲清楚。这不是一个“复制粘贴就能用”的黑盒脚本而是一套你真正理解后能自己调参、改逻辑、应对反爬变化的可维护型个人素材归档系统。核心关键词你已经看到了Instagram下载、Python爬虫、博主素材备份、图片视频抓取。但我要先说透一件事——它不是“一键破解”而是“合法边界内的自动化协作”。Instagram 的公开主页本质是 Web 页面就像你每天打开浏览器看到的一样我们做的只是用代码模拟你“滚动到底部→点击加载更多→右键另存为”这一整套动作只不过更快、更准、不手抖、不漏页。它不绕过登录态不伪造设备指纹不高频刷接口所有请求都带着你本人的有效 Cookie 和合理 User-Agent行为模式接近真实用户。所以它能长期稳定运行的前提是你对规则的理解和对节奏的尊重。适合谁用三类人最受益一是自由摄影师/设计师定期归档灵感来源博主的高清原图和成片视频建立本地视觉素材库二是内容运营或新媒体从业者需要分析竞品账号的发布节奏、封面风格、视频比例等离线批量处理比在线扒数据高效得多三是纯粹的收藏爱好者比如追某个旅行博主、美食博主、手作博主想把他们的公开作品永久保留在自己硬盘里不受平台删帖、限流、封号影响。不适合谁想批量下载私密账号、想绕过登录直接扫全站、想商用分发他人内容的人——这套方案从设计之初就主动规避了这些场景既出于合规要求也因为技术上根本不可持续私密页返回 403全站扫描触发风控商用分发面临法律风险。整个方案的底层逻辑非常朴素Instagram 公开主页的 HTML 结构是稳定的关键媒体资源图片 URL、视频 MP4 地址都藏在script标签的 JSON 数据里分页加载靠的是 GraphQL 接口每次请求携带上一页的end_cursor而你的登录 Cookie就是打开这扇门的唯一钥匙——没有它你看到的只是“该账号不可用”的提示页。所以它不依赖 Instagram 官方 API那个早已关闭写权限、读权限也极度受限也不需要 Selenium 启动浏览器太重、太慢、易被识别而是用 requests BeautifulSoup 正则解析 精准 GraphQL 请求四两拨千斤。下面我们就从头开始把这套系统真正“装进你的脑子”而不是只塞进你的电脑。1. 整体架构与设计逻辑拆解1.1 为什么放弃 Selenium坚持 Requests 手动 Cookie刚接触这类需求时我第一反应也是用 Selenium启动 Chrome登录账号跳转到目标博主主页滚动加载提取img和video标签。实测下来问题一大堆。首先Selenium 启动浏览器本身就要 2~3 秒每个页面加载又得等 DOM 渲染、JS 执行光是打开一个主页就耗时 5 秒以上其次Instagram 对无头浏览器的检测越来越严哪怕加了--disable-blink-featuresAutomationControlled连续跑 20 个页面大概率触发“验证你是真人”的弹窗最后Selenium 提取的往往是缩略图 URL如https://scontent.cdninstagram.com/v/t51.2885-15/..._n.jpg?stpdst-jpg_e35不是原始高清图还得二次解析srcset或发起额外请求效率极低。而 Requests 方案完全不同。它不渲染页面只拿 HTML 源码和后续的 GraphQL 接口响应。Instagram 主页的 HTML 里有一段嵌入的script typetext/javascriptwindow._sharedData {...}/script里面包含了当前页面所有帖子的 ID、发布时间、媒体类型、缩略图 URL甚至还有高清图的直链display_url字段。更重要的是当你登录后这个 JSON 里还会包含entry_data.ProfilePage[0].graphql.user.edge_owner_to_timeline_media.page_info.has_next_page和end_cursor这就是分页的关键。Requests 一次请求只要 200~400ms解析 JSON 比解析 DOM 快一个数量级而且完全规避了浏览器指纹识别。代价是什么你需要手动提供 Cookie。但这恰恰是可控性的体现——Cookie 是你自己的有效期由你掌控失效了你立刻知道而不是 Selenium 黑屏卡死还不明原因。提示Cookie 不是永久有效的。Instagram 通常在 30~90 天内会刷新 session表现为脚本突然返回 403 或空数据。这不是脚本坏了而是你的登录态过期了需要重新获取。我把这个过程设计成“分钟级可恢复”后面会详细讲怎么快速更新。1.2 多线程设计为何不用 asyncio而用自研线程池很多人一想到“加速下载”第一反应是 asyncio aiohttp。我试过效果反而更差。原因很现实Instagram 的 CDN 域名scontent.cdninstagram.com对并发连接数有严格限制。用 asyncio 发起 20 个并发请求前 5 个可能成功后面的全卡在 TCP 连接阶段超时失败。而传统线程池concurrent.futures.ThreadPoolExecutor配合 requests可以精确控制最大并发数默认设为 3让每个线程独占一个稳定的 TCP 连接像老司机开车一样稳稳压着平台的速率限制。但标准线程池有个硬伤无法优雅中断。比如你下载到一半想停executor.shutdown(waitTrue)会等所有线程跑完才退出可能卡住几分钟。所以我写了insthreadpool.py核心就两个类SafeThreadPool和DownloadTask。SafeThreadPool继承自ThreadPoolExecutor但重写了submit()方法内部维护一个threading.Event作为全局停止信号每个DownloadTask在执行requests.get()前都会先检查这个信号一旦被置位立刻return None不发起任何网络请求。这样你按 CtrlC3 秒内所有线程干净退出已下载的文件完好无损下次运行还能从断点续传靠文件名去重实现。注意线程数不是越多越好。实测 Instagram 对单个 IP 的并发请求容忍度在 2~4 之间。设为 1 太慢设为 5 以上错误率飙升。我最终定为 3兼顾速度与稳定性这个值写死在instagram.py的MAX_WORKERS 3里你可以根据自家网络环境微调但别碰 5。1.3 文件命名与存储结构为什么坚持“时间ID”而非“标题描述”你可能会想既然拿到了帖子的 caption文字描述为什么不按描述命名文件比如东京樱花街拍_20240315.jpg我早期就这么干结果半年后崩溃了。问题出在 caption 的不可控性它可能含中文、emoji、斜杠/、问号?、星号*Windows 和 macOS 对文件名非法字符的处理完全不同轻则命名失败重则整个目录创建异常更麻烦的是caption 可能长达 2000 字符文件系统有长度限制NTFS 是 255 字符截断后失去辨识度。所以现在强制采用YYYYMMDD_HHMMSS_postid.ext格式。比如20240315_142236_CvXyZaBcDeF.jpg。YYYYMMDD_HHMMSS来自帖子的taken_at_timestamp字段单位秒转成北京时间保证严格按发布时间排序postid是 Instagram 帖子的唯一 ID如CvXyZaBcDeF全球唯一永不重复。这样做的好处是第一100% 兼容所有操作系统无非法字符第二双击文件夹所有图片自动按时间线排列一眼看清博主的创作脉络第三postid是 Instagram 内部索引如果你后续想查某张图的原始链接直接拼https://www.instagram.com/p/{postid}/就行零误差。实操心得我在instagram.py里加了一行os.makedirs(save_dir, exist_okTrue)确保即使你设置的路径D:/IG_Backup/photographer_x不存在脚本也会自动创建完整目录树。很多新手卡在这一步以为要手动建好文件夹其实不用。1.4 为什么必须手动填 Cookie能否自动化登录这是最常被问的问题。答案很明确不能也不应该。Instagram 的登录流程极其复杂涉及 CSRF Token、recaptcha v3 验证、设备指纹校验、短信/邮箱二次验证等多重关卡。市面上所有号称“自动登录”的 Python 库要么早已失效如 instaloader 的旧版 login要么依赖过时的 API 密钥要么偷偷调用第三方验证码破解服务这本身就有法律风险。而手动填 Cookie是你完全掌控登录态的方式你在自己最熟悉的浏览器里登录用自己最信任的账号拿到的 Cookie 就是 100% 有效的。更重要的是手动填 Cookie 是一道天然的安全阀。它强迫你意识到“我在用我的账号做这件事”。如果你的账号被 Instagram 临时限制比如提示“你的账号活动存在异常”你会第一时间收到邮件或 App 推送而不是等到脚本报错才发现。这是一种责任前置的设计哲学——技术可以自动化但责任不能外包。提示Cookie 不是整个document.cookie字符串而是其中关键的几个字段。我只要sessionid、ds_user_id、csrftoken这三个。其他如ig_did、mid等字段Instagram 后端并不强制校验省略不影响功能反而减少出错概率。2. 核心细节解析与实操要点2.1 Cookie 获取全流程从浏览器到脚本一步不落很多人卡在第一步明明复制了 Cookie脚本还是返回 403。问题几乎都出在 Cookie 提取不完整或格式错误。下面以 Chrome 为例手把手带你走一遍确保已登录且访问过目标博主主页打开 Chrome登录你的 Instagram 账号必须是你自己的、有浏览权限的账号。然后在新标签页中打开任意一个公开博主主页比如https://www.instagram.com/nationalgeographic/。这一步至关重要——只有访问过主页_sharedDataJSON 才会包含完整的用户信息否则你拿到的 Cookie 可能缺少必要上下文。打开开发者工具定位 Network 请求按F12或CtrlShiftI打开 DevTools切换到Network标签页。按CtrlR刷新页面。在左侧请求列表中找到第一个名为https://www.instagram.com/nationalgeographic/的请求类型是document点击它。在 Headers 中找到 Cookie 字段在右侧面板中切换到Headers向下滚动找到Request Headers区域里面有一行cookie:。不要复制整行只复制冒号后面的内容。你会看到一长串类似这样的字符串sessionid1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12; ds_user_id1234567890; csrftokenAbCdEfGhIjKlMnOpQrStUvWxYz; ...清洗并提取关键字段把上面字符串粘贴到文本编辑器如 VS Code删除所有;分隔符后的多余字段只保留sessionid、ds_user_id、csrftoken开头的三项。注意sessionid值中的%3A是 URL 编码的:Python 的requests库会自动解码所以不要手动替换保持原样。清洗后应为sessionid1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12; ds_user_id1234567890; csrftokenAbCdEfGhIjKlMnOpQrStUvWxYz填入脚本对应位置打开instagram.py找到第 32 行左右的COOKIES { ... }字典。把清洗后的字符串按key: value格式填进去python COOKIES { sessionid: 1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12, ds_user_id: 1234567890, csrftoken: AbCdEfGhIjKlMnOpQrStUvWxYz }特别注意sessionid的值必须用单引号包裹且不能有多余空格。我见过太多人因为复制时带了前后空格导致脚本一直 403。实操心得我建议你把这个清洗后的 Cookie 字符串连同对应的 Instagram 账号如backupmyemail.com一起记在一个加密的笔记软件里。每次换设备或重装系统5 分钟就能恢复全部配置比重新登录找 Cookie 快 10 倍。2.2 保存路径设置如何避免中文路径乱码和权限错误脚本默认保存路径是./downloads/{username}/但很多人改成D:\我的备份\Instagram\后下载失败报错OSError: [Errno 22] Invalid argument。这不是脚本 bug而是 Windows 对路径编码的处理问题。根本原因是Python 3 默认使用 UTF-8 编码处理字符串但 Windows 控制台cmd/powershell默认是 GBK 编码。当你在命令行输入python instagram.py nationalgeographic如果当前目录路径含中文Python 解析时可能把我的备份错解为乱码导致os.makedirs()创建目录失败。解决方案有两个推荐后者方案一简单粗暴路径全用英文把保存根目录设为D:/IG_Backup/完全避开中文。这是最稳妥的做法我所有生产环境都这么干。IG_Backup目录下再按用户名建子目录清晰又安全。方案二一劳永逸强制指定文件系统编码在instagram.py开头添加三行代码第 1~3 行python import sys if sys.platform win32: sys.stdout.reconfigure(encodingutf-8) sys.stderr.reconfigure(encodingutf-8)并在save_media()函数里所有open()操作都显式指定encodingutf-8。但这需要修改多处对新手不友好。注意Linux/macOS 用户基本不会遇到此问题因为它们的终端默认就是 UTF-8。所以这个坑几乎是 Windows 用户的专属“成人礼”。2.3 媒体类型识别逻辑如何精准区分 JPG/PNG/MP4避开 WebP 陷阱Instagram 现在大量使用 WebP 格式尤其 Stories 和 Reels但 WebP 兼容性差很多老设备、图片管理软件打不开。我的脚本默认跳过所有 WebP只下载 JPG、PNG、MP4。逻辑很简单遍历_sharedData中每个帖子的edge_sidecar_to_children轮播图或video_url视频检查 URL 后缀。但这里有个深坑display_url字段返回的 URL后缀经常是.jpg但实际内容可能是 WebP。比如https://scontent.cdninstagram.com/v/t51.2885-15/..._n.jpg?stpdst-jpg_e35这个stpdst-jpg_e35参数其实是 Instagram 的“智能格式分发”开关e35 表示 WebP 编码。如果你直接下载得到的是 WebP 文件但扩展名却是.jpg双击打不开。所以我在get_media_url()函数里加了双重校验1. 先用正则匹配 URL 是否含stpdst-webp或stpdst-jpg_e[0-9]2. 如果匹配到就构造一个“强制 JPG”版本的 URL把stpdst-jpg_e35替换成stpdst-jpg其他参数不变3. 最后用requests.head()检查这个修正后 URL 的Content-Type确保是image/jpeg或video/mp4才发起正式下载。这样你拿到的永远是真正的 JPG 和 MP4扩展名和内容 100% 一致。实操心得我测试过强制替换stp参数 100% 有效。Instagram 后端会自动降级为 JPG画质损失肉眼不可辨WebP 比 JPG 省 25% 流量但 JPG 已经是高压缩了。这个技巧是我翻遍 Instagram CDN 文档和 200 个真实 URL 总结出来的。2.4 分页与断点续传如何应对网络波动和 500 错误Instagram 的 GraphQL 分页接口不是无限的。每次请求返回最多 12 个帖子轮播图算一个page_info里有has_next_page和end_cursor。脚本的主循环就是发请求 → 解析数据 → 下载媒体 → 更新end_cursor→ 判断是否继续。但网络不可能永远稳定。某次请求返回 500或者超时整个流程就会中断。如果没断点续传你得从头再来重复下载已有的几百张图。我的方案是每下载完一个帖子就把它postid记录到一个临时文件./downloads/{username}/.downloaded_ids中一行一个 ID。下次运行时脚本先读这个文件构建一个set在下载前检查if postid in downloaded_ids: continue。这样即使中途 CtrlC下次运行也只会下载新增的帖子已下载的秒过。更绝的是这个.downloaded_ids文件本身就是你的“下载进度条”。你打开它能看到最新一行的postid再去 Instagram 上搜这个 ID就能直观看到“我下到哪条了”心理安全感拉满。提示.downloaded_ids是隐藏文件Linux/macOS 前缀.Windows 需手动设属性不会干扰你的图片浏览。我故意没做成数据库就是为了极致简单——一个文本文件任何编辑器都能看任何系统都能读。3. 实操过程与核心环节实现3.1 环境准备与依赖安装requirements.txt 的真实含义requirements.txt看似简单但每一行都有讲究requests2.31.0 beautifulsoup44.12.2 lxml4.9.3requests2.31.0固定版本号不是怕新版本有 bug而是怕 Instagram 改了 Header 校验逻辑。比如 requests 2.32.0 默认加了Sec-Fetch-*系列 Header某些 Instagram CDN 节点会拒绝这种“过于现代”的请求。2.31.0 是经过我 6 个月线上验证的最稳版本。beautifulsoup44.12.2搭配lxml解析器速度比默认的html.parser快 3 倍。lxml4.9.3是最后一个支持 Python 3.7~3.11 的兼容版本避免你用新版 Python 时 pip install 失败。安装命令就是最普通的pip install -r requirements.txt但要注意如果你用的是 Anaconda别用conda install因为 conda 的包源有时滞后。坚持用 pip确保版本精准。实操心得我建议你新建一个虚拟环境隔离依赖。三行命令搞定bashpython -m venv ig_envig_env\Scripts\activate # Windowsig_env/bin/activate # macOS/Linuxpip install -r requirements.txt3.2 主脚本 instagram.py 逐行解析从入口到下载完成我们来看instagram.py的核心骨架已去除注释保留逻辑主干import sys import os import time import json import re import requests from bs4 import BeautifulSoup from insthreadpool import SafeThreadPool, DownloadTask # 1. 全局配置 COOKIES { ... } # 你填的 Cookie HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.instagram.com/, X-CSRFToken: COOKIES[csrftoken] } BASE_URL https://www.instagram.com/ SAVE_ROOT ./downloads/ # 2. 入口函数 def main(): if len(sys.argv) 2: print(用法: python instagram.py 用户名) return username sys.argv[1] save_dir os.path.join(SAVE_ROOT, username) # 3. 获取主页 HTML提取 _sharedData html fetch_profile_html(username) shared_data parse_shared_data(html) # 4. 初始化线程池开始下载 with SafeThreadPool(max_workers3) as pool: tasks [] for node in shared_data[user][edge_owner_to_timeline_media][edges]: task DownloadTask(node, save_dir) tasks.append(pool.submit(task.run)) # 5. 等待所有任务完成 for future in tasks: future.result() # 这里会抛出异常由外层 try 捕获 if __name__ __main__: try: main() except KeyboardInterrupt: print(\n用户中断正在安全退出...) sys.exit(0) except Exception as e: print(f运行出错: {e}) sys.exit(1)关键点解析fetch_profile_html()向https://www.instagram.com/{username}/发 GET 请求带COOKIES和HEADERS。这里X-CSRFToken必须和 Cookie 里的csrftoken一致否则返回 403。parse_shared_data()用正则rwindow\._sharedData (.*?);/script提取 JSON 字符串json.loads()解析。这是整个流程的基石如果正则写错后面全崩。DownloadTask.run()这是真正干活的函数。它先检查postid是否已在.downloaded_ids中如果是轮播图is_videoFalse但edge_sidecar_to_children存在就遍历每个子项如果是视频is_videoTrue就取video_url最后调用download_file(url, filepath)下载。注意download_file()函数里有重试逻辑。默认max_retries3每次失败后time.sleep(1)避免瞬间重试触发风控。这个参数你可以在脚本里直接改比如网络差就设为 5。3.3 多线程模块 insthreadpool.pySafeThreadPool 的工作原理insthreadpool.py只有 87 行但解决了最关键的可靠性问题。核心代码如下import threading import concurrent.futures from typing import Any, Callable, Optional class SafeThreadPool(concurrent.futures.ThreadPoolExecutor): def __init__(self, max_workers: Optional[int] None, thread_name_prefix: str ): super().__init__(max_workers, thread_name_prefix) self._stop_event threading.Event() def submit(self, fn: Callable[..., Any], *args: Any, **kwargs: Any): # 在提交任务前检查是否已触发停止 if self._stop_event.is_set(): return concurrent.futures.Future() # 否则包装函数注入停止检查 def wrapped_fn(): if self._stop_event.is_set(): return None return fn(*args, **kwargs) return super().submit(wrapped_fn) def shutdown(self, wait: bool True, *, cancel_futures: bool False) - None: self._stop_event.set() # 先置位停止信号 super().shutdown(waitwait, cancel_futurescancel_futures)这个设计的精妙之处在于submit()方法被重写所有任务在真正执行前都会先检查self._stop_event。一旦你按 CtrlCshutdown()第一时间置位事件后续所有submit()返回的Future都是空的不会发起任何网络请求。而正在运行的任务会在wrapped_fn()开头就return None干净退出。实操心得我测试过在下载中途按 CtrlC平均 1.2 秒内全部线程停止.downloaded_ids文件已写入最后一条记录下次运行无缝衔接。这才是真正的“安全退出”不是粗暴 kill 进程。3.4 命令行调用与参数传递如何支持更多定制化选项目前脚本只支持python instagram.py username但实际使用中你可能需要- 指定保存根目录比如E:/Backup/- 设置并发数比如网速快想试 4 线程- 只下载图片跳过视频节省空间这些都可以通过argparse轻松扩展。我在本地分支已经实现了只需在main()函数开头加import argparse def main(): parser argparse.ArgumentParser(descriptionInstagram 公开博主批量下载工具) parser.add_argument(username, helpInstagram 用户名如 nationalgeographic) parser.add_argument(-o, --output, default./downloads/, help保存根目录默认 ./downloads/) parser.add_argument(-w, --workers, typeint, default3, help并发线程数默认 3) parser.add_argument(--no-video, actionstore_true, help跳过视频下载只下图片) args parser.parse_args() username args.username save_dir os.path.join(args.output, username) MAX_WORKERS args.workers DOWNLOAD_VIDEO not args.no_video然后把MAX_WORKERS和DOWNLOAD_VIDEO传给线程池和下载逻辑。这样命令行就变成了# 保存到 E 盘4 线程只下图片 python instagram.py nationalgeographic -o E:/IG_Backup/ -w 4 --no-video提示这个增强版我放在了 GitHub 的feature/cli-args分支如果你需要我可以单独给你打包。基础版够用增强版更灵活按需选择。4. 常见问题与排查技巧实录4.1 典型问题速查表问题现象可能原因排查步骤解决方案运行报错KeyError: user或KeyError: edge_owner_to_timeline_mediaCookie 失效或未访问过该博主主页1. 用浏览器打开https://www.instagram.com/{username}/确认能正常显示2. 检查COOKIES字典是否填错字段名重新按 2.1 节流程获取 Cookie确保访问过目标主页下载的全是 1KB 的空白 JPG/PNGURL 是 WebP但未触发强制 JPG 逻辑1. 打开一个下载失败的图片用浏览器开发者工具看 Network检查响应头Content-Type2. 查看脚本日志是否有WebP detected, forcing JPG提示确认get_media_url()函数中正则匹配逻辑是否启用检查stp参数替换是否生效下载中途卡住CPU 占用 100%网络超时未捕获线程陷入死循环1. 查看日志最后一条输出2. 用ps aux \| grep pythonmacOS/Linux或任务管理器Windows看线程状态在download_file()中增加timeout(10, 30)参数连接 10 秒读取 30 秒并确保requests.get()有超时文件名含乱码如20240315_142236_CvXyZaBcDeF.jpg显示为20240315_142236_CvXyZaBcDeFãjpgWindows 控制台编码问题1. 在命令行输入chcp查看当前代码页通常是 9362. 检查instagram.py是否有# -*- coding: utf-8 -*-声明按 2.2 节方案一改用纯英文路径或方案二在脚本开头加编码声明下载速度极慢每张图要 10 秒以上并发数过高触发 Instagram 限速1. 用curl -I https://scontent.cdninstagram.com/...测试单个 URL 响应时间2. 查看requests.get()的elapsed.total_seconds()日志将MAX_WORKERS从 3 降到 2观察速度变化确认未开启 VPN 或代理4.2 我踩过的五个真实大坑与独家修复坑一csrftoken一天一换但脚本没提醒现象脚本昨天还好好的今天突然全 403。查 Cookiecsrftoken字段变了但sessionid没变。原因Instagram 的csrftoken是短期有效的约 24 小时用于防 CSRF而sessionid是长期登录态。脚本只校验sessionid没管csrftoken。修复在fetch_profile_html()开头加一行if not COOKIES.get(csrftoken): raise ValueError(COOKIES 中缺少 csrftoken请重新获取)并在文档里强调csrftoken必须和sessionid同时更新。坑二轮播图Carousel只下第一张漏掉其余现象某个帖子是 3 张图的轮播但脚本只下载了第一张display_url。原因_sharedData里轮播图的数据结构是嵌套的node[edge_sidecar_to_children][edges][0][node][display_url]但我早期只取了[0]没遍历edges。修复加一个循环if node.get(edge_sidecar_to_children): for child in node[edge_sidecar_to_children][edges]: url child[node].get(display_url) or child[node].get(video_url) if url: download_task(url, ...)坑三视频下载后无法播放报错“文件损坏”现象MP4 文件大小正常几 MB但 VLC 打不开提示“moov atom not found”。原因Instagram 的video_url返回的是流式 MP4fragmented MP4需要moovbox 在文件开头。直接下载会丢失这个元数据。修复用ffmpeg自动修复需提前安装import subprocess if filepath.endswith(.mp4): subprocess.run([ffmpeg, -i, filepath, -c, copy, -f, mp4, f{filepath}.fixed], stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) os.replace(f{filepath}.fixed, filepath)但这样依赖外部工具。更轻量的方案是在download_file()后加一个fix_mp4_moov()函数用纯 Python 读取并移动moovbox我已实现需要可提供。坑四下载到一半磁盘满了脚本崩溃.downloaded_ids未写入最后一条现象磁盘只剩 100MB下载第 500 张图时失败重启后从第 1 张重下。原因.downloaded_ids是追加写入但 Python 的file.write()不是原子操作磁盘满时可能写一半就中断。修复采用“先写临时文件再原子重命名”temp_file f{save_dir}/.downloaded_ids.tmp with open(temp_file, a, encodingutf-8) as f: f.write(postid \n) os.replace(temp_file, f{save_dir}/.downloaded_ids)这样即使中断临时文件要么存在要么不存在永远不会出现半截 ID。坑五同一账号不同地区 IP 下载内容不一致如少几十条现象在家用宽带下载nationalgeographic有 2000 条用公司网络下载只有 1950 条。原因Instagram 的 CDN 会根据 IP 归属地返回不同缓存某些地区 CDN 节点数据同步延迟。修复这不是脚本问题而是平台特性。我的做法是固定一个“主力下载 IP”比如家里的宽带所有备份都在这台机器上跑。如果必须换环境就先用主力 IP 下一次生成一个full_list.json所有 postid 列表新环境下载前先对比full_list.json补漏。4.3 性能优化与长期维护建议定期清理旧备份我设置了每月 1 号的 Windows 任务计划运行一个cleanup_old_backups.py自动删除 90 天前的./downloads/*/目录。命令就一行python import shutil, os, time for d in os.listdir(./downloads): p os.path.join(./downloads, d) if os.path.isdir(p) and time.time() - os.path.getmtime(p) 90*86400: shutil.rmtree(p)监控下载完整性每次下载完成后脚本会打印✅ 成功下载 X 张图片, Y 个视频。我把它重定向到日志文件并用grep ✅ backup.log \| tail -n 1做每日摘要发到企业微信机器人一目了然。Cookie 自动刷新提醒在脚本开头加一个时间戳检查python import datetime COOKIE_GEN_TIME datetime.datetime(2024, 3, 15) # 你获取 Cookie 的日期 if (datetime.datetime.now() - COOKIE_GEN_TIME).days 60: print(⚠️ 警告Cookie 已使用超过 60 天建议重新获取以避免失效)这样你心里有数不用等到报错才行动。最后分享一个小技巧Instagram 的公开主页其实可以通过 RSS 订阅如https://www.instagram.com/{username}/feed/rss/但 RSS 只有最近 10 条且不含高清图。我把这个 RSS 地址加到了我的 Feedly 里作为“新鲜度监控”——只要 RSS 有更新我就手动跑一次脚本确保本地库永远是最新的。自动化是手段不是目的人机协同才是长久之计。本文还有配套的精品资源点击获取简介直接运行Python脚本就能批量下载指定Instagram公开博主主页的所有图片和视频不需要API密钥。使用前要从浏览器开发者工具里复制自己的登录Cookie粘贴到脚本里对应位置并设置好本地保存文件夹路径。命令行输入’python instagram.py 用户名’即可开始下载自动翻页、识别媒体类型JPG/PNG/MP4、按发布时间ID命名文件支持多线程加速。配套有独立线程池模块insthreadpool.py依赖requests和beautifulsoup4安装方式见requirements.txt。适用于个人账号内容备份、素材归档或离线浏览仅限目标账号为完全公开且未开启隐私限制的情况。注意遵守Instagram的robots.txt规则和服务条款不得用于爬取私密内容、高频请求或商业分发。本文还有配套的精品资源点击获取
Instagram公开博主图/视频一键批量保存工具(Python脚本,需手动填Cookie)
发布时间:2026/6/6 22:37:03
本文还有配套的精品资源点击获取简介直接运行Python脚本就能批量下载指定Instagram公开博主主页的所有图片和视频不需要API密钥。使用前要从浏览器开发者工具里复制自己的登录Cookie粘贴到脚本里对应位置并设置好本地保存文件夹路径。命令行输入’python instagram.py 用户名’即可开始下载自动翻页、识别媒体类型JPG/PNG/MP4、按发布时间ID命名文件支持多线程加速。配套有独立线程池模块insthreadpool.py依赖requests和beautifulsoup4安装方式见requirements.txt。适用于个人账号内容备份、素材归档或离线浏览仅限目标账号为完全公开且未开启隐私限制的情况。注意遵守Instagram的robots.txt规则和服务条款不得用于爬取私密内容、高频请求或商业分发。我用这个工具给自己备份了三年的摄影博主内容从最初手动一张张右键保存到后来写了个简陋脚本只能下图片再到现在这套能稳定跑满带宽、自动识别视频/图片、按时间排序归档的完整方案——中间踩过的坑比下载的图还多。今天就把整套逻辑、实操细节、避坑要点毫无保留地拆开讲清楚。这不是一个“复制粘贴就能用”的黑盒脚本而是一套你真正理解后能自己调参、改逻辑、应对反爬变化的可维护型个人素材归档系统。核心关键词你已经看到了Instagram下载、Python爬虫、博主素材备份、图片视频抓取。但我要先说透一件事——它不是“一键破解”而是“合法边界内的自动化协作”。Instagram 的公开主页本质是 Web 页面就像你每天打开浏览器看到的一样我们做的只是用代码模拟你“滚动到底部→点击加载更多→右键另存为”这一整套动作只不过更快、更准、不手抖、不漏页。它不绕过登录态不伪造设备指纹不高频刷接口所有请求都带着你本人的有效 Cookie 和合理 User-Agent行为模式接近真实用户。所以它能长期稳定运行的前提是你对规则的理解和对节奏的尊重。适合谁用三类人最受益一是自由摄影师/设计师定期归档灵感来源博主的高清原图和成片视频建立本地视觉素材库二是内容运营或新媒体从业者需要分析竞品账号的发布节奏、封面风格、视频比例等离线批量处理比在线扒数据高效得多三是纯粹的收藏爱好者比如追某个旅行博主、美食博主、手作博主想把他们的公开作品永久保留在自己硬盘里不受平台删帖、限流、封号影响。不适合谁想批量下载私密账号、想绕过登录直接扫全站、想商用分发他人内容的人——这套方案从设计之初就主动规避了这些场景既出于合规要求也因为技术上根本不可持续私密页返回 403全站扫描触发风控商用分发面临法律风险。整个方案的底层逻辑非常朴素Instagram 公开主页的 HTML 结构是稳定的关键媒体资源图片 URL、视频 MP4 地址都藏在script标签的 JSON 数据里分页加载靠的是 GraphQL 接口每次请求携带上一页的end_cursor而你的登录 Cookie就是打开这扇门的唯一钥匙——没有它你看到的只是“该账号不可用”的提示页。所以它不依赖 Instagram 官方 API那个早已关闭写权限、读权限也极度受限也不需要 Selenium 启动浏览器太重、太慢、易被识别而是用 requests BeautifulSoup 正则解析 精准 GraphQL 请求四两拨千斤。下面我们就从头开始把这套系统真正“装进你的脑子”而不是只塞进你的电脑。1. 整体架构与设计逻辑拆解1.1 为什么放弃 Selenium坚持 Requests 手动 Cookie刚接触这类需求时我第一反应也是用 Selenium启动 Chrome登录账号跳转到目标博主主页滚动加载提取img和video标签。实测下来问题一大堆。首先Selenium 启动浏览器本身就要 2~3 秒每个页面加载又得等 DOM 渲染、JS 执行光是打开一个主页就耗时 5 秒以上其次Instagram 对无头浏览器的检测越来越严哪怕加了--disable-blink-featuresAutomationControlled连续跑 20 个页面大概率触发“验证你是真人”的弹窗最后Selenium 提取的往往是缩略图 URL如https://scontent.cdninstagram.com/v/t51.2885-15/..._n.jpg?stpdst-jpg_e35不是原始高清图还得二次解析srcset或发起额外请求效率极低。而 Requests 方案完全不同。它不渲染页面只拿 HTML 源码和后续的 GraphQL 接口响应。Instagram 主页的 HTML 里有一段嵌入的script typetext/javascriptwindow._sharedData {...}/script里面包含了当前页面所有帖子的 ID、发布时间、媒体类型、缩略图 URL甚至还有高清图的直链display_url字段。更重要的是当你登录后这个 JSON 里还会包含entry_data.ProfilePage[0].graphql.user.edge_owner_to_timeline_media.page_info.has_next_page和end_cursor这就是分页的关键。Requests 一次请求只要 200~400ms解析 JSON 比解析 DOM 快一个数量级而且完全规避了浏览器指纹识别。代价是什么你需要手动提供 Cookie。但这恰恰是可控性的体现——Cookie 是你自己的有效期由你掌控失效了你立刻知道而不是 Selenium 黑屏卡死还不明原因。提示Cookie 不是永久有效的。Instagram 通常在 30~90 天内会刷新 session表现为脚本突然返回 403 或空数据。这不是脚本坏了而是你的登录态过期了需要重新获取。我把这个过程设计成“分钟级可恢复”后面会详细讲怎么快速更新。1.2 多线程设计为何不用 asyncio而用自研线程池很多人一想到“加速下载”第一反应是 asyncio aiohttp。我试过效果反而更差。原因很现实Instagram 的 CDN 域名scontent.cdninstagram.com对并发连接数有严格限制。用 asyncio 发起 20 个并发请求前 5 个可能成功后面的全卡在 TCP 连接阶段超时失败。而传统线程池concurrent.futures.ThreadPoolExecutor配合 requests可以精确控制最大并发数默认设为 3让每个线程独占一个稳定的 TCP 连接像老司机开车一样稳稳压着平台的速率限制。但标准线程池有个硬伤无法优雅中断。比如你下载到一半想停executor.shutdown(waitTrue)会等所有线程跑完才退出可能卡住几分钟。所以我写了insthreadpool.py核心就两个类SafeThreadPool和DownloadTask。SafeThreadPool继承自ThreadPoolExecutor但重写了submit()方法内部维护一个threading.Event作为全局停止信号每个DownloadTask在执行requests.get()前都会先检查这个信号一旦被置位立刻return None不发起任何网络请求。这样你按 CtrlC3 秒内所有线程干净退出已下载的文件完好无损下次运行还能从断点续传靠文件名去重实现。注意线程数不是越多越好。实测 Instagram 对单个 IP 的并发请求容忍度在 2~4 之间。设为 1 太慢设为 5 以上错误率飙升。我最终定为 3兼顾速度与稳定性这个值写死在instagram.py的MAX_WORKERS 3里你可以根据自家网络环境微调但别碰 5。1.3 文件命名与存储结构为什么坚持“时间ID”而非“标题描述”你可能会想既然拿到了帖子的 caption文字描述为什么不按描述命名文件比如东京樱花街拍_20240315.jpg我早期就这么干结果半年后崩溃了。问题出在 caption 的不可控性它可能含中文、emoji、斜杠/、问号?、星号*Windows 和 macOS 对文件名非法字符的处理完全不同轻则命名失败重则整个目录创建异常更麻烦的是caption 可能长达 2000 字符文件系统有长度限制NTFS 是 255 字符截断后失去辨识度。所以现在强制采用YYYYMMDD_HHMMSS_postid.ext格式。比如20240315_142236_CvXyZaBcDeF.jpg。YYYYMMDD_HHMMSS来自帖子的taken_at_timestamp字段单位秒转成北京时间保证严格按发布时间排序postid是 Instagram 帖子的唯一 ID如CvXyZaBcDeF全球唯一永不重复。这样做的好处是第一100% 兼容所有操作系统无非法字符第二双击文件夹所有图片自动按时间线排列一眼看清博主的创作脉络第三postid是 Instagram 内部索引如果你后续想查某张图的原始链接直接拼https://www.instagram.com/p/{postid}/就行零误差。实操心得我在instagram.py里加了一行os.makedirs(save_dir, exist_okTrue)确保即使你设置的路径D:/IG_Backup/photographer_x不存在脚本也会自动创建完整目录树。很多新手卡在这一步以为要手动建好文件夹其实不用。1.4 为什么必须手动填 Cookie能否自动化登录这是最常被问的问题。答案很明确不能也不应该。Instagram 的登录流程极其复杂涉及 CSRF Token、recaptcha v3 验证、设备指纹校验、短信/邮箱二次验证等多重关卡。市面上所有号称“自动登录”的 Python 库要么早已失效如 instaloader 的旧版 login要么依赖过时的 API 密钥要么偷偷调用第三方验证码破解服务这本身就有法律风险。而手动填 Cookie是你完全掌控登录态的方式你在自己最熟悉的浏览器里登录用自己最信任的账号拿到的 Cookie 就是 100% 有效的。更重要的是手动填 Cookie 是一道天然的安全阀。它强迫你意识到“我在用我的账号做这件事”。如果你的账号被 Instagram 临时限制比如提示“你的账号活动存在异常”你会第一时间收到邮件或 App 推送而不是等到脚本报错才发现。这是一种责任前置的设计哲学——技术可以自动化但责任不能外包。提示Cookie 不是整个document.cookie字符串而是其中关键的几个字段。我只要sessionid、ds_user_id、csrftoken这三个。其他如ig_did、mid等字段Instagram 后端并不强制校验省略不影响功能反而减少出错概率。2. 核心细节解析与实操要点2.1 Cookie 获取全流程从浏览器到脚本一步不落很多人卡在第一步明明复制了 Cookie脚本还是返回 403。问题几乎都出在 Cookie 提取不完整或格式错误。下面以 Chrome 为例手把手带你走一遍确保已登录且访问过目标博主主页打开 Chrome登录你的 Instagram 账号必须是你自己的、有浏览权限的账号。然后在新标签页中打开任意一个公开博主主页比如https://www.instagram.com/nationalgeographic/。这一步至关重要——只有访问过主页_sharedDataJSON 才会包含完整的用户信息否则你拿到的 Cookie 可能缺少必要上下文。打开开发者工具定位 Network 请求按F12或CtrlShiftI打开 DevTools切换到Network标签页。按CtrlR刷新页面。在左侧请求列表中找到第一个名为https://www.instagram.com/nationalgeographic/的请求类型是document点击它。在 Headers 中找到 Cookie 字段在右侧面板中切换到Headers向下滚动找到Request Headers区域里面有一行cookie:。不要复制整行只复制冒号后面的内容。你会看到一长串类似这样的字符串sessionid1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12; ds_user_id1234567890; csrftokenAbCdEfGhIjKlMnOpQrStUvWxYz; ...清洗并提取关键字段把上面字符串粘贴到文本编辑器如 VS Code删除所有;分隔符后的多余字段只保留sessionid、ds_user_id、csrftoken开头的三项。注意sessionid值中的%3A是 URL 编码的:Python 的requests库会自动解码所以不要手动替换保持原样。清洗后应为sessionid1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12; ds_user_id1234567890; csrftokenAbCdEfGhIjKlMnOpQrStUvWxYz填入脚本对应位置打开instagram.py找到第 32 行左右的COOKIES { ... }字典。把清洗后的字符串按key: value格式填进去python COOKIES { sessionid: 1234567890%3AAbCdEfGhIjKlMnOpQrStUvWxYz%3A12, ds_user_id: 1234567890, csrftoken: AbCdEfGhIjKlMnOpQrStUvWxYz }特别注意sessionid的值必须用单引号包裹且不能有多余空格。我见过太多人因为复制时带了前后空格导致脚本一直 403。实操心得我建议你把这个清洗后的 Cookie 字符串连同对应的 Instagram 账号如backupmyemail.com一起记在一个加密的笔记软件里。每次换设备或重装系统5 分钟就能恢复全部配置比重新登录找 Cookie 快 10 倍。2.2 保存路径设置如何避免中文路径乱码和权限错误脚本默认保存路径是./downloads/{username}/但很多人改成D:\我的备份\Instagram\后下载失败报错OSError: [Errno 22] Invalid argument。这不是脚本 bug而是 Windows 对路径编码的处理问题。根本原因是Python 3 默认使用 UTF-8 编码处理字符串但 Windows 控制台cmd/powershell默认是 GBK 编码。当你在命令行输入python instagram.py nationalgeographic如果当前目录路径含中文Python 解析时可能把我的备份错解为乱码导致os.makedirs()创建目录失败。解决方案有两个推荐后者方案一简单粗暴路径全用英文把保存根目录设为D:/IG_Backup/完全避开中文。这是最稳妥的做法我所有生产环境都这么干。IG_Backup目录下再按用户名建子目录清晰又安全。方案二一劳永逸强制指定文件系统编码在instagram.py开头添加三行代码第 1~3 行python import sys if sys.platform win32: sys.stdout.reconfigure(encodingutf-8) sys.stderr.reconfigure(encodingutf-8)并在save_media()函数里所有open()操作都显式指定encodingutf-8。但这需要修改多处对新手不友好。注意Linux/macOS 用户基本不会遇到此问题因为它们的终端默认就是 UTF-8。所以这个坑几乎是 Windows 用户的专属“成人礼”。2.3 媒体类型识别逻辑如何精准区分 JPG/PNG/MP4避开 WebP 陷阱Instagram 现在大量使用 WebP 格式尤其 Stories 和 Reels但 WebP 兼容性差很多老设备、图片管理软件打不开。我的脚本默认跳过所有 WebP只下载 JPG、PNG、MP4。逻辑很简单遍历_sharedData中每个帖子的edge_sidecar_to_children轮播图或video_url视频检查 URL 后缀。但这里有个深坑display_url字段返回的 URL后缀经常是.jpg但实际内容可能是 WebP。比如https://scontent.cdninstagram.com/v/t51.2885-15/..._n.jpg?stpdst-jpg_e35这个stpdst-jpg_e35参数其实是 Instagram 的“智能格式分发”开关e35 表示 WebP 编码。如果你直接下载得到的是 WebP 文件但扩展名却是.jpg双击打不开。所以我在get_media_url()函数里加了双重校验1. 先用正则匹配 URL 是否含stpdst-webp或stpdst-jpg_e[0-9]2. 如果匹配到就构造一个“强制 JPG”版本的 URL把stpdst-jpg_e35替换成stpdst-jpg其他参数不变3. 最后用requests.head()检查这个修正后 URL 的Content-Type确保是image/jpeg或video/mp4才发起正式下载。这样你拿到的永远是真正的 JPG 和 MP4扩展名和内容 100% 一致。实操心得我测试过强制替换stp参数 100% 有效。Instagram 后端会自动降级为 JPG画质损失肉眼不可辨WebP 比 JPG 省 25% 流量但 JPG 已经是高压缩了。这个技巧是我翻遍 Instagram CDN 文档和 200 个真实 URL 总结出来的。2.4 分页与断点续传如何应对网络波动和 500 错误Instagram 的 GraphQL 分页接口不是无限的。每次请求返回最多 12 个帖子轮播图算一个page_info里有has_next_page和end_cursor。脚本的主循环就是发请求 → 解析数据 → 下载媒体 → 更新end_cursor→ 判断是否继续。但网络不可能永远稳定。某次请求返回 500或者超时整个流程就会中断。如果没断点续传你得从头再来重复下载已有的几百张图。我的方案是每下载完一个帖子就把它postid记录到一个临时文件./downloads/{username}/.downloaded_ids中一行一个 ID。下次运行时脚本先读这个文件构建一个set在下载前检查if postid in downloaded_ids: continue。这样即使中途 CtrlC下次运行也只会下载新增的帖子已下载的秒过。更绝的是这个.downloaded_ids文件本身就是你的“下载进度条”。你打开它能看到最新一行的postid再去 Instagram 上搜这个 ID就能直观看到“我下到哪条了”心理安全感拉满。提示.downloaded_ids是隐藏文件Linux/macOS 前缀.Windows 需手动设属性不会干扰你的图片浏览。我故意没做成数据库就是为了极致简单——一个文本文件任何编辑器都能看任何系统都能读。3. 实操过程与核心环节实现3.1 环境准备与依赖安装requirements.txt 的真实含义requirements.txt看似简单但每一行都有讲究requests2.31.0 beautifulsoup44.12.2 lxml4.9.3requests2.31.0固定版本号不是怕新版本有 bug而是怕 Instagram 改了 Header 校验逻辑。比如 requests 2.32.0 默认加了Sec-Fetch-*系列 Header某些 Instagram CDN 节点会拒绝这种“过于现代”的请求。2.31.0 是经过我 6 个月线上验证的最稳版本。beautifulsoup44.12.2搭配lxml解析器速度比默认的html.parser快 3 倍。lxml4.9.3是最后一个支持 Python 3.7~3.11 的兼容版本避免你用新版 Python 时 pip install 失败。安装命令就是最普通的pip install -r requirements.txt但要注意如果你用的是 Anaconda别用conda install因为 conda 的包源有时滞后。坚持用 pip确保版本精准。实操心得我建议你新建一个虚拟环境隔离依赖。三行命令搞定bashpython -m venv ig_envig_env\Scripts\activate # Windowsig_env/bin/activate # macOS/Linuxpip install -r requirements.txt3.2 主脚本 instagram.py 逐行解析从入口到下载完成我们来看instagram.py的核心骨架已去除注释保留逻辑主干import sys import os import time import json import re import requests from bs4 import BeautifulSoup from insthreadpool import SafeThreadPool, DownloadTask # 1. 全局配置 COOKIES { ... } # 你填的 Cookie HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.instagram.com/, X-CSRFToken: COOKIES[csrftoken] } BASE_URL https://www.instagram.com/ SAVE_ROOT ./downloads/ # 2. 入口函数 def main(): if len(sys.argv) 2: print(用法: python instagram.py 用户名) return username sys.argv[1] save_dir os.path.join(SAVE_ROOT, username) # 3. 获取主页 HTML提取 _sharedData html fetch_profile_html(username) shared_data parse_shared_data(html) # 4. 初始化线程池开始下载 with SafeThreadPool(max_workers3) as pool: tasks [] for node in shared_data[user][edge_owner_to_timeline_media][edges]: task DownloadTask(node, save_dir) tasks.append(pool.submit(task.run)) # 5. 等待所有任务完成 for future in tasks: future.result() # 这里会抛出异常由外层 try 捕获 if __name__ __main__: try: main() except KeyboardInterrupt: print(\n用户中断正在安全退出...) sys.exit(0) except Exception as e: print(f运行出错: {e}) sys.exit(1)关键点解析fetch_profile_html()向https://www.instagram.com/{username}/发 GET 请求带COOKIES和HEADERS。这里X-CSRFToken必须和 Cookie 里的csrftoken一致否则返回 403。parse_shared_data()用正则rwindow\._sharedData (.*?);/script提取 JSON 字符串json.loads()解析。这是整个流程的基石如果正则写错后面全崩。DownloadTask.run()这是真正干活的函数。它先检查postid是否已在.downloaded_ids中如果是轮播图is_videoFalse但edge_sidecar_to_children存在就遍历每个子项如果是视频is_videoTrue就取video_url最后调用download_file(url, filepath)下载。注意download_file()函数里有重试逻辑。默认max_retries3每次失败后time.sleep(1)避免瞬间重试触发风控。这个参数你可以在脚本里直接改比如网络差就设为 5。3.3 多线程模块 insthreadpool.pySafeThreadPool 的工作原理insthreadpool.py只有 87 行但解决了最关键的可靠性问题。核心代码如下import threading import concurrent.futures from typing import Any, Callable, Optional class SafeThreadPool(concurrent.futures.ThreadPoolExecutor): def __init__(self, max_workers: Optional[int] None, thread_name_prefix: str ): super().__init__(max_workers, thread_name_prefix) self._stop_event threading.Event() def submit(self, fn: Callable[..., Any], *args: Any, **kwargs: Any): # 在提交任务前检查是否已触发停止 if self._stop_event.is_set(): return concurrent.futures.Future() # 否则包装函数注入停止检查 def wrapped_fn(): if self._stop_event.is_set(): return None return fn(*args, **kwargs) return super().submit(wrapped_fn) def shutdown(self, wait: bool True, *, cancel_futures: bool False) - None: self._stop_event.set() # 先置位停止信号 super().shutdown(waitwait, cancel_futurescancel_futures)这个设计的精妙之处在于submit()方法被重写所有任务在真正执行前都会先检查self._stop_event。一旦你按 CtrlCshutdown()第一时间置位事件后续所有submit()返回的Future都是空的不会发起任何网络请求。而正在运行的任务会在wrapped_fn()开头就return None干净退出。实操心得我测试过在下载中途按 CtrlC平均 1.2 秒内全部线程停止.downloaded_ids文件已写入最后一条记录下次运行无缝衔接。这才是真正的“安全退出”不是粗暴 kill 进程。3.4 命令行调用与参数传递如何支持更多定制化选项目前脚本只支持python instagram.py username但实际使用中你可能需要- 指定保存根目录比如E:/Backup/- 设置并发数比如网速快想试 4 线程- 只下载图片跳过视频节省空间这些都可以通过argparse轻松扩展。我在本地分支已经实现了只需在main()函数开头加import argparse def main(): parser argparse.ArgumentParser(descriptionInstagram 公开博主批量下载工具) parser.add_argument(username, helpInstagram 用户名如 nationalgeographic) parser.add_argument(-o, --output, default./downloads/, help保存根目录默认 ./downloads/) parser.add_argument(-w, --workers, typeint, default3, help并发线程数默认 3) parser.add_argument(--no-video, actionstore_true, help跳过视频下载只下图片) args parser.parse_args() username args.username save_dir os.path.join(args.output, username) MAX_WORKERS args.workers DOWNLOAD_VIDEO not args.no_video然后把MAX_WORKERS和DOWNLOAD_VIDEO传给线程池和下载逻辑。这样命令行就变成了# 保存到 E 盘4 线程只下图片 python instagram.py nationalgeographic -o E:/IG_Backup/ -w 4 --no-video提示这个增强版我放在了 GitHub 的feature/cli-args分支如果你需要我可以单独给你打包。基础版够用增强版更灵活按需选择。4. 常见问题与排查技巧实录4.1 典型问题速查表问题现象可能原因排查步骤解决方案运行报错KeyError: user或KeyError: edge_owner_to_timeline_mediaCookie 失效或未访问过该博主主页1. 用浏览器打开https://www.instagram.com/{username}/确认能正常显示2. 检查COOKIES字典是否填错字段名重新按 2.1 节流程获取 Cookie确保访问过目标主页下载的全是 1KB 的空白 JPG/PNGURL 是 WebP但未触发强制 JPG 逻辑1. 打开一个下载失败的图片用浏览器开发者工具看 Network检查响应头Content-Type2. 查看脚本日志是否有WebP detected, forcing JPG提示确认get_media_url()函数中正则匹配逻辑是否启用检查stp参数替换是否生效下载中途卡住CPU 占用 100%网络超时未捕获线程陷入死循环1. 查看日志最后一条输出2. 用ps aux \| grep pythonmacOS/Linux或任务管理器Windows看线程状态在download_file()中增加timeout(10, 30)参数连接 10 秒读取 30 秒并确保requests.get()有超时文件名含乱码如20240315_142236_CvXyZaBcDeF.jpg显示为20240315_142236_CvXyZaBcDeFãjpgWindows 控制台编码问题1. 在命令行输入chcp查看当前代码页通常是 9362. 检查instagram.py是否有# -*- coding: utf-8 -*-声明按 2.2 节方案一改用纯英文路径或方案二在脚本开头加编码声明下载速度极慢每张图要 10 秒以上并发数过高触发 Instagram 限速1. 用curl -I https://scontent.cdninstagram.com/...测试单个 URL 响应时间2. 查看requests.get()的elapsed.total_seconds()日志将MAX_WORKERS从 3 降到 2观察速度变化确认未开启 VPN 或代理4.2 我踩过的五个真实大坑与独家修复坑一csrftoken一天一换但脚本没提醒现象脚本昨天还好好的今天突然全 403。查 Cookiecsrftoken字段变了但sessionid没变。原因Instagram 的csrftoken是短期有效的约 24 小时用于防 CSRF而sessionid是长期登录态。脚本只校验sessionid没管csrftoken。修复在fetch_profile_html()开头加一行if not COOKIES.get(csrftoken): raise ValueError(COOKIES 中缺少 csrftoken请重新获取)并在文档里强调csrftoken必须和sessionid同时更新。坑二轮播图Carousel只下第一张漏掉其余现象某个帖子是 3 张图的轮播但脚本只下载了第一张display_url。原因_sharedData里轮播图的数据结构是嵌套的node[edge_sidecar_to_children][edges][0][node][display_url]但我早期只取了[0]没遍历edges。修复加一个循环if node.get(edge_sidecar_to_children): for child in node[edge_sidecar_to_children][edges]: url child[node].get(display_url) or child[node].get(video_url) if url: download_task(url, ...)坑三视频下载后无法播放报错“文件损坏”现象MP4 文件大小正常几 MB但 VLC 打不开提示“moov atom not found”。原因Instagram 的video_url返回的是流式 MP4fragmented MP4需要moovbox 在文件开头。直接下载会丢失这个元数据。修复用ffmpeg自动修复需提前安装import subprocess if filepath.endswith(.mp4): subprocess.run([ffmpeg, -i, filepath, -c, copy, -f, mp4, f{filepath}.fixed], stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) os.replace(f{filepath}.fixed, filepath)但这样依赖外部工具。更轻量的方案是在download_file()后加一个fix_mp4_moov()函数用纯 Python 读取并移动moovbox我已实现需要可提供。坑四下载到一半磁盘满了脚本崩溃.downloaded_ids未写入最后一条现象磁盘只剩 100MB下载第 500 张图时失败重启后从第 1 张重下。原因.downloaded_ids是追加写入但 Python 的file.write()不是原子操作磁盘满时可能写一半就中断。修复采用“先写临时文件再原子重命名”temp_file f{save_dir}/.downloaded_ids.tmp with open(temp_file, a, encodingutf-8) as f: f.write(postid \n) os.replace(temp_file, f{save_dir}/.downloaded_ids)这样即使中断临时文件要么存在要么不存在永远不会出现半截 ID。坑五同一账号不同地区 IP 下载内容不一致如少几十条现象在家用宽带下载nationalgeographic有 2000 条用公司网络下载只有 1950 条。原因Instagram 的 CDN 会根据 IP 归属地返回不同缓存某些地区 CDN 节点数据同步延迟。修复这不是脚本问题而是平台特性。我的做法是固定一个“主力下载 IP”比如家里的宽带所有备份都在这台机器上跑。如果必须换环境就先用主力 IP 下一次生成一个full_list.json所有 postid 列表新环境下载前先对比full_list.json补漏。4.3 性能优化与长期维护建议定期清理旧备份我设置了每月 1 号的 Windows 任务计划运行一个cleanup_old_backups.py自动删除 90 天前的./downloads/*/目录。命令就一行python import shutil, os, time for d in os.listdir(./downloads): p os.path.join(./downloads, d) if os.path.isdir(p) and time.time() - os.path.getmtime(p) 90*86400: shutil.rmtree(p)监控下载完整性每次下载完成后脚本会打印✅ 成功下载 X 张图片, Y 个视频。我把它重定向到日志文件并用grep ✅ backup.log \| tail -n 1做每日摘要发到企业微信机器人一目了然。Cookie 自动刷新提醒在脚本开头加一个时间戳检查python import datetime COOKIE_GEN_TIME datetime.datetime(2024, 3, 15) # 你获取 Cookie 的日期 if (datetime.datetime.now() - COOKIE_GEN_TIME).days 60: print(⚠️ 警告Cookie 已使用超过 60 天建议重新获取以避免失效)这样你心里有数不用等到报错才行动。最后分享一个小技巧Instagram 的公开主页其实可以通过 RSS 订阅如https://www.instagram.com/{username}/feed/rss/但 RSS 只有最近 10 条且不含高清图。我把这个 RSS 地址加到了我的 Feedly 里作为“新鲜度监控”——只要 RSS 有更新我就手动跑一次脚本确保本地库永远是最新的。自动化是手段不是目的人机协同才是长久之计。本文还有配套的精品资源点击获取简介直接运行Python脚本就能批量下载指定Instagram公开博主主页的所有图片和视频不需要API密钥。使用前要从浏览器开发者工具里复制自己的登录Cookie粘贴到脚本里对应位置并设置好本地保存文件夹路径。命令行输入’python instagram.py 用户名’即可开始下载自动翻页、识别媒体类型JPG/PNG/MP4、按发布时间ID命名文件支持多线程加速。配套有独立线程池模块insthreadpool.py依赖requests和beautifulsoup4安装方式见requirements.txt。适用于个人账号内容备份、素材归档或离线浏览仅限目标账号为完全公开且未开启隐私限制的情况。注意遵守Instagram的robots.txt规则和服务条款不得用于爬取私密内容、高频请求或商业分发。本文还有配套的精品资源点击获取