├── .gitignore ├── README.md ├── index_decode.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # idea 132 | .idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # B站漫画下载 2 | 已经支持整本漫画下载,其他还没研究怎么下,暂时对我来说够用了 3 | 4 | # 运行方式 5 | 1. 打开章节对应链接如`https://manga.bilibili.com/mc25966/447883?from=manga_detail`,其中447883为章节id 6 | 2. 修改代码中cookie信息,cookie在main.py中headers中,自行修改 7 | 3. 运行main.py,修改download_manga_episode的参数,运行download_manga_episode(447883)即可下载该章节 8 | 9 | # 后期计划 10 | * 下载后转换为mobi格式,便于kindle阅读 11 | * 自动下载一整本漫画 12 | 13 | [http://hentaix.cn/B%E7%AB%99%E6%BC%AB%E7%94%BB%E7%88%AC%E8%99%AB%E5%AE%9E%E6%88%98/](http://hentaix.cn/B%E7%AB%99%E6%BC%AB%E7%94%BB%E7%88%AC%E8%99%AB%E5%AE%9E%E6%88%98/) 14 | -------------------------------------------------------------------------------- /index_decode.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | from pprint import pprint 4 | import numpy as np 5 | import zipfile 6 | import json 7 | 8 | 9 | def decode_index_data(season_id: int, episode_id: int, buf): 10 | u = [66, 73, 76, 73, 67, 79, 77, 73, 67] 11 | l = len(u) 12 | e = buf[l:] 13 | # print(buf) 14 | _e = [] 15 | for i in range(len(e)): 16 | _e.append(e[i]) 17 | e = np.uint8(_e) 18 | # print(e) 19 | n = [0, 0, 0, 0, 0, 0, 0, 0] 20 | n = np.array(n, dtype='uint8') 21 | n[0] = episode_id 22 | n[1] = episode_id >> 8 23 | n[2] = episode_id >> 16 24 | n[3] = episode_id >> 24 25 | n[4] = season_id 26 | n[5] = season_id >> 8 27 | n[6] = season_id >> 16 28 | n[7] = season_id >> 24 29 | # print(n) 30 | _n = 0 31 | r = len(e) 32 | while _n < r: 33 | e[_n] = e[_n] ^ n[_n % 8] 34 | _n = _n + 1 35 | pass 36 | # print("解密后:") 37 | # print(e) 38 | ret = bytes(e) 39 | # print(ret) 40 | z = zipfile.ZipFile(io.BytesIO(ret), 'r') 41 | j = z.read('index.dat') 42 | # print(j) 43 | # pprint(json.loads(j)['pics']) 44 | return json.loads(j)['pics'] 45 | pass 46 | 47 | 48 | if __name__ == "__main__": 49 | season_id = 25966 50 | episode_id = 376715 51 | f = open('data.index.28227a12', 'rb') 52 | buf = f.read() 53 | f.close() 54 | print(len(buf)) 55 | decode_index_data(season_id, episode_id, buf) 56 | pass 57 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import requests 5 | import json 6 | from pprint import pprint 7 | from index_decode import decode_index_data 8 | 9 | download_path = './manhua' 10 | headers = { 11 | "accept": "application/json, text/plain, */*", 12 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8", 13 | "content-type": "application/json;charset=UTF-8", 14 | "origin": "https://manga.bilibili.com", 15 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36", 16 | "cookie": "" 17 | } 18 | headers_cdn = { 19 | 'Host': 'manga.hdslb.com', 20 | 'Origin': 'https://manga.bilibili.com', 21 | } 22 | 23 | 24 | def download_manga_all(comic_id: int): 25 | url = "https://manga.bilibili.com/twirp/comic.v2.Comic/ComicDetail?device=pc&platform=web" 26 | res = requests.post(url, 27 | json.dumps({ 28 | "comic_id": comic_id 29 | }), headers=headers) 30 | data = json.loads(res.text)['data'] 31 | comic_title = data['title'] 32 | root_path = os.path.join(download_path, comic_title) 33 | if not os.path.exists(root_path): 34 | os.makedirs(root_path) 35 | for ep in data['ep_list']: 36 | if not ep['is_locked']: 37 | print('downloading ep:', ep['short_title'], ep['title']) 38 | download_manga_episode(ep['id'], root_path) 39 | pass 40 | pass 41 | pass 42 | 43 | 44 | def download_manga_episode(episode_id: int, root_path: str): 45 | res = requests.post('https://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisode?device=pc&platform=web', 46 | json.dumps({ 47 | "id": episode_id 48 | }), headers=headers) 49 | data = json.loads(res.text) 50 | # comic_title = data['data']['comic_title'] 51 | short_title = data['data']['short_title'] 52 | # title = comic_title + '_' + short_title + '_' + data['data']['title'] 53 | title = short_title + '_' + data['data']['title'] 54 | comic_id = data['data']['comic_id'] 55 | print('正在下载:', title) 56 | 57 | # 获取索引文件cdn位置 58 | res = requests.post('https://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex?device=pc&platform=web', 59 | json.dumps({ 60 | "ep_id": episode_id 61 | }), headers=headers) 62 | data = json.loads(res.text) 63 | index_url = 'https://manga.hdslb.com' + data['data']['path'] 64 | print('获取索引文件cdn位置:', index_url) 65 | # 获取索引文件 66 | res = requests.get(index_url) 67 | # 解析索引文件 68 | pics = decode_index_data(comic_id, episode_id, res.content) 69 | # print(pics) 70 | ep_path = os.path.join(root_path, title) 71 | if not os.path.exists(ep_path): 72 | os.makedirs(ep_path) 73 | for i, e in enumerate(pics): 74 | url = get_image_url(e) 75 | print(i, e) 76 | res = requests.get(url) 77 | with open(os.path.join(ep_path, str(i) + '.jpg'), 'wb+') as f: 78 | f.write(res.content) 79 | pass 80 | if i % 4 == 0 and i != 0: 81 | time.sleep(2) 82 | pass 83 | pass 84 | pass 85 | 86 | 87 | def get_image_url(img_url): 88 | # 获取图片token 89 | res = requests.post('https://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken?device=pc&platform=web', 90 | json.dumps({ 91 | "urls": json.dumps([img_url]) 92 | }), headers=headers) 93 | data = json.loads(res.text)['data'][0] 94 | url = data['url'] + '?token=' + data['token'] 95 | return url 96 | pass 97 | 98 | 99 | if __name__ == "__main__": 100 | download_manga_all(25966) 101 | # download_manga_episode(448369, os.path.join(download_path, '辉夜大小姐想让我告白 ~天才们的恋爱头脑战~')) 102 | # get_image_url('/bfs/manga/f311955085404cab705e881d0a81204098967c1e.jpg') 103 | pass 104 | --------------------------------------------------------------------------------