0.1: Downloader Implemented.

This commit is contained in:
2026-01-23 13:00:09 +08:00
commit 81df352607
7 changed files with 760 additions and 0 deletions

85
downloader/README.md Normal file
View File

@@ -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-msiframe 页面 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 未安装,请先执行安装命令再运行脚本
- 如果下载目录已有文件,会跳过重复下载

416
downloader/downloader.py Normal file
View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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