import { Page, chromium } from "playwright"; import sheet from "./sheet.js"; import { ListWineResponseData, WineListItem } from "./model/WineList.js"; import { PageResponse } from "./model/util.js"; import { StaticWineDetailResponseData } from "./model/StaticWineDetail.js"; import { UserWineDetailResponseData } from "./model/UserWineDetail.js"; import path from "path"; const wsEndpoint = "ws://localhost:12224/d73f2ab43afd2d5beb5bf5a7e626b5d1"; const browser = await chromium.connect(wsEndpoint, { slowMo: 1000, }); const MIAN_PAGE = "https://www.vivino.cc/"; const requestMap = { list: "/app-api/appapi/wine/web/vintage/nodeFind", staticDetail: "/app-api/appapi/wine/web/findWineStaticDetailsWeb", userSpecialDetail: "/app-api/appapi/wine/web/findWineUserSpecialWeb", }; const page = await browser.newPage({ baseURL: MIAN_PAGE, }); page.setViewportSize({ width: 2560, height: 1440, }); let times = 0; const MAX_PAGE = 2; await Promise.all([page.goto("/search"), startSpide()]); await browser.close(); async function startSpide() { while (times < MAX_PAGE) { await spideWineList(page); const locator = page.locator(".btn-next"); if (await locator.isDisabled()) { break; } await locator.click(); times++; } await sheet?.workbook.xlsx.writeFile( path.resolve(process.cwd(), "./store/红酒-" + Date.now() + ".xlsx") ); } async function spideWineList(page: Page) { const wineList = await waitForListRequestResponse(page); for (let index = 0; index < wineList.length; index++) { const wine = wineList[index]; const { uuid, vintageName } = wine; const url = new URL("https://www.vivino.cc/detail"); url.searchParams.set("wineUUID", uuid); url.searchParams.set("name", vintageName); const newPage = await browser.newPage(); const [_, row] = await Promise.all([ newPage.goto(url.href), spideWineDetail(newPage, wine), ]); sheet?.addRow(row); } } async function spideWineDetail(page: Page, wine: WineListItem) { const staticDetailResponse = await page.waitForResponse((respone) => respone.url().includes(requestMap.staticDetail) ); const userDetailResponse = await page.waitForResponse((respone) => respone.url().includes(requestMap.userSpecialDetail) ); const { data: wineStaticDetail } = await staticDetailResponse.json(); const { data: wineUserDetail } = await userDetailResponse.json(); const { vintageRate, price, numOfVintageRates, country, imgUrl } = wine; const { wineName, alcohol, wineryCountryName, wineryRegionName, wineryName, flavor, typeName, style, grapes, } = wineStaticDetail as StaticWineDetailResponseData; const {} = wineUserDetail as UserWineDetailResponseData; await page.close(); return [ wineName, price, vintageRate, numOfVintageRates, country, imgUrl, `${wineryCountryName}·${wineryRegionName}·${wineryName}`, `${wineryCountryName}·${wineryRegionName}`, flavor, typeName, grapes.map((grape) => grape.grapesName).join("、"), style, alcohol, ]; } async function waitForListRequestResponse(page: Page) { const response = await page.waitForResponse((response) => response.url().includes(requestMap.list) ); if (response) { const res: PageResponse = await response.json(); return res.data.records; } return response; }