Medium API 爬取资料与突破 Cloudflare 防护|完整 GraphQL 操作教学
针对 Medium 使用者担心文章无备份、Cloudflare 阻挡爬虫的痛点,详解如何透过 Medium API 与 GraphQL 取得追踪人数、文章列表及内容,并示范用 Cloudflare Worker 成功绕过防护,实现自动备份与资料同步。
基于 SEO 考量,本文标题与描述经 AI 调整,原始版本请参考内文。
文章目录
Medium API 资料爬虫与 Cloudflare 攻防的心路历程
使用 Medium Graphql Private API 爬取资料、备份文章及 Cloudflare 验证阻挡突破方式。
背景
自 2018 年开始经营 Medium 撰写文章,大约在 2022 年时累积的文章数量已超过 50+ 篇,接近 6+ 万字加上约 3 GB 的文章图片 (直至 2025 已破 120+ 篇/10+万字/6 GB+图片); 没有备份! 没有备份! 没有备份! ,因为我一直以来都直接用 Medium 写文章(编辑器真的好用), 写完就直接储存发布,本地没有任何备份。
那时候有一股不安感油然而生,如果哪一天 Medium 倒了我的文章不就不见了?(而且那几年 Medium 状况一直都很不好,没有赚钱) 或是我的帐号突然被停权心血不就都付之一炬了?
于是在那年开始研究如何爬取 Medium 资料、尤其是文章内容跟如何下载备份。
Medium HTML ❌
结构非常复杂、易改变、爬取缓慢,直接起浏览器爬内容是下下策。
Medium Official API ❌
Warning The Medium API is no longer supported. We do not recommend using it.
Medium 曾经提供过官方的 API 接口,但是应该在推出没多久就停用了;推测应该是跟产品目标不同,Medium 是私域流量应该要尽量把内容留在站内,而不是开 API 让外部有机会串接拿到资料。
Medium Unofficial API 🤔
也因为官方不提供 API,有民间大神自行开发对外的 API 接口让有需求的使用者串接;但其中原理我推测也是打 Medium Private API,只是过了它那一层的封装桥接,帮你把请求转换成 Private API 取得原始资料。
收费也不便宜,约每 2,500次请求 $USD 5 美元 ;这个服务做蛮久的,但既然最终都是打 Medium Private API,那我自己嗅探自己打就好了。
Medium Private API ✅
何谓 Private API? 就是这个 API 接口 并非给外部使用、没有文件、可能随时停用、可能有法律风险 ;等于 直接去看他网站 / App 的行为找到呼叫的对应 API ,自己写程式重用这个 API 捞资料到自己的服务上。
https://medium.com/_/graphql
经过网路请求嗅探,我们可以发现所有的 Medium API 都是使用 https://medium.com/_/graphql 这个接口,只要我们能在自己的程式上呼叫并取得相同的资料,这条路就能达成。
Medium Private API — 嗅探 Medium Graphql (取得追踪人数为例)
我的第一个应用是想爬取我的追踪人数显示在我的入口网站。
因此我必须爬取 Medium 上我的帐号的追踪人数。
我们先打开浏览器的「开发者模式」(Chrome -> 右键 -> 检查) 切换到「Network」页签,勾选「Preserve log」、搜寻匡输入「
graphql」进入 Medium 帐号追踪者页面:「
https://medium.com/@YOUR_USER_NAME/followers」 只能用这个格式的 Follower 网址嗅探,https://YOUR_USER_NAME.medium.com/followers这种格式的没有呼叫取得 Followers API。
一个一个查看左方「graphql」请求,在「Response」资料中搜寻「
followerCount」关键字,找到有这个关键字且username同网页的使用者名称 & 追踪数字跟网页显示相同,找到这个目标请求。找到目标的 API 后,切换到 Payload -> View Source -> 复制以下全部内容
现在我们已经有取得 Medium 追踪者的 API & Payload 了,用 Postman 测试看看!
Medium 取得追踪人数 Endpoint — Request & Response
Endpoint:
1
POST https://medium.com/_/graphql
Authorization: No authorization required (不用带 Token or set cookies)
Payload:
1
2
3
4
5
6
7
8
9
10
[
{
"operationName": "UserFollowers",
"variables": {
"id": null,
"username": "zhgchgli"
},
"query": "query UserFollowers($username: ID, $id: ID, $paging: PagingOptions) {\n userResult(username: $username, id: $id) {\n __typename\n ... on User {\n id\n followersUserConnection(paging: $paging) {\n pagingInfo {\n next {\n from\n limit\n __typename\n }\n __typename\n }\n users {\n ...FollowList_publisher\n __typename\n }\n __typename\n }\n ...UserCanonicalizer_user\n ...FollowersHeader_publisher\n ...NoFollows_publisher\n __typename\n }\n }\n}\n\nfragment collectionUrl_collection on Collection {\n id\n domain\n slug\n __typename\n}\n\nfragment CollectionAvatar_collection on Collection {\n name\n avatar {\n id\n __typename\n }\n ...collectionUrl_collection\n __typename\n id\n}\n\nfragment SignInOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment SignUpOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment SusiModal_collection on Collection {\n name\n ...SignInOptions_collection\n ...SignUpOptions_collection\n __typename\n id\n}\n\nfragment PublicationFollowButton_collection on Collection {\n id\n slug\n name\n ...SusiModal_collection\n __typename\n}\n\nfragment PublicationFollowRow_collection on Collection {\n __typename\n id\n name\n description\n ...CollectionAvatar_collection\n ...PublicationFollowButton_collection\n}\n\nfragment userUrl_user on User {\n __typename\n id\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n hasSubdomain\n username\n}\n\nfragment UserAvatar_user on User {\n __typename\n id\n imageId\n membership {\n tier\n __typename\n id\n }\n name\n username\n ...userUrl_user\n}\n\nfragment isUserVerifiedBookAuthor_user on User {\n verifications {\n isBookAuthor\n __typename\n }\n __typename\n id\n}\n\nfragment SignInOptions_user on User {\n id\n name\n imageId\n __typename\n}\n\nfragment SignUpOptions_user on User {\n id\n name\n imageId\n __typename\n}\n\nfragment SusiModal_user on User {\n ...SignInOptions_user\n ...SignUpOptions_user\n __typename\n id\n}\n\nfragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {\n id\n type\n slug\n name\n collection {\n slug\n __typename\n id\n }\n user {\n id\n name\n username\n newsletterV3 {\n id\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment useNewsletterV3Subscription_user on User {\n id\n username\n newsletterV3 {\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n id\n }\n __typename\n}\n\nfragment useAuthorFollowSubscribeButton_user on User {\n id\n name\n ...useNewsletterV3Subscription_user\n __typename\n}\n\nfragment useAuthorFollowSubscribeButton_newsletterV3 on NewsletterV3 {\n id\n name\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n}\n\nfragment AuthorFollowSubscribeButton_user on User {\n id\n name\n imageId\n ...SusiModal_user\n ...useAuthorFollowSubscribeButton_user\n newsletterV3 {\n id\n ...useAuthorFollowSubscribeButton_newsletterV3\n __typename\n }\n __typename\n}\n\nfragment UserFollowRow_user on User {\n __typename\n id\n name\n bio\n ...UserAvatar_user\n ...isUserVerifiedBookAuthor_user\n ...AuthorFollowSubscribeButton_user\n}\n\nfragment FollowsHeader_publisher on Publisher {\n __typename\n id\n name\n ... on Collection {\n ...collectionUrl_collection\n __typename\n id\n }\n ... on User {\n ...userUrl_user\n __typename\n id\n }\n}\n\nfragment FollowList_publisher on Publisher {\n id\n ... on Collection {\n ...PublicationFollowRow_collection\n __typename\n id\n }\n ... on User {\n ...UserFollowRow_user\n __typename\n id\n }\n __typename\n}\n\nfragment UserCanonicalizer_user on User {\n id\n username\n hasSubdomain\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment FollowersHeader_publisher on Publisher {\n ...FollowsHeader_publisher\n ... on Collection {\n subscriberCount\n __typename\n id\n }\n ... on User {\n socialStats {\n followerCount\n __typename\n }\n __typename\n id\n }\n __typename\n}\n\nfragment NoFollows_publisher on Publisher {\n id\n name\n __typename\n}\n"
}
]
username : 换成你要查询的使用者名称。
Postman :
POST https://medium.com/_/graphqlBody -> raw -> JSON
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[
{
"data": {
"userResult": {
"__typename": "User",
"id": "8854784154b8",
"followersUserConnection": {
"pagingInfo": {
"next": {
"from": "1140babdfef7",
"limit": 8,
"__typename": "PageParams"
},
"__typename": "Paging"
},
"users": [
///.. 略
],
"__typename": "UserConnection"
},
"username": "zhgchgli",
"hasSubdomain": true,
"customDomainState": {
"live": {
"domain": "zhgchgli.medium.com",
"__typename": "CustomDomain"
},
"__typename": "CustomDomainState"
},
"name": "ZhgChgLi",
"socialStats": {
"followerCount": 1050,
"__typename": "SocialStats"
}
}
}
}
]
关键资讯: response[0]["data"]["userResult"]["socialStats"]["followerCount"] 就是目标追踪人数了!
Ruby Demo Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def load_medium_followers(username)
begin
url = "https://medium.com/_/graphql"
uri = URI.parse(url)
payload = [
{
"operationName": "UserFollowers",
"variables": {
"id": nil,
"username": username,
"paging": nil
},
"query": "query UserFollowers($username: ID, $id: ID, $paging: PagingOptions) {\n userResult(username: $username, id: $id) {\n __typename\n ... on User {\n id\n followersUserConnection(paging: $paging) {\n pagingInfo {\n next {\n from\n limit\n __typename\n }\n __typename\n }\n users {\n ...FollowList_publisher\n __typename\n }\n __typename\n }\n ...UserCanonicalizer_user\n ...FollowersHeader_publisher\n ...NoFollows_publisher\n __typename\n }\n }\n}\n\nfragment collectionUrl_collection on Collection {\n id\n domain\n slug\n __typename\n}\n\nfragment CollectionAvatar_collection on Collection {\n name\n avatar {\n id\n __typename\n }\n ...collectionUrl_collection\n __typename\n id\n}\n\nfragment SignInOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment SignUpOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment SusiModal_collection on Collection {\n name\n ...SignInOptions_collection\n ...SignUpOptions_collection\n __typename\n id\n}\n\nfragment PublicationFollowButton_collection on Collection {\n id\n slug\n name\n ...SusiModal_collection\n __typename\n}\n\nfragment PublicationFollowRow_collection on Collection {\n __typename\n id\n name\n description\n ...CollectionAvatar_collection\n ...PublicationFollowButton_collection\n}\n\nfragment userUrl_user on User {\n __typename\n id\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n hasSubdomain\n username\n}\n\nfragment UserAvatar_user on User {\n __typename\n id\n imageId\n membership {\n tier\n __typename\n id\n }\n name\n username\n ...userUrl_user\n}\n\nfragment isUserVerifiedBookAuthor_user on User {\n verifications {\n isBookAuthor\n __typename\n }\n __typename\n id\n}\n\nfragment SignInOptions_user on User {\n id\n name\n imageId\n __typename\n}\n\nfragment SignUpOptions_user on User {\n id\n name\n imageId\n __typename\n}\n\nfragment SusiModal_user on User {\n ...SignInOptions_user\n ...SignUpOptions_user\n __typename\n id\n}\n\nfragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {\n id\n type\n slug\n name\n collection {\n slug\n __typename\n id\n }\n user {\n id\n name\n username\n newsletterV3 {\n id\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment useNewsletterV3Subscription_user on User {\n id\n username\n newsletterV3 {\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n id\n }\n __typename\n}\n\nfragment useAuthorFollowSubscribeButton_user on User {\n id\n name\n ...useNewsletterV3Subscription_user\n __typename\n}\n\nfragment useAuthorFollowSubscribeButton_newsletterV3 on NewsletterV3 {\n id\n name\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n}\n\nfragment AuthorFollowSubscribeButton_user on User {\n id\n name\n imageId\n ...SusiModal_user\n ...useAuthorFollowSubscribeButton_user\n newsletterV3 {\n id\n ...useAuthorFollowSubscribeButton_newsletterV3\n __typename\n }\n __typename\n}\n\nfragment UserFollowRow_user on User {\n __typename\n id\n name\n bio\n ...UserAvatar_user\n ...isUserVerifiedBookAuthor_user\n ...AuthorFollowSubscribeButton_user\n}\n\nfragment FollowsHeader_publisher on Publisher {\n __typename\n id\n name\n ... on Collection {\n ...collectionUrl_collection\n __typename\n id\n }\n ... on User {\n ...userUrl_user\n __typename\n id\n }\n}\n\nfragment FollowList_publisher on Publisher {\n id\n ... on Collection {\n ...PublicationFollowRow_collection\n __typename\n id\n }\n ... on User {\n ...UserFollowRow_user\n __typename\n id\n }\n __typename\n}\n\nfragment UserCanonicalizer_user on User {\n id\n username\n hasSubdomain\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment FollowersHeader_publisher on Publisher {\n ...FollowsHeader_publisher\n ... on Collection {\n subscriberCount\n __typename\n id\n }\n ... on User {\n socialStats {\n followerCount\n __typename\n }\n __typename\n id\n }\n __typename\n}\n\nfragment NoFollows_publisher on Publisher {\n id\n name\n __typename\n}\n"
}
];
headers = {
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
"Content-Type" => "application/json"
}
https = Net::HTTP.new(uri.host, uri.port)
https.read_timeout = 30
https.open_timeout = 10
https.use_ssl = true
# --- TLS / Certificate verification setup ---
# Some OpenSSL builds/configs enable CRL checking, which can fail with:
# "certificate verify failed (unable to get certificate CRL)".
# Net::HTTP/OpenSSL does not automatically fetch CRLs, so we use a default
# cert store and clear CRL-related flags to avoid hard failures while still
# verifying the peer certificate.
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
store = OpenSSL::X509::Store.new
store.set_default_paths
# Ensure no CRL-check flags are enabled by default
store.flags = 0
https.cert_store = store
# Allow overriding CA bundle paths via environment variables if needed.
if ENV['SSL_CERT_FILE'] && !ENV['SSL_CERT_FILE'].empty?
https.ca_file = ENV['SSL_CERT_FILE']
end
if ENV['SSL_CERT_DIR'] && !ENV['SSL_CERT_DIR'].empty?
https.ca_path = ENV['SSL_CERT_DIR']
end
# (Optional) timeouts to avoid hanging on network issues
https.open_timeout = 10
https.read_timeout = 30
# --- end TLS setup ---
req = Net::HTTP::Post.new(uri.request_uri, headers)
req.body = JSON.dump(payload)
res = https.request(req)
json = JSON.parse(res.body)
count = json&.dig(0, "data", "userResult", "socialStats", "followerCount")
count ? count.to_i : 0
return "1K+" unless count.to_i > 0
return "#{count.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}+"
rescue => e
"1K+"
end
end
Medium Private API — 取得帐号的所有文章
用相同的方式我们也可以取得目标帐号的文章列表。
Graphql Query Body:
1
2
3
4
5
6
7
8
9
10
11
12
[
{
"operationName": "UserProfileQuery",
"variables": {
"homepagePostsFrom": null,
"includeDistributedResponses": true,
"id": "8854784154b8",
"homepagePostsLimit": 10
},
"query": "query UserProfileQuery($id: ID, $username: ID, $homepagePostsLimit: PaginationLimit, $homepagePostsFrom: String = null, $includeDistributedResponses: Boolean = true) {\n userResult(id: $id, username: $username) {\n __typename\n ... on User {\n id\n name\n viewerIsUser\n viewerEdge {\n id\n isFollowing\n __typename\n }\n homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {\n posts {\n id\n __typename\n }\n __typename\n }\n ...UserCanonicalizer_user\n ...UserProfileScreen_user\n ...EntityDrivenSubscriptionLandingPageScreen_writer\n ...useShouldShowEntityDrivenSubscription_creator\n __typename\n }\n }\n}\n\nfragment UserCanonicalizer_user on User {\n id\n username\n hasSubdomain\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment UserProfileScreen_user on User {\n __typename\n id\n viewerIsUser\n ...PublisherHeader_publisher\n ...PublisherHomePosts_publisher\n ...UserSubdomainFlow_user\n ...UserProfileMetadata_user\n ...SuspendedBannerLoader_user\n ...ExpandablePost_user\n ...useAnalytics_user\n}\n\nfragment PublisherHeader_publisher on Publisher {\n id\n ...PublisherHeaderBackground_publisher\n ...PublisherHeaderNameplate_publisher\n ...PublisherHeaderActions_publisher\n ...PublisherHeaderNav_publisher\n __typename\n}\n\nfragment PublisherHeaderBackground_publisher on Publisher {\n __typename\n id\n customStyleSheet {\n ...PublisherHeaderBackground_customStyleSheet\n __typename\n id\n }\n ... on Collection {\n colorPalette {\n tintBackgroundSpectrum {\n backgroundColor\n __typename\n }\n __typename\n }\n isAuroraVisible\n legacyHeaderBackgroundImage {\n id\n originalWidth\n focusPercentX\n focusPercentY\n __typename\n }\n ...collectionTintBackgroundTheme_collection\n __typename\n id\n }\n ...publisherUrl_publisher\n}\n\nfragment PublisherHeaderBackground_customStyleSheet on CustomStyleSheet {\n id\n global {\n colorPalette {\n background {\n rgb\n __typename\n }\n __typename\n }\n __typename\n }\n header {\n headerScale\n backgroundImageDisplayMode\n backgroundImageVerticalAlignment\n backgroundColorDisplayMode\n backgroundColor {\n alpha\n rgb\n ...getHexFromColorValue_colorValue\n ...getOpaqueHexFromColorValue_colorValue\n __typename\n }\n secondaryBackgroundColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n postBackgroundColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n backgroundImage {\n ...MetaHeaderBackground_imageMetadata\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment getHexFromColorValue_colorValue on ColorValue {\n rgb\n alpha\n __typename\n}\n\nfragment getOpaqueHexFromColorValue_colorValue on ColorValue {\n rgb\n __typename\n}\n\nfragment MetaHeaderBackground_imageMetadata on ImageMetadata {\n id\n originalWidth\n __typename\n}\n\nfragment collectionTintBackgroundTheme_collection on Collection {\n colorPalette {\n ...collectionTintBackgroundTheme_colorPalette\n __typename\n }\n customStyleSheet {\n id\n ...collectionTintBackgroundTheme_customStyleSheet\n __typename\n }\n __typename\n id\n}\n\nfragment collectionTintBackgroundTheme_colorPalette on ColorPalette {\n ...customTintBackgroundTheme_colorPalette\n __typename\n}\n\nfragment customTintBackgroundTheme_colorPalette on ColorPalette {\n tintBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n __typename\n}\n\nfragment ThemeUtil_colorSpectrum on ColorSpectrum {\n backgroundColor\n ...ThemeUtilInterpolateHelpers_colorSpectrum\n __typename\n}\n\nfragment ThemeUtilInterpolateHelpers_colorSpectrum on ColorSpectrum {\n colorPoints {\n ...ThemeUtil_colorPoint\n __typename\n }\n __typename\n}\n\nfragment ThemeUtil_colorPoint on ColorPoint {\n color\n point\n __typename\n}\n\nfragment collectionTintBackgroundTheme_customStyleSheet on CustomStyleSheet {\n id\n ...customTintBackgroundTheme_customStyleSheet\n __typename\n}\n\nfragment customTintBackgroundTheme_customStyleSheet on CustomStyleSheet {\n id\n global {\n colorPalette {\n primary {\n colorPalette {\n ...customTintBackgroundTheme_colorPalette\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment publisherUrl_publisher on Publisher {\n id\n __typename\n ... on Collection {\n ...collectionUrl_collection\n __typename\n id\n }\n ... on User {\n ...userUrl_user\n __typename\n id\n }\n}\n\nfragment collectionUrl_collection on Collection {\n id\n domain\n slug\n __typename\n}\n\nfragment userUrl_user on User {\n __typename\n id\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n hasSubdomain\n username\n}\n\nfragment PublisherHeaderNameplate_publisher on Publisher {\n ...PublisherAvatar_publisher\n ...PublisherHeaderLogo_publisher\n ...PublisherFollowerCount_publisher\n __typename\n}\n\nfragment PublisherAvatar_publisher on Publisher {\n __typename\n ... on Collection {\n id\n ...CollectionAvatar_collection\n __typename\n }\n ... on User {\n id\n ...UserAvatar_user\n __typename\n }\n}\n\nfragment CollectionAvatar_collection on Collection {\n name\n avatar {\n id\n __typename\n }\n ...collectionUrl_collection\n __typename\n id\n}\n\nfragment UserAvatar_user on User {\n __typename\n id\n imageId\n mediumMemberAt\n name\n username\n ...userUrl_user\n}\n\nfragment PublisherHeaderLogo_publisher on Publisher {\n __typename\n id\n customStyleSheet {\n id\n header {\n logoImage {\n id\n originalHeight\n originalWidth\n __typename\n }\n appNameColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n appNameTreatment\n __typename\n }\n __typename\n }\n name\n ... on Collection {\n isAuroraVisible\n logo {\n id\n originalHeight\n originalWidth\n __typename\n }\n __typename\n id\n }\n ...CustomHeaderTooltip_publisher\n ...publisherUrl_publisher\n}\n\nfragment CustomHeaderTooltip_publisher on Publisher {\n __typename\n id\n customStyleSheet {\n id\n header {\n appNameTreatment\n nameTreatment\n __typename\n }\n __typename\n }\n ... on Collection {\n isAuroraVisible\n slug\n __typename\n id\n }\n}\n\nfragment PublisherFollowerCount_publisher on Publisher {\n __typename\n id\n ... on Collection {\n slug\n subscriberCount\n __typename\n id\n }\n ... on User {\n socialStats {\n followerCount\n __typename\n }\n username\n __typename\n id\n }\n}\n\nfragment PublisherHeaderActions_publisher on Publisher {\n __typename\n ...MetaHeaderPubMenu_publisher\n ... on Collection {\n ...CollectionFollowButton_collection\n __typename\n id\n }\n ... on User {\n ...FollowAndSubscribeButtons_user\n __typename\n id\n }\n}\n\nfragment MetaHeaderPubMenu_publisher on Publisher {\n __typename\n ... on Collection {\n ...MetaHeaderPubMenu_publisher_collection\n __typename\n id\n }\n ... on User {\n ...MetaHeaderPubMenu_publisher_user\n __typename\n id\n }\n}\n\nfragment MetaHeaderPubMenu_publisher_collection on Collection {\n id\n slug\n name\n domain\n newsletterV3 {\n slug\n __typename\n id\n }\n ...MutePopoverOptions_collection\n __typename\n}\n\nfragment MutePopoverOptions_collection on Collection {\n id\n __typename\n}\n\nfragment MetaHeaderPubMenu_publisher_user on User {\n id\n username\n ...MutePopoverOptions_creator\n __typename\n}\n\nfragment MutePopoverOptions_creator on User {\n id\n __typename\n}\n\nfragment CollectionFollowButton_collection on Collection {\n __typename\n id\n name\n slug\n ...collectionUrl_collection\n ...SusiClickable_collection\n}\n\nfragment SusiClickable_collection on Collection {\n ...SusiContainer_collection\n __typename\n id\n}\n\nfragment SusiContainer_collection on Collection {\n name\n ...SignInOptions_collection\n ...SignUpOptions_collection\n __typename\n id\n}\n\nfragment SignInOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment SignUpOptions_collection on Collection {\n id\n name\n __typename\n}\n\nfragment FollowAndSubscribeButtons_user on User {\n ...UserFollowButton_user\n ...UserSubscribeButton_user\n __typename\n id\n}\n\nfragment UserFollowButton_user on User {\n ...UserFollowButtonSignedIn_user\n ...UserFollowButtonSignedOut_user\n __typename\n id\n}\n\nfragment UserFollowButtonSignedIn_user on User {\n id\n __typename\n}\n\nfragment UserFollowButtonSignedOut_user on User {\n id\n ...SusiClickable_user\n __typename\n}\n\nfragment SusiClickable_user on User {\n ...SusiContainer_user\n __typename\n id\n}\n\nfragment SusiContainer_user on User {\n ...SignInOptions_user\n ...SignUpOptions_user\n __typename\n id\n}\n\nfragment SignInOptions_user on User {\n id\n name\n __typename\n}\n\nfragment SignUpOptions_user on User {\n id\n name\n __typename\n}\n\nfragment UserSubscribeButton_user on User {\n id\n isPartnerProgramEnrolled\n name\n viewerEdge {\n id\n isFollowing\n isUser\n __typename\n }\n viewerIsUser\n newsletterV3 {\n id\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n }\n ...useNewsletterV3Subscription_user\n ...MembershipUpsellModal_user\n __typename\n}\n\nfragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {\n id\n type\n slug\n name\n collection {\n slug\n __typename\n id\n }\n user {\n id\n name\n username\n newsletterV3 {\n id\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment useNewsletterV3Subscription_user on User {\n id\n username\n newsletterV3 {\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n id\n }\n __typename\n}\n\nfragment MembershipUpsellModal_user on User {\n id\n name\n imageId\n postSubscribeMembershipUpsellShownAt\n newsletterV3 {\n id\n __typename\n }\n __typename\n}\n\nfragment PublisherHeaderNav_publisher on Publisher {\n __typename\n id\n customStyleSheet {\n navigation {\n navItems {\n name\n ...PublisherHeaderNavLink_headerNavigationItem\n __typename\n }\n __typename\n }\n __typename\n id\n }\n ...PublisherHeaderNavLink_publisher\n ... on Collection {\n domain\n isAuroraVisible\n slug\n navItems {\n tagSlug\n title\n url\n __typename\n }\n __typename\n id\n }\n ... on User {\n customDomainState {\n live {\n domain\n __typename\n }\n __typename\n }\n hasSubdomain\n username\n about\n homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {\n posts {\n id\n __typename\n }\n __typename\n }\n __typename\n id\n }\n}\n\nfragment PublisherHeaderNavLink_headerNavigationItem on HeaderNavigationItem {\n href\n name\n tags {\n id\n normalizedTagSlug\n __typename\n }\n type\n __typename\n}\n\nfragment PublisherHeaderNavLink_publisher on Publisher {\n __typename\n id\n ... on Collection {\n slug\n __typename\n id\n }\n}\n\nfragment PublisherHomePosts_publisher on Publisher {\n __typename\n id\n homepagePostsConnection(\n paging: {limit: $homepagePostsLimit, from: $homepagePostsFrom}\n includeDistributedResponses: $includeDistributedResponses\n ) {\n posts {\n ...PublisherHomePosts_post\n __typename\n }\n pagingInfo {\n next {\n from\n limit\n __typename\n }\n __typename\n }\n __typename\n }\n ...CardByline_publisher\n ...NewsletterV3Promo_publisher\n ...PublisherHomePosts_user\n}\n\nfragment PublisherHomePosts_post on Post {\n id\n collection {\n id\n name\n ...collectionUrl_collection\n __typename\n }\n ...ExpandablePost_post\n __typename\n}\n\nfragment ExpandablePost_post on Post {\n id\n creator {\n ...ExpandablePost_user\n __typename\n id\n }\n collection {\n ...CardByline_collection\n __typename\n id\n }\n ...InteractivePostBody_postPreview\n firstPublishedAt\n isLocked\n isSeries\n isShortform\n latestPublishedAt\n inResponseToCatalogResult {\n __typename\n }\n mediumUrl\n postResponses {\n count\n __typename\n }\n previewImage {\n id\n focusPercentX\n focusPercentY\n __typename\n }\n readingTime\n sequence {\n slug\n __typename\n }\n title\n uniqueSlug\n visibility\n ...CardByline_post\n ...ExpandablePostFooter_post\n ...InResponseToEntityPreview_post\n ...PostScrollTracker_post\n ...ReadMore_post\n ...HighDensityPreview_post\n __typename\n}\n\nfragment ExpandablePost_user on User {\n __typename\n name\n username\n ...CardByline_user\n id\n}\n\nfragment CardByline_user on User {\n __typename\n id\n name\n username\n mediumMemberAt\n socialStats {\n followerCount\n __typename\n }\n ...userUrl_user\n ...UserMentionTooltip_user\n}\n\nfragment UserMentionTooltip_user on User {\n id\n name\n username\n bio\n imageId\n mediumMemberAt\n ...UserAvatar_user\n ...UserFollowButton_user\n __typename\n}\n\nfragment CardByline_collection on Collection {\n __typename\n id\n name\n ...collectionUrl_collection\n}\n\nfragment InteractivePostBody_postPreview on Post {\n extendedPreviewContent(\n truncationConfig: {previewParagraphsWordCountThreshold: 400, minimumWordLengthForTruncation: 150, truncateAtEndOfSentence: true, showFullImageCaptions: true, shortformPreviewParagraphsWordCountThreshold: 30, shortformMinimumWordLengthForTruncation: 30}\n ) {\n bodyModel {\n ...PostBody_bodyModel\n __typename\n }\n isFullContent\n __typename\n }\n __typename\n id\n}\n\nfragment PostBody_bodyModel on RichText {\n sections {\n name\n startIndex\n textLayout\n imageLayout\n backgroundImage {\n id\n originalHeight\n originalWidth\n __typename\n }\n videoLayout\n backgroundVideo {\n videoId\n originalHeight\n originalWidth\n previewImageId\n __typename\n }\n __typename\n }\n paragraphs {\n id\n ...PostBodySection_paragraph\n __typename\n }\n ...normalizedBodyModel_richText\n __typename\n}\n\nfragment PostBodySection_paragraph on Paragraph {\n name\n ...PostBodyParagraph_paragraph\n __typename\n id\n}\n\nfragment PostBodyParagraph_paragraph on Paragraph {\n name\n type\n ...ImageParagraph_paragraph\n ...TextParagraph_paragraph\n ...IframeParagraph_paragraph\n ...MixtapeParagraph_paragraph\n __typename\n id\n}\n\nfragment ImageParagraph_paragraph on Paragraph {\n href\n layout\n metadata {\n id\n originalHeight\n originalWidth\n focusPercentX\n focusPercentY\n alt\n __typename\n }\n ...Markups_paragraph\n ...ParagraphRefsMapContext_paragraph\n ...PostAnnotationsMarker_paragraph\n __typename\n id\n}\n\nfragment Markups_paragraph on Paragraph {\n name\n text\n hasDropCap\n dropCapImage {\n ...MarkupNode_data_dropCapImage\n __typename\n id\n }\n markups {\n type\n start\n end\n href\n anchorType\n userId\n linkMetadata {\n httpStatus\n __typename\n }\n __typename\n }\n __typename\n id\n}\n\nfragment MarkupNode_data_dropCapImage on ImageMetadata {\n ...DropCap_image\n __typename\n id\n}\n\nfragment DropCap_image on ImageMetadata {\n id\n originalHeight\n originalWidth\n __typename\n}\n\nfragment ParagraphRefsMapContext_paragraph on Paragraph {\n id\n name\n text\n __typename\n}\n\nfragment PostAnnotationsMarker_paragraph on Paragraph {\n ...PostViewNoteCard_paragraph\n __typename\n id\n}\n\nfragment PostViewNoteCard_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment TextParagraph_paragraph on Paragraph {\n type\n hasDropCap\n ...Markups_paragraph\n ...ParagraphRefsMapContext_paragraph\n __typename\n id\n}\n\nfragment IframeParagraph_paragraph on Paragraph {\n iframe {\n mediaResource {\n id\n iframeSrc\n iframeHeight\n iframeWidth\n title\n __typename\n }\n __typename\n }\n layout\n ...getEmbedlyCardUrlParams_paragraph\n ...Markups_paragraph\n __typename\n id\n}\n\nfragment getEmbedlyCardUrlParams_paragraph on Paragraph {\n type\n iframe {\n mediaResource {\n iframeSrc\n __typename\n }\n __typename\n }\n __typename\n id\n}\n\nfragment MixtapeParagraph_paragraph on Paragraph {\n type\n mixtapeMetadata {\n href\n mediaResource {\n mediumCatalog {\n id\n __typename\n }\n __typename\n }\n __typename\n }\n ...GenericMixtapeParagraph_paragraph\n __typename\n id\n}\n\nfragment GenericMixtapeParagraph_paragraph on Paragraph {\n text\n mixtapeMetadata {\n href\n thumbnailImageId\n __typename\n }\n markups {\n start\n end\n type\n href\n __typename\n }\n __typename\n id\n}\n\nfragment normalizedBodyModel_richText on RichText {\n paragraphs {\n markups {\n type\n __typename\n }\n ...getParagraphHighlights_paragraph\n ...getParagraphPrivateNotes_paragraph\n __typename\n }\n sections {\n startIndex\n ...getSectionEndIndex_section\n __typename\n }\n ...getParagraphStyles_richText\n ...getParagraphSpaces_richText\n __typename\n}\n\nfragment getParagraphHighlights_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment getParagraphPrivateNotes_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment getSectionEndIndex_section on Section {\n startIndex\n __typename\n}\n\nfragment getParagraphStyles_richText on RichText {\n paragraphs {\n text\n type\n __typename\n }\n sections {\n ...getSectionEndIndex_section\n __typename\n }\n __typename\n}\n\nfragment getParagraphSpaces_richText on RichText {\n paragraphs {\n layout\n metadata {\n originalHeight\n originalWidth\n __typename\n }\n type\n ...paragraphExtendsImageGrid_paragraph\n __typename\n }\n ...getSeriesParagraphTopSpacings_richText\n ...getPostParagraphTopSpacings_richText\n __typename\n}\n\nfragment paragraphExtendsImageGrid_paragraph on Paragraph {\n layout\n type\n __typename\n id\n}\n\nfragment getSeriesParagraphTopSpacings_richText on RichText {\n paragraphs {\n id\n __typename\n }\n sections {\n startIndex\n __typename\n }\n __typename\n}\n\nfragment getPostParagraphTopSpacings_richText on RichText {\n paragraphs {\n layout\n text\n __typename\n }\n sections {\n startIndex\n __typename\n }\n __typename\n}\n\nfragment CardByline_post on Post {\n ...DraftStatus_post\n __typename\n id\n}\n\nfragment DraftStatus_post on Post {\n id\n pendingCollection {\n id\n creator {\n id\n __typename\n }\n ...BoldCollectionName_collection\n __typename\n }\n statusForCollection\n creator {\n id\n __typename\n }\n isPublished\n __typename\n}\n\nfragment BoldCollectionName_collection on Collection {\n id\n name\n __typename\n}\n\nfragment ExpandablePostFooter_post on Post {\n id\n allowResponses\n postResponses {\n count\n __typename\n }\n isLimitedState\n ...ExpandablePostCardOverflowButton_post\n ...BookmarkButton_post\n ...PostFooterSocialPopover_post\n ...MultiVote_post\n ...OverflowMenuButtonWithNegativeSignal_post\n __typename\n}\n\nfragment ExpandablePostCardOverflowButton_post on Post {\n creator {\n id\n __typename\n }\n ...ExpandablePostCardEditorWriterButton_post\n ...ExpandablePostCardReaderButton_post\n __typename\n id\n}\n\nfragment ExpandablePostCardEditorWriterButton_post on Post {\n id\n collection {\n id\n name\n slug\n __typename\n }\n allowResponses\n clapCount\n visibility\n mediumUrl\n responseDistribution\n ...useIsPinnedInContext_post\n ...CopyFriendLinkMenuItem_post\n ...NewsletterV3EmailToSubscribersMenuItem_post\n ...OverflowMenuItemUndoClaps_post\n __typename\n}\n\nfragment useIsPinnedInContext_post on Post {\n id\n collection {\n id\n __typename\n }\n pendingCollection {\n id\n __typename\n }\n pinnedAt\n pinnedByCreatorAt\n __typename\n}\n\nfragment CopyFriendLinkMenuItem_post on Post {\n id\n __typename\n}\n\nfragment NewsletterV3EmailToSubscribersMenuItem_post on Post {\n id\n creator {\n id\n newsletterV3 {\n id\n subscribersCount\n __typename\n }\n __typename\n }\n isNewsletter\n isAuthorNewsletter\n __typename\n}\n\nfragment OverflowMenuItemUndoClaps_post on Post {\n id\n clapCount\n ...ClapMutation_post\n __typename\n}\n\nfragment ClapMutation_post on Post {\n __typename\n id\n clapCount\n ...MultiVoteCount_post\n}\n\nfragment MultiVoteCount_post on Post {\n id\n ...PostVotersNetwork_post\n __typename\n}\n\nfragment PostVotersNetwork_post on Post {\n id\n voterCount\n recommenders {\n name\n __typename\n }\n __typename\n}\n\nfragment ExpandablePostCardReaderButton_post on Post {\n id\n collection {\n id\n __typename\n }\n creator {\n id\n __typename\n }\n clapCount\n ...ClapMutation_post\n __typename\n}\n\nfragment BookmarkButton_post on Post {\n visibility\n ...SusiClickable_post\n ...AddToCatalogBookmarkButton_post\n __typename\n id\n}\n\nfragment SusiClickable_post on Post {\n id\n mediumUrl\n ...SusiContainer_post\n __typename\n}\n\nfragment SusiContainer_post on Post {\n id\n __typename\n}\n\nfragment AddToCatalogBookmarkButton_post on Post {\n ...AddToCatalogBase_post\n __typename\n id\n}\n\nfragment AddToCatalogBase_post on Post {\n id\n __typename\n}\n\nfragment PostFooterSocialPopover_post on Post {\n id\n mediumUrl\n title\n ...SharePostButton_post\n __typename\n}\n\nfragment SharePostButton_post on Post {\n id\n __typename\n}\n\nfragment MultiVote_post on Post {\n id\n clapCount\n creator {\n id\n ...SusiClickable_user\n __typename\n }\n isPublished\n ...SusiClickable_post\n collection {\n id\n slug\n __typename\n }\n isLimitedState\n ...MultiVoteCount_post\n __typename\n}\n\nfragment OverflowMenuButtonWithNegativeSignal_post on Post {\n id\n ...OverflowMenuWithNegativeSignal_post\n ...CreatorActionOverflowPopover_post\n __typename\n}\n\nfragment OverflowMenuWithNegativeSignal_post on Post {\n id\n creator {\n id\n __typename\n }\n collection {\n id\n __typename\n }\n ...OverflowMenuItemUndoClaps_post\n __typename\n}\n\nfragment CreatorActionOverflowPopover_post on Post {\n allowResponses\n id\n statusForCollection\n isLocked\n isPublished\n clapCount\n mediumUrl\n pinnedAt\n pinnedByCreatorAt\n curationEligibleAt\n mediumUrl\n responseDistribution\n visibility\n inResponseToPostResult {\n __typename\n }\n inResponseToCatalogResult {\n __typename\n }\n pendingCollection {\n id\n name\n creator {\n id\n __typename\n }\n avatar {\n id\n __typename\n }\n domain\n slug\n __typename\n }\n creator {\n id\n ...MutePopoverOptions_creator\n ...auroraHooks_publisher\n __typename\n }\n collection {\n id\n name\n creator {\n id\n __typename\n }\n avatar {\n id\n __typename\n }\n domain\n slug\n ...MutePopoverOptions_collection\n ...auroraHooks_publisher\n __typename\n }\n ...useIsPinnedInContext_post\n ...NewsletterV3EmailToSubscribersMenuItem_post\n ...OverflowMenuItemUndoClaps_post\n __typename\n}\n\nfragment auroraHooks_publisher on Publisher {\n __typename\n ... on Collection {\n isAuroraEligible\n isAuroraVisible\n viewerEdge {\n id\n isEditor\n __typename\n }\n __typename\n id\n }\n ... on User {\n isAuroraVisible\n __typename\n id\n }\n}\n\nfragment InResponseToEntityPreview_post on Post {\n id\n inResponseToEntityType\n __typename\n}\n\nfragment PostScrollTracker_post on Post {\n id\n collection {\n id\n __typename\n }\n sequence {\n sequenceId\n __typename\n }\n __typename\n}\n\nfragment ReadMore_post on Post {\n mediumUrl\n readingTime\n ...usePostUrl_post\n __typename\n id\n}\n\nfragment usePostUrl_post on Post {\n id\n creator {\n ...userUrl_user\n __typename\n id\n }\n collection {\n id\n domain\n slug\n __typename\n }\n isSeries\n mediumUrl\n sequence {\n slug\n __typename\n }\n uniqueSlug\n __typename\n}\n\nfragment HighDensityPreview_post on Post {\n id\n title\n previewImage {\n id\n focusPercentX\n focusPercentY\n __typename\n }\n extendedPreviewContent(\n truncationConfig: {previewParagraphsWordCountThreshold: 400, minimumWordLengthForTruncation: 150, truncateAtEndOfSentence: true, showFullImageCaptions: true, shortformPreviewParagraphsWordCountThreshold: 30, shortformMinimumWordLengthForTruncation: 30}\n ) {\n subtitle\n __typename\n }\n ...HighDensityFooter_post\n __typename\n}\n\nfragment HighDensityFooter_post on Post {\n id\n readingTime\n tags {\n ...TopicPill_tag\n __typename\n }\n ...BookmarkButton_post\n ...ExpandablePostCardOverflowButton_post\n ...OverflowMenuButtonWithNegativeSignal_post\n __typename\n}\n\nfragment TopicPill_tag on Tag {\n __typename\n id\n displayTitle\n}\n\nfragment CardByline_publisher on Publisher {\n __typename\n ... on User {\n id\n ...CardByline_user\n __typename\n }\n ... on Collection {\n id\n ...CardByline_collection\n __typename\n }\n}\n\nfragment NewsletterV3Promo_publisher on Publisher {\n __typename\n ... on User {\n ...NewsletterV3Promo_publisher_User\n __typename\n id\n }\n ... on Collection {\n ...NewsletterV3Promo_publisher_Collection\n __typename\n id\n }\n}\n\nfragment NewsletterV3Promo_publisher_User on User {\n id\n username\n name\n viewerIsUser\n newsletterV3 {\n id\n ...NewsletterV3Promo_newsletterV3\n __typename\n }\n __typename\n}\n\nfragment NewsletterV3Promo_newsletterV3 on NewsletterV3 {\n slug\n name\n description\n promoHeadline\n promoBody\n ...NewsletterV3AmpButton_newsletterV3\n ...NewsletterV3SubscribeButton_newsletterV3\n ...NewsletterV3SubscribeByEmail_newsletterV3\n __typename\n id\n}\n\nfragment NewsletterV3AmpButton_newsletterV3 on NewsletterV3 {\n id\n collection {\n ...collectionDefaultBackgroundTheme_collection\n __typename\n id\n }\n __typename\n}\n\nfragment collectionDefaultBackgroundTheme_collection on Collection {\n colorPalette {\n ...collectionDefaultBackgroundTheme_colorPalette\n __typename\n }\n customStyleSheet {\n id\n ...collectionDefaultBackgroundTheme_customStyleSheet\n __typename\n }\n __typename\n id\n}\n\nfragment collectionDefaultBackgroundTheme_colorPalette on ColorPalette {\n ...customDefaultBackgroundTheme_colorPalette\n __typename\n}\n\nfragment customDefaultBackgroundTheme_colorPalette on ColorPalette {\n highlightSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n defaultBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n tintBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n __typename\n}\n\nfragment collectionDefaultBackgroundTheme_customStyleSheet on CustomStyleSheet {\n id\n ...customDefaultBackgroundTheme_customStyleSheet\n __typename\n}\n\nfragment customDefaultBackgroundTheme_customStyleSheet on CustomStyleSheet {\n id\n global {\n colorPalette {\n primary {\n colorPalette {\n ...customDefaultBackgroundTheme_colorPalette\n __typename\n }\n __typename\n }\n background {\n colorPalette {\n ...customDefaultBackgroundTheme_colorPalette\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment NewsletterV3SubscribeButton_newsletterV3 on NewsletterV3 {\n id\n name\n slug\n type\n user {\n id\n name\n username\n __typename\n }\n collection {\n slug\n ...SusiClickable_collection\n ...collectionDefaultBackgroundTheme_collection\n __typename\n id\n }\n ...SusiClickable_newsletterV3\n ...useNewsletterV3Subscription_newsletterV3\n __typename\n}\n\nfragment SusiClickable_newsletterV3 on NewsletterV3 {\n ...SusiContainer_newsletterV3\n __typename\n id\n}\n\nfragment SusiContainer_newsletterV3 on NewsletterV3 {\n ...SignInOptions_newsletterV3\n ...SignUpOptions_newsletterV3\n __typename\n id\n}\n\nfragment SignInOptions_newsletterV3 on NewsletterV3 {\n id\n name\n __typename\n}\n\nfragment SignUpOptions_newsletterV3 on NewsletterV3 {\n id\n name\n __typename\n}\n\nfragment NewsletterV3SubscribeByEmail_newsletterV3 on NewsletterV3 {\n id\n slug\n type\n user {\n id\n name\n username\n __typename\n }\n collection {\n ...collectionDefaultBackgroundTheme_collection\n ...collectionUrl_collection\n __typename\n id\n }\n __typename\n}\n\nfragment NewsletterV3Promo_publisher_Collection on Collection {\n id\n slug\n domain\n name\n newsletterV3 {\n id\n ...NewsletterV3Promo_newsletterV3\n __typename\n }\n __typename\n}\n\nfragment PublisherHomePosts_user on User {\n id\n ...useShowAuthorNewsletterV3Promo_user\n __typename\n}\n\nfragment useShowAuthorNewsletterV3Promo_user on User {\n id\n username\n newsletterV3 {\n id\n showPromo\n slug\n __typename\n }\n __typename\n}\n\nfragment UserSubdomainFlow_user on User {\n id\n hasCompletedProfile\n name\n bio\n imageId\n ...UserCompleteProfileDialog_user\n ...UserSubdomainOnboardingDialog_user\n __typename\n}\n\nfragment UserCompleteProfileDialog_user on User {\n id\n name\n bio\n imageId\n hasCompletedProfile\n __typename\n}\n\nfragment UserSubdomainOnboardingDialog_user on User {\n id\n customDomainState {\n pending {\n status\n __typename\n }\n live {\n status\n __typename\n }\n __typename\n }\n username\n __typename\n}\n\nfragment UserProfileMetadata_user on User {\n id\n username\n name\n bio\n socialStats {\n followerCount\n followingCount\n __typename\n }\n ...userUrl_user\n ...UserProfileMetadataHelmet_user\n __typename\n}\n\nfragment UserProfileMetadataHelmet_user on User {\n username\n name\n imageId\n twitterScreenName\n navItems {\n title\n __typename\n }\n __typename\n id\n}\n\nfragment SuspendedBannerLoader_user on User {\n id\n isSuspended\n __typename\n}\n\nfragment useAnalytics_user on User {\n id\n imageId\n name\n username\n __typename\n}\n\nfragment EntityDrivenSubscriptionLandingPageScreen_writer on User {\n name\n imageId\n id\n username\n isPartnerProgramEnrolled\n customStyleSheet {\n ...CustomThemeProvider_customStyleSheet\n ...CustomBackgroundWrapper_customStyleSheet\n ...MetaHeader_customStyleSheet\n __typename\n id\n }\n ...MetaHeader_publisher\n ...userUrl_user\n __typename\n}\n\nfragment CustomThemeProvider_customStyleSheet on CustomStyleSheet {\n id\n ...customDefaultBackgroundTheme_customStyleSheet\n ...customStyleSheetFontTheme_customStyleSheet\n __typename\n}\n\nfragment customStyleSheetFontTheme_customStyleSheet on CustomStyleSheet {\n id\n global {\n fonts {\n font1 {\n name\n __typename\n }\n font2 {\n name\n __typename\n }\n font3 {\n name\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment CustomBackgroundWrapper_customStyleSheet on CustomStyleSheet {\n id\n global {\n colorPalette {\n background {\n ...getHexFromColorValue_colorValue\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment MetaHeader_customStyleSheet on CustomStyleSheet {\n id\n header {\n headerScale\n horizontalAlignment\n __typename\n }\n ...MetaHeaderBackground_customStyleSheet\n ...MetaHeaderEngagement_customStyleSheet\n ...MetaHeaderLogo_customStyleSheet\n ...MetaHeaderNavVertical_customStyleSheet\n ...MetaHeaderTagline_customStyleSheet\n ...MetaHeaderThemeProvider_customStyleSheet\n __typename\n}\n\nfragment MetaHeaderBackground_customStyleSheet on CustomStyleSheet {\n id\n header {\n headerScale\n backgroundImageDisplayMode\n backgroundImageVerticalAlignment\n backgroundColorDisplayMode\n backgroundColor {\n ...getHexFromColorValue_colorValue\n ...getOpaqueHexFromColorValue_colorValue\n __typename\n }\n secondaryBackgroundColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n postBackgroundColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n backgroundImage {\n ...MetaHeaderBackground_imageMetadata\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment MetaHeaderEngagement_customStyleSheet on CustomStyleSheet {\n ...MetaHeaderNav_customStyleSheet\n __typename\n id\n}\n\nfragment MetaHeaderNav_customStyleSheet on CustomStyleSheet {\n id\n navigation {\n navItems {\n ...MetaHeaderNav_headerNavigationItem\n __typename\n }\n __typename\n }\n __typename\n}\n\nfragment MetaHeaderNav_headerNavigationItem on HeaderNavigationItem {\n name\n tagSlugs\n ...MetaHeaderNavLink_headerNavigationItem\n __typename\n}\n\nfragment MetaHeaderNavLink_headerNavigationItem on HeaderNavigationItem {\n name\n ...getNavItemHref_headerNavigationItem\n __typename\n}\n\nfragment getNavItemHref_headerNavigationItem on HeaderNavigationItem {\n href\n type\n tags {\n id\n normalizedTagSlug\n __typename\n }\n __typename\n}\n\nfragment MetaHeaderLogo_customStyleSheet on CustomStyleSheet {\n id\n header {\n nameColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n nameTreatment\n postNameTreatment\n logoImage {\n ...MetaHeaderLogo_imageMetadata\n __typename\n }\n logoScale\n __typename\n }\n __typename\n}\n\nfragment MetaHeaderLogo_imageMetadata on ImageMetadata {\n id\n originalWidth\n originalHeight\n ...PublisherLogo_image\n __typename\n}\n\nfragment PublisherLogo_image on ImageMetadata {\n id\n originalHeight\n originalWidth\n __typename\n}\n\nfragment MetaHeaderNavVertical_customStyleSheet on CustomStyleSheet {\n id\n navigation {\n navItems {\n ...MetaHeaderNavLink_headerNavigationItem\n __typename\n }\n __typename\n }\n ...MetaHeaderNav_customStyleSheet\n __typename\n}\n\nfragment MetaHeaderTagline_customStyleSheet on CustomStyleSheet {\n id\n header {\n taglineColor {\n ...getHexFromColorValue_colorValue\n __typename\n }\n taglineTreatment\n __typename\n }\n __typename\n}\n\nfragment MetaHeaderThemeProvider_customStyleSheet on CustomStyleSheet {\n id\n ...useMetaHeaderTheme_customStyleSheet\n __typename\n}\n\nfragment useMetaHeaderTheme_customStyleSheet on CustomStyleSheet {\n ...customDefaultBackgroundTheme_customStyleSheet\n global {\n colorPalette {\n primary {\n colorPalette {\n tintBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n header {\n backgroundColor {\n colorPalette {\n tintBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n __typename\n }\n __typename\n }\n postBackgroundColor {\n colorPalette {\n tintBackgroundSpectrum {\n ...ThemeUtil_colorSpectrum\n __typename\n }\n __typename\n }\n __typename\n }\n backgroundImage {\n id\n __typename\n }\n __typename\n }\n __typename\n id\n}\n\nfragment MetaHeader_publisher on Publisher {\n __typename\n name\n ...MetaHeaderEngagement_publisher\n ...MetaHeaderLogo_publisher\n ...MetaHeaderNavVertical_publisher\n ...MetaHeaderTagline_publisher\n ...MetaHeaderThemeProvider_publisher\n ...MetaHeaderActions_publisher\n ...MetaHeaderTop_publisher\n ...MetaHeaderNavLink_publisher\n ... on Collection {\n id\n favicon {\n id\n __typename\n }\n tagline\n ...CollectionNavigationContextProvider_collection\n __typename\n }\n ... on User {\n id\n bio\n ...UserProfileCatalogsLink_publisher\n __typename\n }\n}\n\nfragment MetaHeaderEngagement_publisher on Publisher {\n __typename\n ...MetaHeaderNav_publisher\n ...PublisherAboutLink_publisher\n ...PublisherFollowButton_publisher\n ...PublisherFollowerCount_publisher\n ... on Collection {\n creator {\n id\n __typename\n }\n customStyleSheet {\n id\n ...CustomThemeProvider_customStyleSheet\n __typename\n }\n __typename\n id\n }\n ... on User {\n ...UserProfileCatalogsLink_publisher\n ...UserSubscribeButton_user\n customStyleSheet {\n id\n ...CustomThemeProvider_customStyleSheet\n __typename\n }\n __typename\n id\n }\n}\n\nfragment MetaHeaderNav_publisher on Publisher {\n id\n ...MetaHeaderNavLink_publisher\n __typename\n}\n\nfragment MetaHeaderNavLink_publisher on Publisher {\n id\n ...getNavItemHref_publisher\n __typename\n}\n\nfragment getNavItemHref_publisher on Publisher {\n id\n ...publisherUrl_publisher\n __typename\n}\n\nfragment PublisherAboutLink_publisher on Publisher {\n __typename\n id\n ... on Collection {\n slug\n __typename\n id\n }\n ... on User {\n ...userUrl_user\n __typename\n id\n }\n}\n\nfragment PublisherFollowButton_publisher on Publisher {\n __typename\n ... on Collection {\n ...CollectionFollowButton_collection\n __typename\n id\n }\n ... on User {\n ...UserFollowButton_user\n __typename\n id\n }\n}\n\nfragment UserProfileCatalogsLink_publisher on Publisher {\n __typename\n id\n ... on User {\n ...userUrl_user\n homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {\n posts {\n id\n __typename\n }\n __typename\n }\n __typename\n id\n }\n}\n\nfragment MetaHeaderLogo_publisher on Publisher {\n __typename\n id\n name\n ... on Collection {\n logo {\n ...MetaHeaderLogo_imageMetadata\n ...PublisherLogo_image\n __typename\n id\n }\n __typename\n id\n }\n ...auroraHooks_publisher\n}\n\nfragment MetaHeaderNavVertical_publisher on Publisher {\n id\n ...PublisherAboutLink_publisher\n ...MetaHeaderNav_publisher\n ...MetaHeaderNavLink_publisher\n __typename\n}\n\nfragment MetaHeaderTagline_publisher on Publisher {\n __typename\n ... on Collection {\n tagline\n __typename\n id\n }\n ... on User {\n bio\n __typename\n id\n }\n}\n\nfragment MetaHeaderThemeProvider_publisher on Publisher {\n __typename\n customStyleSheet {\n ...MetaHeaderThemeProvider_customStyleSheet\n __typename\n id\n }\n ... on Collection {\n colorPalette {\n ...customDefaultBackgroundTheme_colorPalette\n __typename\n }\n __typename\n id\n }\n}\n\nfragment MetaHeaderActions_publisher on Publisher {\n __typename\n ...MetaHeaderPubMenu_publisher\n ...SearchWidget_publisher\n ... on Collection {\n id\n creator {\n id\n __typename\n }\n customStyleSheet {\n navigation {\n navItems {\n name\n __typename\n }\n __typename\n }\n __typename\n id\n }\n ...CollectionAvatar_collection\n ...CollectionMetabarActionsPopover_collection\n ...MetaHeaderActions_collection_common\n __typename\n }\n ... on User {\n id\n ...UserAvatar_user\n __typename\n }\n}\n\nfragment SearchWidget_publisher on Publisher {\n __typename\n ... on Collection {\n id\n slug\n name\n domain\n __typename\n }\n ... on User {\n id\n name\n __typename\n }\n ...algoliaSearch_publisher\n}\n\nfragment algoliaSearch_publisher on Publisher {\n __typename\n id\n}\n\nfragment CollectionMetabarActionsPopover_collection on Collection {\n id\n slug\n isAuroraEligible\n isAuroraVisible\n newsletterV3 {\n id\n slug\n __typename\n }\n ...collectionUrl_collection\n __typename\n}\n\nfragment MetaHeaderActions_collection_common on Collection {\n creator {\n id\n __typename\n }\n __typename\n id\n}\n\nfragment MetaHeaderTop_publisher on Publisher {\n __typename\n ... on Collection {\n slug\n ...CollectionMetabarActionsPopover_collection\n ...CollectionAvatar_collection\n ...MetaHeaderTop_collection\n __typename\n id\n }\n ... on User {\n username\n id\n __typename\n }\n}\n\nfragment MetaHeaderTop_collection on Collection {\n id\n creator {\n id\n __typename\n }\n __typename\n}\n\nfragment CollectionNavigationContextProvider_collection on Collection {\n id\n domain\n slug\n isAuroraVisible\n __typename\n}\n\nfragment useShouldShowEntityDrivenSubscription_creator on User {\n id\n __typename\n}\n"
}
]
id: 帐号对应的 User Id,上面那个取得追踪者的 Endpoint 有给homepagePostsFrom: 分页参数,可参考回应中的from带入取得所有分页资料
Postman :
Response:
在 response[0]["data"]["userResult"]["homepagePostsConnection"]["posts"] 可以取得文章列表资讯。
Medium Private API — 取得文章内容
再来就是最关键的,爬取文章原始内容。
Graphql Query Body:
1
2
3
4
5
6
7
8
9
[
{
"operationName": "PostViewerEdgeContentQuery",
"variables": {
"postId": "c008a9e8ceca"
},
"query": "query PostViewerEdgeContentQuery($postId: ID!, $postMeteringOptions: PostMeteringOptions) {\n post(id: $postId) {\n ... on Post {\n id\n viewerEdge {\n id\n fullContent(postMeteringOptions: $postMeteringOptions) {\n isLockedPreviewOnly\n validatedShareKey\n bodyModel {\n ...PostBody_bodyModel\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n\nfragment PostBody_bodyModel on RichText {\n sections {\n name\n startIndex\n textLayout\n imageLayout\n backgroundImage {\n id\n originalHeight\n originalWidth\n __typename\n }\n videoLayout\n backgroundVideo {\n videoId\n originalHeight\n originalWidth\n previewImageId\n __typename\n }\n __typename\n }\n paragraphs {\n id\n ...PostBodySection_paragraph\n __typename\n }\n ...normalizedBodyModel_richText\n __typename\n}\n\nfragment PostBodySection_paragraph on Paragraph {\n name\n ...PostBodyParagraph_paragraph\n __typename\n id\n}\n\nfragment PostBodyParagraph_paragraph on Paragraph {\n name\n type\n ...ImageParagraph_paragraph\n ...TextParagraph_paragraph\n ...IframeParagraph_paragraph\n ...MixtapeParagraph_paragraph\n ...CodeBlockParagraph_paragraph\n __typename\n id\n}\n\nfragment ImageParagraph_paragraph on Paragraph {\n href\n layout\n metadata {\n id\n originalHeight\n originalWidth\n focusPercentX\n focusPercentY\n alt\n __typename\n }\n ...Markups_paragraph\n ...ParagraphRefsMapContext_paragraph\n ...PostAnnotationsMarker_paragraph\n __typename\n id\n}\n\nfragment Markups_paragraph on Paragraph {\n name\n text\n hasDropCap\n dropCapImage {\n ...MarkupNode_data_dropCapImage\n __typename\n id\n }\n markups {\n type\n start\n end\n href\n anchorType\n userId\n linkMetadata {\n httpStatus\n __typename\n }\n __typename\n }\n __typename\n id\n}\n\nfragment MarkupNode_data_dropCapImage on ImageMetadata {\n ...DropCap_image\n __typename\n id\n}\n\nfragment DropCap_image on ImageMetadata {\n id\n originalHeight\n originalWidth\n __typename\n}\n\nfragment ParagraphRefsMapContext_paragraph on Paragraph {\n id\n name\n text\n __typename\n}\n\nfragment PostAnnotationsMarker_paragraph on Paragraph {\n ...PostViewNoteCard_paragraph\n __typename\n id\n}\n\nfragment PostViewNoteCard_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment TextParagraph_paragraph on Paragraph {\n type\n hasDropCap\n codeBlockMetadata {\n mode\n lang\n __typename\n }\n ...Markups_paragraph\n ...ParagraphRefsMapContext_paragraph\n __typename\n id\n}\n\nfragment IframeParagraph_paragraph on Paragraph {\n iframe {\n mediaResource {\n id\n iframeSrc\n iframeHeight\n iframeWidth\n title\n __typename\n }\n __typename\n }\n layout\n ...getEmbedlyCardUrlParams_paragraph\n ...Markups_paragraph\n __typename\n id\n}\n\nfragment getEmbedlyCardUrlParams_paragraph on Paragraph {\n type\n iframe {\n mediaResource {\n iframeSrc\n __typename\n }\n __typename\n }\n __typename\n id\n}\n\nfragment MixtapeParagraph_paragraph on Paragraph {\n type\n mixtapeMetadata {\n href\n mediaResource {\n mediumCatalog {\n id\n __typename\n }\n __typename\n }\n __typename\n }\n ...GenericMixtapeParagraph_paragraph\n __typename\n id\n}\n\nfragment GenericMixtapeParagraph_paragraph on Paragraph {\n text\n mixtapeMetadata {\n href\n thumbnailImageId\n __typename\n }\n markups {\n start\n end\n type\n href\n __typename\n }\n __typename\n id\n}\n\nfragment CodeBlockParagraph_paragraph on Paragraph {\n codeBlockMetadata {\n lang\n mode\n __typename\n }\n __typename\n id\n}\n\nfragment normalizedBodyModel_richText on RichText {\n paragraphs {\n markups {\n type\n __typename\n }\n codeBlockMetadata {\n lang\n mode\n __typename\n }\n ...getParagraphHighlights_paragraph\n ...getParagraphPrivateNotes_paragraph\n __typename\n }\n sections {\n startIndex\n ...getSectionEndIndex_section\n __typename\n }\n ...getParagraphStyles_richText\n ...getParagraphSpaces_richText\n __typename\n}\n\nfragment getParagraphHighlights_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment getParagraphPrivateNotes_paragraph on Paragraph {\n name\n __typename\n id\n}\n\nfragment getSectionEndIndex_section on Section {\n startIndex\n __typename\n}\n\nfragment getParagraphStyles_richText on RichText {\n paragraphs {\n text\n type\n __typename\n }\n sections {\n ...getSectionEndIndex_section\n __typename\n }\n __typename\n}\n\nfragment getParagraphSpaces_richText on RichText {\n paragraphs {\n layout\n metadata {\n originalHeight\n originalWidth\n id\n __typename\n }\n type\n ...paragraphExtendsImageGrid_paragraph\n __typename\n }\n ...getSeriesParagraphTopSpacings_richText\n ...getPostParagraphTopSpacings_richText\n __typename\n}\n\nfragment paragraphExtendsImageGrid_paragraph on Paragraph {\n layout\n type\n __typename\n id\n}\n\nfragment getSeriesParagraphTopSpacings_richText on RichText {\n paragraphs {\n id\n __typename\n }\n sections {\n startIndex\n __typename\n }\n __typename\n}\n\nfragment getPostParagraphTopSpacings_richText on RichText {\n paragraphs {\n layout\n text\n codeBlockMetadata {\n lang\n mode\n __typename\n }\n __typename\n }\n sections {\n startIndex\n __typename\n }\n __typename\n}\n"
}
]
posdId: 文章 ID,上面取得文章列表的 API 有给。
Postman :
Response:
文章 Source 如上图:
response[0]["data"]["post"]["viewerEdge"]["fullContent"]["bodyModel"]["paragraphs"]:整篇文章段落,组合起来就是全篇文章["markups"]: 段落的文字样式,例如粗体、超连结…
至于如何把这个 JSON 描述格式转换成 Markdown 可以直接参考或使用我之前开发的开源工具 — ZMediumToMarkdown
- 如果要爬取自己的付费文章需要带上 sid, uid 登入权杖资讯 (参考下文)
Medium x Cloudflare 攻防
本篇文章的第二个主题,就是 Cloudflare,大约在 2025 下半年开始,Medium 的 Cloudflare 防护设定几乎到了最高等级,所有来自云服务的请求都会被阻挡(我用 Google Apps Script / GitHub Actions 全都会被挡),导致爬取资料失败。
阻挡讯息:403
1
<!DOCTYPE html><html lang="en-US"><head><title>Just a moment...</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">....
这让我非常苦恼,因为我的镜像站 ( https://zhgchg.li ) 已经稳定运作了好几年,只要 Medium 有新文章发布,那边就会定时自动执行脚本同步过去 (透过 Private API Graphql), 如果云端服务会被阻挡,我就只能手动在本地电脑执行脚本 。
Header Cookies 多加上 sid, uid ❌
最一开始我在 Header Cookies 多加上 sid, uid 等于是 Medium 登入身份,可以顺利通过,但过了几个月之后连这个方法也行不通了。
uid: 你的使用者 IDsid: 你的 access token (务必保密)
Cloudflare Worker 桥接请求 ✅
乱试一通后找不到方法,突发奇想用 Cloudflare 的魔法打败魔法,用 Cloudflare,没想到就成功了!不会被 Cloudflare Bot 防护阻挡。
Demo Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
if (path == "/graphql") {
let body;
try {
body = await request.json();
} catch {
return new Response("Invalid JSON body", { status: 400 });
}
let apiURL = "https://medium.com/_/graphql";
const apiResponse = await fetch(apiURL, {
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0",
"Cookie": request.headers.get("cookie")
},
body: JSON.stringify(body),
});
const json = await apiResponse.json();
return new Response(JSON.stringify(json), {
status: apiResponse.status,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
}
return new Response("Not Found", { status: 404 });
},
};
Request:
从 https://medium.com/_/graphql 改成你部署的 Cloudflare Worker URL 即可。
总结
以上就是我跟 Medium API 搏斗这么多年的心路历程,目前所有的文章 (Markdown 档)及图片都有备份到 https://zhgchg.li 包含所有附加图片档案,GitHub Repo 有一份、电脑硬碟也有一份,非常安全。
还是希望 Medium 能长长久久!本文内容只供实验参考,作者不负任何使用责任。
有任何问题及指教欢迎 与我联络 。
本文首次发表于 Medium (点击查看原始版本),由 ZMediumToMarkdown 提供自动转换与同步技术。









