使用python将百度网盘批量转存并分享
2022/10/16大约 8 分钟
前言
需要将一批百度网盘资源批量保存到自己的网盘,并分享出来使用,传统手动转存和分享的方式太慢了,所以编写了以下程序快速达到目的。
代码
import base64
import requests
import re
import time
import random
import math
from urllib.parse import unquote
import ddddocr
class BaiDuPan(object):
def __init__(self):
# 创建session并设置初始登录Cookie
self.session = requests.session()
self.session.cookies['BAIDUID'] = ''
self.session.cookies['BDUSS'] = ''
self.session.cookies['STOKEN'] = ''
self.headers = {
'Host': 'pan.baidu.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36',
}
self.login_info = {
'bdstoken': '',
'username': ''
}
'''
验证Cookie是否已登录
返回值errno代表的意思:
0 有效的Cookie;1 init方法中未配置登录Cookie;2 无效的Cookie
'''
def verifyCookie(self):
if(self.session.cookies['BAIDUID'] == ''):
return {'errno': 1, 'err_msg': '请在init方法中配置百度网盘登录Cookie'}
else:
params = {
'clienttype': 0,
'app_id': 250528,
'web': 1,
'dp-logid': self.getDpLogId()
}
response = self.session.get('https://pan.baidu.com/api/loginStatus', params=params, headers=self.headers)
login_info = response.json().get('login_info')
if(response and login_info != None):
print(response.json())
self.login_info['bdstoken'] = login_info.get('bdstoken')
self.login_info['username'] = login_info.get('username')
return {'errno': 0, 'err_msg': '有效的Cookie,用户名:%s' % login_info.get('username')}
else:
return {'errno': 2, 'err_msg': '无效的Cookie!'}
'''
获取指定目录的文件列表,直接返回原始的json
'''
def getFileList(self, dir='/', order='time', desc=1, page=1, num=100):
# 访问首页获取bdstoken
params = {
'clienttype': 0,
'app_id': 250528,
'web': 1,
'dp-logid': self.getDpLogId(),
'order': order,
'desc': desc,
'dir': dir,
'num': num,
'page': page
}
url = 'https://pan.baidu.com/api/list'
headers = self.headers
headers['Referer'] = 'https://pan.baidu.com/disk/main'
response = self.session.get(url, params=params, headers=headers)
return response.json()
'''
识别 验证加密分享时的验证码
返回值errno代表的意思:
0 识别成功;1 识别失败;其他值 获取验证码失败;
'''
def vcodeOCR(self):
# 获取验证码
vcode_res = requests.get('https://pan.baidu.com/api/getcaptcha?prod=shareverify&web=1&channel=chunlei&web=1&app_id=250528&bdstoken=null&clienttype=0', headers=self.headers)
vcode_json = vcode_res.json()
if(vcode_json['errno'] == 0):
# 获取验证码图片
genimage = requests.get(vcode_json['vcode_img'], headers=self.headers)
ocr = ddddocr.DdddOcr()
vcode = ocr.classification(genimage.content)
print("验证码是:", vcode)
errno, err_msg = (1, '识别失败') if(len(vcode) != 4) else (0, '识别成功')
vcode_str = vcode_json['vcode_str']
else:
errno = vcode_json['errno']
err_msg = '获取验证码失败'
vcode_str = ''
vcode = ''
return {'errno': errno, 'err_msg': err_msg, 'vcode': vcode, 'vcode_str': vcode_str}
'''
验证加密分享
返回值errno代表的意思:
0 加密分享验证通过;1 验证码获取失败;2 提取码不正确;3 加密分享验证失败;4 重试几次后,验证码依旧不正确;
'''
def verifyShare(self, surl, pwd, referer, retryTime = 2):
t = str(int(time.time()) * 1000)
params = {
'surl': surl,
't': t,
'channel': 'chunlei',
'web': 1,
'app_id': 250528,
'bdstoken': self.login_info.get('bdstoken'),
'logid': self.getLogid(),
'clienttype': 0
}
url = 'https://pan.baidu.com/share/verify'
form_data = {
'pwd': pwd,
'vcode': '',
'vcode_str': '',
}
# 设置重试机制
is_vcode = False
# 试用几次就可以
for n in range(0, retryTime):
# 自动获取并识别验证码,使用pytesseract自动识别时,可加大重试次数
if is_vcode:
ocr_result = self.vcodeOCR()
if(ocr_result['errno'] == 0):
form_data['vcode'] = ocr_result['vcode']
form_data['vcode_str'] = ocr_result['vcode_str']
elif(ocr_result['errno'] == 1):
continue
else:
return {'errno': 1, 'err_msg': '验证码获取失败:%d' % ocr_result['errno']}
headers = self.headers
headers['referer'] = referer
# verify_json['errno']:-9表示提取码不正确;-62表示需要验证码/验证码不正确(不输入验证码也是此返回值)
verify_res = self.session.post(url, headers=headers, params=params, data=form_data)
verify_json = verify_res.json()
cookie = verify_res.cookies.get('BDCLND')
if(verify_json['errno'] == 0):
return {'errno': 0, 'err_msg': '加密分享验证通过', 'BDCLND': cookie}
elif(verify_json['errno'] == -9):
return {'errno': 2, 'err_msg': '提取码不正确'}
elif(verify_json['errno'] == -62):
is_vcode = True
else:
return {'errno': 3, 'err_msg': '加密分享验证失败:%d' % verify_json['errno']}
return {'errno': 4, 'err_msg': '重试多次后,验证码依旧不正确:%d' % (verify_json['errno'] if("verify_json" in locals()) else -1)}
'''
返回值errno代表的意思:
0 转存成功;1 无效的分享链接;2 分享文件已被删除;
3 分享文件已被取消;4 分享内容侵权,无法访问;5 找不到文件;6 分享文件已过期
7 获取提取码失败;8 获取加密cookie失败; 9 转存失败;
'''
def saveShare(self, url, pwd=None, path='/'):
share_res = self.session.get(url, headers=self.headers)
share_page = share_res.content.decode("utf-8")
'''
1.如果分享链接有密码,会被重定向至输入密码的页面;
2.如果分享链接不存在,会被重定向至404页面https://pan.baidu.com/error/404.html,但是状态码是200;
3.如果分享链接已被删除,页面会提示:啊哦,你来晚了,分享的文件已经被删除了,下次要早点哟。
4.如果分享链接已被取消,页面会提示:啊哦,你来晚了,分享的文件已经被取消了,下次要早点哟。
5.如果分享链接涉及侵权,页面会提示:此链接分享内容可能因为涉及侵权、色情、反动、低俗等信息,无法访问!
6.啊哦!链接错误没找到文件,请打开正确的分享链接!
7.啊哦,来晚了,该分享文件已过期
'''
if('error/404.html' in share_res.url):
return {"errno": 1, "err_msg": "无效的分享链接", "extra": "", "info": ""}
if('你来晚了,分享的文件已经被删除了,下次要早点哟' in share_page):
return {"errno": 2, "err_msg": "分享文件已被删除", "extra": "", "info": ""}
if('你来晚了,分享的文件已经被取消了,下次要早点哟' in share_page):
return {"errno": 3, "err_msg": "分享文件已被取消", "extra": "", "info": ""}
if('此链接分享内容可能因为涉及侵权、色情、反动、低俗等信息,无法访问' in share_page):
return {"errno": 4, "err_msg": "分享内容侵权,无法访问", "extra": "", "info": ""}
if('链接错误没找到文件,请打开正确的分享链接' in share_page):
return {"errno": 5, "err_msg": "链接错误没找到文件", "extra": "", "info": ""}
if('啊哦,来晚了,该分享文件已过期' in share_page):
return {"errno": 6, "err_msg": "分享文件已过期", "extra": "", "info": ""}
# 如果加密分享,需要验证提取码,带上验证通过的Cookie再请求分享链接,即可获取分享文件
BDCLND = ''
if('init' in share_res.url):
surl = re.findall(r'surl=(.+?)$', share_res.url)[0]
referer = share_res.url
verify_result = self.verifyShare(surl, pwd, referer)
if(verify_result['errno'] != 0):
return {"errno": 8, "err_msg": verify_result['err_msg'], "extra": "", "info": ""}
else:
# 加密分享验证通过后,使用全局session刷新页面(全局session中带有解密的Cookie)
BDCLND = verify_result.get('BDCLND')
share_res = self.session.get(url, headers=self.headers)
save_url = 'https://pan.baidu.com/share/transfer'
share_uk = re.findall(r'share_uk:"(\d+)"', share_res.text)[0]
shareid = re.findall(r'shareid:"(\d+)"', share_res.text)[0]
# 531611295189884
fs_id = re.findall(r'fs_id":(\d+),', share_res.text)[0]
sekey = unquote(BDCLND)
logid = self.getLogid()
dplogid = self.getDpLogId()
params = {
'shareid': shareid,
'from': share_uk,
'sekey': sekey,
'channel': 'chunlei',
'web': '1',
'app_id': 250528,
'bdstoken': self.login_info['bdstoken'],
'logid': logid,
'clienttype': '0',
'dp-logid': dplogid,
}
form_data = {
'fsidlist': f'[{fs_id}]',
'path': path
}
headers = self.headers
headers['Origin'] = 'https://pan.baidu.com'
headers['referer'] = url
'''
用带登录Cookie的全局session请求转存
如果有同名文件,保存的时候会自动重命名:类似xxx(1)
暂时不支持超过文件数量的文件保存
'''
save_res = self.session.post(save_url, headers=headers, params=params, data=form_data)
save_json = save_res.json()
errno, err_msg, extra, info = (0, '转存成功', save_json['extra'], save_json['info']) if(save_json['errno'] == 0) else (9, '转存失败:%d' % save_json['errno'], '', '')
return {'errno': errno, 'err_msg': err_msg, "extra": extra, "info": info}
'''
重命名指定文件
0 重命名成功;1 重命名失败;
'''
def rename(self, path, newname):
bdstoken = self.login_info['bdstoken']
url = 'https://pan.baidu.com/api/filemanager'
params = {
'async': 2,
'onnest': 'fail',
'opera': 'rename',
'bdstoken': bdstoken,
'clienttype': 0,
'app_id': 250528,
'web': 1,
'dp-logid': self.getDpLogId()
}
form_data = {"filelist": "[{\"path\":\"%s\",\"newname\":\"%s\"}]" % (path, newname)}
response = self.session.post(url, headers=self.headers, params=params, data=form_data)
if(response.json()['errno'] == 0):
return {'errno': 0, 'err_msg': '重命名成功!'}
else:
return {'errno': 1, 'err_msg': '重命名失败!', 'info': response.json()}
'''
删除指定文件
0 删除成功;1 删除失败;
'''
def delete(self, path):
bdstoken = self.login_info['bdstoken']
url = 'https://pan.baidu.com/api/filemanager'
params = {
'async': 2,
'onnest': 'fail',
'opera': 'delete',
'bdstoken': bdstoken,
'clienttype': 0,
'app_id': 250528,
'web': 1,
'dp-logid': self.getDpLogId()
}
form_data = {"filelist": "[\"%s\"]" % path}
response = self.session.post(url, headers=self.headers, params=params, data=form_data)
if(response.json()['errno'] == 0):
return {'errno': 0, 'err_msg': '删除成功!'}
else:
return {'errno': 1, 'err_msg': '删除失败!', 'info': response.json()}
'''
移动文件至指定目录
0 删除成功;1 删除失败;
destination 移动的目录
'''
def move(self, path, destination, newname=False):
bdstoken = self.login_info['bdstoken']
url = 'https://pan.baidu.com/api/filemanager'
params = {
'async': 2,
'onnest': 'fail',
'opera': 'move',
'bdstoken': bdstoken,
'clienttype': 0,
'app_id': 250528,
'web': 1,
'dp-logid': self.getDpLogId()
}
if(not newname):
newname = path.split('/')[-1]
form_data = {"filelist": "[{\"path\":\"%s\",\"dest\":\"%s\",\"newname\":\"%s\"}]" % (path, destination, newname)}
response = self.session.post(url, headers=self.headers, params=params, data=form_data)
if(response.json()['errno'] == 0):
return {'errno': 0, 'err_msg': '移动成功!'}
else:
return {'errno': 1, 'err_msg': '移动失败!', 'info': response.json()}
'''
创建分享链接
fid_list为列表,例如:[1110768251780445]
0 创建成功;1 创建失败;
'''
def createShareLink(self, fid_list, period=0, pwd=False):
bdstoken = self.login_info['bdstoken']
url = 'https://pan.baidu.com/share/set'
params = {
'bdstoken': bdstoken,
'channel': 'chunlei',
'web': 1,
'app_id': 250528,
'logid': self.getLogid(),
'clienttype': 0
}
if(not pwd):
pwd = self.generatePwd()
'''
schannel=4 不知道什么意思,固定为4
channel_list=[] 不知道什么意思,固定为[]
period=0 0表示永久,7表示7天
pwd=w4y5 分享链接的提取码,可自定义
fid_list=[1110768251780445] 分享文件的id列表,可调用getFileList方法获取文件列表,包含fs_id
'''
form_data = {
'schannel': 4,
'channel_list': '[]',
'period': period,
'pwd': pwd,
'fid_list': str(fid_list),
}
response = self.session.post(url, headers=self.headers, params=params, data=form_data)
if(response.json()['errno'] == 0):
return {'errno': 0, 'err_msg': '创建分享链接成功!', 'info': {'link': response.json()['link'], 'pwd': pwd}}
else:
return {'errno': 1, 'err_msg': '创建分享链接失败!', 'info': response.json()}
def getDpLogId(self, uk=None):
def getRandomInt(num):
return str(math.floor((random.random() * 9 + 1) * math.pow(10, num - 1)))
def validateUk(uk):
return len(uk + '') == 10
def prefixInteger(num, length):
return ''.join(['0' for _ in range(length - len(num))]) + num
def getCountId(countid=30):
if countid < 9999:
countid += 1
else:
countid = 0
return prefixInteger(str(countid), 4)
client = ''
userid = '00' + getRandomInt(8)
sessionid = getRandomInt(6)
if uk and validateUk(uk):
userid = uk
return client + sessionid + userid + getCountId()
def getLogid(self):
logid = base64.b64encode(self.session.cookies.get('BAIDUID').encode()).decode()
return logid
'''
随机生成4位字符串
'''
def generatePwd(self, n=4):
code = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
pwd= ""
for i in range(0, n):
index = random.randrange(0,len(code))
pwd += code[index]
return pwd
if __name__ == "__main__":
baiDuPan = BaiDuPan()
# 验证cookies
# baiDuPan.verifyCookie()
# 转存文件
# fileList = baiDuPan.saveShare('https://pan.baidu.com/s/1Nnipn9HxW8cCgMfRhkHd3Q', 'yn4v', '/')
# to_fs_id = fileList.get('extra').get('list')[0].get('to_fs_id')
# 重命名
# print(baiDuPan.rename('/计算机网络(第7版)-谢希仁-1.epub', '计算机网络(第7版)-谢希仁.epub'))
# 删除文件
# print(baiDuPan.delete('/计算机网络(第7版)-谢希仁.epub'))
# 移动文件
# print(baiDuPan.move('/我的资源/计算机网络(第7版)-谢希仁.epub', '/'))
# 创建分享链接
# print(baiDuPan.createShareLink([to_fs_id]))