diff --git a/lib/routes/xueqiu/cookies.ts b/lib/routes/xueqiu/cookies.ts index 7e11d5218a3fa9..710a3cef7c56a9 100644 --- a/lib/routes/xueqiu/cookies.ts +++ b/lib/routes/xueqiu/cookies.ts @@ -1,25 +1,48 @@ import cache from '@/utils/cache'; import { config } from '@/config'; import puppeteer from '@/utils/puppeteer'; -import { getCookies } from '@/utils/puppeteer-utils'; +import { getCookies, setCookies } from '@/utils/puppeteer-utils'; export const parseToken = (link: string) => cache.tryGet( 'xueqiu:token', async () => { - const browser = await puppeteer({ stealth: true }); - const page = await browser.newPage(); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' ? request.continue() : request.abort(); - }); + const page = await getPuppeteerPage(); await page.goto(link, { waitUntil: 'domcontentloaded', }); await page.evaluate(() => document.documentElement.innerHTML); const cookies = await getCookies(page); + return cookies; }, config.cache.routeExpire, false ); + +export const getPuppeteerPage = async (cookie: string | Record | null = null) => { + const browser = await puppeteer({ stealth: true }); + const page = await browser.newPage(); + await page.setRequestInterception(true); + + if (cookie !== null) { + await setCookies(page, cookie, 'xueqiu.com'); + } + + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + + return page; +}; + +export const getJsonResult = async (url: string, cookie: string | Record | null = null) => { + const page = await getPuppeteerPage(cookie); + + const data = await page.goto(url, { + waitUntil: 'domcontentloaded', + }); + + const res = await data?.json(); + return res; +}; diff --git a/lib/routes/xueqiu/stock-comments.ts b/lib/routes/xueqiu/stock-comments.ts index 401eca00e57665..c81b9add9204fa 100644 --- a/lib/routes/xueqiu/stock-comments.ts +++ b/lib/routes/xueqiu/stock-comments.ts @@ -3,11 +3,10 @@ import { getCurrentPath } from '@/utils/helpers'; const __dirname = getCurrentPath(import.meta.url); import cache from '@/utils/cache'; -import got from '@/utils/got'; -import { load } from 'cheerio'; import { art } from '@/utils/render'; import path from 'node:path'; import { parseDate } from '@/utils/parse-date'; +import { getJsonResult, getPuppeteerPage } from '@/routes/xueqiu/cookies'; import sanitizeHtml from 'sanitize-html'; export const route: Route = { @@ -17,7 +16,7 @@ export const route: Route = { parameters: { id: '股票代码(需要带上交易所)' }, features: { requireConfig: false, - requirePuppeteer: false, + requirePuppeteer: true, antiCrawler: false, supportBT: false, supportPodcast: false, @@ -36,22 +35,24 @@ export const route: Route = { async function handler(ctx) { const id = ctx.req.param('id'); - const res = await got({ - method: 'get', - url: `https://xueqiu.com/query/v1/symbol/search/status?u=11111&count=100&comment=0&symbol=${id}&source=all&sort=time`, - }); + const url = `https://xueqiu.com/query/v1/symbol/search/status?u=11111&count=100&comment=0&symbol=${id}&source=all&sort=time`; + + const res = await getJsonResult(url); // 获取stock_name const stock_name = await cache.tryGet(`stock_name_${id}`, async () => { - const res = await got({ - method: 'get', - url: `https://xueqiu.com/S/${id}`, + const page = await getPuppeteerPage(); + await page.goto(`https://xueqiu.com/S/${id}`, { + waitUntil: 'domcontentloaded', }); - const $ = load(res.data); // 使用 cheerio 加载返回的 HTML - return $('.stock-name').text().split('(')[0]; + await page.waitForSelector('.stock-name'); + + // 获取文本并处理 + const stockName = await page.$eval('.stock-name', (element) => (element.textContent ? element.textContent.split('(')[0].trim() : '')); + return stockName; }); - const data = res.data.list; + const data = res.list; return { title: `${id} ${stock_name} - 评论`, link: `https://xueqiu.com/S/${id}`, diff --git a/lib/routes/xueqiu/user.ts b/lib/routes/xueqiu/user.ts index 4e8f89f63d035e..67afc03eeee702 100644 --- a/lib/routes/xueqiu/user.ts +++ b/lib/routes/xueqiu/user.ts @@ -1,10 +1,8 @@ import { Route } from '@/types'; import cache from '@/utils/cache'; -import got from '@/utils/got'; -import queryString from 'query-string'; import { parseDate } from '@/utils/parse-date'; import sanitizeHtml from 'sanitize-html'; -import { parseToken } from '@/routes/xueqiu/cookies'; +import { getJsonResult, getPuppeteerPage, parseToken } from '@/routes/xueqiu/cookies'; const rootUrl = 'https://xueqiu.com'; @@ -15,7 +13,7 @@ export const route: Route = { parameters: { id: '用户 id, 可在用户主页 URL 中找到', type: '动态的类型, 不填则默认全部' }, features: { requireConfig: false, - requirePuppeteer: false, + requirePuppeteer: true, antiCrawler: false, supportBT: false, supportPodcast: false, @@ -50,34 +48,30 @@ async function handler(ctx) { const link = `${rootUrl}/u/${id}`; const token = await parseToken(link); - const res2 = await got({ - method: 'get', - url: `${rootUrl}/v4/statuses/user_timeline.json`, - searchParams: queryString.stringify({ - user_id: id, - type, - source, - }), - headers: { - Cookie: token, - Referer: link, - }, - }); - const data = res2.data.statuses.filter((s) => s.mark !== 1); // 去除置顶动态 + + const url = `${rootUrl}/v4/statuses/user_timeline.json?user_id=${id}&type=${type}&source=${source}`; + + const res2 = await getJsonResult(url, token); + + const data = res2.statuses.filter((s) => s.mark !== 1); // 去除置顶动态 const items = await Promise.all( data.map((item) => cache.tryGet(item.target, async () => { - const detailResponse = await got({ - method: 'get', - url: rootUrl + item.target, - headers: { - Referer: link, - Cookie: token, - }, + const page = await getPuppeteerPage(token); + await page.goto(`${rootUrl}${item.target}`, { + waitUntil: 'domcontentloaded', }); - const data = JSON.parse(detailResponse.data.match(/SNOWMAN_STATUS = (.*?});/)[1]); + const detailResponse = await page.content(); + + const snowmanStatus = detailResponse.match(/SNOWMAN_STATUS = (.*?});/); + + if (snowmanStatus === null) { + throw new Error('snowmanStatus is null'); + } + + const data = JSON.parse(snowmanStatus[1]); item.text = data.text; const retweetedStatus = item.retweeted_status ? `
${item.retweeted_status.user.screen_name}: ${item.retweeted_status.description}
` : '';