├── .gitignore ├── requirements.txt ├── README.md └── getDynamics.py /.gitignore: -------------------------------------------------------------------------------- 1 | /result.json 2 | /.idea/ 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bilibili_api 2 | aiohttp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 爬取B站动态列表 2 | =================================== 3 | #### 功能 4 | 爬取一位B站用户的全部动态,按时间顺序从旧到新排列,保存为json 5 | 6 | 用途:例如希望检索某一条UP主曾经发过的动态。就可以用此方法存成json,然后在文件里进行字符串查找 7 | 8 | #### 使用 9 | ```shell 10 | git clone https://github.com/Starrah/BilibiliGetDynamics 11 | cd BilibiliGetDynamics 12 | pip install -r requirements.txt 13 | # 默认会自动下载动态中的图片到pics文件夹内 14 | python getDynamics.py 12345678 15 | # 如果不希望自动下载图片(速度会更快),请加--no_download参数: 16 | # python getDynamics.py 12345678 --no_download 17 | ``` 18 | 将上述12345678换成你要查询的用户的UID即可。(查看UID:浏览器打开用户的个人空间,链接会形如https://space.bilibili.com/xxxxxxxx?from=balabalabala (?之后的部分可能有也可能没有,不用管),space.bilibili.com/ 后面紧跟的那一连串数字就是) 19 | 20 | 运行过程中会不断打印当前收到的数据。 21 | 等待最终打印出“已完成”后,打开当前目录下的result.json即可查看结果。 22 | 23 | #### 实现说明 24 | (只有一个文件getDynamics.py、60几行代码,其实有个几分钟从上到下过一下就全看懂了) 25 | 26 | ##### 代码概述 27 | 基于[bilibili-api](https://github.com/Passkou/bilibili-api) 库开发,利用其中封装的User.get_dynamics接口请求json数据。 28 | 主函数是main函数,里面用一个循环调用get_dynamics接口,开始时用offset=0调用 29 | 期间每次请求都会返回一个offset,需要在下一次请求时作为参数传入,从而依次请求各个页面。 30 | 请求到的数据是一个cards数组、每个元素表示一条动态。 31 | cardToObj方法对其进行简单的字段筛选以压缩体积,然后保存起来。 32 | 33 | ##### 已知问题&改进方法 34 | 不能正确处理视频动态情况(其实就是筛选字段没有选对,作者懒了)。 35 | 事实上,第93行(附近)`for card in res["cards"]:`循环内的card变量就是一条动态的完整对象,所有的信息都在这里面,可自行修改获得各种信息。 36 | 更欢迎把您的修改发PR上来。 37 | -------------------------------------------------------------------------------- /getDynamics.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | import sys 5 | import argparse 6 | import aiohttp 7 | from bilibili_api import user 8 | 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument('uid', nargs=1) 11 | parser.add_argument('--no_download', action="store_true", help="同时下载动态中的图片") 12 | args = parser.parse_args() 13 | 14 | if len(sys.argv) == 1: 15 | sys.stderr.write("请输入用户的UID作为参数!") 16 | exit(1) 17 | u = user.User(uid=int(sys.argv[1])) 18 | 19 | 20 | async def fetch(session: aiohttp.ClientSession, url: str, path: str): 21 | try: 22 | async with session.get(url) as resp: 23 | with open(path, 'wb') as fd: 24 | while 1: 25 | chunk = await resp.content.read(1024) # 每次获取1024字节 26 | if not chunk: 27 | break 28 | fd.write(chunk) 29 | # print("downloaded " + url) 30 | except: 31 | print("failed " + url) 32 | 33 | 34 | def copyKeys(src, keys): 35 | res = {} 36 | for k in keys: 37 | if k in src: 38 | res[k] = src[k] 39 | return res 40 | 41 | 42 | def getItem(input): 43 | if "item" in input: 44 | return getItem(input["item"]) 45 | if "videos" in input: 46 | return getVideoItem(input) 47 | else: 48 | return getNormal(input) 49 | 50 | 51 | def getNormal(input): 52 | res = copyKeys(input, ['description', 'pictures', 'content']) 53 | if "pictures" in res: 54 | res["pictures"] = [pic["img_src"] for pic in res["pictures"]] 55 | return res 56 | 57 | 58 | def getVideoItem(input): 59 | res = copyKeys(input, ['title', 'desc', 'dynamic', 'short_link', 'stat', 'tname']) 60 | res["av"] = input["aid"] 61 | res["pictures"] = [input["pic"]] 62 | return res 63 | 64 | 65 | def cardToObj(input): 66 | res = { 67 | "dynamic_id": input["desc"]["dynamic_id"], 68 | "timestamp": input["desc"]["timestamp"], 69 | "type": input["desc"]["type"], 70 | "item": getItem(input["card"]) 71 | } 72 | if "origin" in input["card"]: 73 | originObj = json.loads(input["card"]["origin"]) 74 | res["origin"] = getItem(originObj) 75 | if "user" in originObj and "name" in originObj["user"]: 76 | res["origin_user"] = originObj["user"]["name"] 77 | return res 78 | 79 | 80 | async def main(): 81 | with open("result.json", "w", encoding="UTF-8") as f: 82 | offset = 0 83 | count = 0 84 | if not args.no_download: 85 | os.makedirs("pics", exist_ok=True) 86 | while True: 87 | if offset != 0: 88 | f.write(",") 89 | res = await u.get_dynamics(offset) 90 | if res["has_more"] != 1: 91 | break 92 | offset = res["next_offset"] 93 | for card in res["cards"]: 94 | f.write(",\n" if count > 0 else "[\n") 95 | cardObj = cardToObj(card) 96 | if not args.no_download: 97 | tasks = [] 98 | async with aiohttp.ClientSession() as session: 99 | if "pictures" in cardObj["item"]: 100 | for pic_url in cardObj["item"]["pictures"]: 101 | task = fetch(session, pic_url, os.path.join("pics", os.path.basename(pic_url))) 102 | tasks.append(task) 103 | await asyncio.gather(*tasks) 104 | cardStr = str(cardObj) 105 | f.write(cardStr) 106 | print(cardStr) 107 | count += 1 108 | f.flush() 109 | await asyncio.sleep(1) 110 | f.write("\n]") 111 | print() 112 | print("--------已完成!---------") 113 | 114 | 115 | if __name__ == '__main__': 116 | asyncio.get_event_loop().run_until_complete(main()) 117 | --------------------------------------------------------------------------------