You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Python无需网页爬取获取Substack全量历史帖子

核心原因

Substack默认提供的根RSS地址单次请求最多返回20条最新内容,是平台端做的分页限制,和feedparser解析逻辑无关。

方案1:分页拉取RSS源获取全量内容(无网页爬取、无需BeautifulSoup)

Substack的RSS源原生支持before时间戳分页参数,格式为<substack域名>/feed?before=<unix时间戳>,每次请求会返回该时间节点之前的20条内容。只需要循环发起请求,每次取当前返回结果里最后一篇帖子的发布时间戳,作为下一次请求的before参数,直到某次请求返回空条目,就说明已经拉完所有历史内容。
如果需要拉取订阅账号有权限查看的付费帖子,只需要在请求时带上登录Substack后cookie里的substack.sid字段值即可。

对应修改后的可直接运行代码如下:

import feedparser
import pandas as pd
import time
from datetime import datetime

rawrss = ['https://heathercoxrichardson.substack.com/feed', 'https://marcstein.substack.com/feed']
posts = []

for url in rawrss:
    last_pub_ts = int(time.time())  # 从当前时间开始往前回溯拉取
    while True:
        page_url = f"{url}?before={last_pub_ts}"
        feed = feedparser.parse(page_url)
        if not feed.entries:
            # 无返回内容时说明当前通讯已拉取完毕
            break
        for post in feed.entries:
            posts.append((
                post.title, 
                post.link, 
                post.summary, 
                post.summary_detail, 
                post.content, 
                post.published
            ))
        # 取当前页最后一篇的发布时间作为下一次翻页的锚点
        last_post_time = datetime.strptime(feed.entries[-1].published, "%a, %d %b %Y %H:%M:%S %Z")
        last_pub_ts = int(last_post_time.timestamp())
        # 加短延时避免触发平台频率限制
        time.sleep(0.5)

df = pd.DataFrame(posts, columns=['title', 'link', 'summary', 'summary_detail', 'content', 'published'])
# 按链接去重,处理分页边界的重复内容问题
df = df.drop_duplicates(subset='link').reset_index(drop=True)
print(df)
方案2:调用Substack公开归档API(效率更高,无需解析XML)

Substack自带公开的归档API接口,路径为<substack域名>/api/v1/archive,返回结构化JSON数据,支持自定义单次拉取条数(最高单次50条),支持offset分页,不需要依赖feedparser,也不需要解析网页。返回字段包含标题、永久链接、摘要、正文、发布时间等所有需要的信息,直接处理JSON即可生成DataFrame。
基础实现代码参考:

import requests
import pandas as pd
import time

domains = ['heathercoxrichardson.substack.com', 'marcstein.substack.com']
posts = []

for domain in domains:
    offset = 0
    limit = 50
    while True:
        api_url = f"https://{domain}/api/v1/archive?sort=new&offset={offset}&limit={limit}"
        resp = requests.get(api_url)
        entries = resp.json()
        if not entries:
            break
        for post in entries:
            posts.append((
                post['title'],
                post['canonical_url'],
                post['subtitle'],
                post.get('description', ''),
                post.get('body_html', ''),
                post['post_date']
            ))
        offset += limit
        time.sleep(0.5)

df = pd.DataFrame(posts, columns=['title', 'link', 'summary', 'summary_detail', 'content', 'published'])
df = df.drop_duplicates(subset='link').reset_index(drop=True)
print(df)

注意:如果要拉取付费订阅内容,只需要在请求的headers里带上自己账号的Cookie即可,两种方案都支持。

内容的提问来源于stack exchange,提问作者user53526356

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 18:03:18