feat(card): 优化卡片新增、修复搜卡改版、卡不存在抛异常 其他卡片支持自定义卡片名 修复psa页面改版 修复ccg卡不存在抛异常
This commit is contained in:
@@ -29,7 +29,7 @@ logger = logging.getLogger(__name__)
|
|||||||
card_router = APIRouter(prefix='/card', tags=['卡册模块'])
|
card_router = APIRouter(prefix='/card', tags=['卡册模块'])
|
||||||
|
|
||||||
|
|
||||||
@card_router.get('/search', summary='搜索卡片', response_model=base_schema.BaseResponse[schema.Card])
|
@card_router.get('/search', summary='搜索卡片', response_model=base_schema.BaseResponse[schema.SearchCardOut])
|
||||||
async def get_card_search(
|
async def get_card_search(
|
||||||
cert_number: str = Query(pattern='[a-zA-Z0-9]+'),
|
cert_number: str = Query(pattern='[a-zA-Z0-9]+'),
|
||||||
platform: base_schema.SupportPlatformType = Query(...),
|
platform: base_schema.SupportPlatformType = Query(...),
|
||||||
@@ -190,9 +190,9 @@ async def created_card(
|
|||||||
db_session: AsyncSession = Depends(get_async_session)
|
db_session: AsyncSession = Depends(get_async_session)
|
||||||
):
|
):
|
||||||
card_crud = CRUD(db_session, Card)
|
card_crud = CRUD(db_session, Card)
|
||||||
|
# 卡片库查询
|
||||||
|
card: Card = await card_crud.get(filters={'cert_number': payload.cert_number})
|
||||||
if payload.source.value != 'other':
|
if payload.source.value != 'other':
|
||||||
# 卡片库查询
|
|
||||||
card: Card = await card_crud.get(filters={'cert_number': payload.cert_number})
|
|
||||||
if card is None:
|
if card is None:
|
||||||
# 卡片缓存库转正至卡片库, 重新查询保障数据准确性
|
# 卡片缓存库转正至卡片库, 重新查询保障数据准确性
|
||||||
cache_card_crud = CRUD(db_session, SearchCardCache)
|
cache_card_crud = CRUD(db_session, SearchCardCache)
|
||||||
@@ -232,14 +232,15 @@ async def created_card(
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
card: Card = await card_crud.create(
|
if card is None:
|
||||||
data={
|
card: Card = await card_crud.create(
|
||||||
**payload.dict(exclude_none=True),
|
data={
|
||||||
'create_user_id': login_user.user_id,
|
**payload.dict(exclude_none=True, exclude={'reward'}),
|
||||||
'source': payload.source.value,
|
'create_user_id': login_user.user_id,
|
||||||
},
|
'source': payload.source.value,
|
||||||
filters={'cert_number': payload.cert_number, 'source': payload.source.value}
|
},
|
||||||
)
|
filters={'cert_number': payload.cert_number, 'source': payload.source.value}
|
||||||
|
)
|
||||||
|
|
||||||
collection_crud = CRUD(db_session, UserCollection)
|
collection_crud = CRUD(db_session, UserCollection)
|
||||||
record = {
|
record = {
|
||||||
@@ -250,8 +251,6 @@ async def created_card(
|
|||||||
data=record,
|
data=record,
|
||||||
filters=record
|
filters=record
|
||||||
)
|
)
|
||||||
# if result is False:
|
|
||||||
# raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='卡片已存入')
|
|
||||||
|
|
||||||
return {'data': card}
|
return {'data': card}
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,18 @@ class Card(BaseModel):
|
|||||||
card_spare_picture: Optional[dict] = CardPicture()
|
card_spare_picture: Optional[dict] = CardPicture()
|
||||||
source: SupportPlatformType
|
source: SupportPlatformType
|
||||||
|
|
||||||
|
@field_validator("card_year", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def convert_to_str(cls, v):
|
||||||
|
if isinstance(v, int):
|
||||||
|
v = str(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class CardIn(Card):
|
class CardIn(Card):
|
||||||
category: CardCategoryType
|
category: CardCategoryType
|
||||||
|
source: PlatformSourceType
|
||||||
|
diy_source: Optional[str] = None
|
||||||
reward: Optional[bool] = False
|
reward: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
@@ -49,14 +58,20 @@ class UpdateCard(BaseModel):
|
|||||||
display_price: condecimal(max_digits=10, decimal_places=2)
|
display_price: condecimal(max_digits=10, decimal_places=2)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchCardOut(Card):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CardVisitOut(Card, UpdateCard):
|
class CardVisitOut(Card, UpdateCard):
|
||||||
source: PlatformSourceType
|
source: PlatformSourceType
|
||||||
|
|
||||||
|
|
||||||
class CardOut(CardVisitOut):
|
class CardOut(CardVisitOut):
|
||||||
id: Optional[int] = None
|
id: Optional[int] = None
|
||||||
|
card_year: str | None
|
||||||
purchase_price: Optional[condecimal(max_digits=10, decimal_places=2)] = None
|
purchase_price: Optional[condecimal(max_digits=10, decimal_places=2)] = None
|
||||||
display_price: Optional[condecimal(max_digits=10, decimal_places=2)] = None
|
display_price: Optional[condecimal(max_digits=10, decimal_places=2)] = None
|
||||||
|
diy_source: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class DeleteCardIn(BaseModel):
|
class DeleteCardIn(BaseModel):
|
||||||
|
|||||||
@@ -85,13 +85,13 @@ class SiteApi:
|
|||||||
@async_retry(exceptions=(SearchCardRetry,))
|
@async_retry(exceptions=(SearchCardRetry,))
|
||||||
async def psa(self, cert_number: str | int):
|
async def psa(self, cert_number: str | int):
|
||||||
"""PSA"""
|
"""PSA"""
|
||||||
url = f'https://www.psacard.com/cert/{cert_number}'
|
url = f'https://www.psacard.com/cert/{cert_number}/psa'
|
||||||
headers = {
|
headers = {
|
||||||
# 'X-Requested-With': 'XMLHttpRequest',
|
# 'X-Requested-With': 'XMLHttpRequest',
|
||||||
'Origin': 'https://www.psacard.com',
|
'Origin': 'https://www.psacard.com',
|
||||||
'Referer': f'https://www.psacard.com/cert/{cert_number}',
|
'Referer': f'https://www.psacard.com/cert/{cert_number}',
|
||||||
}
|
}
|
||||||
r = await self.get(url, headers=headers, use_abroad_proxy=True)
|
r = await self.get(url, headers=headers, impersonate="chrome131", use_abroad_proxy=True)
|
||||||
chck_string = 'Just a moment...'
|
chck_string = 'Just a moment...'
|
||||||
if chck_string in r.text:
|
if chck_string in r.text:
|
||||||
raise SearchCardRetry(chck_string)
|
raise SearchCardRetry(chck_string)
|
||||||
@@ -191,16 +191,16 @@ class CardScraper:
|
|||||||
"""PSA"""
|
"""PSA"""
|
||||||
r = await self.api.psa(cert_number)
|
r = await self.api.psa(cert_number)
|
||||||
html = Selector(r.text)
|
html = Selector(r.text)
|
||||||
if html.xpath('//div[contains(@class, "alert-danger")]/p[contains(string(), "not found")]').get() is not None:
|
if html.xpath('//div[contains(@class, "wrapper")]//h1/text()').get() is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
info_elements = html.xpath('//main[@id="mainContent"]/div/table[contains(@class, "text-medium")]//tr')
|
info_elements = html.xpath('//div[@hidden and @id]/div[contains(@class, "py-3")]')
|
||||||
info = dict()
|
info = dict()
|
||||||
for info_element in info_elements:
|
for info_element in info_elements:
|
||||||
name = info_element.xpath('./th[1]').xpath('string()').get()
|
name = info_element.xpath('./dt[1]').xpath('string()').get() or info_element.xpath('./dt/template/@id').get() or ''
|
||||||
name = re.sub(r'\(|\)', '', name)
|
name = re.sub(r'\(|\)', '', name)
|
||||||
name = name.lower().replace(' ', '_').replace('/', '_')
|
name = name.lower().replace(' ', '_').replace('/', '_')
|
||||||
value = info_element.xpath('./td[1]').xpath('string()').get()
|
value = info_element.xpath('./dd[1]').xpath('string()').get()
|
||||||
info[name.strip()] = value.strip()
|
info[name.strip()] = value.strip()
|
||||||
picture_elements = info_elements.xpath('.//img')
|
picture_elements = info_elements.xpath('.//img')
|
||||||
info['pictures'] = [
|
info['pictures'] = [
|
||||||
@@ -212,35 +212,29 @@ class CardScraper:
|
|||||||
|
|
||||||
item = dict(cert_number=None)
|
item = dict(cert_number=None)
|
||||||
# 评级卡号
|
# 评级卡号
|
||||||
item['cert_number'] = info['certification_number']
|
item['cert_number'] = info['cert_number']
|
||||||
# 年份
|
# 年份
|
||||||
item['card_year'] = info.get('year')
|
item['card_year'] = info.get('year')
|
||||||
# 卡片品牌
|
# 卡片品牌
|
||||||
item['card_brand'] = info.get('brand')
|
item['card_brand'] = info.get('brand_title')
|
||||||
# 卡片评分
|
# 卡片评分
|
||||||
card_grade = info.get('card_grade') or info.get('grade') or ''
|
card_grade = info.get('item_grade') or ''
|
||||||
item['card_score'] = regx_match(r'(\d+)', card_grade, first=True)
|
item['card_score'] = regx_match(r'(\d+)', card_grade, first=True)
|
||||||
# 签字评分
|
# 签字评分
|
||||||
auto_score = info.get('autograph_grade') or ''
|
auto_score = info.get('autograph_grade') or ''
|
||||||
item['auto_score'] = regx_match(r'(\d+)', auto_score, first=True)
|
item['auto_score'] = regx_match(r'(\d+)', auto_score, first=True)
|
||||||
# 卡片名称
|
# 卡片名称
|
||||||
item['card_name'] = info.get('player')
|
item['card_name'] = info.get('subject') or info.get('p:16')
|
||||||
# 卡片编号
|
# 卡片编号
|
||||||
item['card_number'] = info.get('card_number')
|
item['card_number'] = info.get('card_number')
|
||||||
# 卡片类型
|
# 卡片类型
|
||||||
item['card_type'] = info.get('sport')
|
item['card_type'] = info.get('category')
|
||||||
images_id = html.xpath(
|
# 卡片正、反图片(远程)
|
||||||
'//script[contains(text(), "getPSACertImages")]/text()'
|
item['card_spare_picture'] = {
|
||||||
).re_first(r'getPSACertImages\(\'(\d+)\'\);')
|
'front_picture': html.xpath('//div[contains(@class, "flex")]//span/noscript/img[@alt="left"]/@src').get(),
|
||||||
|
'reverse_picture': html.xpath('//div[contains(@class, "flex")]//span/noscript/img[@alt="right"]/@src').get()
|
||||||
|
}
|
||||||
|
|
||||||
if images_id:
|
|
||||||
r = await self.api.psa_images(cert_number, images_id)
|
|
||||||
result = r.json()
|
|
||||||
# 卡片正、反图片(远程)
|
|
||||||
item['card_spare_picture'] = {
|
|
||||||
'front_picture': jsonpath(result, '$.PSACertImages[0].OriginalImageUrl', first=True),
|
|
||||||
'reverse_picture': jsonpath(result, '$.PSACertImages[1].OriginalImageUrl', first=True)
|
|
||||||
}
|
|
||||||
# 来源
|
# 来源
|
||||||
item['source'] = 'psa'
|
item['source'] = 'psa'
|
||||||
return {
|
return {
|
||||||
@@ -368,7 +362,7 @@ class CardScraper:
|
|||||||
"""重庆藏卡"""
|
"""重庆藏卡"""
|
||||||
r = await self.api.ccg(cert_number)
|
r = await self.api.ccg(cert_number)
|
||||||
result = r.json()
|
result = r.json()
|
||||||
if result.get('code') != '200':
|
if result.get('code') != '200' or not result['data']:
|
||||||
return
|
return
|
||||||
|
|
||||||
item = dict(cert_number=None)
|
item = dict(cert_number=None)
|
||||||
|
|||||||
Reference in New Issue
Block a user