爬取b站评论

本博客旨在分享关于爬虫技术的学习和实践经验,仅供学习使用,请使用爬虫技术的用户自行承担相应的法律责任,务必在进行任何网络数据抓取操作之前,仔细审查相关法律法规,并取得相应的授权或同意。请确保你的行为符合道德和法律的双重标准,尊重知识产权和网站的服务协议,仅将此技术应用于正当、合法的学习和研究目的。

爬取b站评论

1.单视频评论

爬取某个视频的评论时,只需要对请求载荷的w_rid和wts进行加密,然后请求网络拿到数据,拿到的数据不需要解密。

这里的加密我直接扣js代码破解,比较简单,首先在comment_url.js里面写加密函数。这里有一个参数是从本地的localstorage里面拿到的,这里我直接写死了,但其实也没用,因为o和i都是常量。

java 复制代码
function lt(e) {
    ct = "wbi_img_urls";
    var t, r, n = function(e) {
        var t;
        if (e.useAssignKey)
            return {
                imgKey: e.wbiImgKey,
                subKey: e.wbiSubKey
            };
        var r = (null === (t = function(e) {
            try {
                return "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png-https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png"
            } catch (e) {
                return null
            }
        }(ct)) || void 0 === t ? void 0 : t.split("-")) || []
          , n = r[0]
          , o = r[1]
          , i = n ? ft(n) : e.wbiImgKey
          , a = o ? ft(o) : e.wbiSubKey;
        return {
            imgKey: i,
            subKey: a
        }
    }(arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {
        wbiImgKey: "",
        wbiSubKey: ""
    }), o = n.imgKey, i = n.subKey;
    // ,o = '7cd084941338484aae1ad9425b84077c', i = '4932caff0ff746eab6f01bf08b70ac45';
    if (o && i) {
        for (var a = (t = o + i,
        r = [],
        [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52].forEach((function(e) {
            t.charAt(e) && r.push(t.charAt(e))
        }
        )),
        r.join("").slice(0, 32)), u = Math.round(Date.now() / 1e3), s = Object.assign({}, e, {
            wts: u
        }), c = Object.keys(s).sort(), l = [], f = /[!'()*]/g, d = 0; d < c.length; d++) {
            var p = c[d]
              , h = s[p];
            h && "string" == typeof h && (h = h.replace(f, "")),
            null != h && l.push("".concat(encodeURIComponent(p), "=").concat(encodeURIComponent(h)))
        }
        var y = l.join("&");
        return {
            w_rid: at(y + a),
            wts: u.toString()
        }
    }
    return "sssss"
}
function ft(e) {
    return e.substring(e.lastIndexOf("/") + 1, e.length).split(".")[0]
}
r = function() {
    return e
}
EwordsToBytes=function(e) {
    console.log(e)
    for (var t = [], r = 0; r < 32 * e.length; r += 8)
        t.push(e[r >>> 5] >>> 24 - r % 32 & 255);
    return t
}
EbytesToWords=function(e) {
    for (var t = [], r = 0, n = 0; r < e.length; r++,
    n += 8)
        t[n >>> 5] |= e[r] << 24 - n % 32;
    return t
}
TstringToBytes=function(e) {
    return NstringToBytes(unescape(encodeURIComponent(e)))
},
TbytesToString=function(e) {
    return decodeURIComponent(escape(rt.bin.bytesToString(e)))
}
NstringToBytes= function(e) {
    for (var t = [], r = 0; r < e.length; r++)
        t.push(255 & e.charCodeAt(r));
    return t
},
NbytesToString=function(e) {
    for (var t = [], r = 0; r < e.length; r++)
        t.push(String.fromCharCode(e[r]));
    return t.join("")
}
function hFF(e, t, r, n, o, i, a) {
    var u = e + (t & r | ~t & n) + (o >>> 0) + a;
    return (u << i | u >>> 32 - i) + t
}
function yGG(e, t, r, n, o, i, a) {
    var u = e + (t & n | r & ~n) + (o >>> 0) + a;
    return (u << i | u >>> 32 - i) + t
}
function vHH(e, t, r, n, o, i, a) {
    var u = e + (t ^ r ^ n) + (o >>> 0) + a;
    return (u << i | u >>> 32 - i) + t
}
function bII(e, t, r, n, o, i, a) {
    var u = e + (r ^ (t | ~n)) + (o >>> 0) + a;
    return (u << i | u >>> 32 - i) + t
}
o = function o(i, a) {
    i.constructor == String ? i = a && "binary" === a.encoding ? NstringToBytes(i) : TstringToBytes(i) : r(i) ? i = Array.prototype.slice.call(i, 0) : Array.isArray(i) || i.constructor === Uint8Array || (i = i.toString());
    for (var u = EbytesToWords(i), s = 8 * i.length, c = 1732584193, l = -271733879, f = -1732584194, d = 271733878, p = 0; p < u.length; p++)
        u[p] = 16711935 & (u[p] << 8 | u[p] >>> 24) | 4278255360 & (u[p] << 24 | u[p] >>> 8);
    u[s >>> 5] |= 128 << s % 32,
    u[14 + (s + 64 >>> 9 << 4)] = s;
    var h = o._ff
      , y = o._gg
      , v = o._hh
      , b = o._ii;
    for (p = 0; p < u.length; p += 16) {
        var m = c
          , w = l
          , g = f
          , x = d;
        c = hFF(c, l, f, d, u[p + 0], 7, -680876936),
        d = hFF(d, c, l, f, u[p + 1], 12, -389564586),
        f = hFF(f, d, c, l, u[p + 2], 17, 606105819),
        l = hFF(l, f, d, c, u[p + 3], 22, -1044525330),
        c = hFF(c, l, f, d, u[p + 4], 7, -176418897),
        d = hFF(d, c, l, f, u[p + 5], 12, 1200080426),
        f = hFF(f, d, c, l, u[p + 6], 17, -1473231341),
        l = hFF(l, f, d, c, u[p + 7], 22, -45705983),
        c = hFF(c, l, f, d, u[p + 8], 7, 1770035416),
        d = hFF(d, c, l, f, u[p + 9], 12, -1958414417),
        f = hFF(f, d, c, l, u[p + 10], 17, -42063),
        l = hFF(l, f, d, c, u[p + 11], 22, -1990404162),
        c = hFF(c, l, f, d, u[p + 12], 7, 1804603682),
        d = hFF(d, c, l, f, u[p + 13], 12, -40341101),
        f = hFF(f, d, c, l, u[p + 14], 17, -1502002290),
        c = yGG(c, l = hFF(l, f, d, c, u[p + 15], 22, 1236535329), f, d, u[p + 1], 5, -165796510),
        d = yGG(d, c, l, f, u[p + 6], 9, -1069501632),
        f = yGG(f, d, c, l, u[p + 11], 14, 643717713),
        l = yGG(l, f, d, c, u[p + 0], 20, -373897302),
        c = yGG(c, l, f, d, u[p + 5], 5, -701558691),
        d = yGG(d, c, l, f, u[p + 10], 9, 38016083),
        f = yGG(f, d, c, l, u[p + 15], 14, -660478335),
        l = yGG(l, f, d, c, u[p + 4], 20, -405537848),
        c = yGG(c, l, f, d, u[p + 9], 5, 568446438),
        d = yGG(d, c, l, f, u[p + 14], 9, -1019803690),
        f = yGG(f, d, c, l, u[p + 3], 14, -187363961),
        l = yGG(l, f, d, c, u[p + 8], 20, 1163531501),
        c = yGG(c, l, f, d, u[p + 13], 5, -1444681467),
        d = yGG(d, c, l, f, u[p + 2], 9, -51403784),
        f = yGG(f, d, c, l, u[p + 7], 14, 1735328473),
        c = vHH(c, l = yGG(l, f, d, c, u[p + 12], 20, -1926607734), f, d, u[p + 5], 4, -378558),
        d = vHH(d, c, l, f, u[p + 8], 11, -2022574463),
        f = vHH(f, d, c, l, u[p + 11], 16, 1839030562),
        l = vHH(l, f, d, c, u[p + 14], 23, -35309556),
        c = vHH(c, l, f, d, u[p + 1], 4, -1530992060),
        d = vHH(d, c, l, f, u[p + 4], 11, 1272893353),
        f = vHH(f, d, c, l, u[p + 7], 16, -155497632),
        l = vHH(l, f, d, c, u[p + 10], 23, -1094730640),
        c = vHH(c, l, f, d, u[p + 13], 4, 681279174),
        d = vHH(d, c, l, f, u[p + 0], 11, -358537222),
        f = vHH(f, d, c, l, u[p + 3], 16, -722521979),
        l = vHH(l, f, d, c, u[p + 6], 23, 76029189),
        c = vHH(c, l, f, d, u[p + 9], 4, -640364487),
        d = vHH(d, c, l, f, u[p + 12], 11, -421815835),
        f = vHH(f, d, c, l, u[p + 15], 16, 530742520),
        c = bII(c, l = vHH(l, f, d, c, u[p + 2], 23, -995338651), f, d, u[p + 0], 6, -198630844),
        d = bII(d, c, l, f, u[p + 7], 10, 1126891415),
        f = bII(f, d, c, l, u[p + 14], 15, -1416354905),
        l = bII(l, f, d, c, u[p + 5], 21, -57434055),
        c = bII(c, l, f, d, u[p + 12], 6, 1700485571),
        d = bII(d, c, l, f, u[p + 3], 10, -1894986606),
        f = bII(f, d, c, l, u[p + 10], 15, -1051523),
        l = bII(l, f, d, c, u[p + 1], 21, -2054922799),
        c = bII(c, l, f, d, u[p + 8], 6, 1873313359),
        d = bII(d, c, l, f, u[p + 15], 10, -30611744),
        f = bII(f, d, c, l, u[p + 6], 15, -1560198380),
        l = bII(l, f, d, c, u[p + 13], 21, 1309151649),
        c = bII(c, l, f, d, u[p + 4], 6, -145523070),
        d = bII(d, c, l, f, u[p + 11], 10, -1120210379),
        f = bII(f, d, c, l, u[p + 2], 15, 718787259),
        l = bII(l, f, d, c, u[p + 9], 21, -343485551),
        c = c + m >>> 0,
        l = l + w >>> 0,
        f = f + g >>> 0,
        d = d + x >>> 0
    }
    return endian([c, l, f, d])
};
rotl=function(e, t) {
    return e << t | e >>> 32 - t
},
rotr=function(e, t) {
    return e << 32 - t | e >>> t
}
function endian(e) {
    if (e.constructor == Number)
        return 16711935 & rotl(e, 8) | 4278255360 & rotl(e, 24);
    for (var r = 0; r < e.length; r++)
        e[r] = endian(e[r]);
    return e
}
bytesToHex=function(e) {
    for (var t = [], r = 0; r < e.length; r++)
        t.push((e[r] >>> 4).toString(16)),
        t.push((15 & e[r]).toString(16));
    return t.join("")
}
var Qe= function(t, r) {
    if (null == t)
        throw new Error("Illegal argument " + t);
    var i = EwordsToBytes(o(t, r));
    return r && r.asBytes ? i : r && r.asString ? NbytesToString(i) : bytesToHex(i)
}
function Ze(e) {
    return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e
}
var at = Ze(Qe)
// e={
//     "oid": "1906333968",
//     "type": 1,
//     "mode": 3,
//     "pagination_str": "{\"offset\":\"{\\\"type\\\":1,\\\"direction\\\":1,\\\"session_id\\\":\\\"1778169679258543\\\",\\\"data\\\":{}}\"}",
//     "plat": 1,
//     "web_location": 1315875
// }
e={
    "oid": "1906333968",
    "type": 1,
    "mode": 3,
    "pagination_str": "{\"offset\":\"\"}",
    "plat": 1,
    "seek_rpid": "",
    "web_location": 1315875
}
console.log(lt(e))

然后在py里面调用js文件,获得加密后的载荷数据,然后请求,并对数据进行分析和保存,这里我存成了csv文件。这里需要注意的是参数有的是字符串有的是数字,所以严格按照输出的格式,否则加密结果一致通不过验证,还有就是pagination_str的格式,一定要按照控制台的输出格式写,否则验证失败。评论懒加载,第一次页拿到sessionid后后续请求时会带着sessionid。

java 复制代码
import requests
import urllib.parse
import csv
import execjs
oid="1906333968"#必须是string类型啊!!!
web_location= 1315875#必须是整数类型啊!!!
session_id=""#第一页无session_id
cookies = {
    'buvid3': '6C16A34E-4B78-F350-03AA-71E6B21A703519906infoc',
    'b_nut': '1726211919',
    '_uuid': '828DDCCD-F3CD-3997-11077-1729B6881A6120884infoc',
    'enable_web_push': 'DISABLE',
    'buvid4': '3CB58DB4-B2F0-07C1-06FA-E452949C4A8942274-024082300-j2Owk+KrE1E0oCXj+7DzqA%3D%3D',
    'header_theme_version': 'CLOSE',
    'rpdid': "|(u|kkmlu~ll0J'u~kYkukl|m",
    'fingerprint': '65fbd3ec7ea1fba4aa76eb96cb7f6249',
    'buvid_fp_plain': 'undefined',
    'buvid_fp': '65fbd3ec7ea1fba4aa76eb96cb7f6249',
    'DedeUserID': '37611353',
    'DedeUserID__ckMd5': 'af2f5320e5c29dea',
    'home_feed_column': '5',
    'browser_resolution': '2048-1023',
    'bili_ticket': 'eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzY0OTQzNTgsImlhdCI6MTczNjIzNTA5OCwicGx0IjotMX0.UA_DNnfYHwmuWf3mk3zAc45Ar6QrABl70LmFhjli-ms',
    'bili_ticket_expires': '1736494298',
    'SESSDATA': 'dcf4f3e6%2C1751950746%2C36331%2A12CjDsO7miIb_M9f1MQIIa7qIN5AucRW-WAnR_3eKJ6r4sPE3wgHTKNDZEFG6BeHrHqg4SVmd5YlUxTDM0NVdRQ3hHZHhNMFkyS0JQbjhvRWh2U0RTZElXVHFWSy1ZYkZVYzVHSlNhSWV4WUMxV0pRMHB1ZkV6TEFhd1RfaEZqVG90dUJvazNEUVV3IIEC',
    'bili_jct': '91812d98065f2f1035dfb5271f1057b6',
    'CURRENT_FNVAL': '4048',
    
    #TODO
    'sid': '6rzu47nf',#8位
    'b_lsid': 'EF10A7B92_1944D9191B0',#位
    'bp_t_offset_37611353': '1020612344109072384',#位
    
}

headers = {
    'accept': '*/*',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'no-cache',
    # 'cookie': "buvid3=6C16A34E-4B78-F350-03AA-71E6B21A703519906infoc; b_nut=1726211919; _uuid=828DDCCD-F3CD-3997-11077-1729B6881A6120884infoc; enable_web_push=DISABLE; buvid4=3CB58DB4-B2F0-07C1-06FA-E452949C4A8942274-024082300-j2Owk+KrE1E0oCXj+7DzqA%3D%3D; header_theme_version=CLOSE; rpdid=|(u|kkmlu~ll0J'u~kYkukl|m; fingerprint=65fbd3ec7ea1fba4aa76eb96cb7f6249; buvid_fp_plain=undefined; buvid_fp=65fbd3ec7ea1fba4aa76eb96cb7f6249; DedeUserID=37611353; DedeUserID__ckMd5=af2f5320e5c29dea; home_feed_column=5; browser_resolution=2048-1023; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzY0OTQzNTgsImlhdCI6MTczNjIzNTA5OCwicGx0IjotMX0.UA_DNnfYHwmuWf3mk3zAc45Ar6QrABl70LmFhjli-ms; bili_ticket_expires=1736494298; SESSDATA=dcf4f3e6%2C1751950746%2C36331%2A12CjDsO7miIb_M9f1MQIIa7qIN5AucRW-WAnR_3eKJ6r4sPE3wgHTKNDZEFG6BeHrHqg4SVmd5YlUxTDM0NVdRQ3hHZHhNMFkyS0JQbjhvRWh2U0RTZElXVHFWSy1ZYkZVYzVHSlNhSWV4WUMxV0pRMHB1ZkV6TEFhd1RfaEZqVG90dUJvazNEUVV3IIEC; bili_jct=91812d98065f2f1035dfb5271f1057b6; CURRENT_FNVAL=4048; sid=6rzu47nf; b_lsid=EF10A7B92_1944D9191B0; bp_t_offset_37611353=1020612344109072384",
    'origin': 'https://www.bilibili.com',
    'pragma': 'no-cache',
    'priority': 'u=1, i',
    'referer': 'https://www.bilibili.com/video/BV1xU411U7PW/?spm_id_from=333.1391.0.0&vd_source=fd84ddc58aead0485969c92933b61484',
    'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
    '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/131.0.0.0 Safari/537.36',
}


def save2csv(*args):
    if len(args) < 11:
        raise ValueError("参数错误.")
    with open(f"{args[10]}_{args[11]}.csv", "a", newline='',encoding="utf-8") as f:
        f_csv=csv.writer(f)#写入缓存
        if f.tell() == 0:  # 检查文件是否为空,如果是,则先写入表头
            headers_csv=['rpid','replay_count','message','like','avatar','sex','uname','oid','parent','is_end','all_count','name']
            f_csv.writerow(headers_csv)
        data = list(args)
        f_csv.writerow(data)#写入一行
def handle_content(list_comment,is_end,all_count,name):
    for comment in list_comment:
        rpid=comment["rpid"]#我的id
        count=comment["count"]
        replay_count=comment["rcount"]#回复数
        message=comment["content"]["message"]#回复内容
        like=comment["like"]#点赞数
        avatar=comment["member"]["avatar"]#回复者头像
        sex=comment["member"]["sex"]#回复者性别
        uname=comment["member"]["uname"]#回复者昵称
        oid=comment["oid"]#我以及我的回复者们共用id
        parent=comment["parent"]#回复者id
        if comment["replies"]:
            replies=handle_content(comment["replies"],is_end,all_count,name)
        # save2csv(rpid,replay_count,message,like,avatar,sex,uname,oid,parent,is_end,all_count,name)
        print(rpid,replay_count,message,like,avatar,sex,uname,oid,parent)
def handle_cursor(cursor):
    is_end=cursor["is_end"]#是否最后一页
    all_count=cursor["all_count"]#总评论数
    name=cursor["name"]#热门评论
    return is_end,all_count,name
def get_params(session_id):#{"offset":"{\"type\":1,\"direction\":1,\"session_id\":\"1778143604964054\",\"data\":{}}"}
    pagination_str = "{\"offset\":\"{\\\"type\\\":1,\\\"direction\\\":1,\\\"session_id\\\":\\\""+str(session_id)+"\\\",\\\"data\\\":{}}\"}" if session_id else '{\"offset\":\"\"}'
    params={
    "oid": oid,
    "type": 1,
    "mode": 3,
    "pagination_str": pagination_str,
    "plat": 1,
    'seek_rpid': '',
    "web_location": web_location
    }
    ctx=execjs.compile(open('./bili/comment_url.js','r',encoding='utf-8').read()).call('lt',params)
    params.update({
        'w_rid': ctx["w_rid"],
        'wts': ctx["wts"]
    })
    return params
if __name__=="__main__":
    count=1
    while True:
        params=get_params(session_id)
        print(params)
        response = requests.get(
        'https://api.bilibili.com/x/v2/reply/wbi/main',
        cookies=cookies,
        headers=headers,
        params=params,
    )
        # print(response.text)
        is_end,all_count,name=handle_cursor(response.json()["data"]["cursor"])
        handle_content(response.json()["data"]["replies"],is_end,all_count,name)
        print(f"第{count}页爬完了")
        count+=1
        if is_end==True:
            print(f"爬取完成,一共有{all_count}条")
            break

2.单视频弹幕

相关推荐
慢慢成长的码农16 分钟前
Kotlin 常量定义
开发语言·数据库·kotlin
WebDesign_Mu35 分钟前
HTML+CSS+JS制作中华传统美食主题网站(内附源码,含5个页面)
javascript·css·html
Eiceblue35 分钟前
Python在Excel工作表中创建数据透视表
开发语言·python·visualstudio·excel
nyf_unknown43 分钟前
(vue)el-table-column type=“selection“表格选框怎么根据条件添加禁选
前端·javascript·vue.js
yqcoder1 小时前
el-tabe 配合 xlsx 导出 excel 文件
javascript·vue.js·elementui
2401_858286111 小时前
124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法
c语言·开发语言·数据结构·算法·排序算法·
编程小筑1 小时前
TypeScript语言的网络编程
开发语言·后端·golang
老大白菜1 小时前
第6章:Go语言并发编程
开发语言·后端·golang
黑果果的思考1 小时前
C++例程:使用I/O模拟IIC接口(6)
开发语言·c++
西猫雷婶1 小时前
python学opencv|读取图像(三十)使用cv2.getAffineTransform()函数倾斜拉伸图像
开发语言·python·opencv