diff --git a/README.md b/README.md index 2adb279..1690295 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ - [x] FlexTV - [x] PopkonTV - [x] TwitCasting +- [x] 百度直播 +- [x] 微博直播 - [ ] 更多平台正在更新中 @@ -148,6 +150,12 @@ https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117 TwitCasting: https://twitcasting.tv/c:uonq + +百度直播: +https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category + +微博直播: +https://weibo.com/l/wblive/p/show/1022:2321325026370190442592 ``` 直播间分享地址和网页端长地址都能正常进行录制(抖音尽量用长链接,避免因短链接转换失效导致不能正常录制,而且需要有nodejs环境,否则无法转换)。 @@ -280,11 +288,18 @@ docker-compose stop ## ⏳提交日志 +- 20240423 + - 新增百度直播录制、微博直播录制 + + - 修复斗鱼录制直播回放的问题 + + - 新增直播源地址显示以及输出到日志文件设置 + - 20240311 - 修复海外平台录制bug,增加画质选择,增强录制稳定性 - + - 修复虎牙录制bug (虎牙`一起看`频道 有特殊限制,有时无法录制) - + - 20240309 - 修复虎牙直播、小红书直播和B站直播录制 - 新增5个直播平台录制,包括winktv、flextv、look、popkontv、twitcasting diff --git a/config/config.ini b/config/config.ini index 61e2ec8..8a12f58 100644 --- a/config/config.ini +++ b/config/config.ini @@ -8,6 +8,7 @@ 循环时间(秒) = 120 排队读取网址时间(秒) = 0 是否显示循环秒数 = 否 +是否显示直播源地址 = 否 分段录制是否开启 = 否 视频分段时间(秒) = 1800 ts录制完成后自动转为mp4格式 = 否 @@ -50,6 +51,8 @@ winktv_cookie = flextv_cookie = look_cookie = twitcasting_cookie = +baidu_cookie = +weibo_cookie = [Authorization] popkontv_token = diff --git a/logger.py b/logger.py index 90ddd3f..135db6c 100644 --- a/logger.py +++ b/logger.py @@ -2,5 +2,23 @@ from loguru import logger -# 每天日志自动分文件 -logger.add("./logs/DouyinLiveRecorder.log", rotation="12:00") +logger.add( + "./logs/PlayURL.log", + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {message}", + filter=lambda i: i["level"].name == "INFO", + serialize=False, + enqueue=True, + rotation="12:00", + retention="10 days", +) + +logger.add( + "./logs/DouyinLiveRecorder.log", + level="WARNING", + serialize=False, + enqueue=True, + rotation="12:00", + retention="10 days", +) + diff --git a/main.py b/main.py index 0ede844..4ce73a8 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ Author: Hmily GitHub: https://github.com/ihmily Date: 2023-07-17 23:52:05 -Update: 2024-04-12 18:54:27 +Update: 2024-04-23 21:34:55 Copyright (c) 2023-2024 by Hmily, All Rights Reserved. Function: Record live stream video. """ @@ -49,7 +49,9 @@ from spider import ( get_flextv_stream_data, get_looklive_stream_url, get_popkontv_stream_url, - get_twitcasting_stream_url + get_twitcasting_stream_url, + get_baidu_stream_data, + get_weibo_stream_url ) from web_rid import ( @@ -62,8 +64,8 @@ from utils import ( ) from msg_push import dingtalk, xizhi, tg_bot -version = "v3.0.2" -platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|网易CC|千度热播|猫耳FM|Look直播|TwitCasting" \ +version = "v3.0.3" +platforms = "\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo直播|blued直播|网易CC|千度热播|猫耳FM|Look直播|TwitCasting|百度直播|微博直播" \ "\n海外站点:TikTok|AfreecaTV|PandaTV|WinkTV|FlexTV|PopkonTV" # --------------------------全局变量------------------------------------- @@ -153,7 +155,7 @@ def display_info(): else: start_display_time = now_time except Exception as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") def update_file(file_path: str, old_str: str, new_str: str, start_str: str = None): @@ -484,6 +486,9 @@ def get_huya_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str, proxy_address: str) -> dict: # TODO: 获取斗鱼直播源地址 + if not json_data["is_live"]: + return json_data + video_quality_options = { "原画": '0', "蓝光": '0', @@ -492,25 +497,15 @@ def get_douyu_stream_url(json_data: dict, cookies: str, video_quality: str, prox "标清": '1' } - room_info = json_data.get('pageContext', json_data)['pageProps']['room']['roomInfo']['roomInfo'] - anchor_name = room_info.get('nickname', '') - status = room_info.get('isLive', False) - result = { - "anchor_name": anchor_name, - "is_live": False, - } - # 如果status值为1,则正在直播 - # 这边有个bug,就是如果是直播回放,状态也是在直播 待优化 - if status == 1: - rid = str(room_info['rid']) - rate = video_quality_options.get(video_quality, '0') # 默认为原画 - flv_data = get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_address) - flv_url = flv_data['data'].get('url', None) - if flv_url: - result['flv_url'] = flv_url - result['is_live'] = True - result['record_url'] = flv_url - return result + rid = str(json_data["room_id"]) + json_data.pop("room_id", None) + rate = video_quality_options.get(video_quality, '0') # 默认为原画,只有登录后才能获取最高画质,需要配置cookie + flv_data = get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_address) + flv_url = flv_data['data'].get('url', None) + if flv_url: + json_data['flv_url'] = flv_url + json_data['record_url'] = flv_url + return json_data @trace_error_decorator @@ -599,6 +594,7 @@ def get_bilibili_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_afreecatv_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取afreecatv直播源地址 if not json_data['is_live']: return json_data @@ -620,6 +616,7 @@ def get_afreecatv_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_netease_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取netease直播源地址 if not json_data['is_live']: return json_data stream_list = json_data['stream_list']['resolution'] @@ -642,6 +639,7 @@ def get_netease_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_pandatv_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取pandatv直播源地址 if not json_data['is_live']: return json_data @@ -663,6 +661,7 @@ def get_pandatv_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_winktv_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取winktv直播源地址 if not json_data['is_live']: return json_data @@ -684,6 +683,7 @@ def get_winktv_stream_url(json_data: dict, video_quality: str) -> dict: @trace_error_decorator def get_flextv_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取flextv直播源地址 if not json_data['is_live']: return json_data @@ -703,6 +703,28 @@ def get_flextv_stream_url(json_data: dict, video_quality: str) -> dict: } +@trace_error_decorator +def get_baidu_stream_url(json_data: dict, video_quality: str) -> dict: + # TODO: 获取百度直播源地址 + if not json_data['is_live']: + return json_data + + play_url_list = json_data['play_url_list'] + quality_list = {'原画': 0, '蓝光': 0, '超清': 1, '高清': 2, '标清': 3} + while len(play_url_list) < 4: + play_url_list.append(play_url_list[-1]) + + selected_quality = quality_list[video_quality] + m3u8_url = play_url_list[selected_quality] + + return { + "anchor_name": json_data['anchor_name'], + "is_live": True, + "m3u8_url": m3u8_url, + "record_url": m3u8_url + } + + def push_message(content: str): push_pts = [] if '微信' in live_status_push: @@ -794,7 +816,7 @@ def start_record(url_data: tuple, count_variable: int = -1): cookies=tiktok_cookie) port_info = get_tiktok_stream_url(json_data, record_quality) else: - logger.warning(f"错误信息: 网络异常,请检查网络是否能正常访问TikTok平台") + logger.error(f"错误信息: 网络异常,请检查网络是否能正常访问TikTok平台") elif record_url.find("https://live.kuaishou.com/") > -1: platform = '快手直播' @@ -869,7 +891,7 @@ def start_record(url_data: tuple, count_variable: int = -1): ) port_info = get_afreecatv_stream_url(json_data, record_quality) else: - logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问AfreecaTV平台") + logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问AfreecaTV平台") elif record_url.find("cc.163.com/") > -1: platform = '网易CC直播' @@ -894,7 +916,7 @@ def start_record(url_data: tuple, count_variable: int = -1): ) port_info = get_pandatv_stream_url(json_data, record_quality) else: - logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问PandaTV直播平台") + logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问PandaTV直播平台") elif record_url.find("fm.missevan.com/") > -1: platform = '猫耳FM直播' @@ -912,7 +934,7 @@ def start_record(url_data: tuple, count_variable: int = -1): cookies=winktv_cookie) port_info = get_winktv_stream_url(json_data, record_quality) else: - logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问WinkTV直播平台") + logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问WinkTV直播平台") elif record_url.find("www.flextv.co.kr/") > -1: platform = 'FlexTV' @@ -927,7 +949,7 @@ def start_record(url_data: tuple, count_variable: int = -1): ) port_info = get_flextv_stream_url(json_data, record_quality) else: - logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问FlexTV直播平台") + logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问FlexTV直播平台") elif record_url.find("look.163.com/") > -1: platform = 'Look直播' @@ -949,7 +971,7 @@ def start_record(url_data: tuple, count_variable: int = -1): partner_code=popkontv_partner_code ) else: - logger.warning(f"错误信息: 网络异常,请检查本网络是否能正常访问PopkonTV直播平台") + logger.error(f"错误信息: 网络异常,请检查本网络是否能正常访问PopkonTV直播平台") elif record_url.find("twitcasting.tv/") > -1: platform = 'TwitCasting' @@ -962,8 +984,23 @@ def start_record(url_data: tuple, count_variable: int = -1): password=twitcasting_password ) + elif record_url.find("live.baidu.com/") > -1: + platform = '百度直播' + with semaphore: + json_data = get_baidu_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=baidu_cookie) + port_info = get_baidu_stream_url(json_data, record_quality) + + elif record_url.find("weibo.com/") > -1: + platform = '微博直播' + with semaphore: + port_info = get_weibo_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=weibo_cookie) + else: - logger.warning(f'{record_url} 未知直播地址') + logger.error(f'{record_url} 未知直播地址') return if anchor_name: @@ -1037,10 +1074,10 @@ def start_record(url_data: tuple, count_variable: int = -1): if not os.path.exists(full_path): os.makedirs(full_path) except Exception as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") if not os.path.exists(full_path): - logger.warning( + logger.error( "错误信息: 保存路径不存在,不能生成录制.请避免把本程序放在c盘,桌面,下载文件夹,qq默认传输目录.请重新检查设置") user_agent = ("Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (" @@ -1090,6 +1127,8 @@ def start_record(url_data: tuple, count_variable: int = -1): recording_time_list[record_name] = [start_record_time, record_quality] rec_info = f"\r{anchor_name} 录制视频中: {full_path}" filename_short = full_path + '/' + anchor_name + '_' + now + if show_url: + logger.info(f"{platform} | {anchor_name} | 直播源地址: {port_info['record_url']}") if video_save_type == "FLV": filename = anchor_name + '_' + now + '.flv' @@ -1112,7 +1151,7 @@ def start_record(url_data: tuple, count_variable: int = -1): raise Exception('该直播无flv直播流,请切换视频保存类型') except Exception as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1159,7 +1198,7 @@ def start_record(url_data: tuple, count_variable: int = -1): _output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT) record_finished = True except subprocess.CalledProcessError as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1203,7 +1242,7 @@ def start_record(url_data: tuple, count_variable: int = -1): _output = subprocess.check_output(ffmpeg_command, stderr=subprocess.STDOUT) record_finished = True except subprocess.CalledProcessError as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1255,7 +1294,7 @@ def start_record(url_data: tuple, count_variable: int = -1): threading.Thread(target=converts_m4a, args=(save_file_path,)).start() except subprocess.CalledProcessError as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1304,7 +1343,7 @@ def start_record(url_data: tuple, count_variable: int = -1): threading.Thread(target=converts_m4a, args=(save_file_path,)).start() except subprocess.CalledProcessError as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1339,7 +1378,7 @@ def start_record(url_data: tuple, count_variable: int = -1): record_finished = True except subprocess.CalledProcessError as e: - logger.warning( + logger.error( f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1375,7 +1414,7 @@ def start_record(url_data: tuple, count_variable: int = -1): if ts_to_m4a: threading.Thread(target=converts_m4a, args=(save_file_path,)).start() except subprocess.CalledProcessError as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 no_error = False @@ -1397,7 +1436,7 @@ def start_record(url_data: tuple, count_variable: int = -1): record_finished_2 = False except Exception as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 num = random.randint(-5, 5) + delay_default # 生成-5到5的随机数,加上delay_default @@ -1430,7 +1469,7 @@ def start_record(url_data: tuple, count_variable: int = -1): if loop_time: print('\r检测直播间中...', end="") except Exception as e: - logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") warning_count += 1 time.sleep(2) @@ -1597,6 +1636,7 @@ while True: delay_default = int(read_config_value(config, '录制设置', '循环时间(秒)', 120)) local_delay_default = int(read_config_value(config, '录制设置', '排队读取网址时间(秒)', 0)) loop_time = options.get(read_config_value(config, '录制设置', '是否显示循环秒数', "否"), False) + show_url = options.get(read_config_value(config, '录制设置', '是否显示直播源地址', "否"), False) split_video_by_time = options.get(read_config_value(config, '录制设置', '分段录制是否开启', "否"), False) split_time = str(read_config_value(config, '录制设置', '视频分段时间(秒)', 1800)) ts_to_mp4 = options.get(read_config_value(config, '录制设置', 'ts录制完成后自动转为mp4格式', "否"), @@ -1651,6 +1691,8 @@ while True: flextv_cookie = read_config_value(config, 'Cookie', 'flextv_cookie', '') look_cookie = read_config_value(config, 'Cookie', 'look_cookie', '') twitcasting_cookie = read_config_value(config, 'Cookie', 'twitcasting_cookie', '') + baidu_cookie = read_config_value(config, 'Cookie', 'baidu_cookie', '') + weibo_cookie = read_config_value(config, 'Cookie', 'weibo_cookie', '') if len(video_save_type) > 0: if video_save_type.upper().lower() == "FLV".lower(): @@ -1735,6 +1777,8 @@ while True: 'fm.missevan.com', 'look.163.com', 'twitcasting.tv', + 'live.baidu.com', + 'weibo.com', ] overseas_platform_host = [ 'www.tiktok.com', @@ -1793,7 +1837,7 @@ while True: first_start = False except Exception as err: - logger.warning(f"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}") + logger.error(f"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}") if first_run: t = threading.Thread(target=display_info, args=(), daemon=True) diff --git a/spider.py b/spider.py index c767ebc..beb85bb 100644 --- a/spider.py +++ b/spider.py @@ -4,12 +4,13 @@ Author: Hmily GitHub:https://github.com/ihmily Date: 2023-07-15 23:15:00 -Update: 2024-04-12 19:14:00 +Update: 2024-04-23 21:14:21 Copyright (c) 2023 by Hmily, All Rights Reserved. Function: Get live stream data. """ import hashlib +import random import time import urllib.parse import urllib.error @@ -27,21 +28,19 @@ from utils import ( ) import http.cookiejar - no_proxy_handler = urllib.request.ProxyHandler({}) opener = urllib.request.build_opener(no_proxy_handler) def get_req( - url: str, - proxy_addr: Union[str, None] = None, - headers: Union[dict, None] = None, - data: Union[dict, bytes, None] = None, - json_data: dict = None, - timeout: int = 20, - abroad: bool = False + url: str, + proxy_addr: Union[str, None] = None, + headers: Union[dict, None] = None, + data: Union[dict, bytes, None] = None, + json_data: dict = None, + timeout: int = 20, + abroad: bool = False ) -> Union[str, Any]: - if headers is None: headers = {} try: @@ -51,7 +50,8 @@ def get_req( 'https': proxy_addr } if data or json_data: - response = requests.post(url, data=data, json=json_data, headers=headers, proxies=proxies, timeout=timeout) + response = requests.post(url, data=data, json=json_data, headers=headers, proxies=proxies, + timeout=timeout) else: response = requests.get(url, headers=headers, proxies=proxies, timeout=timeout) resp_str = response.text @@ -89,7 +89,6 @@ def get_req( def get_partner_code(url, params): - parsed_url = urllib.parse.urlparse(url) query_params = urllib.parse.parse_qs(parsed_url.query) @@ -99,6 +98,19 @@ def get_partner_code(url, params): return None +def jsonp_to_json(jsonp_str): + pattern = r'(\w+)\((.*)\);?$' + match = re.search(pattern, jsonp_str) + + if match: + _, json_str = match.groups() + json_obj = json.loads(json_str) + return json_obj + else: + print("No JSON data found in JSONP response.") + return None + + def get_play_url_list(m3u8: str, proxy: Union[str, None] = None, header: Union[dict, None] = None) -> list: resp = get_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=True) play_url_list = [] @@ -321,15 +333,21 @@ def get_douyu_info_data(url: str, proxy_addr: Union[str, None] = None) -> Dict[s else: rid = re.search('douyu.com/(.*?)(?=\?|$)', url).group(1) headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'referer': 'https://www.douyu.com/7644887?dyshid=0-40f7c4a06aae9dc5bede316000031701&dyshci=181', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', } - url2 = f'https://m.douyu.com/{rid}' - html_str = get_req(url=url2, proxy_addr=proxy_addr, headers=headers) - - json_str = re.search('', - html_str).group(1) + url2 = f'https://www.douyu.com/betard/{rid}' + json_str = get_req(url=url2, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) - return json_data + result = { + "anchor_name": json_data['room']['nickname'], + "is_live": False + } + if json_data['room']['videoLoop'] == 0 and json_data['room']['show_status'] ==1: + result["is_live"] = True + result["room_id"] = json_data['room']['room_id'] + + return result @trace_error_decorator @@ -1329,7 +1347,8 @@ def get_popkontv_stream_url( status_msg = json_data["statusMsg"] if json_data['statusCd'] == "L000A": print('获取直播源失败,', status_msg) - raise RuntimeError('你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用') + raise RuntimeError( + '你是未认证会员。登录popkontv官方网站后,在“我的页面”>“修改我的信息”底部进行手机认证后可用') elif json_data['statusCd'] == "L0001": cast_start_date_code = int(cast_start_date_code) - 1 json_str = fetch_data(headers, partner_code) @@ -1350,7 +1369,6 @@ def get_popkontv_stream_url( def login_twitcasting( username: str, password: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None ) -> Union[str, None]: - headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', @@ -1408,7 +1426,6 @@ def get_twitcasting_stream_url( username: Union[str, None] = None, password: Union[str, None] = None, ) -> Dict[str, Any]: - headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', @@ -1423,7 +1440,7 @@ def get_twitcasting_stream_url( def get_data(header): html_str = get_req(url, proxy_addr=proxy_addr, headers=header) - anchor = re.search("