# coding=utf-8 """ 目标站: A123TV 首页: https://a123tv.com """ import re import sys import json import urllib.parse from bs4 import BeautifulSoup sys.path.append('..') from base.spider import Spider class Spider(Spider): def init(self, extend=""): self.site_url = "https://a123tv.com" self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': self.site_url, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' } self.categories = [ {"type_id": "10", "type_name": "电影"}, {"type_id": "11", "type_name": "连续剧"}, {"type_id": "12", "type_name": "综艺"}, {"type_id": "13", "type_name": "动漫"}, {"type_id": "15", "type_name": "福利"} ] # ========== 预定义所有分类的筛选 ========== self.filters = { "10": [{"key": "subtype", "name": "类型", "value": [ {"n": "全部", "v": ""}, {"n": "动作片", "v": "1001"}, {"n": "喜剧片", "v": "1002"}, {"n": "爱情片", "v": "1003"}, {"n": "科幻片", "v": "1004"}, {"n": "恐怖片", "v": "1005"}, {"n": "剧情片", "v": "1006"}, {"n": "战争片", "v": "1007"}, {"n": "纪录片", "v": "1008"}, {"n": "动漫电影", "v": "1009"}, {"n": "奇幻片", "v": "1010"}, {"n": "动画片", "v": "1011"}, {"n": "犯罪片", "v": "1012"}, {"n": "悬疑片", "v": "1013"}, {"n": "邵氏电影", "v": "1014"}, {"n": "歌舞片", "v": "1015"}, {"n": "家庭片", "v": "1016"}, {"n": "古装片", "v": "1017"}, {"n": "历史片", "v": "1018"}, {"n": "4K电影", "v": "1019"} ]}], "11": [{"key": "subtype", "name": "类型", "value": [ {"n": "全部", "v": ""}, {"n": "国产剧", "v": "1101"}, {"n": "香港剧", "v": "1102"}, {"n": "韩国剧", "v": "1103"}, {"n": "欧美剧", "v": "1104"}, {"n": "台湾剧", "v": "1105"}, {"n": "日本剧", "v": "1106"}, {"n": "海外剧", "v": "1107"}, {"n": "泰国剧", "v": "1108"}, {"n": "港台剧", "v": "1110"}, {"n": "日韩剧", "v": "1111"} ]}], "12": [{"key": "subtype", "name": "类型", "value": [ {"n": "全部", "v": ""}, {"n": "内地综艺", "v": "1201"}, {"n": "港台综艺", "v": "1202"}, {"n": "日韩综艺", "v": "1203"}, {"n": "欧美综艺", "v": "1204"} ]}], "13": [{"key": "subtype", "name": "类型", "value": [ {"n": "全部", "v": ""}, {"n": "国产动漫", "v": "1301"}, {"n": "日韩动漫", "v": "1302"}, {"n": "欧美动漫", "v": "1303"} ]}], "15": [{"key": "subtype", "name": "类型", "value": [ {"n": "全部", "v": ""}, {"n": "韩国情色片", "v": "1501"}, {"n": "日本情色片", "v": "1502"} ]}] } # ========================================= def homeContent(self, filter): url = self.site_url + "/" resp = self.fetch(url, headers=self.headers) video_list = [] if resp: soup = BeautifulSoup(resp.text, 'html.parser') containers = soup.select('main > div') if len(containers) >= 2: home_block = containers[1] cards = home_block.select('a.w4-item[href^="/v/"]') for a in cards[:20]: href = a.get('href', '') slug = href.replace('/v/', '').replace('.html', '').strip() if not slug: continue title_tag = a.select_one('.w4-item-info .t') vod_name = title_tag.get_text(strip=True) if title_tag else '' if not vod_name: vod_name = title_tag.get('title', '') if title_tag else '' remark_tag = a.select_one('.w4-item-info .i') vod_remarks = remark_tag.get_text(strip=True) if remark_tag else '' img = a.select_one('figure img') vod_pic = '' if img: src = img.get('data-src', '') or img.get('src', '') if src: vod_pic = src if src.startswith('http') else 'https:' + src if vod_name: video_list.append({ "vod_id": slug, "vod_name": vod_name, "vod_pic": vod_pic, "vod_remarks": vod_remarks }) return {"class": self.categories, "list": video_list, "filters": self.filters} def homeVideoContent(self): return self.homeContent(False) def categoryContent(self, tid, pg, filter, extend): page = int(pg) if pg else 1 # 如果筛选传入了 subtype,覆盖 tid if extend and 'subtype' in extend and extend['subtype']: tid = extend['subtype'] if page == 1: url = f"{self.site_url}/t/{tid}.html" else: url = f"{self.site_url}/t/{tid}/p{page}.html" resp = self.fetch(url, headers=self.headers) if not resp: return {"list": [], "page": page, "pagecount": 1, "limit": 24, "total": 0} soup = BeautifulSoup(resp.text, 'html.parser') video_list = [] list_block = soup.select_one('div.w4-list') if not list_block: return {"list": [], "page": page, "pagecount": 1, "limit": 24, "total": 0} cards = list_block.select('a.w4-item[href^="/v/"]') for a in cards: href = a.get('href', '') slug = href.replace('/v/', '').replace('.html', '').strip() if not slug: continue title_tag = a.select_one('.w4-item-info .t') vod_name = title_tag.get_text(strip=True) if title_tag else '' if not vod_name: vod_name = title_tag.get('title', '') if title_tag else '' remark_tag = a.select_one('.w4-item-info .i') vod_remarks = remark_tag.get_text(strip=True) if remark_tag else '' img = a.select_one('figure img') vod_pic = '' if img: src = img.get('data-src', '') or img.get('src', '') if src: vod_pic = src if src.startswith('http') else 'https:' + src if vod_name: video_list.append({ "vod_id": slug, "vod_name": vod_name, "vod_pic": vod_pic, "vod_remarks": vod_remarks }) # 分页 pagecount = 1 page_block = soup.select_one('div.w4-page') if page_block: page_links = page_block.select('a') for a_tag in page_links: text = a_tag.get_text(strip=True) if text.isdigit(): pagecount = max(pagecount, int(text)) elif '下页' in text or '下一页' in text: pagecount = page + 1 return { "list": video_list, "page": page, "pagecount": pagecount, "limit": 24, "total": len(video_list) * pagecount } def detailContent(self, ids): if not ids: return {"list": []} vod_id = ids[0] url = f"{self.site_url}/v/{vod_id}.html" resp = self.fetch(url, headers=self.headers) if not resp or resp.status_code != 200: return {"list": []} soup = BeautifulSoup(resp.text, 'html.parser') # 标题 title_elem = soup.select_one('h1') or soup.select_one('h2') or soup.select_one('h3.t') vod_name = title_elem.get_text(strip=True) if title_elem else vod_id # 图片 vod_pic = '' player_div = soup.select_one('#awp1') if player_div and player_div.get('data-poster'): vod_pic = player_div.get('data-poster') if vod_pic.startswith('//'): vod_pic = 'https:' + vod_pic if not vod_pic: img_elem = soup.select_one('div.w4-video img') or soup.select_one('main img') if img_elem: src = img_elem.get('data-src', '') or img_elem.get('src', '') if src: vod_pic = src if src.startswith('http') else ('https:' + src) # 从 meta description 提取详细字段 vod_content = '' vod_area = '' vod_actor = '' vod_director = '' vod_year = '' meta_desc = soup.select_one('meta[name="description"]') if meta_desc: desc_text = meta_desc.get('content', '') area_match = re.search(r'地区:([^。]+)', desc_text) if area_match: vod_area = area_match.group(1).strip() actor_match = re.search(r'演员:([^。]+)', desc_text) if actor_match: vod_actor = actor_match.group(1).strip() director_match = re.search(r'导演:([^。]+)', desc_text) if director_match: vod_director = director_match.group(1).strip() plot_match = re.search(r'剧情:(.+)', desc_text) if plot_match: vod_content = plot_match.group(1).strip() else: vod_content = desc_text if not vod_content: desc_elem = soup.select_one('.detail-desc') or soup.select_one('.text-muted') if desc_elem: vod_content = desc_elem.get_text(strip=True) # 播放列表 play_from_list = [] play_url_list = [] pp_match = re.search(r'var\s+pp\s*=\s*({.*?});', resp.text, re.DOTALL) if pp_match: try: pp = json.loads(pp_match.group(1)) la_list = pp.get('la', []) for item in la_list: line_id = item[0] line_name = item[1] total = item[2] episodes = [] for i in range(total): ep_url = f"/v/{vod_id}/{line_id}z{i}.html" ep_name = f"第{i+1:02d}集" if total > 1 else line_name episodes.append(f"{ep_name}${ep_url}") if episodes: play_from_list.append(line_name) play_url_list.append('#'.join(episodes)) except Exception: pass if not play_url_list: line_items = soup.select('div.w4-line a.w4-line-item') if line_items: for a in line_items: name_tag = a.select_one('.w4-line-info .r') source_name = name_tag.get_text(strip=True) if name_tag else '未知线路' href = a.get('href', '') if href: play_from_list.append(source_name) play_url_list.append(f"{source_name}${href}") if not play_url_list: ep_links = soup.select('div.w4-episode-list a') if ep_links: episodes = [] for a in ep_links: name = a.get_text(strip=True) href = a.get('href', '') if name and href: episodes.append(f"{name}${href}") if episodes: play_from_list.append('默认线路') play_url_list.append('#'.join(episodes)) vod_play_from = '$$$'.join(play_from_list) if play_from_list else '默认线路' vod_play_url = '$$$'.join(play_url_list) if play_url_list else '' result = [{ "vod_id": vod_id, "vod_name": vod_name, "vod_pic": vod_pic, "vod_content": vod_content, "vod_actor": vod_actor, "vod_director": vod_director, "vod_area": vod_area, "vod_year": vod_year, "vod_play_from": vod_play_from, "vod_play_url": vod_play_url }] return {"list": result} def searchContent(self, key, quick, pg="1"): page = int(pg) if pg else 1 url = f"{self.site_url}/s/?wd={urllib.parse.quote(key)}" if page > 1: url += f"&page={page}" resp = self.fetch(url, headers=self.headers) if not resp: return {"list": [], "page": page, "pagecount": 1} soup = BeautifulSoup(resp.text, 'html.parser') video_list = [] list_block = soup.select_one('div.w4-list') if list_block: cards = list_block.select('a.w4-item[href^="/v/"]') else: cards = soup.select('a[href^="/v/"]') for a in cards[:20]: href = a.get('href', '') slug = href.replace('/v/', '').replace('.html', '').strip() if not slug: continue title_tag = a.select_one('.w4-item-info .t') vod_name = title_tag.get_text(strip=True) if title_tag else '' if not vod_name: vod_name = title_tag.get('title', '') if title_tag else '' remark_tag = a.select_one('.w4-item-info .i') vod_remarks = remark_tag.get_text(strip=True) if remark_tag else '' img = a.select_one('figure img') vod_pic = '' if img: src = img.get('data-src', '') or img.get('src', '') if src: vod_pic = src if src.startswith('http') else 'https:' + src if vod_name: video_list.append({ "vod_id": slug, "vod_name": vod_name, "vod_pic": vod_pic, "vod_remarks": vod_remarks }) return {"list": video_list, "page": page, "pagecount": 1} def playerContent(self, flag, id, vipFlags): if not id.startswith('http'): url = self.site_url + id else: url = id return {"parse": 1, "url": url, "header": self.headers}