SocialSisterYi / bilibili-API-collect

哔哩哔哩-API收集整理【不断更新中....】
https://socialsisteryi.github.io/bilibili-API-collect/
Other
14.08k stars 1.61k forks source link

用户主页获取投稿列表 -352 新增校验 #868

Open o0HalfLife0o opened 8 months ago

o0HalfLife0o commented 8 months ago

https://api.bilibili.com/x/space/wbi/arc/search?mid=2&ps=30&tid=0&pn=1&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true&dm_img_list=[]&dm_img_str=V2ViR0wgMS&dm_cover_img_str=QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyBEaXJlY3QzRDExIHZzXzVfMCBwc181XzApR29vZ2xlIEluYy4gKEludGVsKQ&w_rid=e8319e347dca849eadcdd27f1a8a5a79&wts=1700567648

用户投稿多了dm_img_listdm_img_strdm_cover_img_str这3个值,缺少这3个值会报352错误,加上这3个值,wbi接口不添加wbi校验值都可以正常获取数据

{
    'code': -352,
    'message': '风控校验失败',
    'ttl': 1,
    'data': {
        'v_voucher': 'voucher_605d9ef3-9ca5-4498-ace1-8613ecf7a24f'
    }
}
zzhjiyin commented 8 months ago

是的,我也遇见同样问题

Luoxin commented 8 months ago

但是经过尝试,加了也还是有可能会有 -352 的报错,这三个字段似乎不是风控的关键内容

power721 commented 8 months ago

等待大佬

power721 commented 8 months ago

需要加User-Agent

Luoxin commented 8 months ago

需要加User-Agent

我添加过一下Header:

并添加了Chrome(v106)的浏览器指纹,但是依然风控,应该是别的原因

nethunter787 commented 8 months ago

同样发现这个问题,关键是 dm_cover_img_str 这个参数,一旦缺省,直接352错误。 晚上计划研究一下

0f-0b commented 8 months ago

dm_img_strdm_cover_img_str 的值似乎可以这样得到。

let version;
let rendererAndVendor;
const gl = document.createElement("canvas").getContext("webgl");
if (gl) {
  version = gl.getParameter(gl.VERSION);
  const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
  if (debugInfo) {
    rendererAndVendor = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) +
      gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
  }
}
if (version === undefined) { // 禁用了 WebGL
  console.log("dm_img_str", "bm8gd2ViZ2");
  console.log("dm_cover_img_str", "bm8gd2ViZ2");
} else {
  console.log("dm_img_str", btoa(version).slice(0, -2));
  if (rendererAndVendor === undefined) { // 禁用了 WEBGL_debug_renderer_info 扩展
    console.log("dm_cover_img_str", "bm8gd2ViZ2wgZXh0ZW5zaW");
  } else {
    console.log("dm_cover_img_str", btoa(rendererAndVendor).slice(0, -2));
  }
}
power721 commented 8 months ago
  • Referer

不能加Referer。 加User-Agent和cookie

zzhjiyin commented 8 months ago
  • Referer

不能加Referer。 加User-Agent和cookie

headers中加上cookie已暂时解决

CzJam commented 8 months ago

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

linix999 commented 8 months ago

https://www.52pojie.cn/thread-1861424-1-1.html 这个参数已经可以解决了

Luoxin commented 8 months ago

不能加Referer。

Referer信息应该不是影响因素,我尝试过添加和移除的场景,被风控的时间是差不多的

WolveStorm commented 8 months ago

这个我试了下,最后加了Headers加了个ua可以访问了,但是我并不确定是否频率高了仍会触发封控,需要观察。 HEADERS = { 'Referer': 'https://www.bilibili.com/', 'Connection': 'close', 'User-Agent': 'Mozilla/5.0' }

Luoxin commented 8 months ago

