Skip to content

Instantly share code, notes, and snippets.

@prnake
Last active November 16, 2021 10:54
Show Gist options
  • Save prnake/3671b73af3df2982d751369014a8c826 to your computer and use it in GitHub Desktop.
Save prnake/3671b73af3df2982d751369014a8c826 to your computer and use it in GitHub Desktop.
Bilibili漫画下载
# Source: https://github.com/LaMP57/BilibiliMangaDownload
# 支持从连续使用多个账号的网页版 SESSDATA,或者使用单个手机抓包得到的 Token,自动购买/解锁章节,验证下载图片完整性连续下载一本漫画的所有章节的功能。
import json
import os
import re
import requests
import zipfile
from io import BytesIO
from PIL import Image
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED
AutoBuy = False
PARAM_ID = "appkey=***&mobi_app=android_comic&version=4.7.0&build=***&channel=xiaomi&platform=android&device=android&buvid=***&machine=Xiaomi%2BM2011K2C&access_key=***&is_teenager=0&ts=***"
USE_SESSION = False
if not PARAM_ID:
PARAM_ID = "device=pc&platform=web"
USE_SESSION = True
URL_DETAIL = "https://manga.bilibili.com/twirp/comic.v2.Comic/ComicDetail?" + PARAM_ID
URL_IMAGE_INDEX = "https://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex?" + PARAM_ID
URL_MANGA_HOST = "https://manga.hdslb.com"
URL_IMAGE_TOKEN = "https://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken?" + PARAM_ID
URL_MANGA_BUY_INFO = "https://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisodeBuyInfo?" + PARAM_ID
URL_MANGA_BUY = "https://manga.bilibili.com/twirp/comic.v1.Comic/BuyEpisode?" + PARAM_ID
def downloadImage(url, path):
if not os.path.exists(path):
r = requests.get(url, stream = True, cookies = cookies)
if r.content.endswith(b'\xff\xd9'):
f = open(path, 'wb')
for chunk in r.iter_content(chunk_size = 1024):
if chunk:
f.write(chunk)
f.close()
else:
print("[ERROR]" + path + " 下载失败,重试中...")
os.remove(path)
downloadImage(url, path)
def getMangaInfo(mcNum):
data = requests.post(URL_DETAIL, data = {'comic_id': mcNum}).json()['data']
data['ep_list'].reverse()
return filterStr(data['title']), data['ep_list']
def getImages(mcNum, chNum):
data = requests.post(URL_IMAGE_INDEX, data = {'ep_id': chNum}, cookies = cookies).json()['data']
data = bytearray(requests.get(data['host'] + data['path']).content[9:])
key = [chNum&0xff, chNum>>8&0xff, chNum>>16&0xff, chNum>>24&0xff, mcNum&0xff, mcNum>>8&0xff, mcNum>>16&0xff, mcNum>>24&0xff]
for i in range(len(data)):
data[i] ^= key[i%8]
file = BytesIO(data)
zf = zipfile.ZipFile(file)
data = json.loads(zf.read('index.dat'))
zf.close()
file.close()
return data['pics']
def getURLwithToken(url):
data = requests.post(URL_IMAGE_TOKEN, data = {"urls": "[\""+url+"\"]"}, cookies = cookies).json()["data"][0]
return '%s?token=%s' % (data["url"], data["token"])
def getChapterName(chapterList, chapterID):
for chapte in chapterList:
if chapte['id'] == chapterID:
return filterStr(chapte['short_title'] + ' ' + chapte['title'])
return None
def downloadChapter(mcNum, chapterID, chapterName):
if not(os.path.exists('downloads/%s/%s' % (mangaTitle, chapterName))) or len(os.listdir('downloads/%s/%s' % (mangaTitle, chapterName))) == 0:
if not(os.path.exists('downloads/%s/%s' % (mangaTitle, chapterName))):
os.mkdir('downloads/%s/%s' % (mangaTitle, chapterName))
try:
if AutoBuy == True:
buy_content = requests.post(URL_MANGA_BUY, data = {'ep_id': chapterID,'buy_method':1}, cookies = cookies).content
print(json.loads(buy_content)["msg"].encode().decode())
while json.loads(buy_content)["code"] != 0:
buy_info_content = json.loads(requests.post(URL_MANGA_BUY_INFO, data = {'ep_id': chapterID}, cookies = cookies).content)
coupon_num = buy_info_content["data"]["remain_coupon"]
if int(coupon_num) == 0:
print("无剩余优惠卷,请输入新的SESSDATA:")
cookies['SESSDATA'] = input().strip()
else:
print("剩余优惠卷%s张,自动使用" % coupon_num)
buy_content = requests.post(URL_MANGA_BUY, data = {'ep_id': chapterID,'buy_method':2,'coupon_id':buy_info_content["data"]["recommend_coupon_id"],'auto_pay_gold_status':1}, cookies = cookies).content
print(json.loads(buy_content)["msg"].encode().decode())
print('[INFO]%s开始下载' % chapterName)
imagesURLs = getImages(mcNum, chapterID)
imagesIndexLength = len(str(len(imagesURLs)))
tasks = list()
for idx, url in enumerate(imagesURLs, 1):
fullURL = getURLwithToken(url)
path = 'downloads/%s/%s/%s.jpg' % (mangaTitle, chapterName, str(idx).zfill(imagesIndexLength))
tasks.append(pool.submit(downloadImage, fullURL, path))
wait(tasks, return_when = ALL_COMPLETED)
print('[INFO]%s下载完成' % chapterName)
except:
print('[ERROR]%s下载失败' % chapterName)
os.rmdir('downloads/%s/%s' % (mangaTitle, chapterName))
else:
print('[INFO]%s已下载' % chapterName)
def filterStr(name):
return re.sub(r'[\/:*?"<>|]', '', name).strip().rstrip('.')
if __name__ == "__main__":
pool = ThreadPoolExecutor(max_workers=4)
if not(os.path.exists('downloads')):
os.mkdir('downloads')
print('请输入mc号:')
print('mc', end='')
mcNum = int(input())
mangaTitle, chapterList = getMangaInfo(mcNum)
print('[INFO]', mangaTitle)
if not(os.path.exists('downloads/%s' % mangaTitle)):
os.mkdir('downloads/%s' % mangaTitle)
# print('1.下载单章\n2.下载全本')
# downloadAll = input()
# if downloadAll == '1':
# downloadAll = False
# print('请输入要下载的章节号:')
# chapterID = int(input())
# else:
# downloadAll = True
downloadAll = True
# print('1.均为免费章节\n2.包含付费章节')
# needsLogin = input()
needsLogin = "2"
cookies = dict()
if needsLogin == '2' and USE_SESSION:
print('请按说明粘贴SESSDATA:')
cookies['SESSDATA'] = input().strip()
if downloadAll:
for chapter in chapterList:
chapterName = filterStr(chapter['short_title'] + ' ' + chapter['title'])
downloadChapter(mcNum, chapter['id'], chapterName)
else:
chapterName = getChapterName(chapterList, chapterID)
downloadChapter(mcNum, chapterID, chapterName)
import os
import re
import shutil
name = "大正少女御伽话"
os.mkdir(name+"_out")
d = os.listdir(name)
d.remove('.DS_Store')
d = sorted(d, key=lambda y: int(re.findall(r'\d+', y)[0]))
print(d)
cnt = 1
for i in d:
s = os.listdir(name+"/"+i)
s = sorted(s, key=lambda y: int(re.findall(r'\d+', y)[0]))
for j in s:
shutil.copy(name+"/"+i+"/"+j,name + "_out/" + str(cnt).zfill(5) + ".jpg")
cnt += 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment