Instagram 是公开网络上数据最丰富的平台之一,同时也是最难以程序化读取的平台之一。个人主页和帖子在客户端通过 JavaScript 渲染,平台对自动化流量的挑战十分积极,向个人主页 URL 发出的普通 HTTP 请求通常只返回一个几乎为空的外壳。本指南将向您展示如何以真正有效的方式用 Python 抓取 Instagram 数据,同时严格限定在公开内容的范围内。

先明确说明:此处的一切都限定在公开账号的公开数据范围内。这意味着公开的个人资料字段、公开帖子的说明文字、公开的点赞和评论数,以及公开帖子的 URL。不涉及私人账号、需要登录才能查看的内容、私信、粉丝列表,以及任何个人的个人数据。Instagram 及其母公司 Meta 在其条款中限制自动化访问,因此在将此方法用于任何实际场景之前,请先阅读文末的合法性部分。对于任何生产或商业用途,官方 Instagram Graph API 才是正确的工具,而不是爬虫。

您将构建什么

一个小型 Python 脚本,接收 Instagram 的公开个人主页 URL,通过带有 JavaScript 令牌的 Crawling API 获取完整渲染后的页面,并解析出几个公开字段:

  • 公开用户名:个人主页上显示的账号 handle。
  • 公开帖子说明:公开帖子上的可见文字。
  • 公开点赞和评论数:帖子显示的汇总数字,不包括背后的具体用户。
  • 公开帖子 URL:每条公开帖子的永久链接。

请注意这里有意省略的内容:没有粉丝列表、没有评论者身份、没有私人账号内容、没有联系方式。这些都是个人的个人数据,在此有意不在范围之内。

为什么普通请求在 Instagram 上会失败

用基础 HTTP 客户端请求 Instagram 的公开个人主页 URL,您会得到一个技术上成功但实际上毫无用处的响应。正文是一个 JavaScript 外壳:真实内容只有在浏览器中页面脚本运行并从内部端点获取数据后才会出现。此外,Instagram 对自动化流量的识别速度极快。数据中心 IP 范围、缺少浏览器行为,以及重复的请求模式,在任何有趣的内容加载之前就会被挑战或限速。

因此,一个可用的 Instagram 爬虫在同一次请求中需要两件事:能够渲染页面的真实浏览器,以及让平台认为是普通访客的 IP 地址。您可以自行构建,使用无头浏览器和轮换住宅代理池,但维护这套技术栈才是大部分工作所在。Crawling API 将两者合并为一次调用。您发送带有 JavaScript 令牌的 URL,它在可信住宅 IP 后面渲染页面,并返回完整的 HTML 供您解析。更深层的背景请参阅我们关于如何爬取 JavaScript 网站的指南。

为什么需要 JS 令牌

Crawlbase 提供两种令牌类型。普通令牌获取静态 HTML;JavaScript (JS) 令牌则先在真实浏览器中渲染页面。Instagram 是客户端渲染的,因此此处需要 JS 令牌。普通令牌返回的是与普通请求相同的外壳,没有任何有用内容可供解析。

前提条件

先准备好以下几样东西,每样都不需要太长时间。

基础 Python 知识。您应能运行脚本并使用 pip 安装包。如果您是 HTML 解析的新手,我们关于如何在 Python 中使用 BeautifulSoup 的入门指南涵盖了提取方面的基础知识。

Python 3.8 或更高版本。使用 python --version 确认。如果尚未安装,请从 python.org 安装。

Crawlbase 账户和 JS 令牌。注册后,打开控制台,从账户文档页面复制您的 JavaScript (JS) 令牌。像对待密码一样保管:它用于验证您的请求身份,请勿将其提交到版本控制系统。

项目设置

创建隔离的虚拟环境,然后安装爬虫所需的两个库。

bash
python --version

python -m venv instagram_env
source instagram_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,使用 instagram_env\Scripts\activate 代替 source 行来激活环境。两个依赖项各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便通过选择器提取各字段。