根据这几天的实验,cookie(如:buvid3, buvid4)、header(如:Referer'User-Agent)、以及请求参数(如:dm_cover_img_str)并没有和风控有强关联,不管是否存在,风控的触发基本相同,目前观察到的,似乎会用请求内容(包括上述的cookir。header、请求参数)作为为请求限频的参数。如果每次请求随机生成并尽可能不重复可以极大的降低风控概率。 另外根据上述行为猜测风控可能会有一些其他参数、行为控制,比如cookie中的bili_ticket,buvid_fp,或者一些上报用户行为的接口相关

Luoxin commented 8 months ago

不知道大家是什么情况,这是我测试时的情况。简化过的参数如下,偶尔失败。

import requests

headers = {
    # "authority": "api.bilibili.com",
    # "accept": "*/*",
    # "accept-language": "en,zh-CN;q=0.9,zh;q=0.8",    # 这行代码之后会被取消注释
    # "cache-control": "no-cache",
    "origin": "https://space.bilibili.com",
    # "pragma": "no-cache",
    "referer": "https://space.bilibili.com/2142762/video",
    # "sec-ch-ua": "^\\^Chromium^^;v=^\\^118^^, ^\\^Microsoft",
    # "sec-ch-ua-mobile": "?0",
    # "sec-ch-ua-platform": "^\\^Windows^^",
    # "sec-fetch-dest": "empty",
    # "sec-fetch-mode": "cors",
    # "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76",
}
cookies = {
    "buvid3": "58CBD4ED-4D82-666C-8EFE-337ABCED53DB95963infoc",
    "b_nut": "1701088795",
    # "_uuid": "65342583-1E84-E6105-F872-1A9C6FE2E38797168infoc",
    # "buvid4": "99832353-6338-2D1C-7AB7-F89A1A5DA21297090-023112712-",
    # "bili_ticket": "eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDEzNDc5OTcsImlhdCI6MTcwMTA4ODczNywicGx0IjotMX0.UcheAgY8MK1JqO6yqWO9vm0fJKjI06-F5BMUVucj2fk",
    # "bili_ticket_expires": "1701347937",
    # "buvid_fp": "094f1315a3795ae7695094f66ba4518e",
    # "b_lsid": "D810BE96C_18C1193F406",
    # "PVID": "1",
    # "innersign": "0",
    # "enable_web_push": "DISABLE",
    # "header_theme_version": "CLOSE",
    # "home_feed_column": "4",
    # "browser_resolution": "945-671"
}
url = "https://api.bilibili.com/x/space/wbi/arc/search"
params = {
    "mid": "2142762",
    "ps": "30",
    "tid": "0",
    "pn": "1",
    "keyword": "",
    "order": "pubdate",
    "platform": "web",
    "web_location": "1550101",
    "order_avoided": "true",
    # "dm_img_list": "^\\[^\\]",
    # "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
    # "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyA2MzAgKDB4MDAwMDU5MUIpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKEludGVsKQ",
    "w_rid": "0ea56ea45305ee113a85d1742ff3be12",
    "wts": "1701105288",
}
for i in range(20):
    response = requests.get(
        url, headers=headers, cookies=cookies, params=params
    )
    print(response.text[:50])

Snipaste_2023-11-28_02-10-25

如果请求头中使用 "accept-language": "en,zh-CN;q=0.9,zh;q=0.8",这样就没报错了,甚至不需要那 3 个 dm 开头的参数。

Snipaste_2023-11-28_02-10-57

https://github.com/SocialSisterYi/bilibili-API-collect/issues/868#issuecomment-1827706741 所言,你可能需要更多的实验来排除,因为单纯添加或修改某一个header或产生类似重置限频的行为

zzhjiyin commented 8 months ago

需要在headers中手动加入cookies 但是问题来了,我都手动添加cookies了,还需要爬虫再跑一次就有点徒劳无功了,请问有什么方式可以获取新的cookies呢?

Allenyep commented 8 months ago

需要在headers中手动加入cookies 但是问题来了,我都手动添加cookies了,还需要爬虫再跑一次就有点徒劳无功了,请问有什么方式可以获取新的cookies呢?

https://api.bilibili.com/x/frontend/finger/spi

ncdn commented 8 months ago

1 use chrome privacy mode , get ua && url's dm_img_str && dm_cover_img_str ,without time testing

2 login mode use ua , cookie's buvid3 && SESSDATA ,2day live

CzJam commented 8 months ago

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

11.29最新补充:dm_cover_img_str现在不能随便填了,经测试后面几个字符并不是标准的base64编码,但整体依然为固定值。可以直接从浏览器里复制出来用

o0HalfLife0o commented 8 months ago

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

11.29最新补充:dm_cover_img_str现在不能随便填了,经测试后面几个字符并不是标准的base64编码,但整体依然为固定值。可以直接从浏览器里复制出来用

我就是直接复制的浏览器的值,这几天一直正常 dm_img_str解密后WebGL 1 dm_cover_img_str解密后ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0)Google Inc. (Intel) 所以其实没必要随便填,直接用现成的值就行了

