删除B站动态或视频的评论(仅限自己的)

上篇文章爬取了B站数据,目的是删除自己的评论,具体在前言有说。现在得到了评论,该怎么删除(仅限自己的评论)?

方法一

在存放评论的文件夹内,运行以下代码,会得到所有要删除的评论的URL,存在当前文件夹内的txt文件中。若你是视频的评论,请自行修改get_url()中生成url的代码。

import os
import csv
import re

dir_path=os.path.abspath(os.path.dirname(__file__))

delete_comment_url_list=[] # 所有要删除的评论的URL


def input_mid(): # 输入帐号ID
    while True:
        mid=input('请输入你的帐号ID,是一串数字,可以在个人空间的URL中看到,或个人空间右侧的UID:')
        pattern=re.compile(r'\D+') # 非数字内容至少出现一次
        try:
            result=pattern.search(mid).group()
        except AttributeError: # 没有匹配到非数字内容,说明输入格式正确,所以终止循环
            break
        except:
            raise Exception('发生了未捕获的异常')
        if result:
            print('无效的帐号ID,请重新输入')
    return mid


def get_dynamic_id(file_name): # 从csv文件名中提取dynamic_id
    temp_str=file_name.split('ID')[1]
    pattern=re.compile(r'\D{1}') # 只匹配一次,即第一个非数字字符
    result=pattern.search(temp_str).group() # ID后面的第一个非数字字符
    dynamic_id=temp_str.split(result,1)[0] # ID这两个字符已去掉,ID后第一个字符是数字,从这个数字开始,到第一个非数字字符结束(不含这个非数字字符),就是dynamic_id
    return dynamic_id


def get_url(mid): # 生成需要删除的评论的URL
    file_list=os.listdir(dir_path)
    for file_name in file_list:
        file_path=dir_path+'/'+file_name
        if (file_name[-3:] in ['csv','CSV']) and (os.path.isfile(file_path)):            
            with open(file_path,'r',newline='',encoding='utf-8') as f:
                reader=csv.DictReader(f) # 将每行中的信息映射到一个OrderedDict。省略fieldnames参数时,键是csv文件的标题
                for row in reader: # row是当前行的内容
                    if row['mid']==mid:
                        dynamic_id=get_dynamic_id(file_name)
                        url='https://t.bilibili.com/'+dynamic_id+'#reply'+row['rpid']
                        delete_comment_url_list.append(url)


def save_data(): # 每行写入一个URL,复制的代理IP池.py
    file_path=dir_path+'/'+'delete_bilibili_comment_url.txt'
    with open(file_path,'w',encoding='utf-8') as f:
        f.write('') # 由于实际写入时是追加,所以每次重新运行时需先清空旧数据
    with open(file_path,'a',encoding='utf-8') as f:
        for i in delete_comment_url_list:
            f.write(i+'\n')
    print('数据已保存到本地')


if __name__=='__main__':
    mid=input_mid()
    get_url(mid)
    save_data()