第一步:获取渲染后的个人主页

首先获取完整的页面。导入 CrawlingAPI,用您的 JS 令牌初始化,然后请求公开的个人主页 URL。在解析之前检查状态码,让失败情况明显可见而不是悄悄忽略。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    page_url = "https://www.instagram.com/nasa/"
    html = crawl(page_url)
    print(html[:500] if html else "No HTML returned")

这两个等待选项对于客户端渲染的目标至关重要。ajax_wait 告诉 API 等待异步内容加载完成,page_wait 则在加载后持续等待固定毫秒数,以便延迟渲染的元素在捕获前出现。5 秒是合理的起点;如果字段返回空则可以适当增加。示例使用了公开组织账号(NASA)正是因为它是公开且非个人的。运行脚本,您应该能看到真实的个人主页标记,这确认了在编写任何选择器之前渲染能正常工作。

Crawlbase Crawling API

Instagram 需要在一次调用中获得可信 IP 后面的渲染页面。Crawling API 接受 JS 令牌,在真实浏览器中运行页面,在服务端轮换住宅 IP,并将完整的 HTML 交给您,让您无需自行运行无头浏览器群和代理池。先在免费套餐上指向公开个人主页试试看。

第二步:用 BeautifulSoup 解析公开字段

拿到渲染后的 HTML,将其加载到 BeautifulSoup 中并提取公开字段。Instagram 在页面的 <meta> 标签和嵌入的 JSON-LD 块中暴露了许多有用的元数据,这比追踪频繁重命名的深层嵌套 CSS 类更稳定。用户名和个人资料描述位于标准 meta 标签中;帖子永久链接位于遵循 /p/<shortcode>/ 模式的锚点 href 中。

python
import re
from bs4 import BeautifulSoup

def meta(soup, prop):
    el = soup.find("meta", attrs={"property": prop})
    return el["content"] if el and el.has_attr("content") else None

def scrape_profile(html):
    soup = BeautifulSoup(html, "html.parser")

    username = meta(soup, "og:title")
    summary = meta(soup, "og:description")

    post_urls = []
    for a in soup.select("a[href^='/p/']"):
        href = a["href"]
        url = f"https://www.instagram.com{href}"
        if url not in post_urls:
            post_urls.append(url)

    return {
        "username": username,
        "summary": summary,
        "post_urls": post_urls,
    }

公开个人主页上的 og:description 标签通常携带汇总的公开数据(粉丝、关注和帖子数)作为单一的摘要字符串。将这些视为公开汇总数据,而不是枚举其背后个人用户的入口。帖子 URL 从锚点中采集并去重,因为同一永久链接可能在渲染后的 DOM 中出现多次。

选择器会漂移

Instagram 无预告地更改其标记和类名,这正是本代码依赖 meta 标签和稳定的 /p/<shortcode>/ URL 形式而非脆弱的嵌套类的原因。当某个字段返回 None 时,请在浏览器开发者工具中重新检查线上页面并更新选择器。定期维护对任何生产级爬虫来说都是正常的,并不意味着出了什么问题。

第三步:从单条帖子提取公开字段

公开帖子页面携带同类型的 meta 和 JSON-LD 数据。从中您可以提取公开说明文字以及公开的点赞和评论数。Instagram 在帖子页面嵌入了 application/ld+json 脚本,通常包含带有这些汇总数字的 interactionStatistic 块,以及说明文字。解析 JSON-LD 比抓取渲染后的控件更持久。

python
import json
from bs4 import BeautifulSoup

def scrape_post(html):
    soup = BeautifulSoup(html, "html.parser")
    block = soup.find("script", attrs={"type": "application/ld+json"})
    if not block:
        return {}

    data = json.loads(block.string)
    likes = comments = None
    for stat in data.get("interactionStatistic", []):
        kind = stat.get("interactionType", "")
        count = stat.get("userInteractionCount")
        if "LikeAction" in kind:
            likes = count
        elif "CommentAction" in kind:
            comments = count

    return {
        "caption": data.get("caption") or data.get("articleBody"),
        "like_count": likes,
        "comment_count": comments,
    }

