BAC DocumentBAC Document
首页
目录
贡献指南
  • QQ交流群(综合技术交流)
  • Telegram交流群(Github Bot推送)
GitHub
首页
目录
贡献指南
  • QQ交流群(综合技术交流)
  • Telegram交流群(Github Bot推送)
GitHub
  • 专栏内容

专栏内容

获取专栏正文内容

https://api.bilibili.com/x/article/view

请求方法: GET

鉴权方式: 请求头 User-Agent

URL 参数:

参数类型内容必要性备注
idnumber专栏文章 ID必要
gaia_sourcestringmain_web不必要
w_ridstringWBI 签名不必要参见 WBI 签名
wtsnumberUNIX 秒级时间戳不必要参见 WBI 签名

JSON 回复:

根对象:

字段类型内容备注
codenumber返回值0: 成功
-352: 请求被风控
-400: 请求错误
-404: 啥都木有
dataobject数据本体见下
messagestring错误信息成功时为 0
ttlnumber1

data 对象:

字段类型内容备注
act_idnumber操作 ID?
apply_timestring应用时间?
attributesnumber属性位?可能不存在
authenMarknull授权码?
authorobject作者信息参考 用户基本信息
banner_urlstring文章头图 URL空则为无
categoriesobject[]专栏分类信息首项为主分区, 第二项为子分区
categoryobject专栏分类信息子分区
check_statenumber检查状态?
check_timestring检查时间?
contentstring文章内容type 字段为 0 为 HTML, 3 为 JSON
content_pic_listunknown内容图片列表?
cover_avidnumber封面视频 AV 号0 为无视频
ctimenumber创建时间UNIX 秒级时间戳
disputeunknown争议信息?
dyn_id_strstring动态 opus id
dynamicstring动态信息?可能不存在
idnumber专栏文章 ID
image_urlsstring[]图片 URL
is_likeboolean是否喜欢?
keywordsstring关键词以 , 分隔
listobject文集信息见下
mediaobject媒体信息?
mtimenumber修改时间UNIX 秒级时间戳
opusobjectopus 信息当 type 字段为 3 时存在, 包含了更加详细的富文本信息
origin_image_urlsstring[]原始图片 URL
origin_template_idnumber原始模板 ID?
originalnumber是否原创0: 非原创
1: 原创
private_pubnumber仅自己可见
publish_timenumber发布时间UNIX 秒级时间戳
reprintnumber是否允许转载0: 不允许
1: 允许规范转载
statenumber专栏状态
statsobject统计数据
summarystring专栏开头部分内容纯文本
tagsobject[]专栏标签
template_idnumber模板 ID?
titlestring专栏标题
top_video_infounknown封面食品信息?
total_art_numnumber作者总文章数
typenumber类型?
version_idnumber版本 ID?
wordsnumber文章总词数

data.categories[] 对象:

另见 专栏分类

字段类型内容备注
idnumber分类 ID
namestring分类名称
parent_idnumber父分类 ID

data.category 对象:

同 data.categories[] 中的对象

data.content 为字符串形式的 JSON 时代表的对象:

字段类型内容备注
opsobject[]以 JSON 呈现的文本内容套了个娃

data.content 代表的对象中的 ops 数组中的对象:

字段类型内容备注
attributeobject?属性见下
insertstring | object插入内容

data.content 代表的对象中的 ops[].attribute 对象:

注: 此处属性备注为页面实际渲染的情况

字段类型内容备注
alignstring?文字对齐参见 text-align
blockquoteboolean?块级引用参见 <blockquote>:块级引用元素
boldboolean?加粗参见 <strong>
classstring?类名参见 class
colorstring?颜色十六进制颜色值, 参见 color
headernumber?标题级别参见 <h1>–<h6>:HTML 区域标题元素
strikeboolean?删除线参见 <s>
linkstring?站内链接参见 <a>:锚元素
italicboolean?斜体参见 <em>:强调元素

data.content 代表的对象中的 ops[].insert 为对象时的对象:

字段类型内容备注
native-imageobject?原生图片见下, 另见 <img>:图像嵌入元素
cut-offobject?分割线见下
video-cardobject?视频卡片见下
article-cardobject?专栏卡片见下
vote-cardobject?投票卡片见下
live-cardobject?投票卡片见下

data.content 代表的对象中的 ops[].insert.native-image 对象:

注: 此处属性备注为页面实际渲染的情况

字段类型内容备注
altstring图像的备用文本描述即 <img> 的 alt 属性
urlstring图像的 URL即 <img> 的 src 属性
widthnumber图像的宽度即 <img> 的 data-w 属性
heightnumber图像的高度即 <img> 的 data-h 属性
sizenumber图像的文件大小即 <img> 的 data-size 属性
statusnumber"loaded"即 <img> 的 data-status 属性

