Files
surugaya-api/surugaya_api/product.py

210 lines
6.0 KiB
Python

import json
from dataclasses import dataclass
from typing import Optional
import aiohttp
from bs4 import BeautifulSoup
from surugaya_api.consts import SURU_COOKIE_STRING
import inspect
from surugaya_api.japan_prefectures import JapanesePrefectures
def get_or_create_aiohttp_session(func):
async def wrapper(*args, **kwargs):
sig = inspect.signature(func)
bound_args = sig.bind(*args, **kwargs)
bound_args.apply_defaults()
aiohttp_session = bound_args.arguments.get("aiohttp_session")
is_created = False
if not aiohttp_session:
is_created = True
aiohttp_session = aiohttp.ClientSession()
bound_args.arguments["aiohttp_session"] = aiohttp_session
result = await func(*bound_args.arguments.values())
if is_created:
await aiohttp_session.close()
return result
return wrapper
@dataclass
class ProductStock:
condition: str
stock: str
price: int
on_sale_price: Optional[int]
@property
def is_on_sale(self) -> bool:
return self.on_sale_price is not None
@staticmethod
def from_item_price(item_price):
data: dict = json.loads(item_price.select_one("input").attrs["data-zaiko"])
return ProductStock(
condition=item_price.select_one("label").attrs["data-label"].strip(),
stock=data["zaiko"],
price=data["baika"],
on_sale_price=data.get("price_sale"),
)
@dataclass
class Product:
id: int
name: str
main_image_href: str
stock: list[ProductStock]
@property
def in_stock(self):
return not self.stock
@get_or_create_aiohttp_session
async def load_product(product_id, aiohttp_session=None):
async with aiohttp_session.get(
url="https://www.suruga-ya.jp/product/detail/" + str(product_id),
headers={
"Cookie": SURU_COOKIE_STRING,
},
) as response:
page = await response.text()
page_bs = BeautifulSoup(page, features="html.parser")
product = Product(
id=int(product_id),
name=page_bs.select_one("#item_title").text.strip(),
main_image_href=page_bs.select_one(".main-pro-img").attrs["src"],
stock=[
ProductStock.from_item_price(item_price)
for item_price in page_bs.select(".item-price")
],
)
return product
@dataclass
class MarketplaceListing:
product_id: int
item_price: int
condition: str
product_link: str
store_name: str
store_id: int
special_info: str
shipping_price: int
@property
def total_price(self) -> int:
return self.item_price + self.shipping_price
@get_or_create_aiohttp_session
async def get_shipping_price_for_store(
store_id,
to_prefecture=JapanesePrefectures.KANAGAWA,
aiohttp_session: Optional[aiohttp.ClientSession] = None,
) -> int:
response = await aiohttp_session.post(
url="https://www.suruga-ya.jp/product-other/shipping-fee",
headers={
"Accept": "application/json, text/javascript, */*; q=0.01",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Origin": "https://www.suruga-ya.jp",
"Cookie": SURU_COOKIE_STRING,
},
data={
"tenpo_cd": store_id,
},
)
response_json = await response.json()
shipping_fees = response_json["data"]
if not shipping_fees["list_pref_fee"]:
shipping_price = shipping_fees["shipping"]["national_fee"]
else:
shipping_filtered = [
x
for x in shipping_fees["list_pref_fee"]
if x["prefecture"] == to_prefecture.value
]
if not shipping_filtered:
shipping_price = shipping_fees["shipping"]["national_fee"]
else:
shipping_price = shipping_filtered[0]["fee"]
return int(shipping_price)
@get_or_create_aiohttp_session
async def get_marketplace_availability(
product_id,
shipping_to_prefecture=JapanesePrefectures.KANAGAWA,
aiohttp_session: Optional[aiohttp.ClientSession] = None,
):
async with aiohttp_session.get(
f"https://www.suruga-ya.jp/product/other/{product_id}"
) as response:
if response.status == 301 or response.status == 302:
# TODO: Handle redirect to /product/detail if only one store is available
return []
page = await response.text()
page_bs = BeautifulSoup(page, features="html.parser")
items = page_bs.select("#tbl_all tr.item")
listings = []
for item in items:
if item.select_one("td:nth-child(3) a").attrs["href"] == "/":
# Main online store, not processing
continue
item_price = (
item.select_one("td:nth-child(1) .title_product > strong")
.text.replace(",", "")
.replace("", "")
)
condition = item.select_one("td:nth-child(2) .title_product").text
product_link = item.select_one("td:nth-child(2) > a").attrs["href"]
store_id = item.select_one("td:nth-child(3) a").attrs["href"].split("/")[-1]
store_name = item.select_one("td:nth-child(3) a").text
shipping_price = await get_shipping_price_for_store(
store_id, shipping_to_prefecture, aiohttp_session
)
special_info = "\n".join(
[x.text for x in item.select("td:nth-child(4) .padT5 div")]
)
listings.append(
MarketplaceListing(
product_id=int(product_id),
item_price=int(item_price),
condition=condition,
product_link=product_link,
store_name=store_name,
store_id=int(store_id),
special_info=special_info,
shipping_price=shipping_price,
)
)
return listings