链接示例(针对动态,不过视频也是以#reply+评论ID拼接)

https://t.bilibili.com/56764707xxxxxxxxxx#reply53xxxxxxxx

打开这个txt,访问里面的链接,就能定位到自己的评论。 如果没有定位到,将页面滚动到底部,手动切换几次页码,如图,再在浏览器地址栏按回车(我没试刷新页面管不管用),这样多试几次,就能定位到自己的评论,然后点击旁边的三个点,删除。

方法二

如果要删除的评论很多,方法一就不合适,太慢了。

先手动删除一条评论,用fiddler抓包,根据URL和参数写出代码,自动批量删除。

代码中需要填上你的Cookie和抓到的表单参数csrf的值才能运行。

另外,csrf参数我猜应该和Flask或Django中的表单隐藏域类似,是表单中一个隐藏的html元素(如图),但我没在右键-检查中找到,有知道获取它的值的方法可以告诉我。

Flask中的表单隐藏域
import requests
import os
import re
import csv
import time

url='https://api.bilibili.com/x/v2/reply/del'
headers={
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.43',
    'Referer':'https://t.bilibili.com/',
    'Cookie':"" # 需要Cookie,可在打开一条动态时用fiddler抓取
}

dir_path=os.path.abspath(os.path.dirname(__file__))
error_info_list=[] # 所有删除评论失败时错误日志需要的信息,列表中每个元素是字典,键是信息中参数名,值是参数值
OK_info_list=[] # 所有删除评论成功时日志需要的信息,列表中每个元素是字典,键是信息中参数名,值是参数值


def input_mid(): # 输入帐号ID
    while True:
        mid=input('请输入你的帐号ID,是一串数字,可以在个人空间的URL中看到,或个人空间右侧的UID:')
        pattern=re.compile(r'\D+') # 非数字内容至少出现一次
        try:
            result=pattern.search(mid).group()
        except AttributeError: # 没有匹配到非数字内容,说明输入格式正确,所以终止循环
            break
        except:
            raise Exception('发生了未捕获的异常')
        if result:
            print('无效的帐号ID,请重新输入')
    return mid


def get_dynamic_id_and_oid_and_type1(file_name): # 获取dynamic_id和oid和type1
    temp_list=file_name.split(',')

    dynamic_id=temp_list[0].split('ID')[1]
    oid=temp_list[1].split('oid')[1]

    temp_str_for_type=temp_list[2].split('type')[1]
    pattern=re.compile(r'\D{1}')
    result=pattern.search(temp_str_for_type).group()
    type1=temp_str_for_type.split(result,1)[0] # 和爬取评论的代码中type_in_url_for_get_comment是同一个值

    return dynamic_id, oid, type1


def get_rpid(file_path,mid): # 获取当前动态中指定用户的所有评论ID
    rpid_list=[] # 有些动态下,同一个人有多条评论,把它们的rpid存在列表中
    with open(file_path,'r',newline='',encoding='utf-8') as f:
        reader=csv.DictReader(f)
        for row in reader:
            if row['mid']==mid:
                rpid_list.append(row['rpid'])
    return rpid_list


def save_error_log(): # 删除失败时,保存日志
    file_name='删除评论失败时错误日志.txt'
    file_path=dir_path+'/'+file_name
    # 每行写入一句日志
    with open(file_path,'w',encoding='utf-8') as f:
        f.write('') # 由于实际写入时是追加,所以每次重新运行时需先清空旧数据
    with open(file_path,'a',encoding='utf-8') as f:
        for error_info_dict in error_info_list:
            t=f'删除B站动态ID{error_info_dict["dynamic_id"]}中评论ID{error_info_dict["rpid"]}时失败,对应的oid是{error_info_dict["oid"]},type是{error_info_dict["type1"]},服务端返回的json数据是{error_info_dict["text"]}'
            f.write(t+'\n')
    print('错误日志已保存到本地')


def save_OK_log(): # 记录已删除的评论信息
    file_name='删除评论成功时日志.txt'
    file_path=dir_path+'/'+file_name
    # 每行写入一句日志
    with open(file_path,'w',encoding='utf-8') as f:
        f.write('') # 由于实际写入时是追加,所以每次重新运行时需先清空旧数据
    with open(file_path,'a',encoding='utf-8') as f:
        for OK_info_dict in OK_info_list:
            t=f'已删除B站动态ID{OK_info_dict["dynamic_id"]}中评论ID{OK_info_dict["rpid"]},服务端返回的json数据是{OK_info_dict["text"]}'
            f.write(t+'\n')
    print('成功日志已保存到本地')


def delete_comment(file_name,file_path,mid): # 删除评论
    # 若执行过程中因报错导致终止,则有一些评论已被删除,剩一些未删除;再次执行时,还会删除之前已删除的评论,B站仍然返回删除成功的json数据(手动构造含#reply的URL,访问时(按博客里方法一多试几次)无法定位到对应的评论,说明真的已被删除)。所以不影响最终结果,我就未适配这种情况(即下次删除前先自动剔除已删除的),只记录了删除成功的日志
    dynamic_id, oid, type1=get_dynamic_id_and_oid_and_type1(file_name)
    rpid_list=get_rpid(file_path,mid)
    if len(rpid_list)>0: # 说明当前csv文件中有指定用户的评论
        for rpid in rpid_list:
            form_data={
                'oid':oid,
                'type':type1,
                'jsonp':'jsonp',
                'rpid':rpid,
                'csrf':'' # 只要保持登录状态,无论是否关闭浏览器再重新打开,csrf的值不变。未测试退出帐号重新登录会不会变,若变,则手动构造含#reply的URL,访问后定位到评论并手动删除评论,期间用fiddler抓取表单数据。除了抓包,应该还能在前端代码中找到csrf的值,因一般提交表单时,input框附近有表单隐藏域,但我在这里没找到。
            }
            r=requests.post(url,headers=headers,data=form_data)
            time.sleep(5)            
            result=r.json()
            global request_count # main函数中已定义
            request_count=request_count+1

            code=result['code']
            message=result['message']
            ttl=result['ttl']
            if not (code==0 and message=='0' and ttl==1): # 删除失败
                error_info_dict={}
                error_info_dict['dynamic_id']=dynamic_id
                error_info_dict['oid']=oid
                error_info_dict['type1']=type1
                error_info_dict['rpid']=rpid
                error_info_dict['text']=r.text # 传r.text就不用分别传code和message和ttl
                error_info_list.append(error_info_dict)
                print('删除失败,详情见本地错误日志文件(程序运行结束且非人为结束才生成)')
            else: # 删除成功
                OK_info_dict={}
                OK_info_dict['dynamic_id']=dynamic_id
                OK_info_dict['rpid']=rpid
                OK_info_dict['text']=r.text # 传r.text就不用分别传code和message和ttl
                OK_info_list.append(OK_info_dict)


if __name__=='__main__':
    csv_file_count=0 # 保存评论数据的csv文件总数
    request_count=0 # 发送删除请求的次数,发送就说明当前文件中有要删除的指定用户的评论,所以也表示含要删除的指定用户的评论的csv文件总数
    mid=input_mid()
    print('正在运行,请耐心等待')
    
    file_list=os.listdir(dir_path)
    for file_name in file_list:
        file_path=dir_path+'/'+file_name
        if (file_name[-3:] in ['csv','CSV']) and (os.path.isfile(file_path)):            
            delete_comment(file_name,file_path,mid)
            csv_file_count=csv_file_count+1
    
    print(f'共有{csv_file_count}个csv文件,其中{request_count}个含要删除的指定用户的评论')
    if request_count>0:
        if len(OK_info_list)>0:
            save_OK_log()
        if len(error_info_list)>0:
            save_error_log()
        else:
            print('所有评论已删除')

猜你喜欢

转载自blog.csdn.net/fj_changing/article/details/121731197