data.content 代表的对象中的 ops[].insert.cut-off 对象:

字段类型内容备注
typestring类型没错, 就是字符串
urlstring分割线图片 URL

data.content 代表的对象中的 ops[].insert.video-card 对象:

字段类型内容备注
altstring
heightnumber卡片高度似乎恒定为 352
idstring视频 id如 av99999999
sizenull
statusstringloaded
tidnumber1.1
urlstring卡片图片 URL
widthnumber卡片宽度似乎恒定为 2632

data.content 代表的对象中的 ops[].insert.article-card 对象:

字段类型内容备注
altstring
heightnumber卡片高度似乎恒定为 320
idstring文章 id如 cv1
sizenull
statusstringloaded
tidnumber2
urlstring卡片图片 URL
widthnumber卡片宽度似乎恒定为 2632

data.content 代表的对象中的 ops[].insert.vote-card 对象:

字段类型内容备注
altstring
heightnumber卡片高度似乎恒定为 320
idstring投票 id确实是字符串, 如 15111509
sizenull
statusstringloaded
tidnumber7
urlstring卡片图片 URL
widthnumber卡片宽度似乎恒定为 2632

data.content 代表的对象中的 ops[].insert.live-card 对象:

字段类型内容备注
altstring
heightnumber卡片高度似乎恒定为 352
idstring直播间 id可能为长也可能为短, 如 lv1 lv5440
sizenull
statusstringloaded
tidnumber8
urlstring卡片图片 URL
widthnumber卡片宽度似乎恒定为 2632

data.list 对象:

字段类型内容备注
apply_timestring空
articles_countnumber0
check_timestring空
ctimenumber创建时间UNIX 秒级时间戳
idnumber文集 ID
image_urlstring文集封面 URL
midnumber作者 mid
namestring文集名称
publish_timenumber最新发布时间UNIX 秒级时间戳
readnumber0
reasonstring审核不通过理由?
statenumber文集状态
summarystring文集描述
update_timenumber文集更新时间UNIX 秒级时间戳
wordsnumber文集总词数

data.media 对象:

字段类型内容备注
areastring
coverstring
media_idnumber
scorenumber
season_idnumber
spoilernumber
titlestring
type_idnumber
type_namestring

data.stats 对象:

字段类型内容备注
coinnumber硬币数
dislikenumber点踩数0
dynamicnumber动态数?
favoritenumber收藏数
likenumber点赞数
replynumber回复数
sharenumber分享数
viewnumber查看数

data.tags[] 对象:

字段类型内容备注
namestring标签名
tidnumber标签 ID

示例:

type=0: cv1 未知的光

