From 9729542b355658423f2e586f8b69cd782337e03e Mon Sep 17 00:00:00 2001 From: glyh Date: Sun, 29 Sep 2024 11:44:35 +0800 Subject: [PATCH] refactor network with aiohttp --- javsp/__main__.py | 3 + javsp/chromium.py | 2 + javsp/crawlers/interface.py | 4 +- javsp/crawlers/proxyfree.py | 30 +- javsp/crawlers/sites/airav.py | 27 +- javsp/crawlers/sites/arzon.py | 12 +- javsp/crawlers/sites/arzon_iv.py | 10 +- javsp/crawlers/sites/avsox.py | 10 +- javsp/crawlers/sites/avwiki.py | 8 +- javsp/crawlers/sites/dl_getchu.py | 8 +- javsp/crawlers/sites/fanza.py | 68 ++-- javsp/crawlers/sites/fc2.py | 10 +- javsp/crawlers/sites/fc2ppvdb.py | 7 +- javsp/crawlers/sites/gyutto.py | 8 +- javsp/crawlers/sites/jav321.py | 6 +- javsp/crawlers/sites/javbus.py | 8 +- javsp/crawlers/sites/javdb.py | 46 +-- javsp/crawlers/sites/javlib.py | 13 +- javsp/crawlers/sites/javmenu.py | 6 +- javsp/crawlers/sites/mgstage.py | 12 +- javsp/crawlers/sites/njav.py | 8 +- javsp/crawlers/sites/prestige.py | 12 +- javsp/func.py | 6 +- javsp/network/client.py | 54 ++- javsp/network/utils.py | 48 +-- javsp/translate.py | 74 ++-- poetry.lock | 635 ++++++++++++++++++++++++------ pyproject.toml | 3 +- unittest/test_proxyfree.py | 4 + 29 files changed, 794 insertions(+), 348 deletions(-) diff --git a/javsp/__main__.py b/javsp/__main__.py index 456bbebf8..a7f407f99 100644 --- a/javsp/__main__.py +++ b/javsp/__main__.py @@ -11,6 +11,7 @@ from pydantic_extra_types.pendulum_dt import Duration from typing import Any, Coroutine, Dict, List from javsp.crawlers.all import crawlers +from javsp.network.client import clear_clients sys.stdout.reconfigure(encoding='utf-8') @@ -549,6 +550,8 @@ async def aentry(): logger.info(f'扫描影片文件:共找到 {movie_count} 部影片') await RunNormalMode(recognized + recognize_fail) + await clear_clients() + sys.exit(0) def entry(): diff --git a/javsp/chromium.py b/javsp/chromium.py index db315293e..1f8d01964 100644 --- a/javsp/chromium.py +++ b/javsp/chromium.py @@ -32,6 +32,8 @@ def decrypt(self, encrypted_value): def get_browsers_cookies(): """获取系统上的所有Chromium系浏览器的JavDB的Cookies""" + if not sys.platform.startswith('win32'): # 不支持windows以外的系统 + return [] # 不予支持: Opera, 360安全&极速, 搜狗使用非标的用户目录或数据格式; QQ浏览器屏蔽站点 user_data_dirs = { 'Chrome': '/Google/Chrome/User Data', diff --git a/javsp/crawlers/interface.py b/javsp/crawlers/interface.py index a641b0a27..c82085554 100644 --- a/javsp/crawlers/interface.py +++ b/javsp/crawlers/interface.py @@ -1,13 +1,13 @@ -from httpx import AsyncClient from javsp.config import CrawlerID from javsp.datatype import MovieInfo from abc import ABC, abstractmethod from typing import Self +from aiohttp import ClientSession class Crawler(ABC): base_url: str - client: AsyncClient + client: ClientSession id: CrawlerID diff --git a/javsp/crawlers/proxyfree.py b/javsp/crawlers/proxyfree.py index 381eeb7af..45da59b94 100644 --- a/javsp/crawlers/proxyfree.py +++ b/javsp/crawlers/proxyfree.py @@ -9,32 +9,32 @@ from javsp.config import CrawlerID from javsp.network.utils import test_connect, choose_one_connectable -from javsp.network.client import get_client +from javsp.network.client import get_session async def _get_avsox_urls() -> list[str]: link = 'https://tellme.pw/avsox' - client = get_client(Url(link)) - resp = await client.get(link) - tree = html.fromstring(resp.text) + s = get_session(Url(link)) + resp = await s.get(link) + tree = html.fromstring(await resp.text()) urls = tree.xpath('//h4/strong/a/@href') return urls async def _get_javbus_urls() -> list[str]: link = 'https://www.javbus.one/' - client = get_client(Url(link)) - resp = await client.get(link) - text = resp.text + s = get_session(Url(link)) + resp = await s.get(link) + text = await resp.text() urls = re.findall(r'防屏蔽地址:(https://(?:[\d\w][-\d\w]{1,61}[\d\w]\.){1,2}[a-z]{2,})', text, re.I | re.A) return urls async def _get_javlib_urls() -> list[str]: link = 'https://github.com/javlibcom' - client = get_client(Url(link)) - resp = await client.get(link) - tree = html.fromstring(resp.text) + s = get_session(Url(link)) + resp = await s.get(link) + tree = html.fromstring(await resp.text()) text = tree.xpath("//div[@class='p-note user-profile-bio mb-3 js-user-profile-bio f4']")[0].text_content() match = re.search(r'[\w\.]+', text, re.A) if match: @@ -45,15 +45,15 @@ async def _get_javlib_urls() -> list[str]: async def _get_javdb_urls() -> list[str]: root_link = 'https://jav524.app' - client = get_client(Url(root_link)) - resp = await client.get(root_link) - tree = html.fromstring(resp.text) + s = get_session(Url(root_link)) + resp = await s.get(root_link) + tree = html.fromstring(await resp.text()) js_links = tree.xpath("//script[@src]/@src") for link in js_links: if '/js/index' in link: link = root_link + link - resp = await client.get(link) - text = resp.text + resp = await s.get(link) + text = await resp.text() match = re.search(r'\$officialUrl\s*=\s*"(https://(?:[\d\w][-\d\w]{1,61}[\d\w]\.){1,2}[a-z]{2,})"', text, flags=re.I | re.A) if match: return [match.group(1)] diff --git a/javsp/crawlers/sites/airav.py b/javsp/crawlers/sites/airav.py index 5afd46998..8bc4fa6e6 100644 --- a/javsp/crawlers/sites/airav.py +++ b/javsp/crawlers/sites/airav.py @@ -1,9 +1,10 @@ """从airav抓取数据""" import re from html import unescape +from typing import Dict from javsp.crawlers.exceptions import MovieNotFoundError -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.network.utils import resolve_site_fallback from javsp.config import Cfg, CrawlerID from javsp.datatype import MovieInfo @@ -13,13 +14,15 @@ class AiravCrawler(Crawler): id = CrawlerID.airav + headers: Dict[str, str] + @classmethod async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.airav.wiki') self.base_url = str(url) - self.client = get_client(url) - self.client.headers['Accept-Language'] = 'zh-TW,zh;q=0.9' + self.client = get_session(url) + self.headers = {'Accept-Language': 'zh-TW,zh;q=0.9'} return self async def search_movie(self, dvdid: str): @@ -30,8 +33,8 @@ async def search_movie(self, dvdid: str): result = [] while len(result) < count: url = f'{self.base_url}/api/video/list?lang=zh-TW&lng=zh-TW&search={dvdid}&page={page}' - response = await self.client.get(url) - resp = response.json() + response = await self.client.get(url, headers=self.headers) + resp = await response.json() # {"offset": 2460, "count": 12345, "result": [...], "status": "ok"} if resp['result']: result.extend(resp['result']) @@ -59,15 +62,15 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: """解析指定番号的影片数据""" # airav也提供简体,但是为了尽量保持女优名等与其他站点一致,抓取繁体的数据 url = f'{self.base_url}/api/video/barcode/{movie.dvdid}?lng=zh-TW' - response = await self.client.get(url) - resp_json = response.json() + response = await self.client.get(url, headers=self.headers) + resp_json = await response.json() # 只在番号是纯数字时,尝试进行搜索,否则可能导致搜索到错误的影片信息 if resp_json['count'] == 0 and re.match(r'\d{6}[-_]\d{2,3}', movie.dvdid): barcode = await self.search_movie(movie.dvdid) if barcode: url = f'{self.base_url}/api/video/barcode/{barcode}?lng=zh-TW' - response = await self.client.get(url) - resp_json = response.json() + response = await self.client.get(url, headers=self.headers) + resp_json = await response.json() if resp_json['count'] == 0: raise MovieNotFoundError(__name__, movie.dvdid, resp_json) @@ -93,8 +96,8 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: if Cfg().crawler.hardworking: # 注意这里用的是获取的dvdid,而不是传入的movie.dvdid(如'1pondo_012717_472'与'012717_472') video_url = f"{self.base_url}/api/video/getVideoMedia?barcode={dvdid}&vid={data['vid']}" - response = await self.client.get(video_url) - resp = response.json() + response = await self.client.get(video_url, headers=self.headers) + resp = await response.json() # 如果失败,结果如 {'msg': 'fail', 'status': 'fail'} if 'data' in resp: # 除url外还有url_cdn, url_hlx, url_hls_cdn字段,后两者为m3u8格式。目前将url作为预览视频的地址 @@ -113,12 +116,14 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: break if __name__ == "__main__": + from javsp.network.client import clear_clients async def test_main(): crawler = await AiravCrawler.create() movie = MovieInfo("DSAD-938") await crawler.crawl_and_fill(movie) print(movie) + await clear_clients() import asyncio asyncio.run(test_main()) diff --git a/javsp/crawlers/sites/arzon.py b/javsp/crawlers/sites/arzon.py index f4887f4d7..f325984d0 100644 --- a/javsp/crawlers/sites/arzon.py +++ b/javsp/crawlers/sites/arzon.py @@ -2,7 +2,7 @@ import re from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from javsp.crawlers.exceptions import * @@ -17,7 +17,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, "https://www.arzon.jp") self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) # https://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1&redirect=https%3A%2F%2Fwww.arzon.jp%2F skip_verify_url = f"{self.base_url}/index.php?action=adult_customer_agecheck&agecheck=1" await self.client.get(skip_verify_url) @@ -30,10 +30,10 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # url = f'{base_url}/imagelist.html?q={full_id}' r = await self.client.get(url) - if r.status_code == 404: + if r.status == 404: raise MovieNotFoundError(__name__, movie.dvdid) # https://stackoverflow.com/questions/15830421/xml-unicode-strings-with-encoding-declaration-are-not-supported - data = html.fromstring(r.content) + data = html.fromstring(await r.read()) urls = data.xpath("//h2/a/@href") if len(urls) == 0: @@ -41,7 +41,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: item_url = self.base_url + urls[0] e = await self.client.get(item_url) - item = html.fromstring(e.content) + item = html.fromstring(await e.read()) title = item.xpath("//div[@class='detail_title_new2']//h1/text()")[0] cover = item.xpath("//td[@align='center']//a/img/@src")[0] @@ -91,6 +91,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: movie.preview_pics = preview_pics if __name__ == "__main__": + from javsp.network.client import clear_clients async def test_main(): crawler = await ArzonCrawler.create() @@ -98,6 +99,7 @@ async def test_main(): try: await crawler.crawl_and_fill(movie) print(movie) + await clear_clients() except Exception as e: print(repr(e)) diff --git a/javsp/crawlers/sites/arzon_iv.py b/javsp/crawlers/sites/arzon_iv.py index a84c97aea..65c9b1367 100644 --- a/javsp/crawlers/sites/arzon_iv.py +++ b/javsp/crawlers/sites/arzon_iv.py @@ -3,7 +3,7 @@ from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from javsp.crawlers.exceptions import * @@ -18,7 +18,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, "https://www.arzon.jp") self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) # https://www.arzon.jp/index.php?action=adult_customer_agecheck&agecheck=1&redirect=https%3A%2F%2Fwww.arzon.jp%2F skip_verify_url = f"{self.base_url}/index.php?action=adult_customer_agecheck&agecheck=1" await self.client.get(skip_verify_url) @@ -31,10 +31,10 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # url = f'{base_url}/imagelist.html?q={full_id}' r = await self.client.get(url) - if r.status_code == 404: + if r.status == 404: raise MovieNotFoundError(__name__, movie.dvdid) # https://stackoverflow.com/questions/15830421/xml-unicode-strings-with-encoding-declaration-are-not-supported - data = html.fromstring(r.content) + data = html.fromstring(await r.read()) urls = data.xpath("//h2/a/@href") if len(urls) == 0: @@ -42,7 +42,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: item_url = self.base_url + urls[0] e = await self.client.get(item_url) - item = html.fromstring(e.content) + item = html.fromstring(await e.read()) title = item.xpath("//div[@class='detail_title_new']//h1/text()")[0] cover = item.xpath("//td[@align='center']//a/img/@src")[0] diff --git a/javsp/crawlers/sites/avsox.py b/javsp/crawlers/sites/avsox.py index 47b0ea32d..75dcd67c2 100644 --- a/javsp/crawlers/sites/avsox.py +++ b/javsp/crawlers/sites/avsox.py @@ -3,7 +3,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -16,7 +16,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, "https://avsox.click/") self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -24,7 +24,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: if full_id.startswith('FC2-'): full_id = full_id.replace('FC2-', 'FC2-PPV-') resp = await self.client.get(f'{self.base_url}tw/search/{full_id}') - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) tree.make_links_absolute(str(resp.url), resolve_base_href=True) ids = tree.xpath("//div[@class='photo-info']/span/date[1]/text()") urls = tree.xpath("//a[contains(@class, 'movie-box')]/@href") @@ -37,9 +37,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # 提取影片信息 resp = await self.client.get(url) - # with open('file.html', 'wb') as f: - # f.write(resp.content) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) container = tree.xpath("/html/body/div[@class='container']")[0] title = container.xpath("h3/text()")[0] cover = container.xpath("//a[@class='bigImage']/@href")[0] diff --git a/javsp/crawlers/sites/avwiki.py b/javsp/crawlers/sites/avwiki.py index 7bc2041e5..6a75dd345 100644 --- a/javsp/crawlers/sites/avwiki.py +++ b/javsp/crawlers/sites/avwiki.py @@ -4,7 +4,7 @@ from javsp.datatype import MovieInfo from javsp.crawlers.interface import Crawler from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.config import CrawlerID from lxml import html @@ -16,7 +16,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://av-wiki.net') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -27,9 +27,9 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: movie.url = url = f'{self.base_url}/{movie.dvdid}' resp = await self.client.get(url) - if resp.status_code == 404: + if resp.status == 404: raise MovieNotFoundError(__name__, movie.dvdid) - tree = html.fromstring(resp.content) + tree = html.fromstring(await resp.text()) cover_tag = tree.xpath("//header/div/a[@class='image-link-border']/img") if cover_tag: diff --git a/javsp/crawlers/sites/dl_getchu.py b/javsp/crawlers/sites/dl_getchu.py index c2ab0814f..a635515d4 100644 --- a/javsp/crawlers/sites/dl_getchu.py +++ b/javsp/crawlers/sites/dl_getchu.py @@ -5,7 +5,7 @@ from javsp.config import CrawlerID from javsp.crawlers.exceptions import MovieNotFoundError from javsp.crawlers.interface import Crawler -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.network.utils import resolve_site_fallback from javsp.crawlers.exceptions import * from javsp.datatype import MovieInfo @@ -55,7 +55,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://dl.getchu.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -68,9 +68,9 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # 抓取网页 url = f'{self.base_url}/i/item{getchu_id}' r = await self.client.get(url) - if r.status_code == 404: + if r.status == 404: raise MovieNotFoundError(__name__, movie.dvdid) - tree = html.fromstring(r.text) + tree = html.fromstring((await r.read()).decode(encoding='euc_jp', errors='ignore')) container = tree.xpath("//form[@action='https://dl.getchu.com/cart/']/div/table[3]") if len(container) > 0: container = container[0] diff --git a/javsp/crawlers/sites/fanza.py b/javsp/crawlers/sites/fanza.py index 66b895df5..b81ac93ae 100644 --- a/javsp/crawlers/sites/fanza.py +++ b/javsp/crawlers/sites/fanza.py @@ -5,19 +5,18 @@ import logging from typing import Dict, List, Tuple -from httpx import Response - from javsp.crawlers.exceptions import MovieNotFoundError, SiteBlocked from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.config import Cfg from javsp.datatype import MovieInfo from lxml import html from lxml.html import HtmlElement +from aiohttp import ClientResponse logger = logging.getLogger(__name__) @@ -31,8 +30,8 @@ def sort_search_result(result: List[Dict]): return sorted_result -def resp2html_wrapper(resp: Response) -> HtmlElement: - tree = html.fromstring(resp.text) +async def resp2html_wrapper(resp: ClientResponse) -> HtmlElement: + tree = html.fromstring(await resp.text()) if 'not available in your region' in tree.text_content(): raise SiteBlocked('FANZA不允许从当前IP所在地区访问,请检查你的网络和代理服务器设置') elif '/login/' in str(resp.url): @@ -88,14 +87,29 @@ def parse_anime_page(movie: MovieInfo, tree: HtmlElement): class FanzaCrawler(Crawler): id = CrawlerID.fanza + headers: Dict[str, str] + + + @classmethod + async def create(cls): + self = cls() + url = await resolve_site_fallback(self.id, 'https://www.dmm.co.jp') + self.base_url = str(url) + self.client = get_session(url) + + # 初始化Request实例(要求携带已通过R18认证的cookies,否则会被重定向到认证页面) + self.client.cookie_jar.update_cookies({'age_check_done': '1'}) + self.headers = {'Accept-Language': 'ja,en-US;q=0.9'} + return self async def get_urls_of_cid(self, cid: str) -> Tuple[str, str]: """搜索cid可能的影片URL""" - r = await self.client.get(f"{self.base_url}search/?redirect=1&enc=UTF-8&category=&searchstr={cid}&commit.x=0&commit.y=0") - if r.status_code == 404: + r = await self.client.get(f"{self.base_url}search/?redirect=1&enc=UTF-8&category=&searchstr={cid}&commit.x=0&commit.y=0", headers=self.headers) + if r.status == 404: raise MovieNotFoundError(__name__, cid) r.raise_for_status() - tree = resp2html_wrapper(r) + + tree = await resp2html_wrapper(r) result = tree.xpath("//ul[@id='list']/li/div/p/a/@href") parsed_result = {} for url in result: @@ -116,36 +130,25 @@ async def get_urls_of_cid(self, cid: str) -> Tuple[str, str]: sorted_result = sort_search_result(parsed_result[cid]) return sorted_result - @classmethod - async def create(cls): - self = cls() - url = await resolve_site_fallback(self.id, 'https://www.dmm.co.jp') - self.base_url = str(url) - self.client = get_client(url) - - # 初始化Request实例(要求携带已通过R18认证的cookies,否则会被重定向到认证页面) - self.client.cookies = {'age_check_done': '1'} - self.client.headers['Accept-Language'] = 'ja,en-US;q=0.9' - return self + async def dispatch(self, type: str, movie: MovieInfo, tree: HtmlElement): + match type: + case 'videoa' | 'dvd' | 'ppr' | 'nikkatsu': + await self.parse_videoa_page(movie, tree) + case 'anime' | 'doujin': + parse_anime_page(movie, tree) async def crawl_and_fill(self, movie: MovieInfo) -> None: """解析指定番号的影片数据""" default_url = f'{self.base_url}digital/videoa/-/detail/=/cid={movie.cid}/' - r0 = await self.client.get(default_url) - if r0.status_code == 404: + r0 = await self.client.get(default_url, headers=self.headers) + if r0.status == 404: urls = await self.get_urls_of_cid(movie.cid) for d in urls: - func_name = f"parse_{d['type']}_page" - if func_name in globals(): - parse_func = globals()[func_name] - else: - logger.debug(f"不知道怎么解析 fanza {d['type']} 的页面: {d['url']}") - continue - r = await self.client.get(d['url']) - tree = resp2html_wrapper(r) try: - parse_func(movie, tree) + r = await self.client.get(d['url'], headers=self.headers) + tree = await resp2html_wrapper(r) + await self.dispatch(d['type'], movie, tree) movie.url = d['url'] break except: @@ -209,8 +212,8 @@ async def parse_videoa_page(self, movie: MovieInfo, tree: HtmlElement): if Cfg().crawler.hardworking: # 预览视频是动态加载的,不在静态网页中 video_url = f'{self.base_url}service/digitalapi/-/html5_player/=/cid={movie.cid}' - resp = await self.client.get(video_url) - tree2 = html.fromstring(resp.text) + resp = await self.client.get(video_url, headers=self.headers) + tree2 = html.fromstring(await resp.text()) # 目前用到js脚本的地方不多,所以不使用专门的js求值模块,先用正则提取文本然后用json解析数据 script = tree2.xpath("//script[contains(text(),'getElementById(\"dmmplayer\")')]/text()")[0].strip() match = re.search(r'\{.*\}', script) @@ -244,3 +247,4 @@ async def test_main(): import asyncio asyncio.run(test_main()) + diff --git a/javsp/crawlers/sites/fc2.py b/javsp/crawlers/sites/fc2.py index 0ce072b90..4ef981ff1 100644 --- a/javsp/crawlers/sites/fc2.py +++ b/javsp/crawlers/sites/fc2.py @@ -10,7 +10,7 @@ from javsp.datatype import MovieInfo from javsp.crawlers.interface import Crawler from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.config import CrawlerID @@ -24,13 +24,13 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://adult.contents.fc2.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def get_movie_score(self, fc2_id: str) -> float | None: """通过评论数据来计算FC2的影片评分(10分制),无法获得评分时返回None""" resp = await self.client.get(f'{self.base_url}/article/{fc2_id}/review') - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) review_tags = tree.xpath("//ul[@class='items_comment_headerReviewInArea']/li") reviews = {} for tag in review_tags: @@ -56,7 +56,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: resp = await self.client.get(url) if '/id.fc2.com/' in str(resp.url): raise SiteBlocked('FC2要求当前IP登录账号才可访问,请尝试更换为日本IP') - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) container = tree.xpath("//div[@class='items_article_left']") if len(container) > 0: container = container[0] @@ -85,7 +85,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: key = desc_frame_url.split('=')[-1] # /widget/article/718323/description?ac=60fc08fa... api_url = f'{self.base_url}/api/v2/videos/{fc2_id}/sample?key={key}' resp = await self.client.get(api_url) - j = resp.json() + j = await resp.json() movie.preview_video = j['path'] else: # 获取影片评分。影片页面的评分只能粗略到星级,且没有分数,要通过类名来判断,如'items_article_Star5'表示5星 diff --git a/javsp/crawlers/sites/fc2ppvdb.py b/javsp/crawlers/sites/fc2ppvdb.py index fbba590c2..8ae6d7415 100644 --- a/javsp/crawlers/sites/fc2ppvdb.py +++ b/javsp/crawlers/sites/fc2ppvdb.py @@ -2,6 +2,7 @@ # BUG: This crawler doesn't work, seemed due to cloudflare +from ssl import ALERT_DESCRIPTION_HANDSHAKE_FAILURE from typing import List @@ -9,7 +10,7 @@ from javsp.lib import strftime_to_minutes from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -23,7 +24,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://fc2ppvdb.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -40,7 +41,7 @@ def get_list_first(list: List): # 抓取网页 url = f'{self.base_url}/articles/{fc2_id}' resp = await self.client.get(url) - tree = html.fromstring(resp.content) + tree = html.fromstring(await resp.text()) # html = get_html(url) container = tree.xpath("//div[@class='container lg:px-5 px-2 py-12 mx-auto']/div[1]") if len(container) > 0: diff --git a/javsp/crawlers/sites/gyutto.py b/javsp/crawlers/sites/gyutto.py index b30200284..632fb9123 100644 --- a/javsp/crawlers/sites/gyutto.py +++ b/javsp/crawlers/sites/gyutto.py @@ -5,7 +5,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -41,7 +41,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'http://gyutto.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -54,9 +54,9 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # 抓取网页 url = f'{self.base_url}/i/item{gyutto_id}?select_uaflag=1' r = await self.client.get(url) - if r.status_code == 404: + if r.status == 404: raise MovieNotFoundError(__name__, movie.dvdid) - tree = html.fromstring(r.text) + tree = html.fromstring(await r.text()) container = tree.xpath("//dl[@class='BasicInfo clearfix']") producer = None diff --git a/javsp/crawlers/sites/jav321.py b/javsp/crawlers/sites/jav321.py index 6a20a98ec..61f609bfd 100644 --- a/javsp/crawlers/sites/jav321.py +++ b/javsp/crawlers/sites/jav321.py @@ -6,7 +6,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -22,14 +22,14 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.jav321.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: """解析指定番号的影片数据""" resp = await self.client.post(f'{self.base_url}/search', data={'sn': movie.dvdid}) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) page_url = tree.xpath("//ul[@class='dropdown-menu']/li/a/@href")[0] #TODO: 注意cid是dmm的概念。如果影片来自MGSTAGE,这里的cid很可能是jav321自己添加的,例如 345SIMM-542 cid = page_url.split('/')[-1] # /video/ipx00177 diff --git a/javsp/crawlers/sites/javbus.py b/javsp/crawlers/sites/javbus.py index b3efaa8dd..3038579cd 100644 --- a/javsp/crawlers/sites/javbus.py +++ b/javsp/crawlers/sites/javbus.py @@ -9,7 +9,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from lxml import html @@ -26,8 +26,8 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.javbus.com') self.base_url = str(url) - self.client = get_client(url) - self.client.cookies = {'age': 'verified', 'dv': '1'} + self.client = get_session(url) + self.client.cookie_jar.update_cookies({'age': 'verified', 'dv': '1'}) self.genre_map = GenreMap('data/genre_javbus.csv') return self @@ -40,7 +40,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: url = f'{self.base_url}/{movie.dvdid}' resp = await self.client.get(url) - tree = html.fromstring(resp.content) + tree = html.fromstring(await resp.text()) # 疑似JavBus检测到类似爬虫的行为时会要求登录,不过发现目前不需要登录也可以从重定向前的网页中提取信息 # 引入登录验证后状态码不再准确,因此还要额外通过检测标题来确认是否发生了404 page_title = tree.xpath('/html/head/title/text()') diff --git a/javsp/crawlers/sites/javdb.py b/javsp/crawlers/sites/javdb.py index ab23e18bd..d101e2c4e 100644 --- a/javsp/crawlers/sites/javdb.py +++ b/javsp/crawlers/sites/javdb.py @@ -2,8 +2,7 @@ import os import re import logging - -from httpx import Cookies +from typing import Dict from javsp.func import * from javsp.avid import guess_av_type @@ -13,7 +12,7 @@ from javsp.crawlers.exceptions import CredentialError, MovieDuplicateError, MovieNotFoundError, SiteBlocked, SitePermissionError, WebsiteError from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from lxml import html @@ -23,23 +22,25 @@ class JavDbCrawler(Crawler): id = CrawlerID.javdb genre_map: GenreMap - cookies_pool: list[Cookies] + cookies_pool: list + headers: Dict[str, str] @classmethod async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.javdb.com') self.base_url = str(url) - self.client = get_client(url) - self.client.headers['Accept-Language'] = 'zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6,ja;q=0.5' + self.client = get_session(url) + self.headers = {'Accept-Language': 'zh-CN,zh;q=0.9,zh-TW;q=0.8,en-US;q=0.7,en;q=0.6,ja;q=0.5'} self.genre_map = GenreMap('data/genre_javdb.csv') self.cookies_pool = [] return self async def get_html_wrapper(self, url: str): """包装外发的request请求并负责转换为可xpath的html,同时处理Cookies无效等问题""" + r = await self.client.get(url) - if r.status_code == 200: + if r.status == 200: # 发生重定向可能仅仅是域名重定向,因此还要检查url以判断是否被跳转到了登录页 if r.history and '/login' in str(r.url): # 仅在需要时去读取Cookies @@ -48,14 +49,14 @@ async def get_html_wrapper(self, url: str): self.cookies_pool = get_browsers_cookies() except (PermissionError, OSError) as e: logger.warning(f"无法从浏览器Cookies文件获取JavDB的登录凭据({e}),可能是安全软件在保护浏览器Cookies文件", exc_info=True) - cookies_pool = [] + self.cookies_pool = [] except Exception as e: logger.warning(f"获取JavDB的登录凭据时出错({e}),你可能使用的是国内定制版等非官方Chrome系浏览器", exc_info=True) - cookies_pool = [] + self.cookies_pool = [] if len(self.cookies_pool) > 0: item = self.cookies_pool.pop() # 更换Cookies时需要创建新的request实例,否则cloudscraper会保留它内部第一次发起网络访问时获得的Cookies - self.client.cookies = item['cookies'] + self.client.cookie_jar.update_cookies = item['cookies'] cookies_source = (item['profile'], item['site']) logger.debug(f'未携带有效Cookies而发生重定向,尝试更换Cookies为: {cookies_source}') return self.get_html_wrapper(url) @@ -65,30 +66,30 @@ async def get_html_wrapper(self, url: str): raise SitePermissionError(f"JavDB: 此资源被限制为仅VIP可见: '{r.history[0].url}'") else: - return html.fromstring(r.text) - elif r.status_code in (403, 503): - tree = html.fromstring(r.text) + return html.fromstring(await r.text()) + elif r.status in (403, 503): + tree = html.fromstring(await r.text()) code_tag = tree.xpath("//span[@class='code-label']/span") error_code = code_tag[0].text if code_tag else None if error_code: if error_code == '1020': - block_msg = f'JavDB: {r.status_code} 禁止访问: 站点屏蔽了来自日本地区的IP地址,请使用其他地区的代理服务器' + block_msg = f'JavDB: {r.status} 禁止访问: 站点屏蔽了来自日本地区的IP地址,请使用其他地区的代理服务器' else: - block_msg = f'JavDB: {r.status_code} 禁止访问: {url} (Error code: {error_code})' + block_msg = f'JavDB: {r.status} 禁止访问: {url} (Error code: {error_code})' else: - block_msg = f'JavDB: {r.status_code} 禁止访问: {url}' + block_msg = f'JavDB: {r.status} 禁止访问: {url}' raise SiteBlocked(block_msg) else: - raise WebsiteError(f'JavDB: {r.status_code} 非预期状态码: {url}') + raise WebsiteError(f'JavDB: {r.status} 非预期状态码: {url}') - async def get_user_info(self, site: str, cookies: Cookies): + async def get_user_info(self, site: str, cookies): """获取cookies对应的JavDB用户信息""" try: self.client.cookies = cookies resp = await self.client.get(f'https://{site}/users/profile') - html_str = resp.text + html_str = await resp.text() tree = html.fromstring(html_str) except Exception as e: logger.info('JavDB: 获取用户信息时出错') @@ -130,7 +131,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: index = ids.index(movie.dvdid.lower()) new_url = movie_urls[index] try: - html2 = await self.get_html_wrapper(new_url) + html2 = await self.get_html_wrapper(self.base_url + new_url) except (SitePermissionError, CredentialError): # 不开VIP不让看,过分。决定榨出能获得的信息,毕竟有时候只有这里能找到标题和封面 box = tree.xpath("//a[@class='box']")[index] @@ -219,7 +220,7 @@ async def crawl_and_fill_cleaned(self, movie: MovieInfo): # 检查封面URL是否真的存在对应图片 if movie.cover is not None: r = await self.client.head(movie.cover) - if r.status_code != 200: + if r.status != 200: movie.cover = None except SiteBlocked: raise @@ -260,7 +261,7 @@ async def collect_actress_alias(self, type=0, use_original=True): count += 1 actor_name = actor.xpath("strong/text()")[0].strip() actor_url = actor.xpath("@href")[0] - # actor_url = f"https://javdb.com{actor_url}" # 构造演员主页的完整URL + actor_url = self.base_url + actor_url # 构造演员主页的完整URL # 进入演员主页,获取更多信息 actor_html = await self.get_html_wrapper(actor_url) @@ -338,6 +339,7 @@ async def collect_actress_alias(self, type=0, use_original=True): if __name__ == "__main__": async def test_main(): + # breakpoint() crawler = await JavDbCrawler.create() movie = MovieInfo('FC2-2735981') try: diff --git a/javsp/crawlers/sites/javlib.py b/javsp/crawlers/sites/javlib.py index c71a5f336..3832acbea 100644 --- a/javsp/crawlers/sites/javlib.py +++ b/javsp/crawlers/sites/javlib.py @@ -1,4 +1,7 @@ """从JavLibrary抓取数据""" + +# BUG: This crawler doesn't work, seemed due to cloudflare + import logging from urllib.parse import urlsplit @@ -7,7 +10,7 @@ from javsp.crawlers.exceptions import MovieDuplicateError, MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -15,14 +18,14 @@ logger = logging.getLogger(__name__) class JavLibCrawler(Crawler): - id = CrawlerID.jav321 + id = CrawlerID.javlib @classmethod async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.javlibrary.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self # TODO: 发现JavLibrary支持使用cid搜索,会直接跳转到对应的影片页面,也许可以利用这个功能来做cid到dvdid的转换 @@ -30,7 +33,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: """解析指定番号的影片数据""" url = new_url = f'{self.base_url}/cn/vl_searchbyid.php?keyword={movie.dvdid}' resp = await self.client.get(url) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) if resp.history and urlsplit(str(resp.url)).netloc == urlsplit(self.base_url).netloc: # 出现301重定向通常且新老地址netloc相同时,说明搜索到了影片且只有一个结果 new_url = resp.url @@ -65,7 +68,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: raise MovieDuplicateError(__name__, movie.dvdid, match_count, pre_choose_urls) # 重新抓取网页 resp = await self.client.get(new_url) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) container = tree.xpath("/html/body/div/div[@id='rightcolumn']")[0] title_tag = container.xpath("div/h3/a/text()") title = title_tag[0] diff --git a/javsp/crawlers/sites/javmenu.py b/javsp/crawlers/sites/javmenu.py index 6553d86a1..15ea78c0c 100644 --- a/javsp/crawlers/sites/javmenu.py +++ b/javsp/crawlers/sites/javmenu.py @@ -4,7 +4,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -19,7 +19,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.javmenu.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -34,7 +34,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: # 被重定向到主页说明找不到影片资源 raise MovieNotFoundError(__name__, movie.dvdid) - tree = html.fromstring(r.text) + tree = html.fromstring(await r.text()) container = tree.xpath("//div[@class='col-md-9 px-0']")[0] title = container.xpath("div[@class='col-12 mb-3']/h1/strong/text()")[0] # 竟然还在标题里插广告,真的疯了。要不是我已经写了抓取器,才懒得维护这个破站 diff --git a/javsp/crawlers/sites/mgstage.py b/javsp/crawlers/sites/mgstage.py index bd9d76840..a352470bf 100644 --- a/javsp/crawlers/sites/mgstage.py +++ b/javsp/crawlers/sites/mgstage.py @@ -6,7 +6,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError, SiteBlocked from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import Cfg, CrawlerID from lxml import html @@ -22,22 +22,22 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.mgstage.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) # 初始化Request实例(要求携带已通过R18认证的cookies,否则会被重定向到认证页面) - self.client.cookies = {'adc': '1'} + self.client.cookie_jar.update_cookies({'adc': '1'}) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: """解析指定番号的影片数据""" url = f'{self.base_url}/product/product_detail/{movie.dvdid}/' resp = await self.client.get(url) - if resp.status_code == 403: + if resp.status == 403: raise SiteBlocked('mgstage不允许从当前IP所在地区访问,请尝试更换为日本地区代理') # url不存在时会被重定向至主页。history非空时说明发生了重定向 elif resp.history: raise MovieNotFoundError(__name__, movie.dvdid) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) # mgstage的文本中含有大量的空白字符('\n \t'),需要使用strip去除 title = tree.xpath("//div[@class='common_detail_cover']/h1/text()")[0].strip() container = tree.xpath("//div[@class='detail_left']")[0] @@ -93,7 +93,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: video_pid = btn_url.split('/')[-1] req_url = f'{self.base_url}/sampleplayer/sampleRespons.php?pid={video_pid}' resp = await self.client.get(req_url) - j = resp.json() + j = await resp.json() video_url = j.get('url') if video_url: # /sample/shirouto/siro/3093/SIRO-3093_sample.ism/request?uid=XXX&pid=XXX diff --git a/javsp/crawlers/sites/njav.py b/javsp/crawlers/sites/njav.py index 5787397c9..72826db02 100644 --- a/javsp/crawlers/sites/njav.py +++ b/javsp/crawlers/sites/njav.py @@ -6,7 +6,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from javsp.lib import strftime_to_minutes @@ -26,7 +26,7 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.njav.tv/') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) return self async def search_video(self, movie: MovieInfo) -> str: @@ -34,7 +34,7 @@ async def search_video(self, movie: MovieInfo) -> str: # 抓取网页 url = f'{self.base_url}ja/search?keyword={id_uc}' resp = await self.client.get(url) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) list = tree.xpath("//div[@class='box-item']/div[@class='detail']/a") video_url = None for item in list: @@ -57,7 +57,7 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: if not url: raise MovieNotFoundError(__name__, movie.dvdid) resp = await self.client.get(url) - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) container = tree.xpath("//div[@class='container']/div/div[@class='col']") if len(container) > 0: container = container[0] diff --git a/javsp/crawlers/sites/prestige.py b/javsp/crawlers/sites/prestige.py index bc0734554..5d0d4c9bb 100644 --- a/javsp/crawlers/sites/prestige.py +++ b/javsp/crawlers/sites/prestige.py @@ -7,7 +7,7 @@ from javsp.crawlers.exceptions import MovieNotFoundError, SiteBlocked from javsp.datatype import MovieInfo from javsp.network.utils import resolve_site_fallback -from javsp.network.client import get_client +from javsp.network.client import get_session from javsp.crawlers.interface import Crawler from javsp.config import CrawlerID from lxml import html @@ -24,10 +24,10 @@ async def create(cls): self = cls() url = await resolve_site_fallback(self.id, 'https://www.prestige-av.com') self.base_url = str(url) - self.client = get_client(url) + self.client = get_session(url) # prestige要求访问者携带已通过R18认证的cookies才能够获得完整数据,否则会被重定向到认证页面 # (其他多数网站的R18认证只是在网页上遮了一层,完整数据已经传回,不影响爬虫爬取) - self.client.cookies = {'__age_auth__': 'true'} + self.client.cookie_jar.update_cookies({'__age_auth__': 'true'}) return self async def crawl_and_fill(self, movie: MovieInfo) -> None: @@ -37,13 +37,13 @@ async def crawl_and_fill(self, movie: MovieInfo) -> None: """ url = f'{self.base_url}/goods/goods_detail.php?sku={movie.dvdid}' resp = await self.client.get(url) - if resp.status_code == 500: + if resp.status == 500: # 500错误表明prestige没有这部影片的数据,不是网络问题,因此不再重试 raise MovieNotFoundError(__name__, movie.dvdid) - elif resp.status_code == 403: + elif resp.status == 403: raise SiteBlocked('prestige不允许从当前IP所在地区访问,请尝试更换为日本地区代理') resp.raise_for_status() - tree = html.fromstring(resp.text) + tree = html.fromstring(await resp.text()) container_tags = tree.xpath("//section[@class='px-4 mb-4 md:px-8 md:mb-16']") if not container_tags: raise MovieNotFoundError(__name__, movie.dvdid) diff --git a/javsp/func.py b/javsp/func.py index 6232747fd..d10ba8222 100644 --- a/javsp/func.py +++ b/javsp/func.py @@ -25,7 +25,7 @@ except ImportError: USE_GUI = False -from javsp.network.utils import get_client, url_download +from javsp.network.utils import get_session, url_download from javsp.lib import re_escape, resource_path @@ -183,9 +183,9 @@ def print_header(title, info=[]): release_url = 'https://github.com/Yuukiy/JavSP/releases/latest' print('正在检查更新...', end='') try: - client = get_client(Url(api_url)) + client = get_session(Url(api_url)) resp = await client.get(api_url) - data = resp.json() + data = await resp.json() latest_version = data['tag_name'] release_time = utc2local(data['published_at']) release_date = release_time.isoformat().split('T')[0] diff --git a/javsp/network/client.py b/javsp/network/client.py index 33232b677..981afeee4 100644 --- a/javsp/network/client.py +++ b/javsp/network/client.py @@ -1,11 +1,12 @@ """网络请求的统一接口""" -from typing import Dict +from typing import Any, Coroutine, Dict from pydantic_core import Url -from httpx import AsyncClient, AsyncHTTPTransport - from javsp.config import Cfg +from aiohttp import BaseConnector, ClientSession, TCPConnector +from aiohttp_socks import ProxyConnector +import asyncio default_headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' @@ -17,29 +18,44 @@ def get_proxy(unproxied: bool): else: return str(Cfg().network.proxy_server) -client_dictionary: Dict[str, AsyncClient] = {} -def get_client(url: Url) -> AsyncClient: +session_dictionary: Dict[str, ClientSession] = {} +proxy_connector: BaseConnector | None = None +def get_session(url: Url) -> ClientSession: if url.host is None: raise Exception(f"Unknown url {url}") else: index = url.host - if index in client_dictionary: - return client_dictionary[index] + if index in session_dictionary: + return session_dictionary[index] else: - unproxied = url.host in Cfg().network.unproxied + proxy = get_proxy(url.host in Cfg().network.unproxied) + - transport = AsyncHTTPTransport( - proxy=get_proxy(unproxied), - retries=Cfg().network.retries) + connector: BaseConnector + if proxy is None: + connector = TCPConnector() + else: + global proxy_connector + if proxy_connector is None: + proxy_connector = ProxyConnector.from_url(proxy) + connector = proxy_connector - client = AsyncClient( - transport=transport, + session = ClientSession( + connector=connector, # 必须使用copy(),否则各个模块对headers的修改都将会指向本模块中定义的headers变量,导致只有最后一个对headers的修改生效 - headers=default_headers.copy(), - timeout=Cfg().network.timeout.total_seconds(), - follow_redirects=True, - ) + headers=default_headers.copy()) + + + session_dictionary[index] = session + + return session + +async def clear_clients(): + close_tasks: list[Coroutine[Any, Any, None]] = [] + for client in session_dictionary.values(): + close_tasks.append(client.close()) - client_dictionary[index] = client + await asyncio.gather(*close_tasks) - return client + if proxy_connector is not None: + await proxy_connector.close() diff --git a/javsp/network/utils.py b/javsp/network/utils.py index 34caf68da..00e379098 100644 --- a/javsp/network/utils.py +++ b/javsp/network/utils.py @@ -1,17 +1,17 @@ from datetime import timedelta import logging import time +from aiohttp import ClientTimeout from tqdm.asyncio import tqdm from typing import Any, Coroutine, NamedTuple import aiofiles -from pretty_errors import os from pydantic.types import ByteSize from pydantic_core import Url from pydantic_extra_types.pendulum_dt import Duration from javsp.config import Cfg, CrawlerID -from javsp.network.client import get_client +from javsp.network.client import get_session, clear_clients import asyncio @@ -28,46 +28,37 @@ def get_rate(self) -> float: async def url_download(url: Url, target_path: str, desc: str | None = None) -> DownloadInfo: url_str = str(url) - if url.scheme == 'file': - path: str = url.path - start_time: float = time.time() - async with aiofiles.open(path, "rb") as src: - async with aiofiles.open(target_path, "wb") as dest: - await dest.write(await src.read()) - filesize = os.path.getsize(path) - elapsed = time.time() - start_time - return DownloadInfo(ByteSize(filesize), Duration(seconds=elapsed)) - if not desc: desc = url_str.split('/')[-1] - client = get_client(url) + s = get_session(url) # REF: https://www.python-httpx.org/advanced/clients/#monitoring-download-progress async with aiofiles.open(target_path, 'wb') as download_file: # NOTE: Create a client for each request for now, need further refactor - async with client.stream("GET", url_str) as response: - total = int(response.headers["Content-Length"]) + + start = time.monotonic() + async with s.get(url_str) as response: + total = response.content_length with tqdm(total=total, unit_scale=True, unit_divisor=1024, unit="B") as progress: - num_bytes_downloaded = response.num_bytes_downloaded - for chunk in response.iter_bytes(): + async for chunk in response.content.iter_any(): await download_file.write(chunk) - progress.update(response.num_bytes_downloaded - num_bytes_downloaded) - num_bytes_downloaded = response.num_bytes_downloaded + progress.update(len(chunk)) - return DownloadInfo(ByteSize(response.num_bytes_downloaded), response.elapsed) + response_time = time.monotonic() - start + return DownloadInfo(ByteSize(total), timedelta(seconds=response_time)) async def test_connect(url_str: str, timeout: Duration) -> bool: """测试与指定url的连接,不使用映射,但使用代理""" try: - client = get_client(Url(url_str)) + s = get_session(Url(url_str)) response = \ - await client.get( + await s.get( url_str, - timeout=timeout.total_seconds(), + timeout=ClientTimeout(total=timeout.total_seconds()), ) - return response.status_code == 200 + return response.status == 200 except Exception as e: logger.debug(f"Not connectable: {url_str}\n" + repr(e)) return False @@ -98,8 +89,17 @@ async def resolve_site_fallback(cr_id: CrawlerID, default: str) -> Url: if __name__ == '__main__': async def aentry(): print(await choose_one_connectable(['http://iandown.what', 'http://www.baidu.com'])) + from javsp.network.client import clear_clients + await clear_clients() # async def aentry(): # print(await test_connect("https://www.y78k.com/", Duration(seconds=3))) + # async def aentry(): + # await asyncio.gather( + # url_download(Url('https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png'), 'gogle_logo.png'), + # url_download(Url('https://ei.phncdn.com/www-static/images/pornhub_logo_straight.svg?cache=2024092501'), 'pornhub_logo.svg'), + # ) + # await clear_clients() + asyncio.run(aentry()) diff --git a/javsp/translate.py b/javsp/translate.py index 1f202209a..66b8cb161 100644 --- a/javsp/translate.py +++ b/javsp/translate.py @@ -1,21 +1,19 @@ """网页翻译接口""" # 由于翻译服务不走代理,而且需要自己的错误处理机制,因此不通过base.py来管理网络请求 import time -from typing import Union import uuid import random import logging from pydantic_core import Url -import httpx from hashlib import md5 __all__ = ['translate', 'translate_movie_info'] -from javsp.config import BaiduTranslateEngine, BingTranslateEngine, Cfg, ClaudeTranslateEngine, GoogleTranslateEngine, OpenAITranslateEngine, TranslateEngine +from javsp.config import Cfg, TranslateEngine from javsp.datatype import MovieInfo -from javsp.network.client import get_proxy +from javsp.network.client import get_session logger = logging.getLogger(__name__) @@ -126,7 +124,7 @@ def translate(texts, engine: TranslateEngine, actress=[]): else: return {'trans': texts} -def baidu_translate(texts, app_id, api_key, to='zh'): +async def baidu_translate(texts, app_id, api_key, to='zh'): """使用百度翻译文本(默认翻译为简体中文)""" api_url = "https://api.fanyi.baidu.com/api/trans/vip/translate" headers = {'Content-Type': 'application/x-www-form-urlencoded'} @@ -140,13 +138,14 @@ def baidu_translate(texts, app_id, api_key, to='zh'): wait = 1.0 - (now - last_access) if wait > 0: time.sleep(wait) - r = httpx.post(api_url, params=payload, headers=headers) - result = r.json() + s = get_session(Url(api_url)) + r = await s.post(api_url, params=payload, headers=headers) + result = await r.json() baidu_translate._last_access = time.perf_counter() return result -def bing_translate(texts, api_key, to='zh-Hans'): +async def bing_translate(texts, api_key, to='zh-Hans'): """使用Bing翻译文本(默认翻译为简体中文)""" api_url = "https://api.cognitive.microsofttranslator.com/translate" params = {'api-version': '3.0', 'to': to, 'includeSentenceLength': True} @@ -157,34 +156,36 @@ def bing_translate(texts, api_key, to='zh-Hans'): 'X-ClientTraceId': str(uuid.uuid4()) } body = [{'text': texts}] - r = httpx.post(api_url, params=params, headers=headers, json=body) - result = r.json() + s = get_session(Url(api_url)) + r = await s.post(api_url, params=params, headers=headers, json=body) + result = await r.json() return result _google_trans_wait = 60 -def google_trans(texts, to='zh_CN'): +async def google_trans(texts, to='zh_CN'): """使用Google翻译文本(默认翻译为简体中文)""" # API: https://www.jianshu.com/p/ce35d89c25c3 # client参数的选择: https://github.com/lmk123/crx-selection-translate/issues/223#issue-184432017 global _google_trans_wait url = f"https://translate.google.com.hk/translate_a/single?client=gtx&dt=t&dj=1&ie=UTF-8&sl=auto&tl={to}&q={texts}" - proxies = get_proxy(False) - r = httpx.get(url, proxies=proxies) - while r.status_code == 429: - logger.warning(f"HTTP {r.status_code}: {r.reason}: Google翻译请求超限,将等待{_google_trans_wait}秒后重试") + s = get_session(Url(url)) + r = await s.get(url) + # TODO: retry已经集成到client里了,这里考虑删除 + while r.status == 429: + logger.warning(f"HTTP {r.status}: {r.reason}: Google翻译请求超限,将等待{_google_trans_wait}秒后重试") time.sleep(_google_trans_wait) - r = httpx.get(url, proxies=proxies) - if r.status_code == 429: + r = await client.get(url) + if r.status == 429: _google_trans_wait += random.randint(60, 90) - if r.status_code == 200: - result = r.json() + if r.status == 200: + result = await r.json() else: - result = {'error_code': r.status_code, 'error_msg': r.reason} + result = {'error_code': r.status, 'error_msg': r.reason} time.sleep(4) # Google翻译的API有QPS限制,因此需要等待一段时间 return result -def claude_translate(texts, api_key, to="zh_CN"): +async def claude_translate(texts, api_key, to="zh_CN"): """使用Claude翻译文本(默认翻译为简体中文)""" api_url = "https://api.anthropic.com/v1/messages" headers = { @@ -198,17 +199,20 @@ def claude_translate(texts, api_key, to="zh_CN"): "max_tokens": 1024, "messages": [{"role": "user", "content": texts}], } - r = httpx.post(api_url, headers=headers, json=data) - if r.status_code == 200: - result = r.json().get("content", [{}])[0].get("text", "").strip() + + s = get_session(Url(api_url)) + r = await s.post(api_url, headers=headers, json=data) + j = await r.json() + if r.status == 200: + result = j.get("content", [{}])[0].get("text", "").strip() else: result = { - "error_code": r.status_code, - "error_msg": r.json().get("error", {}).get("message", r.reason), + "error_code": r.status, + "error_msg": j.get("error", {}).get("message", r.reason), } return result -def openai_translate(texts, url: Url, api_key: str, model: str, to="zh_CN"): +async def openai_translate(texts, url: Url, api_key: str, model: str, to="zh_CN"): """使用 OpenAI 翻译文本(默认翻译为简体中文)""" api_url = str(url) headers = { @@ -230,18 +234,20 @@ def openai_translate(texts, url: Url, api_key: str, model: str, to="zh_CN"): "temperature": 0, "max_tokens": 1024, } - r = httpx.post(api_url, headers=headers, json=data) - if r.status_code == 200: - if 'error' in r.json(): + s = get_session(Url(api_url)) + r = await s.post(api_url, headers=headers, json=data) + if r.status == 200: + j = await r.json() + if 'error' in j: result = { - "error_code": r.status_code, - "error_msg": r.json().get("error", {}).get("message", ""), + "error_code": r.status, + "error_msg": j.get("error", {}).get("message", ""), } else: - result = r.json().get("choices", [{}])[0].get("message", {}).get("content", "").strip() + result = j.get("choices", [{}])[0].get("message", {}).get("content", "").strip() else: result = { - "error_code": r.status_code, + "error_code": r.status, "error_msg": r.reason, } return result diff --git a/poetry.lock b/poetry.lock index f9b1b8d77..5d679f751 100644 --- a/poetry.lock +++ b/poetry.lock @@ -16,6 +16,178 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "aiohappyeyeballs" +version = "2.4.2" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.2-py3-none-any.whl", hash = "sha256:8522691d9a154ba1145b157d6d5c15e5c692527ce6a53c5e5f9876977f6dab2f"}, + {file = "aiohappyeyeballs-2.4.2.tar.gz", hash = "sha256:4ca893e6c5c1f5bf3888b04cb5a3bee24995398efef6e0b9f747b5e89d84fd74"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + +[[package]] +name = "aiohttp" +version = "3.10.8" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a1ba7bc139592339ddeb62c06486d0fa0f4ca61216e14137a40d626c81faf10c"}, + {file = "aiohttp-3.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85e4d7bd05d18e4b348441e7584c681eff646e3bf38f68b2626807f3add21aa2"}, + {file = "aiohttp-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:69de056022e7abf69cb9fec795515973cc3eeaff51e3ea8d72a77aa933a91c52"}, + {file = "aiohttp-3.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee3587506898d4a404b33bd19689286ccf226c3d44d7a73670c8498cd688e42c"}, + {file = "aiohttp-3.10.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe285a697c851734285369614443451462ce78aac2b77db23567507484b1dc6f"}, + {file = "aiohttp-3.10.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10c7932337285a6bfa3a5fe1fd4da90b66ebfd9d0cbd1544402e1202eb9a8c3e"}, + {file = "aiohttp-3.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd9716ef0224fe0d0336997eb242f40619f9f8c5c57e66b525a1ebf9f1d8cebe"}, + {file = "aiohttp-3.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ceacea31f8a55cdba02bc72c93eb2e1b77160e91f8abd605969c168502fd71eb"}, + {file = "aiohttp-3.10.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9721554bfa9e15f6e462da304374c2f1baede3cb06008c36c47fa37ea32f1dc4"}, + {file = "aiohttp-3.10.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:22cdeb684d8552490dd2697a5138c4ecb46f844892df437aaf94f7eea99af879"}, + {file = "aiohttp-3.10.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e56bb7e31c4bc79956b866163170bc89fd619e0581ce813330d4ea46921a4881"}, + {file = "aiohttp-3.10.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3a95d2686bc4794d66bd8de654e41b5339fab542b2bca9238aa63ed5f4f2ce82"}, + {file = "aiohttp-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d82404a0e7b10e0d7f022cf44031b78af8a4f99bd01561ac68f7c24772fed021"}, + {file = "aiohttp-3.10.8-cp310-cp310-win32.whl", hash = "sha256:4e10b04542d27e21538e670156e88766543692a0a883f243ba8fad9ddea82e53"}, + {file = "aiohttp-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:680dbcff5adc7f696ccf8bf671d38366a1f620b5616a1d333d0cb33956065395"}, + {file = "aiohttp-3.10.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:33a68011a38020ed4ff41ae0dbf4a96a202562ecf2024bdd8f65385f1d07f6ef"}, + {file = "aiohttp-3.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c7efa6616a95e3bd73b8a69691012d2ef1f95f9ea0189e42f338fae080c2fc6"}, + {file = "aiohttp-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ddb9b9764cfb4459acf01c02d2a59d3e5066b06a846a364fd1749aa168efa2be"}, + {file = "aiohttp-3.10.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7f270f4ca92760f98a42c45a58674fff488e23b144ec80b1cc6fa2effed377"}, + {file = "aiohttp-3.10.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6984dda9d79064361ab58d03f6c1e793ea845c6cfa89ffe1a7b9bb400dfd56bd"}, + {file = "aiohttp-3.10.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3f6d47e392c27206701565c8df4cac6ebed28fdf6dcaea5b1eea7a4631d8e6db"}, + {file = "aiohttp-3.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a72f89aea712c619b2ca32c6f4335c77125ede27530ad9705f4f349357833695"}, + {file = "aiohttp-3.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36074b26f3263879ba8e4dbd33db2b79874a3392f403a70b772701363148b9f"}, + {file = "aiohttp-3.10.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e32148b4a745e70a255a1d44b5664de1f2e24fcefb98a75b60c83b9e260ddb5b"}, + {file = "aiohttp-3.10.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5aa1a073514cf59c81ad49a4ed9b5d72b2433638cd53160fd2f3a9cfa94718db"}, + {file = "aiohttp-3.10.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3a79200a9d5e621c4623081ddb25380b713c8cf5233cd11c1aabad990bb9381"}, + {file = "aiohttp-3.10.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e45fdfcb2d5bcad83373e4808825b7512953146d147488114575780640665027"}, + {file = "aiohttp-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f78e2a78432c537ae876a93013b7bc0027ba5b93ad7b3463624c4b6906489332"}, + {file = "aiohttp-3.10.8-cp311-cp311-win32.whl", hash = "sha256:f8179855a4e4f3b931cb1764ec87673d3fbdcca2af496c8d30567d7b034a13db"}, + {file = "aiohttp-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:ef9b484604af05ca745b6108ca1aaa22ae1919037ae4f93aaf9a37ba42e0b835"}, + {file = "aiohttp-3.10.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab2d6523575fc98896c80f49ac99e849c0b0e69cc80bf864eed6af2ae728a52b"}, + {file = "aiohttp-3.10.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f5d5d5401744dda50b943d8764508d0e60cc2d3305ac1e6420935861a9d544bc"}, + {file = "aiohttp-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de23085cf90911600ace512e909114385026b16324fa203cc74c81f21fd3276a"}, + {file = "aiohttp-3.10.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4618f0d2bf523043866a9ff8458900d8eb0a6d4018f251dae98e5f1fb699f3a8"}, + {file = "aiohttp-3.10.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21c1925541ca84f7b5e0df361c0a813a7d6a56d3b0030ebd4b220b8d232015f9"}, + {file = "aiohttp-3.10.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:497a7d20caea8855c5429db3cdb829385467217d7feb86952a6107e033e031b9"}, + {file = "aiohttp-3.10.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c887019dbcb4af58a091a45ccf376fffe800b5531b45c1efccda4bedf87747ea"}, + {file = "aiohttp-3.10.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40d2d719c3c36a7a65ed26400e2b45b2d9ed7edf498f4df38b2ae130f25a0d01"}, + {file = "aiohttp-3.10.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57359785f27394a8bcab0da6dcd46706d087dfebf59a8d0ad2e64a4bc2f6f94f"}, + {file = "aiohttp-3.10.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a961ee6f2cdd1a2be4735333ab284691180d40bad48f97bb598841bfcbfb94ec"}, + {file = "aiohttp-3.10.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe3d79d6af839ffa46fdc5d2cf34295390894471e9875050eafa584cb781508d"}, + {file = "aiohttp-3.10.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a281cba03bdaa341c70b7551b2256a88d45eead149f48b75a96d41128c240b3"}, + {file = "aiohttp-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6769d71bfb1ed60321363a9bc05e94dcf05e38295ef41d46ac08919e5b00d19"}, + {file = "aiohttp-3.10.8-cp312-cp312-win32.whl", hash = "sha256:a3081246bab4d419697ee45e555cef5cd1def7ac193dff6f50be761d2e44f194"}, + {file = "aiohttp-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:ab1546fc8e00676febc81c548a876c7bde32f881b8334b77f84719ab2c7d28dc"}, + {file = "aiohttp-3.10.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b1a012677b8e0a39e181e218de47d6741c5922202e3b0b65e412e2ce47c39337"}, + {file = "aiohttp-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2df786c96c57cd6b87156ba4c5f166af7b88f3fc05f9d592252fdc83d8615a3c"}, + {file = "aiohttp-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8885ca09d3a9317219c0831276bfe26984b17b2c37b7bf70dd478d17092a4772"}, + {file = "aiohttp-3.10.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dbf252ac19860e0ab56cd480d2805498f47c5a2d04f5995d8d8a6effd04b48c"}, + {file = "aiohttp-3.10.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2036479b6b94afaaca7d07b8a68dc0e67b0caf5f6293bb6a5a1825f5923000"}, + {file = "aiohttp-3.10.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:365783e1b7c40b59ed4ce2b5a7491bae48f41cd2c30d52647a5b1ee8604c68ad"}, + {file = "aiohttp-3.10.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:270e653b5a4b557476a1ed40e6b6ce82f331aab669620d7c95c658ef976c9c5e"}, + {file = "aiohttp-3.10.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8960fabc20bfe4fafb941067cda8e23c8c17c98c121aa31c7bf0cdab11b07842"}, + {file = "aiohttp-3.10.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f21e8f2abed9a44afc3d15bba22e0dfc71e5fa859bea916e42354c16102b036f"}, + {file = "aiohttp-3.10.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fecd55e7418fabd297fd836e65cbd6371aa4035a264998a091bbf13f94d9c44d"}, + {file = "aiohttp-3.10.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:badb51d851358cd7535b647bb67af4854b64f3c85f0d089c737f75504d5910ec"}, + {file = "aiohttp-3.10.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e860985f30f3a015979e63e7ba1a391526cdac1b22b7b332579df7867848e255"}, + {file = "aiohttp-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:71462f8eeca477cbc0c9700a9464e3f75f59068aed5e9d4a521a103692da72dc"}, + {file = "aiohttp-3.10.8-cp313-cp313-win32.whl", hash = "sha256:177126e971782769b34933e94fddd1089cef0fe6b82fee8a885e539f5b0f0c6a"}, + {file = "aiohttp-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:98a4eb60e27033dee9593814ca320ee8c199489fbc6b2699d0f710584db7feb7"}, + {file = "aiohttp-3.10.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ffef3d763e4c8fc97e740da5b4d0f080b78630a3914f4e772a122bbfa608c1db"}, + {file = "aiohttp-3.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:597128cb7bc5f068181b49a732961f46cb89f85686206289d6ccb5e27cb5fbe2"}, + {file = "aiohttp-3.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f23a6c1d09de5de89a33c9e9b229106cb70dcfdd55e81a3a3580eaadaa32bc92"}, + {file = "aiohttp-3.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da57af0c54a302b7c655fa1ccd5b1817a53739afa39924ef1816e7b7c8a07ccb"}, + {file = "aiohttp-3.10.8-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7a6af57091056a79a35104d6ec29d98ec7f1fb7270ad9c6fff871b678d1ff8"}, + {file = "aiohttp-3.10.8-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32710d6b3b6c09c60c794d84ca887a3a2890131c0b02b3cefdcc6709a2260a7c"}, + {file = "aiohttp-3.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b91f4f62ad39a8a42d511d66269b46cb2fb7dea9564c21ab6c56a642d28bff5"}, + {file = "aiohttp-3.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:471a8c47344b9cc309558b3fcc469bd2c12b49322b4b31eb386c4a2b2d44e44a"}, + {file = "aiohttp-3.10.8-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc0e7f91705445d79beafba9bb3057dd50830e40fe5417017a76a214af54e122"}, + {file = "aiohttp-3.10.8-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:85431c9131a9a0f65260dc7a65c800ca5eae78c4c9931618f18c8e0933a0e0c1"}, + {file = "aiohttp-3.10.8-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b91557ee0893da52794b25660d4f57bb519bcad8b7df301acd3898f7197c5d81"}, + {file = "aiohttp-3.10.8-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:4954e6b06dd0be97e1a5751fc606be1f9edbdc553c5d9b57d72406a8fbd17f9d"}, + {file = "aiohttp-3.10.8-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a087c84b4992160ffef7afd98ef24177c8bd4ad61c53607145a8377457385100"}, + {file = "aiohttp-3.10.8-cp38-cp38-win32.whl", hash = "sha256:e1f0f7b27171b2956a27bd8f899751d0866ddabdd05cbddf3520f945130a908c"}, + {file = "aiohttp-3.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:c4916070e12ae140110aa598031876c1bf8676a36a750716ea0aa5bd694aa2e7"}, + {file = "aiohttp-3.10.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5284997e3d88d0dfb874c43e51ae8f4a6f4ca5b90dcf22995035187253d430db"}, + {file = "aiohttp-3.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9443d9ebc5167ce1fbb552faf2d666fb22ef5716a8750be67efd140a7733738c"}, + {file = "aiohttp-3.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b667e2a03407d79a76c618dc30cedebd48f082d85880d0c9c4ec2faa3e10f43e"}, + {file = "aiohttp-3.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98fae99d5c2146f254b7806001498e6f9ffb0e330de55a35e72feb7cb2fa399b"}, + {file = "aiohttp-3.10.8-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8296edd99d0dd9d0eb8b9e25b3b3506eef55c1854e9cc230f0b3f885f680410b"}, + {file = "aiohttp-3.10.8-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ce46dfb49cfbf9e92818be4b761d4042230b1f0e05ffec0aad15b3eb162b905"}, + {file = "aiohttp-3.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c38cfd355fd86c39b2d54651bd6ed7d63d4fe3b5553f364bae3306e2445f847"}, + {file = "aiohttp-3.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:713dff3f87ceec3bde4f3f484861464e722cf7533f9fa6b824ec82bb5a9010a7"}, + {file = "aiohttp-3.10.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:21a72f4a9c69a8567a0aca12042f12bba25d3139fd5dd8eeb9931f4d9e8599cd"}, + {file = "aiohttp-3.10.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6d1ad868624f6cea77341ef2877ad4e71f7116834a6cd7ec36ec5c32f94ee6ae"}, + {file = "aiohttp-3.10.8-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a78ba86d5a08207d1d1ad10b97aed6ea48b374b3f6831d02d0b06545ac0f181e"}, + {file = "aiohttp-3.10.8-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:aff048793d05e1ce05b62e49dccf81fe52719a13f4861530706619506224992b"}, + {file = "aiohttp-3.10.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d088ca05381fd409793571d8e34eca06daf41c8c50a05aeed358d2d340c7af81"}, + {file = "aiohttp-3.10.8-cp39-cp39-win32.whl", hash = "sha256:ee97c4e54f457c366e1f76fbbf3e8effee9de57dae671084a161c00f481106ce"}, + {file = "aiohttp-3.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:d95ae4420669c871667aad92ba8cce6251d61d79c1a38504621094143f94a8b4"}, + {file = "aiohttp-3.10.8.tar.gz", hash = "sha256:21f8225f7dc187018e8433c9326be01477fb2810721e048b33ac49091b19fb4a"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.12.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + +[[package]] +name = "aiohttp-socks" +version = "0.9.0" +description = "Proxy connector for aiohttp" +optional = false +python-versions = "*" +files = [ + {file = "aiohttp_socks-0.9.0-py3-none-any.whl", hash = "sha256:90a8211fd5b904ccbd010900105f1fd2dab20ae8a07df508df399036ad8d3d88"}, + {file = "aiohttp_socks-0.9.0.tar.gz", hash = "sha256:22159a1af026b229cfe5ea007e065bb3fe56385a951a82623a6f4588a6758003"}, +] + +[package.dependencies] +aiohttp = ">=3.10.0" +python-socks = {version = ">=2.4.3,<3.0.0", extras = ["asyncio"]} + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "annotated-types" version = "0.7.0" @@ -33,43 +205,40 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" [[package]] -name = "anyio" -version = "4.6.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" files = [ - {file = "anyio-4.6.0-py3-none-any.whl", hash = "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a"}, - {file = "anyio-4.6.0.tar.gz", hash = "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -[package.dependencies] -exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} -idna = ">=2.8" -sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} - -[package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"] -trio = ["trio (>=0.26.1)"] - [package.source] type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" [[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, ] +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + [package.source] type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" @@ -489,73 +658,91 @@ url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" [[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "mirrors" - -[[package]] -name = "httpcore" -version = "1.0.5" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "mirrors" - -[[package]] -name = "httpx" -version = "0.27.2" -description = "The next generation HTTP client." +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" -socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""} - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] - [package.source] type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" @@ -896,6 +1083,115 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "packaging" version = "24.1" @@ -1484,6 +1780,31 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "python-socks" +version = "2.5.2" +description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python" +optional = false +python-versions = "*" +files = [ + {file = "python_socks-2.5.2-py3-none-any.whl", hash = "sha256:e2511c0d270d5135f8052d5e7ab7c4f089bd0f3fe0f54b8c322f8cbda5db2b2e"}, + {file = "python_socks-2.5.2.tar.gz", hash = "sha256:1a5220d159f88a92ef2f77d1acb77d175d40cb34af9176609d3cf728cb7499c7"}, +] + +[package.dependencies] +async-timeout = {version = ">=3.0.1", optional = true, markers = "python_version < \"3.11\" and extra == \"asyncio\""} + +[package.extras] +anyio = ["anyio (>=3.3.4,<5.0.0)"] +asyncio = ["async-timeout (>=3.0.1)"] +curio = ["curio (>=1.4)"] +trio = ["trio (>=0.16.0)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "pywin32" version = "306" @@ -1675,38 +1996,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" -[[package]] -name = "sniffio" -version = "1.3.1" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, - {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, -] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "mirrors" - -[[package]] -name = "socksio" -version = "1.0.0" -description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5." -optional = false -python-versions = ">=3.6" -files = [ - {file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"}, - {file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"}, -] - -[package.source] -type = "legacy" -url = "https://pypi.tuna.tsinghua.edu.cn/simple" -reference = "mirrors" - [[package]] name = "time-machine" version = "2.15.0" @@ -1951,6 +2240,116 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "yarl" +version = "1.13.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "yarl-1.13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:82e692fb325013a18a5b73a4fed5a1edaa7c58144dc67ad9ef3d604eccd451ad"}, + {file = "yarl-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df4e82e68f43a07735ae70a2d84c0353e58e20add20ec0af611f32cd5ba43fb4"}, + {file = "yarl-1.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec9dd328016d8d25702a24ee274932aebf6be9787ed1c28d021945d264235b3c"}, + {file = "yarl-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5820bd4178e6a639b3ef1db8b18500a82ceab6d8b89309e121a6859f56585b05"}, + {file = "yarl-1.13.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86c438ce920e089c8c2388c7dcc8ab30dfe13c09b8af3d306bcabb46a053d6f7"}, + {file = "yarl-1.13.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3de86547c820e4f4da4606d1c8ab5765dd633189791f15247706a2eeabc783ae"}, + {file = "yarl-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca53632007c69ddcdefe1e8cbc3920dd88825e618153795b57e6ebcc92e752a"}, + {file = "yarl-1.13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4ee1d240b84e2f213565f0ec08caef27a0e657d4c42859809155cf3a29d1735"}, + {file = "yarl-1.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c49f3e379177f4477f929097f7ed4b0622a586b0aa40c07ac8c0f8e40659a1ac"}, + {file = "yarl-1.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5c5e32fef09ce101fe14acd0f498232b5710effe13abac14cd95de9c274e689e"}, + {file = "yarl-1.13.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab9524e45ee809a083338a749af3b53cc7efec458c3ad084361c1dbf7aaf82a2"}, + {file = "yarl-1.13.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b1481c048fe787f65e34cb06f7d6824376d5d99f1231eae4778bbe5c3831076d"}, + {file = "yarl-1.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:31497aefd68036d8e31bfbacef915826ca2e741dbb97a8d6c7eac66deda3b606"}, + {file = "yarl-1.13.1-cp310-cp310-win32.whl", hash = "sha256:1fa56f34b2236f5192cb5fceba7bbb09620e5337e0b6dfe2ea0ddbd19dd5b154"}, + {file = "yarl-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:1bbb418f46c7f7355084833051701b2301092e4611d9e392360c3ba2e3e69f88"}, + {file = "yarl-1.13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:216a6785f296169ed52cd7dcdc2612f82c20f8c9634bf7446327f50398732a51"}, + {file = "yarl-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40c6e73c03a6befb85b72da213638b8aaa80fe4136ec8691560cf98b11b8ae6e"}, + {file = "yarl-1.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2430cf996113abe5aee387d39ee19529327205cda975d2b82c0e7e96e5fdabdc"}, + {file = "yarl-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fb4134cc6e005b99fa29dbc86f1ea0a298440ab6b07c6b3ee09232a3b48f495"}, + {file = "yarl-1.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309c104ecf67626c033845b860d31594a41343766a46fa58c3309c538a1e22b2"}, + {file = "yarl-1.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f90575e9fe3aae2c1e686393a9689c724cd00045275407f71771ae5d690ccf38"}, + {file = "yarl-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d2e1626be8712333a9f71270366f4a132f476ffbe83b689dd6dc0d114796c74"}, + {file = "yarl-1.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b66c87da3c6da8f8e8b648878903ca54589038a0b1e08dde2c86d9cd92d4ac9"}, + {file = "yarl-1.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cf1ad338620249f8dd6d4b6a91a69d1f265387df3697ad5dc996305cf6c26fb2"}, + {file = "yarl-1.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9915300fe5a0aa663c01363db37e4ae8e7c15996ebe2c6cce995e7033ff6457f"}, + {file = "yarl-1.13.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:703b0f584fcf157ef87816a3c0ff868e8c9f3c370009a8b23b56255885528f10"}, + {file = "yarl-1.13.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1d8e3ca29f643dd121f264a7c89f329f0fcb2e4461833f02de6e39fef80f89da"}, + {file = "yarl-1.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7055bbade838d68af73aea13f8c86588e4bcc00c2235b4b6d6edb0dbd174e246"}, + {file = "yarl-1.13.1-cp311-cp311-win32.whl", hash = "sha256:a3442c31c11088e462d44a644a454d48110f0588de830921fd201060ff19612a"}, + {file = "yarl-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:81bad32c8f8b5897c909bf3468bf601f1b855d12f53b6af0271963ee67fff0d2"}, + {file = "yarl-1.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f452cc1436151387d3d50533523291d5f77c6bc7913c116eb985304abdbd9ec9"}, + {file = "yarl-1.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9cec42a20eae8bebf81e9ce23fb0d0c729fc54cf00643eb251ce7c0215ad49fe"}, + {file = "yarl-1.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d959fe96e5c2712c1876d69af0507d98f0b0e8d81bee14cfb3f6737470205419"}, + {file = "yarl-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8c837ab90c455f3ea8e68bee143472ee87828bff19ba19776e16ff961425b57"}, + {file = "yarl-1.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94a993f976cdcb2dc1b855d8b89b792893220db8862d1a619efa7451817c836b"}, + {file = "yarl-1.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2442a415a5f4c55ced0fade7b72123210d579f7d950e0b5527fc598866e62c"}, + {file = "yarl-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fdbf0418489525231723cdb6c79e7738b3cbacbaed2b750cb033e4ea208f220"}, + {file = "yarl-1.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b7f6e699304717fdc265a7e1922561b02a93ceffdaefdc877acaf9b9f3080b8"}, + {file = "yarl-1.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bcd5bf4132e6a8d3eb54b8d56885f3d3a38ecd7ecae8426ecf7d9673b270de43"}, + {file = "yarl-1.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2a93a4557f7fc74a38ca5a404abb443a242217b91cd0c4840b1ebedaad8919d4"}, + {file = "yarl-1.13.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:22b739f99c7e4787922903f27a892744189482125cc7b95b747f04dd5c83aa9f"}, + {file = "yarl-1.13.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2db874dd1d22d4c2c657807562411ffdfabec38ce4c5ce48b4c654be552759dc"}, + {file = "yarl-1.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4feaaa4742517eaceafcbe74595ed335a494c84634d33961214b278126ec1485"}, + {file = "yarl-1.13.1-cp312-cp312-win32.whl", hash = "sha256:bbf9c2a589be7414ac4a534d54e4517d03f1cbb142c0041191b729c2fa23f320"}, + {file = "yarl-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:d07b52c8c450f9366c34aa205754355e933922c79135125541daae6cbf31c799"}, + {file = "yarl-1.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:95c6737f28069153c399d875317f226bbdea939fd48a6349a3b03da6829fb550"}, + {file = "yarl-1.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cd66152561632ed4b2a9192e7f8e5a1d41e28f58120b4761622e0355f0fe034c"}, + {file = "yarl-1.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6a2acde25be0cf9be23a8f6cbd31734536a264723fca860af3ae5e89d771cd71"}, + {file = "yarl-1.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18595e6a2ee0826bf7dfdee823b6ab55c9b70e8f80f8b77c37e694288f5de1"}, + {file = "yarl-1.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a31d21089894942f7d9a8df166b495101b7258ff11ae0abec58e32daf8088813"}, + {file = "yarl-1.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45f209fb4bbfe8630e3d2e2052535ca5b53d4ce2d2026bed4d0637b0416830da"}, + {file = "yarl-1.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f722f30366474a99745533cc4015b1781ee54b08de73260b2bbe13316079851"}, + {file = "yarl-1.13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3bf60444269345d712838bb11cc4eadaf51ff1a364ae39ce87a5ca8ad3bb2c8"}, + {file = "yarl-1.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:942c80a832a79c3707cca46bd12ab8aa58fddb34b1626d42b05aa8f0bcefc206"}, + {file = "yarl-1.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:44b07e1690f010c3c01d353b5790ec73b2f59b4eae5b0000593199766b3f7a5c"}, + {file = "yarl-1.13.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:396e59b8de7e4d59ff5507fb4322d2329865b909f29a7ed7ca37e63ade7f835c"}, + {file = "yarl-1.13.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3bb83a0f12701c0b91112a11148b5217617982e1e466069d0555be9b372f2734"}, + {file = "yarl-1.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c92b89bffc660f1274779cb6fbb290ec1f90d6dfe14492523a0667f10170de26"}, + {file = "yarl-1.13.1-cp313-cp313-win32.whl", hash = "sha256:269c201bbc01d2cbba5b86997a1e0f73ba5e2f471cfa6e226bcaa7fd664b598d"}, + {file = "yarl-1.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:1d0828e17fa701b557c6eaed5edbd9098eb62d8838344486248489ff233998b8"}, + {file = "yarl-1.13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8be8cdfe20787e6a5fcbd010f8066227e2bb9058331a4eccddec6c0db2bb85b2"}, + {file = "yarl-1.13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:08d7148ff11cb8e886d86dadbfd2e466a76d5dd38c7ea8ebd9b0e07946e76e4b"}, + {file = "yarl-1.13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4afdf84610ca44dcffe8b6c22c68f309aff96be55f5ea2fa31c0c225d6b83e23"}, + {file = "yarl-1.13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0d12fe78dcf60efa205e9a63f395b5d343e801cf31e5e1dda0d2c1fb618073d"}, + {file = "yarl-1.13.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:298c1eecfd3257aa16c0cb0bdffb54411e3e831351cd69e6b0739be16b1bdaa8"}, + {file = "yarl-1.13.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c14c16831b565707149c742d87a6203eb5597f4329278446d5c0ae7a1a43928e"}, + {file = "yarl-1.13.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a9bacedbb99685a75ad033fd4de37129449e69808e50e08034034c0bf063f99"}, + {file = "yarl-1.13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:658e8449b84b92a4373f99305de042b6bd0d19bf2080c093881e0516557474a5"}, + {file = "yarl-1.13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:373f16f38721c680316a6a00ae21cc178e3a8ef43c0227f88356a24c5193abd6"}, + {file = "yarl-1.13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:45d23c4668d4925688e2ea251b53f36a498e9ea860913ce43b52d9605d3d8177"}, + {file = "yarl-1.13.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f7917697bcaa3bc3e83db91aa3a0e448bf5cde43c84b7fc1ae2427d2417c0224"}, + {file = "yarl-1.13.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5989a38ba1281e43e4663931a53fbf356f78a0325251fd6af09dd03b1d676a09"}, + {file = "yarl-1.13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:11b3ca8b42a024513adce810385fcabdd682772411d95bbbda3b9ed1a4257644"}, + {file = "yarl-1.13.1-cp38-cp38-win32.whl", hash = "sha256:dcaef817e13eafa547cdfdc5284fe77970b891f731266545aae08d6cce52161e"}, + {file = "yarl-1.13.1-cp38-cp38-win_amd64.whl", hash = "sha256:7addd26594e588503bdef03908fc207206adac5bd90b6d4bc3e3cf33a829f57d"}, + {file = "yarl-1.13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a0ae6637b173d0c40b9c1462e12a7a2000a71a3258fa88756a34c7d38926911c"}, + {file = "yarl-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:576365c9f7469e1f6124d67b001639b77113cfd05e85ce0310f5f318fd02fe85"}, + {file = "yarl-1.13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78f271722423b2d4851cf1f4fa1a1c4833a128d020062721ba35e1a87154a049"}, + {file = "yarl-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d74f3c335cfe9c21ea78988e67f18eb9822f5d31f88b41aec3a1ec5ecd32da5"}, + {file = "yarl-1.13.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1891d69a6ba16e89473909665cd355d783a8a31bc84720902c5911dbb6373465"}, + {file = "yarl-1.13.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fb382fd7b4377363cc9f13ba7c819c3c78ed97c36a82f16f3f92f108c787cbbf"}, + {file = "yarl-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8854b9f80693d20cec797d8e48a848c2fb273eb6f2587b57763ccba3f3bd4b"}, + {file = "yarl-1.13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbf2c3f04ff50f16404ce70f822cdc59760e5e2d7965905f0e700270feb2bbfc"}, + {file = "yarl-1.13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fb9f59f3848edf186a76446eb8bcf4c900fe147cb756fbbd730ef43b2e67c6a7"}, + {file = "yarl-1.13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ef9b85fa1bc91c4db24407e7c4da93a5822a73dd4513d67b454ca7064e8dc6a3"}, + {file = "yarl-1.13.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:098b870c18f1341786f290b4d699504e18f1cd050ed179af8123fd8232513424"}, + {file = "yarl-1.13.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8c723c91c94a3bc8033dd2696a0f53e5d5f8496186013167bddc3fb5d9df46a3"}, + {file = "yarl-1.13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:44a4c40a6f84e4d5955b63462a0e2a988f8982fba245cf885ce3be7618f6aa7d"}, + {file = "yarl-1.13.1-cp39-cp39-win32.whl", hash = "sha256:84bbcdcf393139f0abc9f642bf03f00cac31010f3034faa03224a9ef0bb74323"}, + {file = "yarl-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:fc2931ac9ce9c61c9968989ec831d3a5e6fcaaff9474e7cfa8de80b7aff5a093"}, + {file = "yarl-1.13.1-py3-none-any.whl", hash = "sha256:6a5185ad722ab4dd52d5fb1f30dcc73282eb1ed494906a92d1a228d3f89607b0"}, + {file = "yarl-1.13.1.tar.gz", hash = "sha256:ec8cfe2295f3e5e44c51f57272afbd69414ae629ec7c6b27f5a410efc78b70a0"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "zipp" version = "3.20.2" @@ -1978,4 +2377,4 @@ reference = "mirrors" [metadata] lock-version = "2.0" python-versions = "<3.13,>=3.10" -content-hash = "3c98b4c2562b1cc5d88474d6962ab34e60be1be488d840c691c0d0e1095d7285" +content-hash = "4f40efe2d34c2dd6b279869363068ee58b82ac0de10b674eaf50acc3160f8527" diff --git a/pyproject.toml b/pyproject.toml index a74d2bc1b..c25caa463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,9 @@ confz = "^2.0.1" pydantic-extra-types = "^2.9.0" pendulum = "^3.0.0" slimeface = "^2024.9.27" -httpx = {extras = ["socks"], version = "^0.27.2"} aiofiles = "^24.1.0" +aiohttp = "^3.10.8" +aiohttp-socks = "^0.9.0" [tool.poetry.scripts] javsp = "javsp.__main__:entry" diff --git a/unittest/test_proxyfree.py b/unittest/test_proxyfree.py index 65151a9d4..7738a7361 100644 --- a/unittest/test_proxyfree.py +++ b/unittest/test_proxyfree.py @@ -3,11 +3,13 @@ from javsp.crawlers.proxyfree import get_proxy_free_url from javsp.config import CrawlerID +from javsp.network.client import clear_clients def test_get_url(): async def wrap(): assert await get_proxy_free_url(CrawlerID.javlib) != None assert await get_proxy_free_url(CrawlerID.javdb) != None + await clear_clients() asyncio.run(wrap()) @@ -15,11 +17,13 @@ def test_get_url_with_prefer(): async def wrap(): prefer_url = 'https://www.baidu.com' assert prefer_url == await get_proxy_free_url(CrawlerID.javlib, prefer_url) + await clear_clients() asyncio.run(wrap()) if __name__ == "__main__": async def aentry(): print(await get_proxy_free_url(CrawlerID.javlib)) + await clear_clients() tracemalloc.start() asyncio.run(aentry(), debug=True)