对于io_data_processor:

1.将请求相关封装成了Request类
2.新增了获取cookies后使用aiohttp请求的机制,理论上可以提速+减少不必要的性能开销,并且将cookies存储至cache
3.整理代码
新增config.py,用于配置cache路径
添加依赖项Brotli
This commit is contained in:
2022-08-08 13:32:31 +08:00
parent 68028cf3d9
commit e473dca4df
5 changed files with 267 additions and 88 deletions

View File

@@ -2,13 +2,13 @@
# A comma-separated list of package or module names from where C extensions may # A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may # be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. # run arbitrary code.
extension-pkg-allow-list=lxml extension-pkg-allow-list=lxml, pydantic
# A comma-separated list of package or module names from where C extensions may # A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may # be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list # run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.) # for backward compatibility.)
extension-pkg-whitelist=lxml extension-pkg-whitelist=lxml, pydantic
# List of module names for which member attributes should not be checked # List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime # (useful for modules/projects where namespaces are manipulated during runtime

View File

@@ -1,19 +1,26 @@
from typing import Any import os
from asyncio import gather from asyncio import gather
from re import I from re import I
from playwright.async_api import Browser, async_playwright from typing import Any
from ujson import loads, JSONDecodeError
import aiohttp import aiohttp
from nonebot import get_driver, on_regex
from nonebot import on_regex, get_driver
from nonebot.adapters.onebot.v11 import GROUP, MessageEvent from nonebot.adapters.onebot.v11 import GROUP, MessageEvent
from nonebot.matcher import Matcher
from nonebot.log import logger from nonebot.log import logger
from nonebot.matcher import Matcher
from ujson import JSONDecodeError, dumps, loads
from playwright.async_api import (
Browser,
Response,
async_playwright
)
from ..utils.message_analyzer import handle_bind_message, handle_stats_query_message from ..utils.config import Config
from ..utils.sql import query_bind_info, write_bind_info from ..utils.sql import query_bind_info, write_bind_info
from ..utils.message_analyzer import (
_BROWSER: Browser | None = None handle_bind_message,
handle_stats_query_message
)
IOBind = on_regex(pattern=r'^io绑定|^iobind', flags=I, permission=GROUP) IOBind = on_regex(pattern=r'^io绑定|^iobind', flags=I, permission=GROUP)
@@ -21,6 +28,8 @@ IOStats = on_regex(pattern=r'^io查|^iostats', flags=I, permission=GROUP)
driver = get_driver() driver = get_driver()
config = Config.parse_obj(get_driver().config)
@IOBind.handle() @IOBind.handle()
async def _(event: MessageEvent, matcher: Matcher): async def _(event: MessageEvent, matcher: Matcher):
@@ -28,7 +37,7 @@ async def _(event: MessageEvent, matcher: Matcher):
if decoded_message[0] is None: if decoded_message[0] is None:
await matcher.finish(decoded_message[1][0]) await matcher.finish(decoded_message[1][0])
if decoded_message[0] == 'ID': if decoded_message[0] == 'ID':
user_id_stats = await check_user_id(user_id=decoded_message[1][1]) user_id_stats = await check_user_id(decoded_message[1][1])
if user_id_stats[0] is False: if user_id_stats[0] is False:
await matcher.finish(user_id_stats[1]) await matcher.finish(user_id_stats[1])
else: else:
@@ -40,13 +49,17 @@ async def _(event: MessageEvent, matcher: Matcher):
elif user_data[1] is False: elif user_data[1] is False:
await matcher.finish(f'用户信息请求错误:\n{user_data[2]["error"]}') await matcher.finish(f'用户信息请求错误:\n{user_data[2]["error"]}')
else: else:
user_id = await get_user_id(user_data=user_data[2]) user_id = await get_user_id(user_data[2])
if event.sender.user_id is None: # 理论上是不会有None出现的, ide快乐行属于是 if event.sender.user_id is None: # 理论上是不会有None出现的, ide快乐行属于是
logger.error('获取QQ号失败') logger.error('获取QQ号失败')
await matcher.finish('获取QQ号失败') await matcher.finish('获取QQ号失败')
await matcher.finish(await write_bind_info(qq_number=event.sender.user_id, await matcher.finish(
await write_bind_info(
qq_number=event.sender.user_id,
user=user_id, user=user_id,
game_type='IO')) game_type='IO'
)
)
@IOStats.handle() @IOStats.handle()
@@ -56,7 +69,7 @@ async def _(event: MessageEvent, matcher: Matcher):
await matcher.finish(decoded_message[1][0]) await matcher.finish(decoded_message[1][0])
elif decoded_message[0] == 'AT': elif decoded_message[0] == 'AT':
if event.is_tome() is True: if event.is_tome() is True:
await matcher.finish(message='不能查询bot的信息') await matcher.finish('不能查询bot的信息')
bind_info = await query_bind_info(qq_number=decoded_message[1][1], game_type='IO') bind_info = await query_bind_info(qq_number=decoded_message[1][1], game_type='IO')
if bind_info is None: if bind_info is None:
message = '未查询到绑定信息' message = '未查询到绑定信息'
@@ -75,65 +88,22 @@ async def _(event: MessageEvent, matcher: Matcher):
message = await generate_message(user_id=decoded_message[1][1]) message = await generate_message(user_id=decoded_message[1][1])
elif decoded_message[0] == 'Name': elif decoded_message[0] == 'Name':
message = await generate_message(user_name=decoded_message[1][1]) message = await generate_message(user_name=decoded_message[1][1])
await matcher.finish(message=message) await matcher.finish(message)
@driver.on_startup
async def _():
await Request.init_cache()
await Request.read_cache()
@driver.on_shutdown @driver.on_shutdown
async def _(): async def _():
if isinstance(_BROWSER, Browser): await Request.close_browser()
await _BROWSER.close()
async def init_playwright() -> Browser: async def get_user_data(
'''初始化playwright''' user_name: str = None,
global _BROWSER
p = await async_playwright().start()
_BROWSER = await p.firefox.launch()
return _BROWSER
async def get_browser() -> Browser:
'''获取浏览器对象'''
return _BROWSER or await init_playwright()
async def request(url: str) -> tuple[bool, bool, dict[str, Any]]:
'''请求api'''
try:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
data = await resp.json()
return (True, data['success'], data)
except aiohttp.client_exceptions.ClientConnectorError as error:
logger.error(f'请求错误\n{error}')
return (False, False, {})
except aiohttp.client_exceptions.ContentTypeError:
# 如果有五秒盾就用firefox硬穿
browser = await get_browser()
page = await browser.new_page()
await page.goto(url)
attempts = 0
while True:
text = await page.locator("body").text_content()
if text is None:
continue
attempts += 1
if await page.title() == 'Please Wait... | Cloudflare':
break
try:
data = loads(text)
except JSONDecodeError:
await page.wait_for_timeout(1000)
else:
await page.close()
return (True, data['success'], data)
if attempts >= 60:
break
await page.close()
return (True, False, {'error': '绕过五秒盾失败'})
async def get_user_data(user_name: str = None,
user_id: str = None user_id: str = None
) -> tuple[bool, bool, dict[str, Any]]: ) -> tuple[bool, bool, dict[str, Any]]:
'''获取用户数据''' '''获取用户数据'''
@@ -143,10 +113,11 @@ async def get_user_data(user_name: str = None,
user_data_url = f'https://ch.tetr.io/api/users/{user_id}' user_data_url = f'https://ch.tetr.io/api/users/{user_id}'
else: else:
raise ValueError('预期外行为, 请上报GitHub') raise ValueError('预期外行为, 请上报GitHub')
return await request(url=user_data_url) return await Request.request(user_data_url)
async def get_solo_data(user_name: str = None, async def get_solo_data(
user_name: str = None,
user_id: str = None user_id: str = None
) -> tuple[bool, bool, dict[str, Any]]: ) -> tuple[bool, bool, dict[str, Any]]:
'''获取Solo数据''' '''获取Solo数据'''
@@ -156,7 +127,7 @@ async def get_solo_data(user_name: str = None,
user_solo_url = f'https://ch.tetr.io/api/users/{user_id}/records' user_solo_url = f'https://ch.tetr.io/api/users/{user_id}/records'
else: else:
raise ValueError('预期外行为, 请上报GitHub') raise ValueError('预期外行为, 请上报GitHub')
return await request(url=user_solo_url) return await Request.request(user_solo_url)
async def get_user_id(user_data: dict) -> str: async def get_user_id(user_data: dict) -> str:
@@ -168,11 +139,11 @@ async def check_user_id(user_id: str) -> tuple[bool, str]:
'''检查用户ID是否有效''' '''检查用户ID是否有效'''
user_data = await get_user_data(user_id=user_id) user_data = await get_user_data(user_id=user_id)
if user_data[0] is False: if user_data[0] is False:
return (False, '用户信息请求失败') return False, '用户信息请求失败'
if user_data[1] is False: if user_data[1] is False:
return (False, f'用户信息请求错误:\n{user_data[2]["error"]}') return False, f'用户信息请求错误:\n{user_data[2]["error"]}'
if user_id == user_data[2]['data']['user']['_id']: if user_id == user_data[2]['data']['user']['_id']:
return (True, '') return True, ''
raise ValueError('服务器返回的userID和用户提供的不一致, 这种情况理论上不应该发生, 以防万一还是写一下x') raise ValueError('服务器返回的userID和用户提供的不一致, 这种情况理论上不应该发生, 以防万一还是写一下x')
@@ -226,8 +197,10 @@ async def get_blitz_stats(solo_data: dict) -> dict[str, Any]:
async def generate_message(user_name: str = None, user_id: str = None) -> str: async def generate_message(user_name: str = None, user_id: str = None) -> str:
'''生成消息''' '''生成消息'''
user_data, solo_data = await gather(get_user_data(user_name=user_name, user_id=user_id), user_data, solo_data = await gather(
get_solo_data(user_name=user_name, user_id=user_id)) get_user_data(user_name=user_name, user_id=user_id),
get_solo_data(user_name=user_name, user_id=user_id)
)
if user_data[0] is False: if user_data[0] is False:
return '用户信息请求失败' return '用户信息请求失败'
if user_data[1] is False: if user_data[1] is False:
@@ -254,10 +227,138 @@ async def generate_message(user_name: str = None, user_id: str = None) -> str:
return f'{message}\nSolo统计数据请求失败' return f'{message}\nSolo统计数据请求失败'
if solo_data[1] is False: if solo_data[1] is False:
return f'{message}\nSolo统计数据请求错误:\n{solo_data[2]["error"]}' return f'{message}\nSolo统计数据请求错误:\n{solo_data[2]["error"]}'
sprint_stats, blitz_stats = await gather(get_sprint_stats(solo_data[2]), sprint_stats, blitz_stats = await gather(
get_blitz_stats(solo_data[2])) get_sprint_stats(solo_data[2]),
get_blitz_stats(solo_data[2])
)
message += f'\n40L: {sprint_stats["Time"]}s' if 'Time' in sprint_stats else '' message += f'\n40L: {sprint_stats["Time"]}s' if 'Time' in sprint_stats else ''
message += f' ( #{sprint_stats["Rank"]} )' if 'Rank' in sprint_stats else '' message += f' ( #{sprint_stats["Rank"]} )' if 'Rank' in sprint_stats else ''
message += f'\nBlitz: {blitz_stats["Score"]}' if 'Score' in blitz_stats else '' message += f'\nBlitz: {blitz_stats["Score"]}' if 'Score' in blitz_stats else ''
message += f' ( #{blitz_stats["Rank"]} )' if 'Rank' in blitz_stats else '' message += f' ( #{blitz_stats["Rank"]} )' if 'Rank' in blitz_stats else ''
return message return message
class Request:
_browser: Browser | None = None
_headers: dict | None = None
_cookies: dict | None = None
@classmethod
async def _init_playwright(cls) -> Browser:
'''初始化playwright'''
playwright = await async_playwright().start()
cls._browser = await playwright.firefox.launch()
return cls._browser
@classmethod
async def _get_browser(cls) -> Browser:
'''获取浏览器对象'''
return cls._browser or await cls._init_playwright()
@classmethod
async def _anti_cloudflare(cls, url: str) -> tuple[bool, bool, dict[str, Any]]:
'''用firefox硬穿五秒盾'''
browser = await cls._get_browser()
context = await browser.new_context()
page = await context.new_page()
response = await page.goto(url)
attempts = 0
while attempts < 60:
attempts += 1
text = await page.locator("body").text_content()
if text is None:
await page.wait_for_timeout(1000)
continue
if await page.title() == 'Please Wait... | Cloudflare':
# TODO 有无人来做一个过验证码(
break
try:
data = loads(text)
except JSONDecodeError:
await page.wait_for_timeout(1000)
else:
assert isinstance(response, Response)
cls._headers = await response.request.all_headers()
cls._cookies = {i['name']: i['value'] for i in await context.cookies()}
await cls._write_cache()
await page.close()
await context.close()
return True, data['success'], data
await page.close()
await context.close()
return True, False, {'error': '绕过五秒盾失败'}
@classmethod
async def init_cache(cls) -> None:
'''初始化缓存文件'''
if not os.path.exists(os.path.dirname(config.cache_path)):
os.makedirs(os.path.dirname(config.cache_path))
if not os.path.exists(config.cache_path):
with open(file=config.cache_path, mode='w', encoding='UTF-8') as file:
file.write(
dumps(
{
'headers': cls._browser,
'cookies': cls._cookies
}
)
)
@classmethod
async def read_cache(cls) -> None:
'''读取缓存文件'''
try:
with open(file=config.cache_path, mode='r', encoding='UTF-8') as file:
json = loads(file.read())
cls._headers = json['headers']
cls._cookies = json['cookies']
except FileNotFoundError:
await cls.init_cache()
except PermissionError:
os.remove(config.cache_path)
await cls.init_cache()
except JSONDecodeError:
os.remove(config.cache_path)
await cls.init_cache()
@classmethod
async def _write_cache(cls) -> None:
'''写入缓存文件'''
try:
with open(file=config.cache_path, mode='r+', encoding='UTF-8') as file:
file.write(
dumps(
{
'headers': cls._browser,
'cookies': cls._cookies
}
)
)
except FileNotFoundError:
await cls.init_cache()
except PermissionError:
os.remove(config.cache_path)
await cls.init_cache()
except JSONDecodeError:
os.remove(config.cache_path)
await cls.init_cache()
@classmethod
async def request(cls, url: str) -> tuple[bool, bool, dict[str, Any]]:
'''请求api'''
try:
async with aiohttp.ClientSession(cookies=cls._cookies) as session:
async with session.get(url, headers=cls._headers) as resp:
data = await resp.json()
return True, data['success'], data
except aiohttp.client_exceptions.ClientConnectorError as error:
logger.error(f'请求错误\n{error}')
return False, False, {}
except aiohttp.client_exceptions.ContentTypeError:
return await cls._anti_cloudflare(url)
@classmethod
async def close_browser(cls) -> None:
'''关闭浏览器对象'''
if isinstance(cls._browser, Browser):
await cls._browser.close()

View File

@@ -0,0 +1,5 @@
from pydantic import BaseModel
class Config(BaseModel):
cache_path: str = 'cache/nonebot_plugin_tetris_stats/cache'

80
poetry.lock generated
View File

@@ -100,6 +100,14 @@ python-versions = "*"
pycodestyle = ">=2.8.0" pycodestyle = ">=2.8.0"
toml = "*" toml = "*"
[[package]]
name = "brotli"
version = "1.0.9"
description = "Python bindings for the Brotli compression library"
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "2.1.0" version = "2.1.0"
@@ -562,7 +570,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "tomlkit" name = "tomlkit"
version = "0.11.1" version = "0.11.2"
description = "Style preserving TOML library" description = "Style preserving TOML library"
category = "main" category = "main"
optional = false optional = false
@@ -688,7 +696,7 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.10,<3.11" python-versions = "^3.10,<3.11"
content-hash = "ee024f6b87812714d3b664289d4a47bf1228e49dc3f797f728a27d50fc169f9f" content-hash = "acda543ec591c9d54e4a869521d9e2457fa441805c1172ca2f50205c4f587e38"
[metadata.files] [metadata.files]
aiohttp = [ aiohttp = [
@@ -795,6 +803,70 @@ autopep8 = [
{file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"},
{file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"},
] ]
brotli = [
{file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"},
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"},
{file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"},
{file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"},
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"},
{file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"},
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"},
{file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"},
{file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"},
{file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"},
{file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"},
{file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"},
{file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"},
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"},
{file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"},
{file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"},
{file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"},
{file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"},
{file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"},
{file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"},
{file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"},
{file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"},
{file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"},
{file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"},
{file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"},
{file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"},
{file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"},
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"},
{file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"},
{file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"},
{file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"},
{file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"},
{file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"},
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"},
{file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"},
{file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"},
{file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"},
{file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"},
{file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"},
{file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"},
{file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"},
{file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"},
]
charset-normalizer = [ charset-normalizer = [
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
@@ -1440,8 +1512,8 @@ tomli = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
] ]
tomlkit = [ tomlkit = [
{file = "tomlkit-0.11.1-py3-none-any.whl", hash = "sha256:1c5bebdf19d5051e2e1de6cf70adfc5948d47221f097fcff7a3ffc91e953eaf5"}, {file = "tomlkit-0.11.2-py3-none-any.whl", hash = "sha256:69e0675671a2eed1c08a53f342c955c4ead5d373a10f756219bf39f3d4f0018a"},
{file = "tomlkit-0.11.1.tar.gz", hash = "sha256:61901f81ff4017951119cd0d1ed9b7af31c821d6845c8c477587bbdcd5e5854e"}, {file = "tomlkit-0.11.2.tar.gz", hash = "sha256:d1b49c3e460f5910b22d799b13513504acb4f5fcaee01660ee66f07bd45a271c"},
] ]
types-pytz = [ types-pytz = [
{file = "types-pytz-2022.1.2.tar.gz", hash = "sha256:1a8b25c225c5e6bd8468aa9eb45ddd3b337f6716d4072ad0aa4ef1e41478eebc"}, {file = "types-pytz-2022.1.2.tar.gz", hash = "sha256:1a8b25c225c5e6bd8468aa9eb45ddd3b337f6716d4072ad0aa4ef1e41478eebc"},

View File

@@ -18,6 +18,7 @@ lxml = "^4.9.1"
pandas = "^1.4.3" pandas = "^1.4.3"
playwright = "^1.24.1" playwright = "^1.24.1"
ujson = "^5.4.0" ujson = "^5.4.0"
Brotli = "^1.0.9"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
mypy = "^0.971" mypy = "^0.971"