mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-02-03 02:04:06 +08:00
fix(network): improve torrent fetch reliability and error handling
- Add browser-like headers and full Chrome User-Agent to avoid Cloudflare blocking - Use appropriate Accept headers for torrent files (application/x-bittorrent) - Increase timeouts (connect: 5s→10s, read: 10s→30s) for slow responses - Filter out None values from failed torrent fetches before sending to qBittorrent - Add try-catch around add_torrents to prevent request crashes - Improve logging from DEBUG to WARNING level for better visibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,11 @@ class DownloadClient(TorrentPath):
|
||||
from .client.aria2_downloader import Aria2Downloader
|
||||
|
||||
return Aria2Downloader(host, username, password)
|
||||
elif type == "mock":
|
||||
from .client.mock_downloader import MockDownloader
|
||||
|
||||
logger.info("[Downloader] Using MockDownloader for local development")
|
||||
return MockDownloader()
|
||||
else:
|
||||
logger.error(f"[Downloader] Unsupported downloader type: {type}")
|
||||
raise Exception(f"Unsupported downloader type: {type}")
|
||||
@@ -141,6 +146,11 @@ class DownloadClient(TorrentPath):
|
||||
torrent_file = await asyncio.gather(
|
||||
*[req.get_content(t.url) for t in torrent]
|
||||
)
|
||||
# Filter out None values (failed fetches)
|
||||
torrent_file = [f for f in torrent_file if f is not None]
|
||||
if not torrent_file:
|
||||
logger.warning(f"[Downloader] Failed to fetch torrent files for: {bangumi.official_title}")
|
||||
return False
|
||||
torrent_url = None
|
||||
else:
|
||||
if "magnet" in torrent.url:
|
||||
@@ -148,17 +158,24 @@ class DownloadClient(TorrentPath):
|
||||
torrent_file = None
|
||||
else:
|
||||
torrent_file = await req.get_content(torrent.url)
|
||||
if torrent_file is None:
|
||||
logger.warning(f"[Downloader] Failed to fetch torrent file for: {bangumi.official_title}")
|
||||
return False
|
||||
torrent_url = None
|
||||
if await self.client.add_torrents(
|
||||
torrent_urls=torrent_url,
|
||||
torrent_files=torrent_file,
|
||||
save_path=bangumi.save_path,
|
||||
category="Bangumi",
|
||||
):
|
||||
logger.debug(f"[Downloader] Add torrent: {bangumi.official_title}")
|
||||
return True
|
||||
else:
|
||||
logger.debug(f"[Downloader] Torrent added before: {bangumi.official_title}")
|
||||
try:
|
||||
if await self.client.add_torrents(
|
||||
torrent_urls=torrent_url,
|
||||
torrent_files=torrent_file,
|
||||
save_path=bangumi.save_path,
|
||||
category="Bangumi",
|
||||
):
|
||||
logger.debug(f"[Downloader] Add torrent: {bangumi.official_title}")
|
||||
return True
|
||||
else:
|
||||
logger.debug(f"[Downloader] Torrent added before: {bangumi.official_title}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"[Downloader] Failed to add torrent for {bangumi.official_title}: {e}")
|
||||
return False
|
||||
|
||||
async def move_torrent(self, hashes, location):
|
||||
|
||||
@@ -67,6 +67,8 @@ class RequestContent(RequestURL):
|
||||
req = await self.get_url(_url)
|
||||
if req:
|
||||
return req.content
|
||||
logger.warning(f"[Network] Failed to get content from {_url}")
|
||||
return None
|
||||
|
||||
async def check_connection(self, _url):
|
||||
return await self.check_url(_url)
|
||||
|
||||
@@ -26,7 +26,7 @@ async def get_shared_client() -> httpx.AsyncClient:
|
||||
return _shared_client
|
||||
if _shared_client is not None:
|
||||
await _shared_client.aclose()
|
||||
timeout = httpx.Timeout(connect=5.0, read=10.0, write=10.0, pool=10.0)
|
||||
timeout = httpx.Timeout(connect=10.0, read=30.0, write=10.0, pool=10.0)
|
||||
if settings.proxy.enable:
|
||||
if "http" in settings.proxy.type:
|
||||
if settings.proxy.username:
|
||||
@@ -50,31 +50,50 @@ async def get_shared_client() -> httpx.AsyncClient:
|
||||
|
||||
|
||||
class RequestURL:
|
||||
# More complete User-Agent to avoid Cloudflare blocking
|
||||
DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
def __init__(self):
|
||||
self.header = {"user-agent": "Mozilla/5.0", "Accept": "application/xml"}
|
||||
self.header = {"User-Agent": self.DEFAULT_UA, "Accept": "application/xml"}
|
||||
self._client: httpx.AsyncClient | None = None
|
||||
|
||||
def _get_headers(self, url: str) -> dict:
|
||||
"""Get appropriate headers based on URL type."""
|
||||
base_headers = {
|
||||
"User-Agent": self.DEFAULT_UA,
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
# For torrent files, use different Accept header
|
||||
if url.endswith(".torrent") or "/download/" in url:
|
||||
base_headers["Accept"] = "application/x-bittorrent, application/octet-stream, */*"
|
||||
else:
|
||||
base_headers["Accept"] = "application/xml, text/xml, */*"
|
||||
return base_headers
|
||||
|
||||
async def get_url(self, url, retry=3):
|
||||
try_time = 0
|
||||
headers = self._get_headers(url)
|
||||
while True:
|
||||
try:
|
||||
req = await self._client.get(url=url, headers=self.header)
|
||||
req = await self._client.get(url=url, headers=headers)
|
||||
logger.debug(f"[Network] Successfully connected to {url}. Status: {req.status_code}")
|
||||
req.raise_for_status()
|
||||
return req
|
||||
except httpx.HTTPStatusError:
|
||||
logger.debug(f"[Network] HTTP error from {url}.")
|
||||
except httpx.HTTPStatusError as e:
|
||||
logger.warning(f"[Network] HTTP {e.response.status_code} from {url}")
|
||||
break
|
||||
except httpx.RequestError:
|
||||
logger.debug(
|
||||
f"[Network] Cannot connect to {url}. Wait for 5 seconds."
|
||||
except httpx.RequestError as e:
|
||||
logger.warning(
|
||||
f"[Network] Request error for {url}: {type(e).__name__}. Retry {try_time + 1}/{retry}"
|
||||
)
|
||||
try_time += 1
|
||||
if try_time >= retry:
|
||||
break
|
||||
await asyncio.sleep(5)
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
logger.warning(f"[Network] Unexpected error for {url}: {e}")
|
||||
break
|
||||
logger.error(f"[Network] Unable to connect to {url}, Please check your network settings")
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user