#coding=utf-8 #!/usr/bin/python import sys import json import urllib.parse from datetime import date sys.path.append('..') from base.spider import Spider # 全局常量 与JS文件完全对齐 DOUBAN_API = "https://frodo.douban.com/api/v2" API_KEY = "0ac44ae016490db2204ce0a042db2916" USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat" REFERER = "https://servicewechat.com/wx2f9b06c1de1ccfca/84/page-frame.html" # 图片防盗链后缀 PIC_SUFFIX = '@Referer=https://api.douban.com/@User-Agent=Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/113.0.0.0%20Safari/537.36' # 今日日期,用于判断是否上映 TODAY = date.today().isoformat() class Spider(Spider): def getName(self): return "豆瓣" def init(self, extend=""): print("============{0}============".format(extend)) pass def isVideoFormat(self, url): pass def manualVideoCheck(self): pass # 核心:判断影片是否已上映(对齐JS isReleased) def isReleased(self, item): if item.get("release_date"): return item["release_date"] <= TODAY if item.get("date"): return item["date"] <= TODAY return True # 核心:构建标准vod结构 对齐JS buildVod def buildVod(self, item): # 1. 解析类型 movie/剧集 tv/剧集 rawType = (item.get("type") or "").strip().lower() typeName = "" if "movie" in rawType: typeName = "电影" elif "tv" in rawType: typeName = "剧集" # 2. 解析年份+地区 card_subtitle格式 2025 / 中国大陆 card_subtitle = item.get("card_subtitle", "") year = "" region = "" import re match = re.match(r"^(\d{4})\s*\/\s*([^\/]+)", card_subtitle) if match: year = match.group(1) region = match.group(2).strip() # 3. 评分处理 rating_val = "暂无" rating = item.get("rating") if rating and rating.get("value", 0) != 0: rating_val = str(rating["value"]) # 4. 图片处理 pic_url = "" pic = item.get("pic", {}) if pic and pic.get("normal"): pic_url = pic["normal"] + PIC_SUFFIX return { "vod_id": str(item.get("id", "")), "vod_name": item.get("title", ""), "vod_pic": pic_url, "vod_remarks": f"{typeName}评分 {rating_val}", "vod_year": f"{year}{region}" if year else "" } def homeContent(self, filter): result = {} cateManual = { "热播剧集": "tv_hot", "热门电影": "hot_movie", "热播综艺": "show_hot", "电影筛选": "movie", "电视筛选": "tv", "电影榜单": "rank_list_movie", "电视榜单": "rank_list_tv" } classes = [] for k in cateManual: classes.append({ 'type_name': k, 'type_id': cateManual[k] }) result['class'] = classes if filter: result['filters'] = self.config['filter'] return result # 首页推荐列表(对齐JS homeVod) def homeVideoContent(self): try: params = { "apikey": API_KEY, "has_schedule": True, "count": 30 } query = urllib.parse.urlencode(params) url = f"{DOUBAN_API}/subject_collection/subject_real_time_hotest/items?{query}" rsp = self.fetch(url, headers=self.header, timeout=4000) jo = json.loads(rsp.text) joList = jo.get("subject_collection_items", []) lists = [] for item in joList: item_type = item.get("type", "") if item_type not in ["movie", "tv"]: continue # 过滤未上映 if not self.isReleased(item): continue vod = self.buildVod(item) if vod["vod_pic"]: lists.append(vod) return {"list": lists} except Exception as e: return {"list": []} # 分类列表(对齐JS category) def categoryContent(self, tid, pg, filter, extend): result = { "list": [], "page": int(pg), "pagecount": 9999, "limit": 30, "total": 999999 } try: start = (int(pg) - 1) * 30 path = "" req_params = { "apikey": API_KEY, "has_schedule": True, "start": start, "count": 30 } # 分支逻辑完全对齐JS if tid == "hot_movie": path = "/movie/hot_gaia" req_params["sort"] = extend.get("sort", "recommend") req_params["area"] = extend.get("area", "全部") data_key = "items" elif tid in ["tv_hot", "show_hot"]: sub_type = extend.get("type", tid) path = f"/subject_collection/{sub_type}/items" data_key = "subject_collection_items" elif tid == "movie": tags = [] if extend.get("类型"): tags.append(extend["类型"]) if extend.get("地区"): tags.append(extend["地区"]) if extend.get("年代"): tags.append(extend["年代"]) req_params["tags"] = ",".join([t for t in tags if t.strip()]) req_params["sort"] = extend.get("sort", "T") path = "/movie/recommend" data_key = "items" elif tid == "tv": tags = [] if extend.get("类型"): tags.append(extend["类型"]) if extend.get("电视剧形式"): tags.append(extend["电视剧形式"]) if extend.get("综艺形式"): tags.append(extend["综艺形式"]) if extend.get("地区"): tags.append(extend["地区"]) if extend.get("年代"): tags.append(extend["年代"]) if extend.get("平台"): tags.append(extend["平台"]) req_params["tags"] = ",".join([t for t in tags if t.strip()]) req_params["sort"] = extend.get("sort", "T") path = "/tv/recommend" data_key = "items" elif tid in ["rank_list_movie", "rank_list_tv"]: # 默认榜单ID对齐JS default_rank = { "rank_list_movie": "movie_real_time_hotest", "rank_list_tv": "tv_real_time_hotest" }[tid] rank_id = extend.get("榜单", default_rank) path = f"/subject_collection/{rank_id}/items" data_key = "subject_collection_items" else: return result # 拼接请求URL query_str = urllib.parse.urlencode(req_params) url = f"{DOUBAN_API}{path}?{query_str}" rsp = self.fetch(url, headers=self.header, timeout=4000) jo = json.loads(rsp.text) jolist = jo.get(data_key, []) videos = [] for vod_item in jolist: if not self.isReleased(vod_item): continue vod = self.buildVod(vod_item) if vod["vod_pic"]: videos.append(vod) result["list"] = videos return result except Exception as e: return result # 详情、播放、搜索 对齐JS空实现 def detailContent(self, array): return [] def searchContent(self, key, quick, pg=1): return {"list": []} def playerContent(self, flag, id, vipFlags): return {} # 请求头 和JS完全一致 header = { "Host": "frodo.douban.com", "Connection": "Keep-Alive", "Referer": REFERER, "User-Agent": USER_AGENT } def localProxy(self, param): return [200, "video/MP2T", b"", ""] # 筛选配置 完全复制JS filters config = { "player": {}, "filter": { "hot_movie": [ {"key": "sort", "name": "排序", "value": [ {"n": "热度", "v": "recommend"}, {"n": "最新", "v": "time"}, {"n": "评分", "v": "rank"} ]}, {"key": "area", "name": "地区", "value": [ {"n": "全部", "v": "全部"}, {"n": "华语", "v": "华语"}, {"n": "欧美", "v": "欧美"}, {"n": "韩国", "v": "韩国"}, {"n": "日本", "v": "日本"} ]} ], "tv_hot": [ {"key": "type", "name": "分类", "value": [ {"n": "综合", "v": "tv_hot"}, {"n": "国产剧", "v": "tv_domestic"}, {"n": "欧美剧", "v": "tv_american"}, {"n": "日剧", "v": "tv_japanese"}, {"n": "韩剧", "v": "tv_korean"}, {"n": "动画", "v": "tv_animation"} ]} ], "show_hot": [ {"key": "type", "name": "分类", "value": [ {"n": "综合", "v": "show_hot"}, {"n": "国内", "v": "show_domestic"}, {"n": "国外", "v": "show_foreign"} ]} ], "movie": [ {"key": "类型", "name": "类型", "value": [ {"n": "全部类型", "v": ""}, {"n": "喜剧", "v": "喜剧"}, {"n": "爱情", "v": "爱情"}, {"n": "动作", "v": "动作"}, {"n": "科幻", "v": "科幻"}, {"n": "动画", "v": "动画"}, {"n": "悬疑", "v": "悬疑"}, {"n": "犯罪", "v": "犯罪"}, {"n": "惊悚", "v": "惊悚"}, {"n": "冒险", "v": "冒险"}, {"n": "音乐", "v": "音乐"}, {"n": "历史", "v": "历史"}, {"n": "奇幻", "v": "奇幻"}, {"n": "恐怖", "v": "恐怖"}, {"n": "战争", "v": "战争"}, {"n": "传记", "v": "传记"}, {"n": "歌舞", "v": "歌舞"}, {"n": "武侠", "v": "武侠"}, {"n": "情色", "v": "情色"}, {"n": "灾难", "v": "灾难"}, {"n": "西部", "v": "西部"}, {"n": "纪录片", "v": "纪录片"}, {"n": "短片", "v": "短片"} ]}, {"key": "地区", "name": "地区", "value": [ {"n": "全部地区", "v": ""}, {"n": "华语", "v": "华语"}, {"n": "欧美", "v": "欧美"}, {"n": "韩国", "v": "韩国"}, {"n": "日本", "v": "日本"}, {"n": "中国大陆", "v": "中国大陆"}, {"n": "美国", "v": "美国"}, {"n": "中国香港", "v": "中国香港"}, {"n": "中国台湾", "v": "中国台湾"}, {"n": "英国", "v": "英国"}, {"n": "法国", "v": "法国"}, {"n": "德国", "v": "德国"}, {"n": "意大利", "v": "意大利"}, {"n": "西班牙", "v": "西班牙"}, {"n": "印度", "v": "印度"}, {"n": "泰国", "v": "泰国"}, {"n": "俄罗斯", "v": "俄罗斯"}, {"n": "加拿大", "v": "加拿大"}, {"n": "澳大利亚", "v": "澳大利亚"}, {"n": "爱尔兰", "v": "爱尔兰"}, {"n": "瑞典", "v": "瑞典"}, {"n": "巴西", "v": "巴西"}, {"n": "丹麦", "v": "丹麦"} ]}, {"key": "sort", "name": "排序", "value": [ {"n": "近期热度", "v": "T"}, {"n": "首映时间", "v": "R"}, {"n": "高分优先", "v": "S"} ]}, {"key": "年代", "name": "年代", "value": [ {"n": "全部年代", "v": ""}, {"n": "2026", "v": "2026"}, {"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"}, {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, {"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, {"n": "2010年代", "v": "2010年代"}, {"n": "2000年代", "v": "2000年代"}, {"n": "90年代", "v": "90年代"}, {"n": "80年代", "v": "80年代"}, {"n": "70年代", "v": "70年代"}, {"n": "60年代", "v": "60年代"}, {"n": "更早", "v": "更早"} ]} ], "tv": [ {"key": "类型", "name": "类型", "value": [ {"n": "不限", "v": ""}, {"n": "电视剧", "v": "电视剧"}, {"n": "综艺", "v": "综艺"} ]}, {"key": "电视剧形式", "name": "电视剧形式", "value": [ {"n": "不限", "v": ""}, {"n": "喜剧", "v": "喜剧"}, {"n": "爱情", "v": "爱情"}, {"n": "悬疑", "v": "悬疑"}, {"n": "动画", "v": "动画"}, {"n": "武侠", "v": "武侠"}, {"n": "古装", "v": "古装"}, {"n": "家庭", "v": "家庭"}, {"n": "犯罪", "v": "犯罪"}, {"n": "科幻", "v": "科幻"}, {"n": "恐怖", "v": "恐怖"}, {"n": "历史", "v": "历史"}, {"n": "战争", "v": "战争"}, {"n": "动作", "v": "动作"}, {"n": "冒险", "v": "冒险"}, {"n": "传记", "v": "传记"}, {"n": "剧情", "v": "剧情"}, {"n": "奇幻", "v": "奇幻"}, {"n": "惊悚", "v": "惊悚"}, {"n": "灾难", "v": "灾难"}, {"n": "歌舞", "v": "歌舞"}, {"n": "音乐", "v": "音乐"} ]}, {"key": "综艺形式", "name": "综艺形式", "value": [ {"n": "不限", "v": ""}, {"n": "真人秀", "v": "真人秀"}, {"n": "脱口秀", "v": "脱口秀"}, {"n": "音乐", "v": "音乐"}, {"n": "歌舞", "v": "歌舞"} ]}, {"key": "地区", "name": "地区", "value": [ {"n": "全部地区", "v": ""}, {"n": "华语", "v": "华语"}, {"n": "欧美", "v": "欧美"}, {"n": "国外", "v": "国外"}, {"n": "韩国", "v": "韩国"}, {"n": "日本", "v": "日本"}, {"n": "中国大陆", "v": "中国大陆"}, {"n": "中国香港", "v": "中国香港"}, {"n": "美国", "v": "美国"}, {"n": "英国", "v": "英国"}, {"n": "泰国", "v": "泰国"}, {"n": "中国台湾", "v": "中国台湾"}, {"n": "意大利", "v": "意大利"}, {"n": "法国", "v": "法国"}, {"n": "德国", "v": "德国"}, {"n": "西班牙", "v": "西班牙"}, {"n": "俄罗斯", "v": "俄罗斯"}, {"n": "瑞典", "v": "瑞典"}, {"n": "巴西", "v": "巴西"}, {"n": "丹麦", "v": "丹麦"}, {"n": "印度", "v": "印度"}, {"n": "加拿大", "v": "加拿大"}, {"n": "爱尔兰", "v": "爱尔兰"}, {"n": "澳大利亚", "v": "Australia"} ]}, {"key": "sort", "name": "排序", "value": [ {"n": "近期热度", "v": "T"}, {"n": "首播时间", "v": "R"}, {"n": "高分优先", "v": "S"} ]}, {"key": "年代", "name": "年代", "value": [ {"n": "全部", "v": ""}, {"n": "2026", "v": "2026"}, {"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"}, {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, {"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, {"n": "2010年代", "v": "2010年代"}, {"n": "2000年代", "v": "2000年代"}, {"n": "90年代", "v": "90年代"}, {"n": "80年代", "v": "80年代"}, {"n": "70年代", "v": "70年代"}, {"n": "60年代", "v": "60年代"}, {"n": "更早", "v": "更早"} ]}, {"key": "平台", "name": "平台", "value": [ {"n": "全部", "v": ""}, {"n": "腾讯视频", "v": "腾讯视频"}, {"n": "爱奇艺", "v": "爱奇艺"}, {"n": "优酷", "v": "优酷"}, {"n": "湖南卫视", "v": "湖南卫视"}, {"n": "Netflix", "v": "Netflix"}, {"n": "HBO", "v": "HBO"}, {"n": "BBC", "v": "BBC"}, {"n": "NHK", "v": "NHK"}, {"n": "CBS", "v": "CBS"}, {"n": "NBC", "v": "NBC"}, {"n": "tvN", "v": "tvN"} ]} ], "rank_list_movie": [ {"key": "榜单", "name": "榜单", "value": [ {"n": "实时热门电影", "v": "movie_real_time_hotest"}, {"n": "一周口碑电影榜", "v": "movie_weekly_best"}, {"n": "豆瓣电影Top250", "v": "movie_top250"} ]} ], "rank_list_tv": [ {"key": "榜单", "name": "榜单", "value": [ {"n": "实时热门电视", "v": "tv_real_time_hotest"}, {"n": "华语口碑剧集榜", "v": "tv_chinese_best_weekly"}, {"n": "全球口碑剧集榜", "v": "tv_global_best_weekly"}, {"n": "国内口碑综艺榜", "v": "show_chinese_best_weekly"}, {"n": "国外口碑综艺榜", "v": "show_global_best_weekly"} ]} ] } }