这只提取汇总的、非个人的字段:说明文字、公开点赞数和公开评论数。它不读取个别评论、评论者 handle,或是谁点赞了这条帖子。这种克制是刻意为之的,也是让工作具有可辩护性的原因。点赞数和评论数是数字;背后的人不是您可以采集的数据。

第四步:整合在一起

现在将获取和解析组合成一个可运行的脚本,读取公开个人主页,然后访问其前几条公开帖子。

python
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['status_code']}")
    return None

def main():
    profile_url = "https://www.instagram.com/nasa/"
    html = crawl(profile_url)
    if not html:
        return

    profile = scrape_profile(html)
    records = []
    for post_url in profile["post_urls"][:5]:
        post_html = crawl(post_url)
        if post_html:
            record = scrape_post(post_html)
            record["url"] = post_url
            records.append(record)
        time.sleep(3)

    output = {"username": profile["username"], "posts": records}
    print(json.dumps(output, indent=2, ensure_ascii=False))

if __name__ == "__main__":
    main()

请求之间的 time.sleep(3) 不是装饰性的。控制速率是保持运行健康的最重要因素,后面会再次提到。[:5] 切片是为了保持演示规模较小;只有在您的速率控制和请求量是负责任的情况下才适合增加这个数字。

输出示例

运行完整脚本,您将得到一条公开字段的整洁记录,可以写入 JSON、CSV 或数据库。

json
{
  "username": "NASA (@nasa)",
  "posts": [
    {
      "caption": "A new view of a distant galaxy cluster.",
      "like_count": 412338,
      "comment_count": 1894,
      "url": "https://www.instagram.com/p/Cxample123/"
    }
  ]
}

保持不被封锁

即使渲染由 Crawling API 处理,Instagram 仍会监控具有爬虫特征的流量。以下几个习惯可以让运行保持健康,它们适用于任何高难度且防御严密的目标。

  • 控制请求速率。在紧密循环中连续获取页面是触发限速的最快方式。添加真实的延迟(如上面的 time.sleep),并抵制过度并发的冲动。
  • 依赖轮换。住宅 IP 池将请求分散到许多真实用户地址,不会让任何单一地址触发限速。Crawling API 为您处理这一切;如果您自建技术栈,这是最需要做对的部分。我们关于如何使用轮换代理的指南有更深入的介绍。
  • 读取状态码。当运行开始返回挑战或错误时,说明当前速率或 IP 层级已不再足够。退让而不是硬撑。
  • 保持低请求量并分散目标。公开数据研究不需要爬取一个账号的全部历史记录。按需取样后停止。

更广泛的策略请参阅网络抓取中如何绕过 CAPTCHA,以及我们关于如何用 Python 抓取 JavaScript 页面的深度指南。如果您希望通过轮换池路由自己的流量而不使用托管 API,Smart AI Proxy 提供与其相同的住宅轮换功能,作为即插即用的代理端点。

抓取 Instagram 是否合法?

这是您在编写生产代码之前必须阅读的部分。Instagram 由 Meta 所有,Meta 的服务条款严格限制自动化访问和数据采集。无论您的工具多么谨慎,自动化抓取都可能违反这些条款,上述代码也不会改变这一点,只是让技术层面的工作能够运行。请阅读 Meta 和 Instagram 的服务条款以及 Instagram 的 robots.txt,并将两者都视为您采集内容的边界。

需要严格遵守的诚实规则。只采集公开账号的公开数据:任何人无需登录即可看到的公开个人资料字段、公开帖子说明、公开点赞和评论数,以及公开帖子 URL。绝不抓取私人账号、需要登录的内容、私信、粉丝或关注列表、个别评论者或点赞者的身份,以及任何个人的个人数据。绝不绕过身份验证、以编程方式解决登录挑战,或使用他人的凭证访问内容。这些都是明确的红线,本指南从设计上就停留在所有这些红线的公开一侧。

