Kaynağa Gözat

增加修改封面逻辑

zhangyong 7 ay önce
ebeveyn
işleme
c91353c8f6

+ 5 - 0
common/__init__.py

@@ -0,0 +1,5 @@
+from .common_log import Common
+from .feishu_utils import Feishu
+from .mysql_db import MysqlHelper
+from .redis import SyncRedisHelper
+from .aliyun_log import AliyunLogger

+ 146 - 0
common/aliyun_log.py

@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+"""
+公共方法,包含:生成log / 查询log
+"""
+import json
+
+from aliyun.log import LogClient, GetLogsRequest
+from datetime import date, timedelta
+from datetime import datetime
+from typing import Optional
+from aliyun.log import PutLogsRequest, LogClient, LogItem
+
+class AliyunLogger:
+    # 查询阿里云日志
+    @staticmethod
+    def query_logs_by_status():
+        pq_list = []
+        # 当前时间
+        now = datetime.now()
+
+        # 设置开始时间为当前时间的 1 分钟前
+        start_time = int((now - timedelta(minutes=1)).timestamp())
+
+        # 设置结束时间为当前时间
+        end_time = int(now.timestamp())
+        accessKeyId = "LTAIWYUujJAm7CbH"
+        accessKey = "RfSjdiWwED1sGFlsjXv0DlfTnZTG1P"
+
+        project = "manager-new"
+        logstore = "request-log"
+        endpoint = "cn-hangzhou.log.aliyuncs.com"
+
+        try:
+            # 创建 LogClient 实例
+            client = LogClient(endpoint, accessKeyId, accessKey)
+
+            # 新的查询条件
+            query = "* and url: updateVideoRecommendStatus | select trim(split(requestBody, ',')[1], '[]') as videoid, trim(split(requestBody, ',')[3], '[]') as videorecomendstatus HAVING videorecomendstatus='-6'"
+
+            # 创建查询日志的请求
+            request = GetLogsRequest(
+                project=project,
+                logstore=logstore,
+                fromTime=start_time,
+                toTime=end_time,
+                topic="",
+                query=query,
+                line=100,
+                offset=0,
+                reverse=False
+            )
+
+            # 打印请求参数
+            print(f"请求参数: {request}")
+
+            # 执行查询请求
+            response = client.get_logs(request)
+            if response.get_count() > 0:
+                logs = response.get_logs()
+                for log in logs:
+                    contents = log.get_contents()
+
+                    # 确保处理内容时不产生错误
+                    log_str = {}
+                    for k, v in contents.items():
+                        if isinstance(v, bytes):
+                            log_str[k] = v.decode('utf-8', errors='replace')  # 解码字节为字符串,替换错误字符
+                        elif isinstance(v, str):
+                            log_str[k] = v  # 保持字符串类型
+                        else:
+                            log_str[k] = str(v)  # 其他类型转换为字符串
+                    video_id = log_str['videoid']
+                    AliyunLogger.logging(video_id, "扫描到一条视频ID,等待封面处理", "1001")
+                    # 输出日志内容
+                    pq_list.append(video_id)
+                return pq_list
+            else:
+                print("没有符合条件的日志")
+                return pq_list
+        except Exception as e:
+            print(f"查询日志时出错: {e}")
+            return pq_list
+
+    # 写入阿里云日志
+    @staticmethod
+    def logging(video_id: str,
+                message: str,
+                code: str,
+                data: Optional[str] = None,
+                old_cover_url: Optional[str] = None,
+                new_cover_url: Optional[str] = None
+                ):
+        """
+        写入阿里云日志
+        测试库: https://sls.console.aliyun.com/lognext/project/crawler-log-dev/logsearch/crawler-log-dev
+        正式库: https://sls.console.aliyun.com/lognext/project/crawler-log-prod/logsearch/crawler-log-prod
+        """
+        accessKeyId = "LTAIWYUujJAm7CbH"
+        accessKey = "RfSjdiWwED1sGFlsjXv0DlfTnZTG1P"
+
+        project = "crawler-log-prod"
+        logstore = "video_cover_info"
+        endpoint = "cn-hangzhou.log.aliyuncs.com"
+        try:
+            # if data:
+            #     data = dict(item.split(":", 1) for item in data.split(","))
+            contents = [
+                ("video_id", video_id),
+                ("message", message),
+                ("code", code),
+                ("old_cover_url", old_cover_url if old_cover_url is not None else ""),
+                ("new_cover_url", new_cover_url if new_cover_url is not None else ""),
+                ("data", json.dumps(data, ensure_ascii=False) if data else ""),
+            ]
+            # 创建 LogClient 实例
+            client = LogClient(endpoint, accessKeyId, accessKey)
+            log_group = []
+            log_item = LogItem()
+            log_item.set_contents(contents)
+            log_group.append(log_item)
+            # 写入日志
+            request = PutLogsRequest(
+                project=project,
+                logstore=logstore,
+                topic="",
+                source="",
+                logitems=log_group,
+                compress=False,
+            )
+
+            client.put_logs(request)
+        except Exception as e:
+            print(f"写入日志失败: {e}")
+
+
+# 示例使用
+if __name__ == "__main__":
+    # 当前时间
+    now = datetime.now()
+
+    # 设置开始时间为当前时间的 1 分钟前
+    start_time = int((now - timedelta(minutes=1)).timestamp())
+
+    # 设置结束时间为当前时间
+    end_time = int(now.timestamp())
+    AliyunLogger.query_logs_by_status()