TommyLe1 commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

ncdn commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

mode 1 is okay 2days

TommyLe1 commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

mode 1 is okay 2days

thank U a lot

o0HalfLife0o commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

我也是,今天不行了

TommyLe1 commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

我也是,今天不行了

看上面ncdn大佬有解决办法

o0HalfLife0o commented 8 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

我也是,今天不行了

看上面ncdn大佬有解决办法

~是ua和那俩个值要对应,对吗~ 我刚试了下,和ua无关,补上dm_img_list=[] 这个参数,加上dm_img_strdm_cover_img_str就可以了

srx-2000 commented 7 months ago

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

11.29最新补充:dm_cover_img_str现在不能随便填了,经测试后面几个字符并不是标准的base64编码,但整体依然为固定值。可以直接从浏览器里复制出来用

应该是显卡和浏览器信息经过base64之后去掉最后两位,但是经过测试在dm_img_list置空的前提下,显卡信息加密出来的值本身并不校验【但是如果没有dm_img_list的轨迹的话,单个dm_cover_img_str请求多了之后有概率失败】。所以个人的解决方法是直接随机一个小数,并且用base64加密一下去掉最后两位,测了一下还是相对稳定的。

iiicebearrr commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序

  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)
o0HalfLife0o commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

我目前是每次都随机数并base64加密来作为dm_img_str和dm_cover_img_str值,可以不用sleep,立刻获取数据

iiicebearrr commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

llyer commented 7 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

mode 1 is okay 2days

thank U a lot

什么意思,加 cookie 只能生效两天吗?

llyer commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

好像跟你们说的都没关系啊,直接复制完整cookie就能正常访问,浏览器邮件复制链接的到nodejs中直接用fetch跑,可以直接获得数据,不用这三个新参数,只用一个 https://api.bilibili.com/x/space/wbi/arc/search?mid=2 这个链接就有数据,没cookie的话怎么试都没用。我试了加了你们这三个参数,还是访问不了啊

image

iiicebearrr commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

好像跟你们说的都没关系啊,直接复制完整cookie就能正常访问,浏览器邮件复制链接的到nodejs中直接用fetch跑,可以直接获得数据,不用这三个新参数,只用一个 https://api.bilibili.com/x/space/wbi/arc/search?mid=2 这个链接就有数据,没cookie的话怎么试都没用。我试了加了你们这三个参数,还是访问不了啊

image

上面复现的不是基于cookie的, 要完整的url参数才可以, 参考encrypt_string以及后面重新拼接url的部份, 这个复现方案是不需要cookie的,只需要mid。需要cookie的话要考虑cookie的维护复杂度吧,这个没测试过,直接复制cookie能访问的话可能是阿B的策略允许

HashLiver commented 7 months ago

我目前是每次都随机数并base64加密来作为dm_img_str和dm_cover_img_str值,可以不用sleep,立刻获取数据

不知道你们用的是什么随机数,至少我无论是base64随机数还是随机字符还是352。是不是我请求的参数有问题。

curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" -e "https://space.bilibili.com/" -H "Content-Type: application/json" -H "accept-language: zh-CN,zh;q=0.9" "https://api.bilibili.com/x/space/wbi/arc/search?mid=2&tid=0&order=pubdate&order_avoided=true&platform=web&web_location=1550101&dm_img_list=[]&dm_img_str=ODQ5NDgwND&dm_cover_img_str=NDg0OTM0MD&ps=30&pn=1&w_rid=7d327b25554dde6e27c8d65775288811&wts=1702221586" -b "buvid3=055AC15E-C41A-E796-602B-95A5E80CAA7F18734infoc; buvid4=3B796197-5865-F7B6-EC4C-32F618CA502A18734-023121015-"

o0HalfLife0o commented 7 months ago

我目前是每次都随机数并base64加密来作为dm_img_str和dm_cover_img_str值,可以不用sleep,立刻获取数据

不知道你们用的是什么随机数,至少我无论是base64随机数还是随机字符还是352。是不是我请求的参数有问题。

curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" -e "https://space.bilibili.com/" -H "Content-Type: application/json" -H "accept-language: zh-CN,zh;q=0.9" "https://api.bilibili.com/x/space/wbi/arc/search?mid=2&tid=0&order=pubdate&order_avoided=true&platform=web&web_location=1550101&dm_img_list=[]&dm_img_str=ODQ5NDgwND&dm_cover_img_str=NDg0OTM0MD&ps=30&pn=1&w_rid=7d327b25554dde6e27c8d65775288811&wts=1702221586" -b "buvid3=055AC15E-C41A-E796-602B-95A5E80CAA7F18734infoc; buvid4=3B796197-5865-F7B6-EC4C-32F618CA502A18734-023121015-"

        params["dm_img_list"] = []
        dm_rand = ['QQ','Qg','Qw','RA','RQ']
        params["dm_img_str"] = random.choice(dm_rand)
        params["dm_cover_img_str"] = random.choice(dm_rand)
llyer commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

好像跟你们说的都没关系啊,直接复制完整cookie就能正常访问,浏览器邮件复制链接的到nodejs中直接用fetch跑,可以直接获得数据,不用这三个新参数,只用一个 https://api.bilibili.com/x/space/wbi/arc/search?mid=2 这个链接就有数据,没cookie的话怎么试都没用。我试了加了你们这三个参数,还是访问不了啊 image

上面复现的不是基于cookie的, 要完整的url参数才可以, 参考encrypt_string以及后面重新拼接url的部份, 这个复现方案是不需要cookie的,只需要mid。需要cookie的话要考虑cookie的维护复杂度吧,这个没测试过,直接复制cookie能访问的话可能是阿B的策略允许

@iiicebearrr 你的意思是这两种情况都可以实现访问吗?两种都可以访问?一种用cookie,另外一种用wbi,还有现在新加的dm_img_list、dm_img_str、dm_cover_img_str 这几个参数吗

iiicebearrr commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

好像跟你们说的都没关系啊,直接复制完整cookie就能正常访问,浏览器邮件复制链接的到nodejs中直接用fetch跑,可以直接获得数据,不用这三个新参数,只用一个 https://api.bilibili.com/x/space/wbi/arc/search?mid=2 这个链接就有数据,没cookie的话怎么试都没用。我试了加了你们这三个参数,还是访问不了啊 image

上面复现的不是基于cookie的, 要完整的url参数才可以, 参考encrypt_string以及后面重新拼接url的部份, 这个复现方案是不需要cookie的,只需要mid。需要cookie的话要考虑cookie的维护复杂度吧,这个没测试过,直接复制cookie能访问的话可能是阿B的策略允许

@iiicebearrr 你的意思是这两种情况都可以实现访问吗?两种都可以访问?一种用cookie,另外一种用wbi,还有现在新加的dm_img_list、dm_img_str、dm_cover_img_str 这几个参数吗