对于任何实际或商业用途,正确的工具是官方 Instagram Graph API。它专为授权访问您拥有或管理的账号而构建,提供有保障的数据结构,并让您保持在 Meta 条款范围之内。本文是一个范围严格限定在公开账号公开数据的技术演示,不是对大规模个人数据采集的背书,也不涵盖任何登录墙后的内容。如果您的项目需要超过少量公开字段的数据,Graph API 或正式的数据协议才是正确路径,而不是设计更精巧的爬虫。

回顾

核心要点

  • Instagram 是客户端渲染且有机器人防御的。普通请求返回空壳,因此必须先渲染页面再进行解析。
  • 渲染和可信 IP 属于同一次调用。带有 JS 令牌的 Crawling API 同时完成两者;ajax_waitpage_wait 控制等待内容的时长。
  • 解析稳定的信号。Meta 标签、/p/<shortcode>/ URL 形式和 JSON-LD 比脆弱的嵌套类更持久。
  • 只取公开汇总数据。提取用户名、说明文字、点赞和评论数,以及帖子 URL;绝不采集粉丝列表、评论者身份或私人内容。
  • 控制速率、依赖轮换,并优先使用 Graph API。保持低请求量,依赖住宅轮换,对任何实际或商业用途使用官方 Instagram Graph API。

常见问题

为什么普通请求从 Instagram 返回不到任何数据?

因为 Instagram 通过 JavaScript 在客户端渲染其个人主页和帖子内容。初始 HTML 是一个外壳,只有在浏览器中页面脚本运行后才会填充内容,因此原始 HTTP 请求返回的正文几乎为空。要获得真实的公开数据,必须先渲染页面,这正是 Crawling API 的 JS 令牌为您处理的事情。

Instagram 需要普通令牌还是 JS 令牌?

JS 令牌。普通令牌获取静态 HTML,而 Instagram 上的静态 HTML 与普通请求返回的空壳相同。JS 令牌在返回 HTML 之前先在真实浏览器中渲染页面,因此当 BeautifulSoup 解析时公开字段已经存在。

抓取 Instagram 哪些数据是安全的?

只有公开账号的公开数据:公开用户名、公开帖子说明、作为汇总数字的公开点赞和评论数,以及公开帖子 URL。私人账号、需要登录的内容、私信、粉丝和关注列表,以及个别评论者或点赞者的身份均为禁区。这些是个人数据,采集它们违反 Meta 的条款,在许多地方也违反隐私法律。

我应该使用官方 Instagram Graph API 还是抓取网站?

对于任何实际、持续或商业用途,请使用官方 Instagram Graph API。它是获批准的路径,提供有保障的数据结构,并让您保持在 Meta 条款范围之内。在没有 API 访问权限的情况下,使用此处方法抓取少量公开字段适合轻量级的公开数据研究,前提是您遵守条款、robots.txt 和速率限制。

如何避免在抓取 Instagram 时被封锁?

保持较低的每 IP 请求速率,在请求之间添加真实延迟,交替不同目标而不是爬取同一账号的全部历史记录,并通过轮换住宅 IP 路由流量以防止任何单一地址触发限速。Crawling API 为您管理轮换和可信 IP 池。密切关注状态码,一旦开始看到挑战就立即退让。

我可以抓取私人账号或粉丝列表吗?

不能,本指南也刻意不介绍这些。私人账号内容位于身份验证之后,粉丝列表和个人用户身份属于个人数据。抓取需要登录才能访问的内容、枚举粉丝,或绕过身份验证访问任何此类内容,超出了本文范围,且违反 Meta 的条款。对于您拥有或管理的账号的授权访问,官方 Instagram Graph API 才是正确路径。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量