+ 52 - 0
common/common_log.py

@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+公共方法,包含:生成log / 删除log / 下载方法 / 删除 weixinzhishu_chlsfiles / 过滤词库 / 保存视频信息至本地 txt / 翻译 / ffmpeg
+"""
+import os
+import sys
+
+sys.path.append(os.getcwd())
+from datetime import date, timedelta
+from datetime import datetime
+from loguru import logger
+
+proxies = {"http": None, "https": None}
+
+
+class Common:
+    # 统一获取当前时间 <class 'datetime.datetime'>  2022-04-14 20:13:51.244472
+    now = datetime.now()
+    # 昨天 <class 'str'>  2022-04-13
+    yesterday = (date.today() + timedelta(days=-1)).strftime("%Y-%m-%d")
+    # 今天 <class 'datetime.date'>  2022-04-14
+    today = date.today()
+    # 明天 <class 'str'>  2022-04-15
+    tomorrow = (date.today() + timedelta(days=1)).strftime("%Y-%m-%d")
+
+    # 使用 logger 模块生成日志
+    @staticmethod
+    def logger(log_type):
+        try:
+            """
+            使用 logger 模块生成日志
+            """
+            # 日志路径
+            log_dir = f"./logs/{log_type}/"
+            log_path = os.getcwd() + os.sep + log_dir
+            if not os.path.isdir(log_path):
+                os.makedirs(log_path)
+            # 日志文件名
+            log_name = f"{log_type}-{datetime.now().date().strftime('%Y-%m-%d')}.log"
+
+            # 日志不打印到控制台
+            logger.remove(handler_id=None)
+            # 初始化日志
+            logger.add(os.path.join(log_dir, log_name), level="INFO", rotation="00:00", retention="10 days", enqueue=True)
+
+            return logger
+        except Exception as e:
+            Common.logger("aly-logger").log(f"阿里云日志上报异常{e}")
+            return None
+
+

+ 284 - 0
common/cover_gpt4o.py

@@ -0,0 +1,284 @@
+import requests
+import json
+
+
+class CoverAnalyzer:
+    @staticmethod
+    def cover_list_data(cover_list):
+        list_data = []
+        for cover in cover_list:
+            data_dict = CoverAnalyzer.analyze_covers(cover)
+            list_data.append(data_dict)
+        return list_data
+
+    @staticmethod
+    def analyze_covers(cover):
+        for i in range(3):
+            url = "http://aigc-api.cybertogether.net//aigc/dev/test/gpt"
+            payload = json.dumps({
+                "imageList": [cover],
+                "prompt": "请帮我完成图片分析工作",
+                "responseFormat": {
+                    "type": "json_schema",
+                    "json_schema": {
+                        "strict": True,
+                        "name": "comprehension_result",
+                        "schema": {
+                            "type": "object",
+                            "properties": {
+                                "是否包含老年人为画面主体": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否包含老年人为画面主体,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否有多人为画面主体": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否有多人为画面主体,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否有超过3人为画面主体": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否有超过3人为画面主体,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否充斥画面的密集人群为画面主体": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否充斥画面的密集人群为画面主体,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否存在知名历史、近代人物": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否存在知名历史、近代人物,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否存在人物脸部、头部未完整出现在画面的情况": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否存在人物脸部、头部未完整出现在画面的情况,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否是不以人为主体的鲜花、美景、知名建筑或风景": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否是不以人为主体的鲜花、美景、知名建筑或风景,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否是老照片、怀旧风格": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否是老照片、怀旧风格,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否是农村、军事、综艺演出、历史画面": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否是农村、军事、综艺演出、历史画面,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否有趣味、惊奇的形象或画面为主体": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否有趣味、惊奇的形象或画面为主体,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否以大号文字或密集文字为主体并且不包含人物": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否以大号文字或密集文字为主体并且不包含人物,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否是不包含人物的纯色画面": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否是不包含人物的纯色画面,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                },
+                                "是否是模糊的或清晰度、像素较低的": {
+                                    "type": "object",
+                                    "properties": {
+                                        "result": {
+                                            "description": "请分析所有图片,是否是模糊的或清晰度、像素较低的,如果是返回1,如果不是返回0",
+                                            "type": "number",
+                                            "enum": [1, 0]
+                                        },
+                                        "reason": {
+                                            "description": "得出结论的客观的分析原因和依据",
+                                            "type": "string"
+                                        }
+                                    },
+                                    "required": ["result", "reason"],
+                                    "additionalProperties": False
+                                }
+                            },
+                            "additionalProperties": False,
+                            "required": [
+                                "是否包含老年人为画面主体",
+                                "是否有多人为画面主体",
+                                "是否有超过3人为画面主体",
+                                "是否充斥画面的密集人群为画面主体",
+                                "是否存在知名历史、近代人物",
+                                "是否存在人物脸部、头部未完整出现在画面的情况",
+                                "是否是不以人为主体的鲜花、美景、知名建筑或风景",
+                                "是否是老照片、怀旧风格",
+                                "是否是农村、军事、综艺演出、历史画面",
+                                "是否有趣味、惊奇的形象或画面为主体",
+                                "是否以大号文字或密集文字为主体并且不包含人物",
+                                "是否是不包含人物的纯色画面",
+                                "是否是模糊的或清晰度、像素较低的"
+                            ]
+                        }
+                    }
+                }
+            })
+            headers = {'Content-Type': 'application/json'}
+            try:
+                response = requests.post(url, headers=headers, data=payload)
+                response_data = response.json()
+
+                data = json.loads(response_data.get('data', '{}'))
+
+                result_sum = sum(item['result'] for item in data.values())  # 获取总和
+                data_dict = {
+                    "data": data,
+                    "cover_url": cover,
+                    "cover_sum": result_sum
+                }
+                return data_dict
+            except requests.exceptions.RequestException as e:
+                print(f"请求出错: {e}")
+                continue
+            except json.JSONDecodeError as e:
+                print(f"JSON 解析错误: {e}")
+                continue
+            except Exception as e:
+                print(f"其他错误: {e}")
+                continue
+
+        return None
+
+

+ 412 - 0
common/feishu_utils.py

@@ -0,0 +1,412 @@
+# -*- coding: utf-8 -*-
+# @Time: 2023/12/26
+"""
+飞书表配置: token 鉴权 / 增删改查 / 机器人报警
+"""
+import json
+import os
+import sys
+import requests
+import urllib3
+
+sys.path.append(os.getcwd())
+from common import Common
+
+proxies = {"http": None, "https": None}
+
+
+class Feishu:
+    """
+    编辑飞书云文档
+    """
+    succinct_url = "https://w42nne6hzg.feishu.cn/sheets/"
+    # 飞书路径token
+    @classmethod
+    def spreadsheettoken(cls, crawler):
+        if crawler == "summary":
+            return "KsoMsyP2ghleM9tzBfmcEEXBnXg"
+        else:
+            return crawler
+
+
+
+    # 获取飞书api token
+    @classmethod
+    def get_token(cls):
+        """
+        获取飞书api token
+        :return:
+        """
+        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
+        post_data = {"app_id": "cli_a13ad2afa438d00b",  # 这里账号密码是发布应用的后台账号及密码
+                     "app_secret": "4tK9LY9VbiQlY5umhE42dclBFo6t4p5O"}
+
+        try:
+            urllib3.disable_warnings()
+            response = requests.post(url=url, data=post_data, proxies=proxies, verify=False)
+            tenant_access_token = response.json()["tenant_access_token"]
+            return tenant_access_token
+        except Exception as e:
+            Common.logger("feishu").error("获取飞书 api token 异常:{}", e)
+
+    # 获取表格元数据
+    @classmethod
+    def get_metainfo(cls, crawler):
+        """
+        获取表格元数据
+        :return:
+        """
+        try:
+            get_metainfo_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                               + cls.spreadsheettoken(crawler) + "/metainfo"
+
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "extFields": "protectedRange",  # 额外返回的字段,extFields=protectedRange时返回保护行列信息
+                "user_id_type": "open_id"  # 返回的用户id类型,可选open_id,union_id
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_metainfo_url, headers=headers, params=params, proxies=proxies, verify=False)
+            response = json.loads(r.content.decode("utf8"))
+            return response
+        except Exception as e:
+            Common.logger("feishu").error("获取表格元数据异常:{}", e)
+
+    # 读取工作表中所有数据
+    @classmethod
+    def get_values_batch(cls, crawler, sheetid):
+        """
+        读取工作表中所有数据
+        :param crawler: 哪个爬虫
+        :param sheetid: 哪张表
+        :return: 所有数据
+        """
+        try:
+            get_values_batch_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                   + cls.spreadsheettoken(crawler) + "/values_batch_get"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "ranges": sheetid,
+                "valueRenderOption": "ToString",
+                "dateTimeRenderOption": "",
+                "user_id_type": "open_id"
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_values_batch_url, headers=headers, params=params, proxies=proxies, verify=False)
+            response = json.loads(r.content.decode("utf8"))
+            values = response["data"]["valueRanges"][0]["values"]
+            return values
+        except Exception as e:
+            Common.logger("feishu").error("读取工作表所有数据异常:{}", e)
+
+    # 工作表,插入行或列
+    @classmethod
+    def insert_columns(cls, crawler, sheetid, majordimension, startindex, endindex):
+        """
+        工作表插入行或列
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫的云文档
+        :param sheetid:哪张工作表
+        :param majordimension:行或者列, ROWS、COLUMNS
+        :param startindex:开始位置
+        :param endindex:结束位置
+        """
+        try:
+            insert_columns_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                 + cls.spreadsheettoken(crawler) + "/insert_dimension_range"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "dimension": {
+                    "sheetId": sheetid,
+                    "majorDimension": majordimension,  # 默认 ROWS ,可选 ROWS、COLUMNS
+                    "startIndex": startindex,  # 开始的位置
+                    "endIndex": endindex  # 结束的位置
+                },
+                "inheritStyle": "AFTER"  # BEFORE 或 AFTER,不填为不继承 style
+            }
+
+            urllib3.disable_warnings()
+            r = requests.post(url=insert_columns_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("插入行或列:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("插入行或列异常:{}", e)
+
+    # 写入数据
+    @classmethod
+    def update_values(cls, crawler, sheetid, ranges, values):
+        """
+        写入数据
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫的云文档
+        :param sheetid:哪张工作表
+        :param ranges:单元格范围
+        :param values:写入的具体数据,list
+        """
+        try:
+            update_values_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                + cls.spreadsheettoken(crawler) + "/values_batch_update"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "valueRanges": [
+                    {
+                        "range": sheetid + "!" + ranges,
+                        "values": values
+                    },
+                ],
+            }
+            urllib3.disable_warnings()
+            r = requests.post(url=update_values_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("写入数据:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("写入数据异常:{}", e)
+
+    # 合并单元格
+    @classmethod
+    def merge_cells(cls, crawler, sheetid, ranges):
+        """
+        合并单元格
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid:哪张工作表
+        :param ranges:需要合并的单元格范围
+        """
+        try:
+            merge_cells_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                              + cls.spreadsheettoken(crawler) + "/merge_cells"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+
+            body = {
+                "range": sheetid + "!" + ranges,
+                "mergeType": "MERGE_ROWS"
+            }
+            urllib3.disable_warnings()
+            r = requests.post(url=merge_cells_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("合并单元格:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("合并单元格异常:{}", e)
+
+    # 读取单元格数据
+    @classmethod
+    def get_range_value(cls, crawler, sheetid, cell):
+        """
+        读取单元格内容
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid: 哪张工作表
+        :param cell: 哪个单元格
+        :return: 单元格内容
+        """
+        try:
+            get_range_value_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                  + cls.spreadsheettoken(crawler) + "/values/" + sheetid + "!" + cell
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            params = {
+                "valueRenderOption": "FormattedValue",
+
+                # dateTimeRenderOption=FormattedString 计算并对时间日期按照其格式进行格式化,但不会对数字进行格式化,返回格式化后的字符串。
+                "dateTimeRenderOption": "",
+
+                # 返回的用户id类型,可选open_id,union_id
+                "user_id_type": "open_id"
+            }
+            urllib3.disable_warnings()
+            r = requests.get(url=get_range_value_url, headers=headers, params=params, proxies=proxies, verify=False)
+            # print(r.text)
+            return r.json()["data"]["valueRange"]["values"][0]
+        except Exception as e:
+            Common.logger("feishu").error("读取单元格数据异常:{}", e)
+    # 获取表内容
+    @classmethod
+    def get_sheet_content(cls, crawler, sheet_id):
+        try:
+            sheet = Feishu.get_values_batch(crawler, sheet_id)
+            content_list = []
+            for x in sheet:
+                for y in x:
+                    if y is None:
+                        pass
+                    else:
+                        content_list.append(y)
+            return content_list
+        except Exception as e:
+            Common.logger("feishu").error(f'get_sheet_content:{e}\n')
+
+    # 删除行或列,可选 ROWS、COLUMNS
+    @classmethod
+    def dimension_range(cls, log_type, crawler, sheetid, major_dimension, startindex, endindex):
+        """
+        删除行或列
+        :param log_type: 日志路径
+        :param crawler: 哪个爬虫
+        :param sheetid:工作表
+        :param major_dimension:默认 ROWS ,可选 ROWS、COLUMNS
+        :param startindex:开始的位置
+        :param endindex:结束的位置
+        :return:
+        """
+        try:
+            dimension_range_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/" \
+                                  + cls.spreadsheettoken(crawler) + "/dimension_range"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            body = {
+                "dimension": {
+                    "sheetId": sheetid,
+                    "majorDimension": major_dimension,
+                    "startIndex": startindex,
+                    "endIndex": endindex
+                }
+            }
+            urllib3.disable_warnings()
+            r = requests.delete(url=dimension_range_url, headers=headers, json=body, proxies=proxies, verify=False)
+            Common.logger("feishu").info("删除视频数据:{}", r.json()["msg"])
+        except Exception as e:
+            Common.logger("feishu").error("删除视频数据异常:{}", e)
+
+    # 获取用户 ID
+    @classmethod
+    def get_userid(cls, username):
+        try:
+            url = "https://open.feishu.cn/open-apis/user/v1/batch_get_id?"
+            headers = {
+                "Authorization": "Bearer " + cls.get_token(),
+                "Content-Type": "application/json; charset=utf-8"
+            }
+            name_phone_dict = {
+                "xinxin": "15546206651",
+                "muxinyi": "13699208058",
+                "wangxueke": "13513479926",
+                "yuzhuoyi": "18624010360",
+                "luojunhui": "18801281360",
+                "fanjun": "15200827642",
+                "zhangyong": "17600025055",
+                'liukunyu': "18810931977"
+            }
+            username = name_phone_dict.get(username)
+
+            data = {"mobiles": [username]}
+            urllib3.disable_warnings()
+            r = requests.get(url=url, headers=headers, params=data, verify=False, proxies=proxies)
+            open_id = r.json()["data"]["mobile_users"][username][0]["open_id"]
+
+            return open_id
+        except Exception as e:
+            Common.logger("feishu").error(f"get_userid异常:{e}\n")
+
+    # 飞书机器人
+    @classmethod
+    def bot(cls, log_type, crawler, text, mark_name):
+        try:
+
+            headers = {'Content-Type': 'application/json'}
+            if crawler == "机器自动改造消息通知":
+                url = "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/KsoMsyP2ghleM9tzBfmcEEXBnXg?sheet=bc154d"
+                users = f"<at id=" + str(cls.get_userid(log_type)) + f">{mark_name}</at>"
+            elif crawler == "快手关键词搜索":
+                url = "https://open.feishu.cn/open-apis/bot/v2/hook/e7697dc6-5254-4411-8b59-3cd0742bf703"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/KsoMsyP2ghleM9tzBfmcEEXBnXg?sheet=U1gySe"
+                users = "".join([f'<at id="{cls.get_userid(type)}">{name}</at>' for type, name in
+                                 zip(log_type, mark_name)])
+                # users = f"<at id=" + str(cls.get_userid(log_type)) + f">{mark_name}</at>"
+            else:
+                url = "https://open.feishu.cn/open-apis/bot/v2/hook/7928f182-08c1-4c4d-b2f7-82e10c93ca80"
+                sheet_url = "https://w42nne6hzg.feishu.cn/sheets/KsoMsyP2ghleM9tzBfmcEEXBnXg?sheet=bc154d"
+                users = f"<at id=" + str(cls.get_userid(log_type)) + f">{mark_name}</at>"
+            data = json.dumps({
+                "msg_type": "interactive",
+                "card": {
+                    "config": {
+                        "wide_screen_mode": True,
+                        "enable_forward": True
+                    },
+                    "elements": [{
+                        "tag": "div",
+                        "text": {
+                            "content": users + text,
+                            "tag": "lark_md"
+                        }
+                    }, {
+                        "actions": [{
+                            "tag": "button",
+                            "text": {
+                                "content": "详情,点击~~~~~",
+                                "tag": "lark_md"
+                            },
+                            "url": sheet_url,
+                            "type": "default",
+                            "value": {}
+                        }],
+                        "tag": "action"
+                    }],
+                    "header": {
+                        "title": {
+                            "content": "📣消息提醒",
+                            "tag": "plain_text"
+                        }
+                    }
+                }
+            })
+            urllib3.disable_warnings()
+            r = requests.post(url, headers=headers, data=data, verify=False, proxies=proxies)
+            Common.logger("feishu").info(f'触发机器人消息:{r.status_code}, {text}')
+        except Exception as e:
+            Common.logger("feishu").error(f"bot异常:{e}\n")
+
+    # 飞书机器人-改造计划完成通知
+    @classmethod
+    def finish_bot(cls, text, url, content):
+        try:
+            headers = {'Content-Type': 'application/json'}
+            data = json.dumps({
+                "msg_type": "interactive",
+                "card": {
+                    "config": {
+                        "wide_screen_mode": True,
+                        "enable_forward": True
+                    },
+                    "elements": [{
+                        "tag": "div",
+                        "text": {
+                            "content": text,
+                            "tag": "lark_md"
+                        }
+                    }],
+                    "header": {
+                        "title": {
+                            "content": content,
+                            "tag": "plain_text"
+                        }
+                    }
+                }
+            })
+            urllib3.disable_warnings()
+            r = requests.post(url, headers=headers, data=data, verify=False, proxies=proxies)
+            Common.logger("feishu").info(f'触发机器人消息:{r.status_code}, {text}')
+        except Exception as e:
+            Common.logger("feishu").error(f"bot异常:{e}\n")
+
+
+if __name__ == "__main__":
+    Feishu.bot('recommend', '抖音', '测试: 抖音cookie失效,请及时更换')
+

+ 69 - 0
common/mysql_db.py

@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+"""
+数据库连接及操作
+"""
+import redis
+import pymysql
+# from common import Common
+
+class MysqlHelper:
+    @classmethod
+    def connect_mysql(cls):
+        # 创建一个 Connection 对象,代表了一个数据库连接
+        connection = pymysql.connect(
+            host="rm-bp1159bu17li9hi94.mysql.rds.aliyuncs.com",  # 数据库IP地址,内网地址
+            # host="rm-bp1159bu17li9hi94ro.mysql.rds.aliyuncs.com",# 数据库IP地址,外网地址
+            port=3306,  # 端口号
+            user="crawler",  # mysql用户名
+            passwd="crawler123456@",  # mysql用户登录密码
+            db="piaoquan-crawler",  # 数据库名
+            # 如果数据库里面的文本是utf8编码的,charset指定是utf8
+            charset="utf8")
+        return connection
+
+    @classmethod
+    def get_values(cls, sql, params=None):
+        try:
+            # 连接数据库
+            connect = cls.connect_mysql()
+            # 返回一个 Cursor对象
+            mysql = connect.cursor()
+
+            if params:
+                # 如果传递了 params 参数
+                mysql.execute(sql, params)
+            else:
+                # 如果没有传递 params 参数
+                mysql.execute(sql)
+            # fetchall方法返回的是一个元组,里面每个元素也是元组,代表一行记录
+            data = mysql.fetchall()
+
+            # 关闭数据库连接
+            connect.close()
+
+            # 返回查询结果,元组
+            return data
+        except Exception as e:
+            print(f"get_values异常:{e}\n")
+
+    @classmethod
+    def update_values(cls, sql):
+        # 连接数据库
+        connect = cls.connect_mysql()
+        # 返回一个 Cursor对象
+        mysql = connect.cursor()
+        try:
+            # 执行 sql 语句
+            res = mysql.execute(sql)
+            # 注意 一定要commit,否则添加数据不生效
+            connect.commit()
+            return res
+        except Exception as e:
+            # 发生错误时回滚
+            connect.rollback()
+        # 关闭数据库连接
+        connect.close()
+
+
+
+

+ 107 - 0
common/pq_cover.py

@@ -0,0 +1,107 @@
+import requests
+import json
+
+
+class PQ:
+    @staticmethod
+    def get_pq_all_cover(video_id: str):
+        """获取所有封面"""
+        url = "https://longvideoapi.piaoquantv.com/longvideoapi/video/getCoverImagePaths"
+        payload = f"versionCode=31&platform=PC&appType=5&loginUid=208&id={video_id}&needSelfCoverImg=1"
+        headers = {
+            'accept': '*/*',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
+            'origin': 'https://admin.piaoquantv.com',
+            'pragma': 'no-cache',
+            'priority': 'u=1, i',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"macOS"',
+            'sec-fetch-dest': 'empty',
+            'sec-fetch-mode': 'cors',
+            'sec-fetch-site': 'same-site',
+            'Cookie': 'JSESSIONID=694A2E661D405FD3A62B2F4AE84B91D4'
+        }
+
+        response = requests.request("POST", url, headers=headers, data=payload)
+        response = response.json()
+        code = response['code']
+        if code != 0:
+            return
+        cover_imgs = response['data']['videoCoverImages']
+        return cover_imgs
+
+    @staticmethod
+    def update_pq_video_cover(video_id: str, cover_url: str, cover_id: int):
+        """修改封面"""
+        if cover_id == None:
+            return 1
+        url = "https://admin.piaoquantv.com/manager/video/multiCover/update"
+
+        payload = json.dumps([
+            {
+                "id": cover_id,
+                "coverUrl": cover_url,
+                "shareWeight": 1000,
+                "distributionWeight": 1000,
+                "videoId": video_id
+            }
+        ])
+        headers = {
+            'accept': 'application/json',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'content-type': 'application/json',
+            'cookie': 'SESSION=NjVmNjUzNDYtZDQzNy00MjA4LTgzMTktY2Q2ODE3ODAxMmQ3',
+            'origin': 'https://admin.piaoquantv.com',
+            'pragma': 'no-cache',
+            'priority': 'u=1, i',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"macOS"',
+            'sec-fetch-dest': 'empty',
+            'sec-fetch-mode': 'cors',
+            'sec-fetch-site': 'same-origin'
+        }
+
+        response = requests.request("POST", url, headers=headers, data=payload)
+        response = response.json()
+        code = response['code']
+        print(code)
+        return code
+
+
+    @staticmethod
+    def get_pq_cover_id(video_id: str):
+        """获取封面id"""
+        url = "https://admin.piaoquantv.com/manager/video/multiCover/listV2"
+
+        payload = json.dumps({
+            "videoId": video_id,
+            "range": "2h"
+        })
+        headers = {
+            'accept': 'application/json',
+            'accept-language': 'zh-CN,zh;q=0.9',
+            'cache-control': 'no-cache',
+            'content-type': 'application/json',
+            'cookie': 'SESSION=NjVmNjUzNDYtZDQzNy00MjA4LTgzMTktY2Q2ODE3ODAxMmQ3',
+            'origin': 'https://admin.piaoquantv.com',
+            'pragma': 'no-cache',
+            'priority': 'u=1, i',
+            'sec-ch-ua-mobile': '?0',
+            'sec-ch-ua-platform': '"macOS"',
+            'sec-fetch-dest': 'empty',
+            'sec-fetch-mode': 'cors',
+            'sec-fetch-site': 'same-origin'
+        }
+        response = requests.request("POST", url, headers=headers, data=payload)
+        response = response.json()
+        code = response['code']
+        if code != 0:
+            return None, None
+        cover_id = response['content'][0]['id']
+        cover_url = response['content'][0]['coverUrl']
+
+        print(cover_id)
+        return cover_id, cover_url

+ 48 - 0
common/redis.py

@@ -0,0 +1,48 @@
+import redis
+
+
+class SyncRedisHelper:
+    _pool: redis.ConnectionPool = None
+    _instance = None
+
+    def __init__(self):
+        if not self._instance:
+            self._pool = self._get_pool()
+            self._instance = self
+
+    def _get_pool(self) -> redis.ConnectionPool:
+        if self._pool is None:
+            self._pool = redis.ConnectionPool(
+                # host="r-bp1mb0v08fqi4hjffupd.redis.rds.aliyuncs.com",  # 外网地址
+                host="r-bp1mb0v08fqi4hjffu.redis.rds.aliyuncs.com",  # 内网地址
+                port=6379,
+                db=0,
+                password="Wqsd@2019",
+                # password="Qingqu2019",
+
+            )
+        return self._pool
+
+    def get_client(self) -> redis.Redis:
+        pool = self._get_pool()
+        client = redis.Redis(connection_pool=pool)
+        return client
+
+    def close(self):
+        if self._pool:
+            self._pool.disconnect(inuse_connections=True)
+
+
+def insert_pq_data(data):
+    task = f"task:pq_video_id"
+    helper = SyncRedisHelper()
+    client = helper.get_client()
+    client.rpush(task, *data)
+
+
+def get_pq_id():
+    task = f"task:pq_video_id"
+    helper = SyncRedisHelper()
+    client = helper.get_client()
+    ret = client.lpop(task)
+    return ret

+ 29 - 0
common/sql_help.py

@@ -0,0 +1,29 @@
+import os
+import sys
+import json
+
+sys.path.append(os.getcwd())
+from common.mysql_db import MysqlHelper
+
+
+class sqlCollect():
+    """
+    视频信息写入库中
+    """
+    @classmethod
+    def insert_video_cover_method(cls, video_id: str, cover_id: str, old_cover_url: str, cover_url_list: str, new_cover_url: str):
+        cover_url_list_json = json.dumps( cover_url_list, ensure_ascii=False )
+
+        insert_sql = f"""INSERT INTO video_cover_method (video_id, cover_id, old_cover_url, cover_url_list, new_cover_url) values ('{video_id}' ,'{cover_id}','{old_cover_url}', '{cover_url_list_json}', '{new_cover_url}')"""
+        MysqlHelper.update_values(
+            sql=insert_sql
+        )
+
+    """查询该账号是否存在"""
+    @classmethod
+    def select_video_cover_method(cls, video_id: str):
+        sql = """SELECT video_id FROM video_cover_method WHERE video_id = %s """
+        data = MysqlHelper.get_values(sql, (video_id))
+        if data:
+            return True
+        return False

+ 18 - 0
job_cover_method.py

@@ -0,0 +1,18 @@
+import time
+from common.redis import get_pq_id
+from video_cover_method.cover_method import CoverMethod
+def video_cover_task_start():
+    while True:
+        try:
+            video_id = get_pq_id()
+            if video_id:
+                CoverMethod.cover_method(video_id)
+            else:
+                print("没有获取待更改封面的视频ID,等待10秒")
+                time.sleep(10)
+        except Exception as e:
+            print("处理任务时出现异常:", e)
+            time.sleep(10)
+            continue
+if __name__ == '__main__':
+    video_cover_task_start()

+ 30 - 0
job_video_id.py

@@ -0,0 +1,30 @@
+from common.aliyun_log import AliyunLogger
+from common.redis import insert_pq_data
+import schedule
+import time
+
+
+def bot():
+    try:
+        print("开始执行")
+        pq_list = AliyunLogger.query_logs_by_status()
+
+        if pq_list:
+            insert_pq_data(pq_list)
+            print(f"插入数据: {pq_list}")
+        else:
+            print("没有新的日志数据")
+
+    except Exception as e:
+        print(f"执行时出错: {e}")
+    finally:
+        print("执行结束")
+
+
+# 每分钟执行一次
+schedule.every().minute.do(bot)
+
+if __name__ == "__main__":
+    while True:
+        schedule.run_pending()
+        time.sleep(1)  # 等待 1 秒

+ 0 - 0
video_cover_method/__init__.py


+ 124 - 0
video_cover_method/cover_method.py

@@ -0,0 +1,124 @@
+from common import Feishu
+from common.aliyun_log import AliyunLogger
+from common.cover_gpt4o import CoverAnalyzer
+from common.pq_cover import PQ
+from common.redis import insert_pq_data
+from common.sql_help import sqlCollect
+import time
+import random
+
+
+class CoverMethod:
+    @staticmethod
+    def cover_data_dispose(data_list):
+        valid_records = [record for record in data_list if record['cover_sum'] > 0]
+
+        if valid_records:
+            # 获取最高的cover_sum
+            highest_sum = max( record['cover_sum'] for record in valid_records )
+            # 获取所有cover_sum等于最高分数的记录
+            candidates = [record for record in valid_records if record['cover_sum'] == highest_sum]
+            # 随机选择一个
+            selected_record = random.choice(candidates)
+            highest_cover_url = selected_record['cover_url']
+            highest_cover_sum = selected_record['cover_sum']
+
+            return highest_cover_url, highest_cover_sum
+        else:
+            return None, None
+
+    @staticmethod
+    def cover_method(video_id):
+        # video_id = "25521693"
+        video_id_status = sqlCollect.select_video_cover_method(video_id)  # 判断该视频id是否处理过
+        if video_id_status:
+            AliyunLogger.logging(video_id, "重复视频ID,不做封面处理", "2001")
+            text = (
+                f"**通知类型**: 重复视频ID,不做封面处理\n"
+                f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+            )
+            Feishu.finish_bot( text,
+                               "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                               "【 封面修改通知 】" )
+            return
+        cover_list = PQ.get_pq_all_cover(video_id)  # 获取所有封面
+        if cover_list:
+            # gpt 评分
+            start_time = time.time()
+            cover_all_data = CoverAnalyzer.cover_list_data(cover_list)
+            end_time = time.time()
+            execution_time = end_time - start_time
+            print(f"代码运行时间: {execution_time:.4f} 秒")
+            highest_cover_url, highest_cover_sum = CoverMethod.cover_data_dispose(cover_all_data)  # 获取分数最高的视频封面
+            if highest_cover_url == None:
+                AliyunLogger.logging( video_id, "视频封面分数都为0,不做封面更改", "2002", cover_all_data)
+                text = (
+                    f"**通知类型**: 视频封面分数都为0,不做封面更改\n"
+                    f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+                )
+                Feishu.finish_bot( text,
+                                   "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                                   "【 封面修改通知 】" )
+                return
+            cover_id, old_cover_url = PQ.get_pq_cover_id(video_id)  # 获取原封面 和 封面ID
+            if cover_id == None:
+                AliyunLogger.logging( video_id, "获取视频原封面和封面ID失败", "3002", cover_all_data, old_cover_url, highest_cover_url)
+                insert_pq_data( [video_id] )
+                return
+            if highest_cover_url == old_cover_url:
+                AliyunLogger.logging( video_id, "原封面与新封面一致,不做封面修改", "2003", cover_all_data, old_cover_url,
+                                      highest_cover_url )
+                text = (
+                    f"**通知类型**: 视频封面分数都为0,不做封面更改\n"
+                    f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+                    f"**原视频封面链接**: {old_cover_url}\n"
+                    f"**新视频封面链接**: {highest_cover_url}\n"
+                    f"**新视频封面分数**: {highest_cover_sum}\n"
+                )
+                Feishu.finish_bot( text,
+                                   "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                                   "【 封面修改通知 】" )
+                return
+            code = PQ.update_pq_video_cover(video_id, highest_cover_url, cover_id)  # 修改封面
+            if code == 0:
+                AliyunLogger.logging( video_id, "修改封面成功", "1000", cover_all_data, old_cover_url, highest_cover_url)
+                text = (
+                    f"**通知类型**: 视频封面修改成功\n"
+                    f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+                    f"**原视频封面链接**: {old_cover_url}\n"
+                    f"**新视频封面链接**: {highest_cover_url}\n"
+                    f"**新视频封面分数**: {highest_cover_sum}\n"
+                )
+                Feishu.finish_bot( text,
+                                   "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                                   "【 封面修改通知 】" )
+                sqlCollect.insert_video_cover_method(video_id, cover_id, old_cover_url, cover_all_data, highest_cover_url)
+                return
+            else:
+                text = (
+                    f"**通知类型**: 视频封面修改失败\n"
+                    f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+                    f"**原视频封面链接**: {old_cover_url}\n"
+                    f"**新视频封面链接**: {highest_cover_url}\n"
+                    f"**新视频封面分数**: {highest_cover_sum}\n"
+                )
+                Feishu.finish_bot( text,
+                                   "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                                   "【 封面修改通知 】" )
+                AliyunLogger.logging( video_id, "修改视频封面失败", "3003", cover_all_data, old_cover_url, highest_cover_url)
+                insert_pq_data( [video_id] )
+        else:
+            AliyunLogger.logging(video_id, "没有获取到封面", "3001")
+            text = (
+                f"**通知类型**: 没有获取到封面\n"
+                f"**站内视频ID链接**: https://admin.piaoquantv.com/cms/post-detail/{video_id}/detail\n"
+            )
+            Feishu.finish_bot( text,
+                               "https://open.feishu.cn/open-apis/bot/v2/hook/63745308-c53e-4030-90b9-77383b057252",
+                               "【 封面修改通知 】" )
+            print(f"{video_id}没有获取到封面")
+            return
+
+
+if __name__ == '__main__':
+    CoverMethod.cover_method()