curl -G 'https://api.bilibili.com/x/article/view' \
--url-query 'id=1' \
-A 'Mozilla/5.0'
查看响应示例:
{
  "code": 0,
  "data": {
    "act_id": 0,
    "apply_time": "",
    "attributes": 24,
    "authenMark": null,
    "author": {
      "face": "https://i1.hdslb.com/bfs/face/89fe260a17891fdadc3365a9698fee52796c7765.jpg",
      "fans": 3051,
      "level": 6,
      "mid": 91221505,
      "name": "健行见远渐忘",
      "nameplate": {
        "condition": "2018.6.26-7.8某一天是年度大会员",
        "image": "https://i0.hdslb.com/bfs/face/421179426c929dfeaed4117461c83f5d07ffb148.png",
        "image_small": "https://i0.hdslb.com/bfs/face/682001c2e1c2ae887bdf2a0e18eef61180c48f84.png",
        "level": "稀有勋章",
        "name": "大会员2018年度勋章",
        "nid": 74
      },
      "official_verify": {
        "desc": "",
        "type": -1
      },
      "pendant": {
        "expire": 0,
        "image": "https://i1.hdslb.com/bfs/face/7ae15f06f8c912435206a2578509d6bc77c12353.png",
        "name": "作文鬼才",
        "pid": 255
      },
      "vip": {
        "avatar_subscript": 1,
        "due_date": 0,
        "label": {
          "label_theme": "annual_vip",
          "path": "http://i0.hdslb.com/bfs/vip/label_annual.png",
          "text": "年度大会员"
        },
        "nickname_color": "#FB7299",
        "status": 1,
        "theme_type": 0,
        "type": 2,
        "vip_pay_type": 0
      }
    },
    "banner_url": "https://i0.hdslb.com/bfs/article/b1e1029c08d8ad1bb06460d736839a7741dd7925.jpg",
    "categories": [
      {
        "id": 3,
        "name": "生活",
        "parent_id": 0
      },
      {
        "id": 15,
        "name": "日常",
        "parent_id": 3
      }
    ],
    "category": {
      "id": 15,
      "name": "日常",
      "parent_id": 3
    },
    "check_state": 0,
    "check_time": "",
    "content": "<p>天空像是倾倒出的墨水,黑得静谧而深邃。黎明还远,光亮全无。</p><p>夜不能寐。披衣,起床。茶香的弥漫,一盏灯的相伴。夜,你是我久别重逢的朋友,那一刹那的相见,带给了我久违的安思。如果不是梦魇的皮闹,我本不该投入你的怀抱,此时此刻,万念侵扰,思绪咆哮。</p><p>台灯在角落里静静注视着我,不稳定的电流偶尔会吱吱作响。一切都像打在海绵上的水,渐渐消失,慢慢隐身于黑夜,独独留下我自己。一盏灯就可以把夜撕出个伤口,我似乎听到夜逃离光亮的脚步声,窸窸窣窣。它在蔓延,吞噬的野心已越过了窗沿。</p><p>我捧着茶杯,仿佛是在捧着一整个空间,凝重而诡异。茶香绕过茶杯脱落的瓷层向四周扩散,空气闻到了茶香,于是身边的气息似乎活跃了起来。转念一想,人生如茶。器皿的束缚不是茶香的归宿,所以人生的低谷也不是生命的全部。黑暗是光明的束缚,但光明却是黑暗的归宿。</p><p>灯光下,书架上一本金色题字的书的书名熠熠发光,我起身取下书,发现是海伦写的《假如给我三天光明》,我内心一热,忽然间有些感动。一个身体残疾的孩子,孱弱的身躯却支撑起了刚劲的灵魂。她的眼前一片漆黑,但内心世界却是光彩艳丽的。她的心中永远都有一抹无形的光,指引着她踏遍人生的路途。</p><p>时钟告诉我现在是凌晨四点。我舒展下筋骨,刚要起身,一只飞虫不偏不斜恰好闯入我的视线。灯光是它唯一的追寻。我不敢有任何的动作,只好也只能静静看着这只可爱的生灵。它好像不知道我是它的伙伴,也许它真的以为光亮是它唯一的追寻。但是我却欣慰于这突如其来的伙伴。光明可以带来很多东西,有时候也包括一份黑暗中的灵魂的一份安慰。我不忍打扰它与光明的约会,于是悄悄起身慢慢摸索着到了阳台。</p><p>阳台和我沉入了外面黑暗的梦乡。隐约可以听到一些不知名的昆虫在开着它们的舞会,我猜它们都端着无形的酒杯。它们在黑暗中买醉,因为明天的天明将是它们的散会。我有些感怀,也有些悲哀。</p><p>夜风股股咆哮着,我昂首顶住风头,仰望着漆黑的四周。很远很远处,灯塔若隐若现。它向来就是一头巨大的野兽,而此时它却空荡的像杯将尽的酒。它散发出柔弱的灯光在风中摇曳,忽明忽暗,像是一场与黑暗的决斗,随时都可能葬身在这黎明的前奏。</p><p>不忍心这惨烈的一幕,我踱步来到窗前。窗子很旧了,旧到已经无法再承受住时间的践踏了。可无论它多么陈旧,此时的它是神奇的——窗外黑暗窗内光。人心不也是一扇窗户吗?一半明媚一半伤。窗户把光亮的一面给了光明,人心是不是也应该把明媚的一半朝向世界呢?</p><p>屋内的灯倏忽闪了闪,我晃过神来,发现天边已经有抹淡红色了。黎明的脚步已经迈出了一半。那只与台灯约会的虫子似乎很满意,兴高采烈的向着呼之欲出的朝阳的方向窜去了。我望着它的背影,轻轻挥了挥手,心中暗念:“亲爱的朋友,恭喜你找到了属于自己的方向和追求,希望你能在光和热中接受生命的升华。”</p><p>天空见亮了,有点空灵,光晕中泛着感动。第一缕曙光穿透了云层,是的,我看到了,那一抹光,越来越近,越来越亮……</p>",
    "content_pic_list": null,
    "cover_avid": 0,
    "ctime": 1497973729,
    "dispute": null,
    "dyn_id_str": "38554821905721204",
    "dynamic": "It's now or never !",
    "id": 1,
    "image_urls": [
      "https://i0.hdslb.com/bfs/article/d2eedf1fd338bceca10099e2f7b33fa9017c859b.jpg"
    ],
    "is_like": false,
    "keywords": "天空像是倾倒出的墨水,黑得静谧而深邃。黎明还远,光亮全无。夜不能寐。披衣,起床。茶香的弥漫,一盏灯的相伴。夜,你是我久别重逢的朋友,那一刹那的相见,带给了我久违的安思。如果不是梦魇的皮闹,我本不该投入",
    "list": {
      "apply_time": "",
      "articles_count": 0,
      "check_time": "",
      "ctime": 1588601669,
      "id": 253534,
      "image_url": "",
      "mid": 91221505,
      "name": "静夜思",
      "publish_time": 1625900652,
      "read": 0,
      "reason": "",
      "state": 1,
      "summary": "",
      "update_time": 1625900518,
      "words": 3059
    },
    "media": {
      "area": "",
      "cover": "",
      "media_id": 0,
      "score": 0,
      "season_id": 0,
      "spoiler": 0,
      "title": "",
      "type_id": 0,
      "type_name": ""
    },
    "mtime": 1589209079,
    "origin_image_urls": [
      "https://i0.hdslb.com/bfs/article/b1e1029c08d8ad1bb06460d736839a7741dd7925.jpg"
    ],
    "origin_template_id": 4,
    "original": 0,
    "private_pub": 0,
    "publish_time": 1519913233,
    "reprint": 0,
    "state": 0,
    "stats": {
      "coin": 2543,
      "dislike": 1,
      "dynamic": 0,
      "favorite": 17520,
      "like": 32484,
      "reply": 14399,
      "share": 616,
      "view": 1631212
    },
    "summary": "天空像是倾倒出的墨水,黑得静谧而深邃。黎明还远,光亮全无。夜不能寐。披衣,起床。茶香的弥漫,一盏灯的相伴。夜,你是我久别重逢的朋友,那一刹那的相见,带给了我久违的安思。如果不是梦魇的皮闹,我本不该投入",
    "tags": [
      {
        "name": "静夜思",
        "tid": 239416
      }
    ],
    "template_id": 4,
    "title": "未知的光",
    "top_video_info": null,
    "total_art_num": 5,
    "type": 0,
    "version_id": 0,
    "words": 1190
  },
  "message": "0",
  "ttl": 1
}

