From 4e792dc7227fd8d70e88d4b45a243422719b0e3c Mon Sep 17 00:00:00 2001 From: Sheyiyuan <2125107118@qq.com> Date: Wed, 19 Mar 2025 09:30:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0up=E7=94=BB=E5=83=8F=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 3 +- main.py | 109 ++++++++++++++++++++++++++++++++++++------ 指标体系构建.md | 56 ++++++++++++++++++++++ 4 files changed, 152 insertions(+), 16 deletions(-) create mode 100644 .DS_Store create mode 100644 指标体系构建.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8a777bdc41f564f46851be690709712846a14eea GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~O(;SS3Oz1(E!bMCh?fxS4;aydN=;1BV9b^#HHT8jS%1hc@q3)v z-4>+^oO(j*>Xb+wC`Asak96HM?fl?Q8#D7JeS&lgtaomw0t1 zWfIQ)AUuzU#h|fyEYm!Q)8SYZ#L*B^t}fy9o`BwnXos zH*1OM;cmMn_PYDCnPYEl?;M>D9+JmYK5G&=d~ZrN49?*NoW**g>^2GE`EYO%eDcR> zBGWsF%_qTe5@LWDAO=>50ki$t^%Yt(EtMD`27bl>?hgVK(KT3VR9goa)dB!o5LN;{ z_7Yel47vtOjW7bjbt#}O<)+2px*X!d)=3zg2etC1`* zKn#3l0DC`>2A=;X-}nD2i3~A74E#?9xYhUj9<0rrtxK!KvsQrKfTCbtsc}&Pjyj4V f7mwm5s1k?^bO2q0rACN=(2szkfebP5s|>sYV7N~w literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index cd919e2..87a8b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ ./venv/ .idea/ -data/ \ No newline at end of file +data/ +*/.DS_Store \ No newline at end of file diff --git a/main.py b/main.py index 8cd0036..ef03b92 100644 --- a/main.py +++ b/main.py @@ -78,20 +78,19 @@ class BiliWebCrawler: # 获取视频最高分辨率(基于dimension对象) max_width = 0 max_height = 0 - for format_info in video_data.get('formats', []): - dimension = format_info.get('dimension', {}) - width = dimension.get('width', 0) - height = dimension.get('height', 0) - rotate = dimension.get('rotate', 0) + dimension = video_data.get('dimension', {}) + width = dimension.get('width', 0) + height = dimension.get('height', 0) + rotate = dimension.get('rotate', 0) - # 处理视频旋转(当rotate=1时宽高互换) - if rotate == 1: - width, height = height, width + # 处理视频旋转(当rotate=1时宽高互换) + if rotate == 1: + width, height = height, width - # 通过像素总量比较分辨率 - if (width * height) > (max_width * max_height): - max_width = width - max_height = height + # 通过像素总量比较分辨率 + if (width * height) > (max_width * max_height): + max_width = width + max_height = height # 将分辨率格式化为 "宽x高" 的字符串 resolution_str = f"{max_width}x{max_height}" if max_width and max_height else "未知" @@ -105,23 +104,96 @@ class BiliWebCrawler: tag_data = [tag['tag_name'] for tag in tag_json.get('data', [])] info = { + 'BV号': self.bvid, 'title': video_data.get('title', ''), - 'up主': video_data.get('owner', {}).get('name', ''), + 'up主名称': video_data.get('owner', {}).get('name', ''), # 新增字段 + 'up主UID': video_data.get('owner', {}).get('mid', ''), # 新增UID字段 '播放量': video_data.get('stat', {}).get('view', 0), '弹幕量': video_data.get('stat', {}).get('danmaku', 0), '点赞量': video_data.get('stat', {}).get('like', 0), '投币量': video_data.get('stat', {}).get('coin', 0), '收藏量': video_data.get('stat', {}).get('favorite', 0), + '分享量': video_data.get('stat', {}).get('share', 0), + '评论量': video_data.get('stat', {}).get('reply', 0), + '发布时间的timestamp': video_data.get('pubdate', 0), '发布时间': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(video_data.get('pubdate', 0))), '分区': video_data.get('tname', ''), '标签': tag_data, + '视频方向': self._get_video_orientation(video_data.get('dimension', {})), '视频最高分辨率': resolution_str, - '视频类型': video_data.get('copyright', 0), - '视频分p数': len(video_data.get('pages', [])) + '视频类型': ["","自制", "转载"][video_data.get('copyright', 0)], + '视频分p数': len(video_data.get('pages', [])), + '视频总时长': self.get_video_length(video_data.get('pages', [])), + '简介': video_data.get('desc', '').replace('\n', '\\n'), } return info + def get_video_length(self,pages): + """获取视频总时长""" + length = 0 + for page in pages: + length += page.get('duration', 0) + return length + + def _get_video_orientation(self, dimension): + """判断视频方向(横屏/竖屏)""" + width = dimension.get('width', 0) + height = dimension.get('height', 0) + rotate = dimension.get('rotate', 0) + + # 处理视频旋转(90度或270度旋转时需要交换宽高) + if rotate in [1, 3]: + width, height = height, width + + return "横屏" if width >= height else "竖屏" + + # 在类方法中添加以下新方法(建议放在 get_video_info 方法之后) + def get_up_info(self, mid): + """获取UP主详细信息""" + if not mid: + return None + + url = f"https://api.bilibili.com/x/web-interface/card?mid={mid}&photo=false" + resp = self._safe_request(url) + if not resp: + return None + + try: + data = resp.json().get('data', {}) + card = data.get('card') + up_info = { + 'uid': mid, + '昵称': card['name'], + '性别': card['sex'], + '头像': card['face'], + '等级': card['level_info']['current_level'], + '粉丝数': card['fans'], + '稿件数': data['archive_count'], + '获赞数': data['like_num'], + } + except Exception as e: + print(f"解析UP主数据失败: {str(e)}") + return None + try: + # 获取投稿列表 + archive_url = f'https://api.bilibili.com/x/space/arc/search?mid={mid}&ps=30' + archive_resp = self._safe_request(archive_url) + if archive_resp and archive_resp.status_code == 200: + archive_data = archive_resp.json() + print(archive_data) + videos = archive_data.get('data', {}).get('list', {}).get('vlist', []) + + # 计算30天前的时间戳 + month_ago = time.time() - 30 * 86400 + # 统计符合时间条件的视频 + recent_count = sum(1 for v in videos if v.get('created') > month_ago) + up_info['近一个月投稿数'] = recent_count + except Exception as e: + print(f"获取投稿数据失败: {str(e)}") + + return up_info + def get_danmaku(self): """获取弹幕数据""" if not self.bvid: @@ -277,6 +349,13 @@ class BiliWebCrawler: comments_filename = os.path.join(video_dir, f'{self.bvid}_{len(comments)}_comments.csv') self.save_to_csv(comments, comments_filename) + # 新增UP主信息记录 + print("正在获取UP主信息...") + up_info = self.get_up_info(video_info.get('up主UID')) + up_info['BV号'] = self.bvid + up_csv_path = os.path.join(base_dir, 'up_info.csv') + self.save_to_csv([up_info], up_csv_path, mode='a') + print(f"抓取完成!结果已保存到 {video_dir}/") else: print("未获取到视频信息,无法进行抓取。") diff --git a/指标体系构建.md b/指标体系构建.md new file mode 100644 index 0000000..9f660c2 --- /dev/null +++ b/指标体系构建.md @@ -0,0 +1,56 @@ +## 外在属性指标 +### 1.基础流量指标 +需要视频BV号。 +- [x] 播放量 +- [x] 点赞量 +- [x] 投币量 +- [x] 收藏量 +- [x] 分享量 +- [x] 评论数 +- [x] 弹幕数 +### 2.up画像指标 +需要up主UID。 +- [x] 粉丝数 +- [x] 总获赞数 +- [x] 投稿数 +- [ ] 近一个月投稿数 +### 3.衍生指标 +- [x] 点赞率=点赞量/播放量 +- [x] 互动率=(点赞量+投币量+收藏量+分享量+评论数+弹幕数)/播放量 +- [x] 外溢系数=分享量/收藏量(反映内容外溢性) +## 内在属性指标 +### 1.内容属性指标 +- [x] 时长 +- [x] 发布时间 +- [x] 标题 +- [x] 分区 +- [x] 标签 +- [x] 最高清晰度 +### 2.内容结构指标 +- [ ] 是否分章节 +- [x] 是否分P +- [ ] 是否有字幕 +## 特殊指标(可能无法直接爬取,需特殊处理) +同时需要人工智能和能工智人。 +### 1.较易处理 +- [x] 是否为联合投稿(Sy:这个可以直接获取,没这么麻烦) +- [ ] 是否为系列作品(标题关键词分析) +- [x] 原创or搬运(只能投一个币为搬运)(Sy:这个也可以直接获取,没这么麻烦) +- [x] 横屏or竖屏(Sy:这个还是可以直接获取,没这么麻烦) +- [ ] 是否进入热门(作为机器学习的预测目标) +### 2.较难处理 +产生较大数据量。 +- [ ] 标题原创性(见DS代码) +- [ ] 标签区分度(见DS代码) +- [ ] 弹幕情感倾向得分(单独处理) +- [ ] 封面(图片分析可以单独写一章了) + +## 展望(即做不了的) +鉴于标题吸引力、剪辑质量、音频质量、封面设计、BGM体验等因素涉及主观判断, +平均播放时长这种B站几年没做外显(没法算完播率)无法直接获取, +领域垂直度等指标很难算, +点击率等指标爬不到, +我们希望后来者能克服这些困难进一步分析(如利用问卷等), +也可以考虑时间序列等因素构建更复杂的模型。 + +(总之我们不做) \ No newline at end of file