commit 81df352607ead71056b54bfcfb0fa801968e26e2 Author: Jacky Yang Date: Fri Jan 23 13:00:09 2026 +0800 0.1: Downloader Implemented. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16de753 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +__pycache__/ +*.py[cod] +*$py.class + +*.so +*.dylib +*.dll + +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg + +MANIFEST + +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +*.mo +*.pot + +*.log + +local_settings.py +db.sqlite3 + +instance/ + +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +.spyderproject +.spyproject + +.idea/ +.vscode/ + +output/ +output_arena/ +arena/ diff --git a/downloader/README.md b/downloader/README.md new file mode 100644 index 0000000..ee74a61 --- /dev/null +++ b/downloader/README.md @@ -0,0 +1,85 @@ +# Downloader 使用说明 + +## 作用 +用于从 5E Arena 比赛页面抓取 iframe 内的 JSON 结果,并按需下载 demo 文件到本地目录。 + +## 运行环境 +- Python 3.9+ +- Playwright + +安装依赖: + +```bash +python -m pip install playwright +python -m playwright install +``` + +## 快速开始 + +单场下载(默认 URL): + +```bash +python downloader.py +``` + +指定比赛 URL: + +```bash +python downloader.py --url https://arena.5eplay.com/data/match/g161-20260118222715609322516 +``` + +批量下载(从文件读取 URL): + +```bash +python downloader.py --url-list gamelist/match_list_2026.txt +``` + +指定输出目录: + +```bash +python downloader.py --out output_arena +``` + +只抓 iframe 数据或只下载 demo: + +```bash +python downloader.py --fetch-type iframe +python downloader.py --fetch-type demo +``` + +## 主要参数 +- --url:单场比赛 URL,未传时使用默认值 +- --url-list:包含多个比赛 URL 的文本文件,一行一个 URL +- --out:输出目录,默认 output_arena +- --match-name:输出目录前缀名,默认从 URL 提取 +- --headless:是否无头模式,true/false,默认 false +- --timeout-ms:页面加载超时毫秒,默认 30000 +- --capture-ms:主页面 JSON 监听时长毫秒,默认 5000 +- --iframe-capture-ms:iframe 页面 JSON 监听时长毫秒,默认 8000 +- --concurrency:并发数量,默认 3 +- --goto-retries:页面打开重试次数,默认 1 +- --fetch-type:抓取类型,iframe/demo/both,默认 both + +## 输出结构 +下载目录会以比赛编号或自定义名称创建子目录: + +``` +output_arena/ + g161-20260118222715609322516/ + iframe_network.json + g161-20260118222715609322516_de_ancient.zip + g161-20260118222715609322516_de_ancient.dem +``` + +## URL 列表格式 +文本文件一行一个 URL,空行和以 # 开头的行会被忽略: + +``` +https://arena.5eplay.com/data/match/g161-20260118222715609322516 +# 注释 +https://arena.5eplay.com/data/match/g161-20260118212021710292006 +``` + +## 常见问题 +- 如果提示 Playwright 未安装,请先执行安装命令再运行脚本 +- 如果下载目录已有文件,会跳过重复下载 diff --git a/downloader/downloader.py b/downloader/downloader.py new file mode 100644 index 0000000..bf67174 --- /dev/null +++ b/downloader/downloader.py @@ -0,0 +1,416 @@ +import argparse +import asyncio +import json +import os +import sys +import time +import urllib.request +from pathlib import Path +from urllib.parse import urlparse + + +def build_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--url", + default="https://arena.5eplay.com/data/match/g161-20260118222715609322516", + ) + parser.add_argument("--url-list", default="") + parser.add_argument("--out", default="output_arena") + parser.add_argument("--match-name", default="") + parser.add_argument("--headless", default="false") + parser.add_argument("--timeout-ms", type=int, default=30000) + parser.add_argument("--capture-ms", type=int, default=5000) + parser.add_argument("--iframe-capture-ms", type=int, default=8000) + parser.add_argument("--concurrency", type=int, default=3) + parser.add_argument("--goto-retries", type=int, default=1) + parser.add_argument("--fetch-type", default="both", choices=["iframe", "demo", "both"]) + return parser + + +def ensure_dir(path): + Path(path).mkdir(parents=True, exist_ok=True) + + +def truthy(value): + return str(value).lower() in {"1", "true", "yes", "y", "on"} + + +def log(message): + stamp = time.strftime("%H:%M:%S") + print(f"[{stamp}] {message}") + + +def safe_folder(value): + keep = [] + for ch in value: + if ch.isalnum() or ch in {"-", "_"}: + keep.append(ch) + return "".join(keep) or "match" + + +def extract_match_code(url): + for part in url.split("/"): + if part.startswith("g") and "-" in part: + return part + return "" + + +def read_url_list(path): + if not path: + return [] + if not os.path.exists(path): + return [] + urls = [] + with open(path, "r", encoding="utf-8-sig") as f: + for line in f: + value = line.strip() + if not value or value.startswith("#"): + continue + urls.append(value) + return urls + + +def collect_demo_urls(value, results): + if isinstance(value, dict): + for key, item in value.items(): + if key == "demo_url" and isinstance(item, str): + results.add(item) + collect_demo_urls(item, results) + elif isinstance(value, list): + for item in value: + collect_demo_urls(item, results) + + +def extract_demo_urls_from_payloads(payloads): + results = set() + for payload in payloads: + collect_demo_urls(payload, results) + return list(results) + + +def extract_demo_urls_from_network(path): + if not os.path.exists(path): + return [] + try: + with open(path, "r", encoding="utf-8") as f: + payload = json.load(f) + except Exception: + return [] + return extract_demo_urls_from_payloads([payload]) + + +def download_file(url, dest_dir): + if not url: + return "" + ensure_dir(dest_dir) + filename = os.path.basename(urlparse(url).path) or "demo.zip" + dest_path = os.path.join(dest_dir, filename) + if os.path.exists(dest_path): + return dest_path + temp_path = dest_path + ".part" + try: + with urllib.request.urlopen(url) as response, open(temp_path, "wb") as f: + while True: + chunk = response.read(1024 * 1024) + if not chunk: + break + f.write(chunk) + os.replace(temp_path, dest_path) + return dest_path + except Exception: + try: + if os.path.exists(temp_path): + os.remove(temp_path) + except Exception: + pass + return "" + + +def download_demo_from_iframe(out_dir, iframe_payloads=None): + if iframe_payloads is None: + network_path = os.path.join(out_dir, "iframe_network.json") + demo_urls = extract_demo_urls_from_network(network_path) + else: + demo_urls = extract_demo_urls_from_payloads(iframe_payloads) + downloaded = [] + for url in demo_urls: + path = download_file(url, out_dir) + if path: + downloaded.append(path) + return downloaded + + +async def safe_goto(page, url, timeout_ms, retries): + attempt = 0 + while True: + try: + await page.goto(url, wait_until="domcontentloaded", timeout=timeout_ms) + return True + except Exception as exc: + attempt += 1 + if attempt > retries: + log(f"打开失败 {url} {exc}") + return False + await page.wait_for_timeout(1000) + + +async def intercept_json_responses(page, sink, capture_ms): + active = True + + async def handle_response(response): + try: + if not active: + return + headers = response.headers + content_type = headers.get("content-type", "") + if "application/json" in content_type or "json" in content_type: + body = await response.json() + sink.append( + { + "url": response.url, + "status": response.status, + "body": body, + } + ) + except Exception: + return + + page.on("response", handle_response) + await page.wait_for_timeout(capture_ms) + active = False + + +async def open_iframe_page( + context, iframe_url, out_dir, timeout_ms, capture_ms, goto_retries, write_iframe_network +): + iframe_page = await context.new_page() + json_sink = [] + response_task = asyncio.create_task(intercept_json_responses(iframe_page, json_sink, capture_ms)) + ok = await safe_goto(iframe_page, iframe_url, timeout_ms, goto_retries) + if not ok: + await response_task + await iframe_page.close() + return json_sink + try: + await iframe_page.wait_for_load_state("domcontentloaded", timeout=timeout_ms) + except Exception: + pass + clicked = False + try: + await iframe_page.wait_for_timeout(1000) + try: + await iframe_page.wait_for_selector(".ya-tab", timeout=timeout_ms) + except Exception: + pass + tab_names = ["5E Swing Score", "5E 摆动分", "摆动分", "Swing Score", "Swing", "SS"] + for name in tab_names: + locator = iframe_page.locator(".ya-tab", has_text=name) + if await locator.count() > 0: + await locator.first.scroll_into_view_if_needed() + await locator.first.click(timeout=timeout_ms, force=True) + clicked = True + break + locator = iframe_page.get_by_role("tab", name=name) + if await locator.count() > 0: + await locator.first.scroll_into_view_if_needed() + await locator.first.click(timeout=timeout_ms, force=True) + clicked = True + break + locator = iframe_page.get_by_role("button", name=name) + if await locator.count() > 0: + await locator.first.scroll_into_view_if_needed() + await locator.first.click(timeout=timeout_ms, force=True) + clicked = True + break + locator = iframe_page.get_by_text(name, exact=True) + if await locator.count() > 0: + await locator.first.scroll_into_view_if_needed() + await locator.first.click(timeout=timeout_ms, force=True) + clicked = True + break + locator = iframe_page.get_by_text(name, exact=False) + if await locator.count() > 0: + await locator.first.scroll_into_view_if_needed() + await locator.first.click(timeout=timeout_ms, force=True) + clicked = True + break + if not clicked: + clicked = await iframe_page.evaluate( + """() => { + const labels = ["5E Swing Score", "5E 摆动分", "摆动分", "Swing Score", "Swing", "SS"]; + const roots = [document]; + const elements = []; + while (roots.length) { + const root = roots.pop(); + const tree = root.querySelectorAll ? Array.from(root.querySelectorAll("*")) : []; + for (const el of tree) { + elements.push(el); + if (el.shadowRoot) roots.push(el.shadowRoot); + } + } + const target = elements.find(el => { + const text = (el.textContent || "").trim(); + if (!text) return false; + if (!labels.some(l => text.includes(l))) return false; + const rect = el.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }); + if (target) { + target.scrollIntoView({block: "center", inline: "center"}); + const rect = target.getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + const events = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]; + for (const type of events) { + target.dispatchEvent(new MouseEvent(type, {bubbles: true, cancelable: true, clientX: x, clientY: y})); + } + return true; + } + return false; + }""" + ) + if not clicked: + clicked = await iframe_page.evaluate( + """() => { + const tabs = Array.from(document.querySelectorAll(".ya-tab")); + if (tabs.length === 0) return false; + const target = tabs.find(tab => { + const text = (tab.textContent || "").replace(/\\s+/g, " ").trim(); + return text.includes("5E Swing Score") || text.includes("5E 摆动分") || text.includes("摆动分"); + }) || tabs[tabs.length - 1]; + if (!target) return false; + target.scrollIntoView({block: "center", inline: "center"}); + const rect = target.getBoundingClientRect(); + const x = rect.left + rect.width / 2; + const y = rect.top + rect.height / 2; + const events = ["pointerdown", "mousedown", "pointerup", "mouseup", "click"]; + for (const type of events) { + target.dispatchEvent(new MouseEvent(type, {bubbles: true, cancelable: true, clientX: x, clientY: y})); + } + return true; + }""" + ) + if not clicked: + tab_locator = iframe_page.locator(".ya-tab") + if await tab_locator.count() > 0: + target = tab_locator.nth(await tab_locator.count() - 1) + box = await target.bounding_box() + if box: + await iframe_page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2) + clicked = True + except Exception: + clicked = False + if clicked: + await iframe_page.wait_for_timeout(1500) + await intercept_json_responses(iframe_page, json_sink, capture_ms) + try: + await iframe_page.wait_for_load_state("networkidle", timeout=timeout_ms) + except Exception: + pass + await response_task + if write_iframe_network: + with open(os.path.join(out_dir, "iframe_network.json"), "w", encoding="utf-8") as f: + json.dump(json_sink, f, ensure_ascii=False, indent=2) + await iframe_page.close() + return json_sink + + +async def run_match(pw, args, url, index, total): + base_out = os.path.abspath(args.out) + ensure_dir(base_out) + match_code = extract_match_code(url) + base_name = args.match_name.strip() or match_code or "match" + if total > 1: + suffix = match_code or str(index + 1) + if base_name != suffix: + name = f"{base_name}-{suffix}" + else: + name = base_name + else: + name = base_name + out_dir = os.path.join(base_out, safe_folder(name)) + ensure_dir(out_dir) + headless = truthy(args.headless) + timeout_ms = args.timeout_ms + capture_ms = args.capture_ms + iframe_capture_ms = args.iframe_capture_ms + goto_retries = args.goto_retries + fetch_type = str(args.fetch_type or "both").lower() + want_iframe = fetch_type in {"iframe", "both"} + want_demo = fetch_type in {"demo", "both"} + + browser = await pw.chromium.launch(headless=headless, slow_mo=50) + context = await browser.new_context(accept_downloads=True) + page = await context.new_page() + + log(f"打开比赛页 {index + 1}/{total}") + ok = await safe_goto(page, url, timeout_ms, goto_retries) + if not ok: + await browser.close() + return + try: + await page.wait_for_load_state("networkidle", timeout=timeout_ms) + except Exception: + pass + + iframe_url = await page.evaluate( + """() => { + const iframe = document.querySelector('iframe') + return iframe ? iframe.getAttribute('src') : null + }""" + ) + iframe_sink = [] + if iframe_url and (want_iframe or want_demo): + log(f"进入内嵌页面 {iframe_url}") + iframe_sink = await open_iframe_page( + context, iframe_url, out_dir, timeout_ms, iframe_capture_ms, goto_retries, want_iframe + ) + + if want_demo: + downloaded = download_demo_from_iframe(out_dir, iframe_sink if iframe_sink else None) + if downloaded: + log(f"已下载 demo: {len(downloaded)}") + + await browser.close() + + +async def run_match_with_semaphore(semaphore, pw, args, url, index, total): + async with semaphore: + try: + await run_match(pw, args, url, index, total) + except Exception as exc: + log(f"任务失败 {url} {exc}") + + +async def run(): + args = build_args().parse_args() + try: + from playwright.async_api import async_playwright + except Exception: + print("Playwright 未安装,请先安装: python -m pip install playwright && python -m playwright install") + sys.exit(1) + + urls = read_url_list(args.url_list) + if not urls: + urls = [args.url] + + async with async_playwright() as pw: + concurrency = max(1, int(args.concurrency or 1)) + semaphore = asyncio.Semaphore(concurrency) + tasks = [ + asyncio.create_task(run_match_with_semaphore(semaphore, pw, args, url, index, len(urls))) + for index, url in enumerate(urls) + ] + if tasks: + await asyncio.gather(*tasks) + + log("完成") + + +def main(): + asyncio.run(run()) + + +if __name__ == "__main__": + main() diff --git a/downloader/gamelist/match_list_2026.txt b/downloader/gamelist/match_list_2026.txt new file mode 100644 index 0000000..97c73cf --- /dev/null +++ b/downloader/gamelist/match_list_2026.txt @@ -0,0 +1,47 @@ +https://arena.5eplay.com/data/match/g161-20260118222715609322516 +https://arena.5eplay.com/data/match/g161-20260118215640650728700 +https://arena.5eplay.com/data/match/g161-20260118212021710292006 +https://arena.5eplay.com/data/match/g161-20260118202243599083093 +https://arena.5eplay.com/data/match/g161-20260118195105311656229 +https://arena.5eplay.com/data/match/g161-20251227204147532432472 +https://arena.5eplay.com/data/match/g161-20251224212749300709409 +https://arena.5eplay.com/data/match/g161-20251224204010707719140 +https://arena.5eplay.com/data/match/g161-n-20251130213145958206941 +https://arena.5eplay.com/data/match/g161-n-20251130210025158075163 +https://arena.5eplay.com/data/match/g161-20251130202604606424766 +https://arena.5eplay.com/data/match/g161-n-20251121221256211567778 +https://arena.5eplay.com/data/match/g161-20251121213002842778327 +https://arena.5eplay.com/data/match/g161-20251121204534531429599 +https://arena.5eplay.com/data/match/g161-20251120225541418811147 +https://arena.5eplay.com/data/match/g161-n-20251120215752770546182 +https://arena.5eplay.com/data/match/g161-n-20251120212307767251203 +https://arena.5eplay.com/data/match/g161-n-20251120204855361553501 +https://arena.5eplay.com/data/match/g161-20251119224637611106951 +https://arena.5eplay.com/data/match/g161-20251119220301211708132 +https://arena.5eplay.com/data/match/g161-20251119212237018904830 +https://arena.5eplay.com/data/match/g161-20251113221747008211552 +https://arena.5eplay.com/data/match/g161-20251113213926308316564 +https://arena.5eplay.com/data/match/g161-20251113205020504700482 +https://arena.5eplay.com/data/match/g161-n-20251222211554225486531 +https://arena.5eplay.com/data/match/g161-n-20251222204652101389654 +https://arena.5eplay.com/data/match/g161-20251213224016824985377 +https://arena.5eplay.com/data/match/g161-n-20251031232529838133039 +https://arena.5eplay.com/data/match/g161-n-20251031222014957918049 +https://arena.5eplay.com/data/match/g161-n-20251031214157458692406 +https://arena.5eplay.com/data/match/g161-n-20251031210748072610729 +https://arena.5eplay.com/data/match/g161-n-20251030222146222677830 +https://arena.5eplay.com/data/match/g161-n-20251030213304728467793 +https://arena.5eplay.com/data/match/g161-n-20251030205820720066790 +https://arena.5eplay.com/data/match/g161-n-20251029215222528748730 +https://arena.5eplay.com/data/match/g161-n-20251029223307353807510 +https://arena.5eplay.com/data/match/g161-n-20251027231404235379274 +https://arena.5eplay.com/data/match/g161-n-20251028213320660376574 +https://arena.5eplay.com/data/match/g161-n-20251028221342615577217 +https://arena.5eplay.com/data/match/g161-n-20251027223836601395494 +https://arena.5eplay.com/data/match/g161-n-20251027215238222152932 +https://arena.5eplay.com/data/match/g161-n-20251027210631831497570 +https://arena.5eplay.com/data/match/g161-n-20251025230600131718164 +https://arena.5eplay.com/data/match/g161-n-20251025213429016677232 +https://arena.5eplay.com/data/match/g161-n-20251025210415433542948 +https://arena.5eplay.com/data/match/g161-n-20251025203218851223471 +https://arena.5eplay.com/data/match/g161-n-20251025195106739608572 \ No newline at end of file diff --git a/downloader/gamelist/match_list_before_0913.txt b/downloader/gamelist/match_list_before_0913.txt new file mode 100644 index 0000000..ead699f --- /dev/null +++ b/downloader/gamelist/match_list_before_0913.txt @@ -0,0 +1,48 @@ +https://arena.5eplay.com/data/match/g161-n-20250913220512141946989 +https://arena.5eplay.com/data/match/g161-n-20250913213107816808164 +https://arena.5eplay.com/data/match/g161-20250913205742414202329 +https://arena.5eplay.com/data/match/g161-n-20250827221331843083555 +https://arena.5eplay.com/data/match/g161-20250817225217269787769 +https://arena.5eplay.com/data/match/g161-20250817221445650638471 +https://arena.5eplay.com/data/match/g161-20250817213333244382504 +https://arena.5eplay.com/data/match/g161-20250817204703953154600 +https://arena.5eplay.com/data/match/g161-n-20250816230720637945240 +https://arena.5eplay.com/data/match/g161-n-20250816223209989476278 +https://arena.5eplay.com/data/match/g161-n-20250816215000584183999 +https://arena.5eplay.com/data/match/g161-n-20250810000507840654837 +https://arena.5eplay.com/data/match/g161-n-20250809232857469499842 +https://arena.5eplay.com/data/match/g161-n-20250809224113646082440 +https://arena.5eplay.com/data/match/g161-20250805224735339106659 +https://arena.5eplay.com/data/match/g161-20250805221246768259380 +https://arena.5eplay.com/data/match/g161-20250805213044671459165 +https://arena.5eplay.com/data/match/g161-n-20250729224539870249509 +https://arena.5eplay.com/data/match/g161-n-20250729221017411617812 +https://arena.5eplay.com/data/match/g161-n-20250726230753271236792 +https://arena.5eplay.com/data/match/g161-n-20250726222011747090952 +https://arena.5eplay.com/data/match/g161-n-20250726213213252258654 +https://arena.5eplay.com/data/match/g161-n-20250726210250462966112 +https://arena.5eplay.com/data/match/g161-n-20250726202108438713376 +https://arena.5eplay.com/data/match/g161-n-20250708223526502973398 +https://arena.5eplay.com/data/match/g161-n-20250629224717702923977 +https://arena.5eplay.com/data/match/g161-n-20250629221632707741592 +https://arena.5eplay.com/data/match/g161-n-20250629214005898851985 +https://arena.5eplay.com/data/match/g161-n-20250625233517097081378 +https://arena.5eplay.com/data/match/g161-n-20250625233517097081378 +https://arena.5eplay.com/data/match/g161-n-20250625233517097081378 +https://arena.5eplay.com/data/match/g161-n-20250625225637201689118 +https://arena.5eplay.com/data/match/g161-n-20250625220051296084673 +https://arena.5eplay.com/data/match/g161-n-20250625212340196552999 +https://arena.5eplay.com/data/match/g161-n-20250625204055608218332 +https://arena.5eplay.com/data/match/g161-n-20250624224559896152236 +https://arena.5eplay.com/data/match/g161-n-20250624221215091912088 +https://arena.5eplay.com/data/match/g161-n-20250624213649835216392 +https://arena.5eplay.com/data/match/g161-20250329215431484950790 +https://arena.5eplay.com/data/match/g161-20250404102704857102834 +https://arena.5eplay.com/data/match/g161-20250404110639758722580 +https://arena.5eplay.com/data/match/g161-20250404113912053638456 +https://arena.5eplay.com/data/match/g161-20250404124315256663822 +https://arena.5eplay.com/data/match/g161-n-20250418212920157087385 +https://arena.5eplay.com/data/match/g161-n-20250423212911381760420 +https://arena.5eplay.com/data/match/g161-n-20250423221015836808051 +https://arena.5eplay.com/data/match/g161-n-20250505212901236776044 +https://arena.5eplay.com/data/match/g161-n-20250505210156662230606 \ No newline at end of file diff --git a/downloader/gamelist/match_list_before_1025.txt b/downloader/gamelist/match_list_before_1025.txt new file mode 100644 index 0000000..7fac0a8 --- /dev/null +++ b/downloader/gamelist/match_list_before_1025.txt @@ -0,0 +1,23 @@ +https://arena.5eplay.com/data/match/g161-n-20251012225545036903374 +https://arena.5eplay.com/data/match/g161-n-20251012220151962958852 +https://arena.5eplay.com/data/match/g161-n-20251012220151962958852 +https://arena.5eplay.com/data/match/g161-n-20251012211416764734636 +https://arena.5eplay.com/data/match/g161-n-20251003170554517340798 +https://arena.5eplay.com/data/match/g161-n-20251006130250489051437 +https://arena.5eplay.com/data/match/g161-n-20251006122000914844735 +https://arena.5eplay.com/data/match/g161-n-20251005185512726501951 +https://arena.5eplay.com/data/match/g161-n-20251005182335443677587 +https://arena.5eplay.com/data/match/g161-n-20251003192720361556278 +https://arena.5eplay.com/data/match/g161-n-20251003185649812523095 +https://arena.5eplay.com/data/match/g161-n-20251003182922419032199 +https://arena.5eplay.com/data/match/g161-n-20251003175831422195120 +https://arena.5eplay.com/data/match/g161-n-20251003170554517340798 +https://arena.5eplay.com/data/match/g161-n-20251003161937522875514 +https://arena.5eplay.com/data/match/g161-n-20250913220512141946989 +https://arena.5eplay.com/data/match/g161-20250913205742414202329 +https://arena.5eplay.com/data/match/g161-n-20250913213107816808164 +https://arena.5eplay.com/data/match/g161-n-20250729221017411617812 +https://arena.5eplay.com/data/match/g161-n-20250816215000584183999 +https://arena.5eplay.com/data/match/g161-n-20250816223209989476278 +https://arena.5eplay.com/data/match/g161-n-20250810000507840654837 +https://arena.5eplay.com/data/match/g161-n-20250809224113646082440 \ No newline at end of file diff --git a/downloader/gamelist/match_list_early_2025.txt b/downloader/gamelist/match_list_early_2025.txt new file mode 100644 index 0000000..bc2e088 --- /dev/null +++ b/downloader/gamelist/match_list_early_2025.txt @@ -0,0 +1,73 @@ +https://arena.5eplay.com/data/match/g161-n-20250103201445137702215 +https://arena.5eplay.com/data/match/g161-n-20250103203331443454143 +https://arena.5eplay.com/data/match/g161-n-20250103211644789725355 +https://arena.5eplay.com/data/match/g161-n-20250105000114157444753 +https://arena.5eplay.com/data/match/g161-n-20250105004102938304243 +https://arena.5eplay.com/data/match/g161-n-20250109205825766219524 +https://arena.5eplay.com/data/match/g161-n-20250109214524585140725 +https://arena.5eplay.com/data/match/g161-n-20250109222317807381679 +https://arena.5eplay.com/data/match/g161-n-20250109225725438125765 +https://arena.5eplay.com/data/match/g161-n-20250110000800438550163 +https://arena.5eplay.com/data/match/g161-n-20250115210950870494621 +https://arena.5eplay.com/data/match/g161-n-20250115214227730237642 +https://arena.5eplay.com/data/match/g161-n-20250115222151238089028 +https://arena.5eplay.com/data/match/g161-n-20250115224837069753503 +https://arena.5eplay.com/data/match/g161-n-20250119201843917352000 +https://arena.5eplay.com/data/match/g161-n-20250119205646572572033 +https://arena.5eplay.com/data/match/g161-n-20250119214057134288558 +https://arena.5eplay.com/data/match/g161-n-20250119221209668234775 +https://arena.5eplay.com/data/match/g161-n-20250212194801048099163 +https://arena.5eplay.com/data/match/g161-n-20250212204500213129957 +https://arena.5eplay.com/data/match/g161-n-20250212211417251548261 +https://arena.5eplay.com/data/match/g161-n-20250212224659856768179 +https://arena.5eplay.com/data/match/g161-n-20250212232524442488205 +https://arena.5eplay.com/data/match/g161-20250214164955786323546 +https://arena.5eplay.com/data/match/g161-20250214172202090993964 +https://arena.5eplay.com/data/match/g161-20250214174757585798948 +https://arena.5eplay.com/data/match/g161-20250215204022294779045 +https://arena.5eplay.com/data/match/g161-20250215211846894242128 +https://arena.5eplay.com/data/match/g161-20250217202409685923399 +https://arena.5eplay.com/data/match/g161-20250217205402386409635 +https://arena.5eplay.com/data/match/g161-20250217212436510051874 +https://arena.5eplay.com/data/match/g161-20250217220552927034811 +https://arena.5eplay.com/data/match/g161-20250218160114138124831 +https://arena.5eplay.com/data/match/g161-20250218162428685487349 +https://arena.5eplay.com/data/match/g161-20250218165542404622024 +https://arena.5eplay.com/data/match/g161-20250218211240395943608 +https://arena.5eplay.com/data/match/g161-20250218214056585823614 +https://arena.5eplay.com/data/match/g161-20250218221355585818088 +https://arena.5eplay.com/data/match/g161-n-20250221200134537532083 +https://arena.5eplay.com/data/match/g161-n-20250221202611846934043 +https://arena.5eplay.com/data/match/g161-n-20250221205801951388015 +https://arena.5eplay.com/data/match/g161-n-20250221212924852778522 +https://arena.5eplay.com/data/match/g161-n-20250221220520358691141 +https://arena.5eplay.com/data/match/g161-n-20250224190530943492421 +https://arena.5eplay.com/data/match/g161-n-20250224192756599598828 +https://arena.5eplay.com/data/match/g161-n-20250224211003642995175 +https://arena.5eplay.com/data/match/g161-n-20250224214246751262216 +https://arena.5eplay.com/data/match/g161-n-20250224221018957359594 +https://arena.5eplay.com/data/match/g161-n-20250227201006443002972 +https://arena.5eplay.com/data/match/g161-n-20250227204400163237739 +https://arena.5eplay.com/data/match/g161-n-20250227211802698292906 +https://arena.5eplay.com/data/match/g161-n-20250301200647442341789 +https://arena.5eplay.com/data/match/g161-n-20250301204325972686590 +https://arena.5eplay.com/data/match/g161-n-20250301211319138257939 +https://arena.5eplay.com/data/match/g161-n-20250301214842394094370 +https://arena.5eplay.com/data/match/g161-n-20250301221920464983026 +https://arena.5eplay.com/data/match/g161-20250301225228585801638 +https://arena.5eplay.com/data/match/g161-20250302154200385322147 +https://arena.5eplay.com/data/match/g161-20250302161030995093939 +https://arena.5eplay.com/data/match/g161-20250302165056088320401 +https://arena.5eplay.com/data/match/g161-20250306212929308811302 +https://arena.5eplay.com/data/match/g161-20250306220339391113038 +https://arena.5eplay.com/data/match/g161-n-20250307202729007357677 +https://arena.5eplay.com/data/match/g161-n-20250307205954649678046 +https://arena.5eplay.com/data/match/g161-n-20250307214542342522277 +https://arena.5eplay.com/data/match/g161-n-20250307220959454626136 +https://arena.5eplay.com/data/match/g161-n-20250311202342544577031 +https://arena.5eplay.com/data/match/g161-n-20250311220347557866712 +https://arena.5eplay.com/data/match/g161-n-20250311212924644001588 +https://arena.5eplay.com/data/match/g161-n-20250311205101348741496 +https://arena.5eplay.com/data/match/g161-n-20250313200635729548487 +https://arena.5eplay.com/data/match/g161-n-20250313204903360834136 +https://arena.5eplay.com/data/match/g161-n-20250313211821260060301 \ No newline at end of file