type=3: cv41358718 通过 DevTools 绕过 SSR 抓包某站专栏正文接口

curl 'https://api.bilibili.com/x/article/view?id=41358718' \
-A 'Mozilla/10.0'
查看响应示例:
{
  "code": 0,
  "message": "0",
  "ttl": 1,
  "data": {
    "id": 41358718,
    "category": {
      "id": 26,
      "parent_id": 17,
      "name": "数码"
    },
    "categories": [
      {
        "id": 17,
        "parent_id": 0,
        "name": "科技"
      },
      {
        "id": 26,
        "parent_id": 17,
        "name": "数码"
      }
    ],
    "title": "通过 DevTools 绕过 SSR 抓包某站专栏正文接口",
    "summary": "背景\n最近没什么事做, 又回 BAC 看看有什么接口可以抓, 于是看到了陈年的专栏正文内容的 Issue #859\n由于泽生自己是写前端的, 对于浏览器 DevTools 的各项调试工具还算是比较了解, 打算想办法把专栏正文的接口抓下来 :)\n思路\n由于专栏正文完整的内容在页面加载的时候就已经通过 SSR (Server-Side Rendering, 服务端渲染) 的方式通过 <script> 标签注入到 HTML 网页中, 直接通过看 DevTools 的网络面板肯定是没用的\n[图片]\n最直接的方法是",
    "banner_url": "",
    "template_id": 4,
    "state": 0,
    "author": {
      "mid": 645769214,
      "name": "Session小胡",
      "face": "https://i2.hdslb.com/bfs/face/77906db03b1eefac02613de184afad03f7bc58d7.jpg",
      "pendant": {
        "pid": 0,
        "name": "",
        "image": "",
        "expire": 0
      },
      "official_verify": {
        "type": -1,
        "desc": ""
      },
      "nameplate": {
        "nid": 4,
        "name": "青铜殿堂",
        "image": "https://i2.hdslb.com/bfs/face/2879cd5fb8518f7c6da75887994c1b2a7fe670bd.png",
        "image_small": "https://i0.hdslb.com/bfs/face/6707c120e00a3445933308fd9b7bd9fad99e9ec4.png",
        "level": "普通勋章",
        "condition": "单个自制视频总播放数>=1万"
      },
      "vip": {
        "type": 1,
        "status": 0,
        "due_date": 0,
        "vip_pay_type": 0,
        "theme_type": 0,
        "label": {
          "path": "",
          "text": "",
          "label_theme": ""
        },
        "avatar_subscript": 0,
        "nickname_color": ""
      },
      "fans": 347,
      "level": 4
    },
    "reprint": 1,
    "image_urls": [
      "https://i0.hdslb.com/bfs/article/9071997152b6fec0ae465fe2a86b580e645769214.jpg"
    ],
    "publish_time": 1744789930,
    "ctime": 1744789930,
    "mtime": 1744789930,
    "stats": {
      "view": 51,
      "favorite": 2,
      "like": 3,
      "dislike": 0,
      "reply": 0,
      "share": 0,
      "coin": 2,
      "dynamic": 0
    },
    "tags": [
      {
        "tid": 12005,
        "name": "HTML"
      },
      {
        "tid": 336733,
        "name": "API"
      },
      {
        "tid": 854101,
        "name": "BAC"
      },
      {
        "tid": 2573142,
        "name": "JavaScript"
      },
      {
        "tid": 2709088,
        "name": "Bilibili"
      },
      {
        "tid": 2719113,
        "name": "GitHub"
      },
      {
        "tid": 2822654,
        "name": "Web"
      },
      {
        "tid": 3161936,
        "name": "Chromium"
      },
      {
        "tid": 3416147,
        "name": "DevTools"
      }
    ],
    "words": 2039,
    "origin_image_urls": [
      "https://i0.hdslb.com/bfs/article/9071997152b6fec0ae465fe2a86b580e645769214.jpg"
    ],
    "list": {
      "id": 326286,
      "mid": 645769214,
      "name": "电脑",
      "image_url": "",
      "update_time": 1744789924,
      "ctime": 1601636259,
      "publish_time": 1744789930,
      "summary": "",
      "words": 9501,
      "read": 0,
      "articles_count": 0,
      "state": 1,
      "reason": "",
      "apply_time": "",
      "check_time": ""
    },
    "is_like": false,
    "media": {
      "score": 0,
      "media_id": 0,
      "title": "",
      "cover": "",
      "area": "",
      "type_id": 0,
      "type_name": "",
      "spoiler": 0,
      "season_id": 0
    },
    "apply_time": "",
    "check_time": "",
    "original": 1,
    "act_id": 0,
    "dispute": null,
    "authenMark": null,
    "cover_avid": 0,
    "top_video_info": null,
    "type": 3,
    "check_state": 0,
    "origin_template_id": 4,
    "private_pub": 0,
    "content_pic_list": null,
    "content": "{\"ops\":[{\"insert\":\"背景\"},{\"attributes\":{\"header\":2},\"insert\":\"\\n\"},{\"insert\":\"最近没什么事做, 又回 BAC 看看有什么接口可以抓, 于是看到了陈年的专栏正文内容的 Issue #859\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"由于泽生自己是写前端的, 对于浏览器 DevTools 的各项调试工具还算是比较了解, 打算想办法把专栏正文的接口抓下来 :)\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"思路\"},{\"attributes\":{\"header\":2},\"insert\":\"\\n\"},{\"insert\":\"由于专栏正文完整的内容在页面加载的时候就已经通过 SSR (Server-Side Rendering, 服务端渲染) 的方式通过 <script> 标签注入到 HTML 网页中, 直接通过看 DevTools 的网络面板肯定是没用的\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"attributes\":{\"class\":\"normal-img\"},\"insert\":{\"native-image\":{\"alt\":\"read-normal-img\",\"url\":\"https://i0.hdslb.com/bfs/new_dyn/7bb121008990850bbd333fe504eebc90645769214.png\",\"width\":748,\"height\":494,\"size\":185239,\"status\":\"loaded\"}}},{\"insert\":\"最直接的方法是找到专栏相关的 JavaScript 代码, 通过大脑在没有 SourceMap 的情况下沿着 bundle 逆向出接口, 这种方法对于泽生明显不可行, 其中一个原因是屏幕太小而源码太大 (-.-;)\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"另一个方法是通过伪造 SSR 不工作, 从而诱导页面脚本手动通过 API 去获取内容, 不过伪造的方法也比较麻烦, 但可行度相对较高 😆\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"关于如何伪造 SSR 不工作的现象, 就需要分析 SSR 是如何被使用的, 某站的 SSR 是通过向页面注入 JavaScript 代码实现的, 具体是在 window 全局对象上加了个属性 __INITIAL_STATE__, 具体结构需要具体页面分析\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"目标就是要在读取之前移除这个属性, 移除方法简单, 无非是直接 window.__INITIAL_STATE__ = undefined (或者任意无效值), 或者 delete window.__INITIAL_STATE__, 别的 ES6 的反射之类的方法就不说了, 这不是重点\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"但这个脚本注入的位置就很恶心, 不在 <head> 里面, 也不在 <body> 底部, 就刚好在读取 __INITIAL_STATE__ 的脚本前面, 这使得无法使用篡改猴一类的用户脚本插件进行修改, 因为提供的用户脚本执行方式会出现还没写属性和已经读过属性的问题 (゜-゜)\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"接着就是传统的 DevTools 调试面板, 但正常手动暂停脚本执行往往已经脚本执行完成了, 解决方法就是网络节流或者 <head> 处用户脚本 debugger 语句\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"正好篡改猴高级设置允许用户脚本执行前暂停开始调试, 原理就是 debugger 语句, 只要把脚本执行位置放在 <body> 之前就有机会打断点把 __INITIAL_STATE__ 删掉 💥\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"attributes\":{\"class\":\"normal-img\"},\"insert\":{\"native-image\":{\"alt\":\"read-normal-img\",\"url\":\"https://i0.hdslb.com/bfs/new_dyn/1a20d186e8c8cddc043b5295d481c719645769214.png\",\"width\":625,\"height\":563,\"size\":320286,\"status\":\"loaded\"}}},{\"insert\":\"实践\"},{\"attributes\":{\"header\":2},\"insert\":\"\\n\"},{\"insert\":\"使用 Chromium (理论上 Firefox 也可以, 但其 DevTools 泽生用起来不是很舒服) 安装篡改猴扩展 🐒, 新建一个用户脚本, 里面是否写内容无所谓, 执行位置为 document-head, 然后在篡改猴的设置里面把调试脚本打开\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"接着打开一个使用 SSR 的网页, 不过注意要保持 DevTools 打开, 否则调试就不会生效, 页面脚本自动暂停后, 你将看到你的脚本和该页面已经加载的内容, 包含 HTML 网页, JavaScript 代码, 和 CSS 文本\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"你所需要做的就是检查 SSR 的情况是否如你所想的那样, 放在 window.__INITIAL_STATE__ 那里, 如果是直接渲染成页面元素的话, 抱歉上帝来了也救不了你, 某站 JavaScript 前端可不会管 HTML 的事情 🫠\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"如果在 __INITIAL_STATE__ 里面, 恭喜, 你可以直接继续了. 如果不是, 那就检查一下当前页面路径之类的, 看看有没有不同版本的相同内容, 毕竟专栏分新旧版, 专栏网页也分新旧版, 往往旧版能带来惊喜 ✨\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"找到了合适的 __INITIAL_STATE__, 你只需要在原地打个断点, 然后继续执行脚本, 页面就会在那里暂停, 接着执行下一步, 此时赋值完成, 可以开始改属性了\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"切到控制台, 执行那句期待已久的语句: window.__INITIAL_STATE__ = undefined, 大功告成! 🎉\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"恢复页面脚本执行, 你会看到页面好像卡了一下, 然后文章还是加载了出来, 这个时候就可以看看网络面板的情况了\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"attributes\":{\"class\":\"normal-img\"},\"insert\":{\"native-image\":{\"alt\":\"read-normal-img\",\"url\":\"https://i0.hdslb.com/bfs/new_dyn/87c88df1e15195996e34835456493d55645769214.png\",\"width\":892,\"height\":831,\"size\":473706,\"status\":\"loaded\"}}},{\"insert\":\"可以观察到, 多了一个你从来没见到过的叫 view 的野生请求, 点开详细, 复制地址, 记下参数, 保存响应, 发 Issue 去也~\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"如果这时回过头看控制台, 你会发现有这么一段错误十分显眼, 无法读取 undefined 的属性, 正是由于这个错误, 加之某站程序员的细心, 你才得以看到文章和抓到接口\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"后话\"},{\"attributes\":{\"header\":2},\"insert\":\"\\n\"},{\"insert\":\"Issue 已经发了, 估计写到文档里也是泽生的事情 🥴\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"这样的因 SSR 而省下的相关接口应该也有很多, 一个个慢慢打断点抓也不是个长久之计, 毕竟一旦直接渲染成 HTML, 就没的办法了 😮‍💨\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"学习正经的前端知识还是有利于不正经的逆向的 📚\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"希望大家都能来一起帮忙, Make BAC Great Again! 🥂\"},{\"attributes\":{\"list\":\"bullet\"},\"insert\":\"\\n\"},{\"insert\":\"有改动, 原文地址: https://sess.xhustudio.eu.org/posts/2025/devtools-vs-ssr-bac/\"},{\"attributes\":{\"blockquote\":true},\"insert\":\"\\n\"}]}",
    "keywords": "BULLET,HEADER,WINDOW,LIST,SSR,WIDTH,html,undefined,VIEW,FIREFOX,BAC,JAVASCRIPT,CLASS,脚本插件,SIZE,TRUE,es6,ALT,长久之计,HTTPS,CSS,控制台,一起帮,A_PI,解决方法,期待已久,大功告成,没什么,有什么,浏览器,很舒服,无所谓,有没有,程序员,有利于,不正经",
    "version_id": 0,
    "opus": {
      "opus_id": 1056353752004427792,
      "opus_source": 2,
      "title": "通过 DevTools 绕过 SSR 抓包某站专栏正文接口",
      "content": {
        "paragraphs": [
          {
            "para_type": 1,
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "背景",
                    "font_size": 22,
                    "style": {
                      "bold": true
                    },
                    "font_level": "xLarge"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "最近没什么事做, 又回 BAC 看看有什么接口可以抓, 于是看到了陈年的专栏正文内容的 Issue #859",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 2
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "由于泽生自己是写前端的, 对于浏览器 DevTools 的各项调试工具还算是比较了解, 打算想办法把专栏正文的接口抓下来 :)",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 1,
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "思路",
                    "font_size": 22,
                    "style": {
                      "bold": true
                    },
                    "font_level": "xLarge"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "由于专栏正文完整的内容在页面加载的时候就已经通过 SSR (Server-Side Rendering, 服务端渲染) 的方式通过 <script> 标签注入到 HTML 网页中, 直接通过看 DevTools 的网络面板肯定是没用的",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 2,
            "pic": {
              "pics": [
                {
                  "url": "https://i0.hdslb.com/bfs/new_dyn/7bb121008990850bbd333fe504eebc90645769214.png",
                  "width": 748,
                  "height": 494,
                  "size": 180.8974609375
                }
              ],
              "style": 1
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "最直接的方法是找到专栏相关的 JavaScript 代码, 通过大脑在没有 SourceMap 的情况下沿着 bundle 逆向出接口, 这种方法对于泽生明显不可行, 其中一个原因是屏幕太小而源码太大 (-.-;)",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 2
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "另一个方法是通过伪造 SSR 不工作, 从而诱导页面脚本手动通过 API 去获取内容, 不过伪造的方法也比较麻烦, 但可行度相对较高 😆",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 3
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "关于如何伪造 SSR 不工作的现象, 就需要分析 SSR 是如何被使用的, 某站的 SSR 是通过向页面注入 JavaScript 代码实现的, 具体是在 window 全局对象上加了个属性 __INITIAL_STATE__, 具体结构需要具体页面分析",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 4
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "目标就是要在读取之前移除这个属性, 移除方法简单, 无非是直接 window.__INITIAL_STATE__ = undefined (或者任意无效值), 或者 delete window.__INITIAL_STATE__, 别的 ES6 的反射之类的方法就不说了, 这不是重点",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 5
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "但这个脚本注入的位置就很恶心, 不在 <head> 里面, 也不在 <body> 底部, 就刚好在读取 __INITIAL_STATE__ 的脚本前面, 这使得无法使用篡改猴一类的用户脚本插件进行修改, 因为提供的用户脚本执行方式会出现还没写属性和已经读过属性的问题 (゜-゜)",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 6
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "接着就是传统的 DevTools 调试面板, 但正常手动暂停脚本执行往往已经脚本执行完成了, 解决方法就是网络节流或者 <head> 处用户脚本 debugger 语句",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 7
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "正好篡改猴高级设置允许用户脚本执行前暂停开始调试, 原理就是 debugger 语句, 只要把脚本执行位置放在 <body> 之前就有机会打断点把 __INITIAL_STATE__ 删掉 💥",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 2,
            "pic": {
              "pics": [
                {
                  "url": "https://i0.hdslb.com/bfs/new_dyn/1a20d186e8c8cddc043b5295d481c719645769214.png",
                  "width": 625,
                  "height": 563,
                  "size": 312.779296875
                }
              ],
              "style": 1
            }
          },
          {
            "para_type": 1,
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "实践",
                    "font_size": 22,
                    "style": {
                      "bold": true
                    },
                    "font_level": "xLarge"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "使用 Chromium (理论上 Firefox 也可以, 但其 DevTools 泽生用起来不是很舒服) 安装篡改猴扩展 🐒, 新建一个用户脚本, 里面是否写内容无所谓, 执行位置为 document-head, 然后在篡改猴的设置里面把调试脚本打开",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 2
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "接着打开一个使用 SSR 的网页, 不过注意要保持 DevTools 打开, 否则调试就不会生效, 页面脚本自动暂停后, 你将看到你的脚本和该页面已经加载的内容, 包含 HTML 网页, JavaScript 代码, 和 CSS 文本",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 3
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "你所需要做的就是检查 SSR 的情况是否如你所想的那样, 放在 window.__INITIAL_STATE__ 那里, 如果是直接渲染成页面元素的话, 抱歉上帝来了也救不了你, 某站 JavaScript 前端可不会管 HTML 的事情 🫠",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 4
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "如果在 __INITIAL_STATE__ 里面, 恭喜, 你可以直接继续了. 如果不是, 那就检查一下当前页面路径之类的, 看看有没有不同版本的相同内容, 毕竟专栏分新旧版, 专栏网页也分新旧版, 往往旧版能带来惊喜 ✨",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 5
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "找到了合适的 __INITIAL_STATE__, 你只需要在原地打个断点, 然后继续执行脚本, 页面就会在那里暂停, 接着执行下一步, 此时赋值完成, 可以开始改属性了",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 6
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "切到控制台, 执行那句期待已久的语句: window.__INITIAL_STATE__ = undefined, 大功告成! 🎉",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 7
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "恢复页面脚本执行, 你会看到页面好像卡了一下, 然后文章还是加载了出来, 这个时候就可以看看网络面板的情况了",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 2,
            "pic": {
              "pics": [
                {
                  "url": "https://i0.hdslb.com/bfs/new_dyn/87c88df1e15195996e34835456493d55645769214.png",
                  "width": 892,
                  "height": 831,
                  "size": 462.603515625
                }
              ],
              "style": 1
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "可以观察到, 多了一个你从来没见到过的叫 view 的野生请求, 点开详细, 复制地址, 记下参数, 保存响应, 发 Issue 去也~",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 2
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "如果这时回过头看控制台, 你会发现有这么一段错误十分显眼, 无法读取 undefined 的属性, 正是由于这个错误, 加之某站程序员的细心, 你才得以看到文章和抓到接口",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 1,
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "后话",
                    "font_size": 22,
                    "style": {
                      "bold": true
                    },
                    "font_level": "xLarge"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 1
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "Issue 已经发了, 估计写到文档里也是泽生的事情 🥴",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 2
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "这样的因 SSR 而省下的相关接口应该也有很多, 一个个慢慢打断点抓也不是个长久之计, 毕竟一旦直接渲染成 HTML, 就没的办法了 😮‍💨",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 3
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "学习正经的前端知识还是有利于不正经的逆向的 📚",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 6,
            "format": {
              "list_format": {
                "level": 1,
                "order": 4
              }
            },
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "希望大家都能来一起帮忙, Make BAC Great Again! 🥂",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          },
          {
            "para_type": 4,
            "text": {
              "nodes": [
                {
                  "node_type": 1,
                  "word": {
                    "words": "有改动, 原文地址: https://sess.xhustudio.eu.org/posts/2025/devtools-vs-ssr-bac/",
                    "font_size": 17,
                    "style": {},
                    "font_level": "regular"
                  }
                }
              ]
            }
          }
        ]
      },
      "tags": [
        {
          "tag": {
            "show_text": "HTML",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "API",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "BAC",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "JavaScript",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "Bilibili",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "GitHub",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "Web",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "Chromium",
            "link_type": 36
          }
        },
        {
          "tag": {
            "show_text": "DevTools",
            "link_type": 36
          }
        }
      ],
      "pub_info": {
        "uid": 645769214,
        "pub_time": 1744789930
      },
      "article": {
        "category_id": 26,
        "list_id": 326286,
        "originality": 1,
        "reproduced": 1,
        "cover": [
          {
            "url": "http://i0.hdslb.com/bfs/article/9071997152b6fec0ae465fe2a86b580e645769214.jpg",
            "width": 768,
            "height": 430,
            "size": 58.13671875
          }
        ],
        "biz_tags": [
          "JavaScript",
          "HTML",
          "Chromium",
          "DevTools",
          "BAC",
          "GitHub",
          "API",
          "Bilibili",
          "Web"
        ]
      },
      "version": {
        "cvid": 41358718,
        "version_id": 140614555890906624
      }
    },
    "dyn_id_str": "1056353752004427792",
    "total_art_num": 4
  }
}
在 GitHub 上编辑此页
最近更新: 2025/4/26 09:04
Contributors: SessionHu
Copyright © 2020-2025 SocialSisterYi | CC-BY-NC-4.0 Licensed