Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(route/xueqiu): fix xueqiu user & stock comments #17880

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions lib/routes/xueqiu/cookies.ts
Original file line number Diff line number Diff line change
@@ -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<string, any> | 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<string, any> | null = null) => {
const page = await getPuppeteerPage(cookie);

const data = await page.goto(url, {
waitUntil: 'domcontentloaded',
});

const res = await data?.json();
return res;
};
27 changes: 14 additions & 13 deletions lib/routes/xueqiu/stock-comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -17,7 +16,7 @@ export const route: Route = {
parameters: { id: '股票代码(需要带上交易所)' },
features: {
requireConfig: false,
requirePuppeteer: false,
requirePuppeteer: true,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
Expand All @@ -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}`,
Expand Down
46 changes: 20 additions & 26 deletions lib/routes/xueqiu/user.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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,
Expand Down Expand Up @@ -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 ? `<blockquote>${item.retweeted_status.user.screen_name}:&nbsp;${item.retweeted_status.description}</blockquote>` : '';
Expand Down
Loading