我给出的复现方案是不需要cookie的,只需要提供mid就能访问到接口数据。 而且我上面给出的复现方案是专门针对主页接口的(https://api.bilibili.com/x/space/wbi/arc/search), 具体可以看下我的个人仓库spiders_for_all中的spiders_for_all.bilibili.spiders.AuthorSpider的实现细节, 这里我不贴具体链接了。

至于其他方案(cookie/wbi等)我没有测试过,你可以参考一下其他用户的回复。

cxw620 commented 7 months ago

虽然感觉楼上都跑题了, wbi 鉴权错误只会导致返回个 v_voucher, 错误码还是 0. 出现 -412 之类的错误考虑 Cookie 没有或者不合法

renmu123 commented 7 months ago

ts实现,无需cookie,dm_img_str等参数 你也可以在这里看源码:https://github.com/renmu123/biliAPI/blob/master/src/user/index.ts#L82 实现来源:https://www.52pojie.cn/thread-1862056-1-1.html

  /**
   * 用户投稿列表
   */
  async getVideos(params: {
    mid: number;
    ps?: number;
    pn?: number;
    tid?: number;
    keyword?: string;
    order?: "pubdate" | "click" | "stow";
  }) {
    const defaultParams = {
      ps: "30",
      tid: "0",
      pn: "1",
      keyword: "",
      order: "pubdate",
      platform: "web",
      web_location: "1550101",
      order_avoided: "true",
    };
    const cookie =
      "buvid3=57ADE427-90A8-6E7D-F341-02E62CA23E1B39631infoc;b_nut=1701088795";
    const signParams = await this.client.WbiSign({
      ...defaultParams,
      ...params,
    });
    return this.request.get(
      `https://api.bilibili.com/x/space/wbi/arc/search?${signParams}`,
      {
        headers: {
          cookie: cookie,
          "User-Agent": "Mozilla/5.0",
          origin: "https://space.bilibili.com",
          "accept-language": "en,zh-CN;q=0.9,zh;q=0.8",
        },
      }
    );
  }
TommyLe1 commented 7 months ago

加了cookies和dm_cover_img_str后前几天都很正常,但是今天突然发现又报-352了,又有什么规则变了吗?

mode 1 is okay 2days

thank U a lot

什么意思,加 cookie 只能生效两天吗?

大佬的意思是登录状态下的cookies两天内有效,未登陆的cookies永久有效。我照这个方法试了下到现在还很稳定。

smufan commented 7 months ago

实测5次均成功。

  1. w_rid按照这个方式计算(字段顺序不对貌似有影响, 建议别改,下面的顺序是devtools里debug扣出来的, 并不是参数升序, 有一定区别):

填入对应的dm_img_str, dm_cover_img_str(这两个看起来是固定值), 以及mid, pn, ps

encrypt_string = "dm_cover_img_str={dm_cover_img_str}&dm_img_list=%5B%5D&dm_img_str={dm_img_str}&keyword=&mid={mid}&order=pubdate&order_avoided=true&platform=web&pn={pn}&ps={ps}&tid=&web_location="

计算w_rid时用上面的fmt出来的string + f"&wts={wts}" + 根据img_keysub_key计算出来的key做一次md5得到

  1. 请求时的实际url需要用 encrypt_string + &w_rid={w_rid} + &wts={wts}这个顺序
  2. 分页查询的时候需要sleep 2-5s

这里只贴一下部份伪代码:

   def get_wrid(self, params: str) -> str:
        md5 = hashlib.md5()

        md5.update(
            (params + self.key).encode(),   # self.key 就是img_key和sub_key计算的,我是直接从js里扣出来转python的这里就不贴了
        )

        return md5.hexdigest()

    def request(self) -> dict:
        wts = round(time.time())

        params = self.encrypt_string_fmt.format(
            mid=self.mid,
            ps=self.page_size,
            pn=self.page_number,
            tid="",
            web_location="",
            dm_img_str=settings.BILIBILI_PARAM_DM_IMG_STR,
            dm_cover_img_str=settings.BILIBILI_PARAM_DM_COVER_IMG_STR,
        )

        wrid = self.get_wrid(params + f"&wts={wts}")

        params = "&".join([params, f"w_rid={wrid}", f"&wts={wts}"])

        return requests.get(self.api, headers={"User-Agent": 这里用库换成随机的ua}, params=params)

增大测试次数发现在cookie中添加SESSDATA能一定程度上提高成功率, 但是分页多了以后仍然有可能会触发风控:(

好像跟你们说的都没关系啊,直接复制完整cookie就能正常访问,浏览器邮件复制链接的到nodejs中直接用fetch跑,可以直接获得数据,不用这三个新参数,只用一个 https://api.bilibili.com/x/space/wbi/arc/search?mid=2 这个链接就有数据,没cookie的话怎么试都没用。我试了加了你们这三个参数,还是访问不了啊 image

上面复现的不是基于cookie的, 要完整的url参数才可以, 参考encrypt_string以及后面重新拼接url的部份, 这个复现方案是不需要cookie的,只需要mid。需要cookie的话要考虑cookie的维护复杂度吧,这个没测试过,直接复制cookie能访问的话可能是阿B的策略允许

@iiicebearrr 你的意思是这两种情况都可以实现访问吗?两种都可以访问?一种用cookie,另外一种用wbi,还有现在新加的dm_img_list、dm_img_str、dm_cover_img_str 这几个参数吗

我给出的复现方案是不需要cookie的,只需要提供mid就能访问到接口数据。 而且我上面给出的复现方案是专门针对主页接口的(https://api.bilibili.com/x/space/wbi/arc/search), 具体可以看下我的个人仓库spiders_for_all中的spiders_for_all.bilibili.spiders.AuthorSpider的实现细节, 这里我不贴具体链接了。

至于其他方案(cookie/wbi等)我没有测试过,你可以参考一下其他用户的回复。

请求头中有你的登录信息, 如果在无痕模式下就会出现352

My-Responsitories commented 7 months ago

实测dm_img_str, dm_cover_img_str可为随机长度的可打印字符再base64, 且user-agent会对校验造成显著影响.

对于某个ip, 一般会有多个ua是免校验的, 即使不sleep异步请求99个mid也不会出现-352(特殊的, Mozilla/5.0似乎每个ip都是免校验的), 这种ua到了另一个ip不一定免校验, 这种ua和版本号有关, 即便只把版本号改为上/下个版本, 也会丢失该效果, 但修改操作系统不会产生影响. 暂不确定这种ua会不会因为大量请求重新被拉黑

对于由python的fake_useragent库所生成的ua, 在不sleep异步请求99个mid时大约\frac{1}{3}的请求会出现-352

如果使用非免校验的ua, 不sleep异步请求99个mid, 大约只有\frac{1}{5}不出现-352错误, 且短时间内使用该ua请求任意mid都是-352

未测试cookie, buvid3对其的影响, 有没有refereraccept-language对其没有影响

测试(sign函数自行实现):

ua = fake_useragent.FakeUserAgent()

async def req(client, mid):
    params = {
        'mid': str(mid),
        'ps': '30',
        'pn': '1',
        'order': 'pubdate',
        'dm_img_list': '[]',
        'dm_img_str': base64.b64encode("".join(random.choices(string.printable, k=random.randint(16, 64))).encode())[:-2].decode(),
        'dm_cover_img_str': base64.b64encode("".join(random.choices(string.printable, k=random.randint(32, 128))).encode())[:-2].decode(),
        'wts': str(int(time.time()))
    }

    response = await client.get(
        'https://api.bilibili.com/x/space/wbi/arc/search',
        headers={'user-agent': ua.random},
        params=sign(params)
    )
    code = response.json()['code']
    return code

async def main():
    client = httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(retries=2, http2=True))
    codes = await asyncio.gather(*(
        req(client, mid)
        for mid in range(1, 100)
    ))
    print(Counter(codes))

asyncio.run(main())
z0z0r4 commented 7 months ago

emm,我这边也观察到有 -352 相关的 issue...

这边有大佬做个总结吗,看迷糊了,具体原因是新参数 dm_img_list dm_img_str dm_cover_img_str 吗?

直接拷贝 WEB 上生成的链接,然后加上 HEADERS={'Referer': 'https://www.bilibili.com/', 'User-Agent': 'Mozilla/5.0'} 我这就能不限速请求 700+ 次数,姑且是看作只有三个参数的改动

image

z0z0r4 commented 7 months ago

近几天感觉风控没有那么严格了。如果是一个星期前,连Chrome无痕模式正常浏览用户投稿页面都有概率出现-352

那是因为 dm_img_list 缺失吧

z0z0r4 commented 7 months ago

这个就不太理解了...accept-language 算什么鬼

但是在请求头中加入 accept-language: en,zh-CN;q=0.9,zh;q=0.8 就可以连续请求了。

https://www.52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1862056&pid=48739723

My-Responsitories commented 7 months ago

emm,我这边也观察到有 -352 相关的 issue...

这边有大佬做个总结吗,看迷糊了,具体原因是新参数 dm_img_list dm_img_str dm_cover_img_str 吗?

直接拷贝 WEB 上生成的链接,然后加上 HEADERS={'Referer': 'https://www.bilibili.com/', 'User-Agent': 'Mozilla/5.0'} 我这就能不限速请求 700+ 次数,姑且是看作只有三个参数的改动

Mozilla/5.0这个ua好像有点特殊, 其他会出现-352的链接都可以用这个ua不限量高并发请求

z0z0r4 commented 7 months ago

emm,我这边也观察到有 -352 相关的 issue... 这边有大佬做个总结吗,看迷糊了,具体原因是新参数 dm_img_list dm_img_str dm_cover_img_str 吗? 直接拷贝 WEB 上生成的链接,然后加上 HEADERS={'Referer': 'https://www.bilibili.com/', 'User-Agent': 'Mozilla/5.0'} 我这就能不限速请求 700+ 次数,姑且是看作只有三个参数的改动

Mozilla/5.0这个ua好像有点特殊, 其他会出现-352的链接都可以用这个ua不限量高并发请求

这样吗?那我再测试下别的

My-Responsitories commented 7 months ago

这个就不太理解了...accept-language 算什么鬼

但是在请求头中加入 accept-language: en,zh-CN;q=0.9,zh;q=0.8 就可以连续请求了。

52pojie.cn/forum.php?mod=redirect&goto=findpost&ptid=1862056&pid=48739723

这边测试加上accept-language: en,zh-CN;q=0.9,zh;q=0.8并没有明显减少-352出现次数, 这边测试开100并发只有18个成功, 不加也有16个成功, 但是UA用Mozilla/5.0100并发也可以100%成功

z0z0r4 commented 7 months ago

呆滞.jpg 虽然这个 headers 很诱人,但还有别的接口是这个太假的 header 过不了的吧

是不是后端正则偶然写炸了...

z0z0r4 commented 7 months ago

不同的 event 会产生不同的 type,观察 dm_img_list 当前无论输入什么值都不会影响访问,即使 json 格式错误,有待后续,估计B站没做好就上线了

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from datetime import datetime

# 提供的数据
data = []

# 解析时间戳并按时间排序
data.sort(key=lambda x: x["timestamp"])
timestamps = [datetime.utcfromtimestamp(entry["timestamp"]) for entry in data]

# 提取坐标
x_values = [entry["x"] for entry in data]
y_values = [entry["y"] for entry in data]
z_values = [entry["z"] for entry in data]

# 创建3D图表
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# 连线绘制
ax.plot(x_values, y_values, z_values, label='Trajectory', marker='o')

# 添加标题和标签
ax.set_title('3D Trajectory Visualization')
ax.set_xlabel('X Coordinate')
ax.set_ylabel('Y Coordinate')
ax.set_zlabel('Z Coordinate')

# 显示图例
ax.legend()

# 显示图表
plt.show()

image