From 03f5ce91cbd2b04a5ec5f76ae62cd001b1e018e6 Mon Sep 17 00:00:00 2001 From: asqwe1 <40950663+asqwe1@users.noreply.github.com> Date: Thu, 1 May 2025 22:45:49 +0800 Subject: [PATCH] feat(route): add new route m.sohu.com (#18965) * add m.sohu.com route * modify mobiles.ts * modify mobile.ts * modify mobile.ts use WapHomeRenderData * modify mobile.ts * modify mobile.ts * modify mobile.ts * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * modify process more pubdate, handling video/picture post * fix local variable now is not used * modify ts * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update lib/routes/sohu/mobile.ts Co-authored-by: Tony * Update mobile.ts * Update mobile.ts --------- --- lib/routes/sohu/mobile.ts | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 lib/routes/sohu/mobile.ts diff --git a/lib/routes/sohu/mobile.ts b/lib/routes/sohu/mobile.ts new file mode 100644 index 0000000000..49dc699263 --- /dev/null +++ b/lib/routes/sohu/mobile.ts @@ -0,0 +1,150 @@ +import { Route } from '@/types'; +import ofetch from '@/utils/ofetch'; +import cache from '@/utils/cache'; +import * as cheerio from 'cheerio'; +import logger from '@/utils/logger'; +import { parseDate } from '@/utils/parse-date'; + +export const route: Route = { + path: '/mobile', + categories: ['new-media'], + example: '/sohu/mobile', + parameters: {}, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['m.sohu.com/limit'], + target: '/mobile', + }, + ], + name: '首页新闻', + maintainers: ['asqwe1'], + handler, + description: '订阅手机搜狐网的首页新闻', +}; + +async function handler() { + const response = await ofetch('https://m.sohu.com/limit'); + // 从HTML中提取JSON数据 + const $ = cheerio.load(response); + const jsonScript = $('script:contains("WapHomeRenderData")').text(); + const jsonMatch = jsonScript?.match(/window\.WapHomeRenderData\s*=\s*({.*})/s); + if (!jsonMatch?.[1]) { + throw new Error('WapHomeRenderData 数据未找到'); + } + const renderData = JSON.parse(jsonMatch[1]); + const list = extractPlateBlockNewsLists(renderData) + .filter((item) => item.id && item.url?.startsWith('//')) + .map((item) => ({ + title: item.title, + link: new URL(item.url.split('?')[0], 'https://m.sohu.com').href, + })); + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + try { + const detailResp = await ofetch(item.link); + const $d = cheerio.load(detailResp); + + let description = ''; + let pubDate = ''; + if (item.link.includes('/xtopic/')) { + const fullArticleUrl = $d('.tpl-top-text-item-content').prop('href')?.split('?')[0]?.replace('www.sohu.com/', 'm.sohu.com/'); + const response = await ofetch(`https:${fullArticleUrl}`); + const $ = cheerio.load(response); + description = getDescription($); + pubDate = extractPubDate($); + } + + return { + ...item, + description: description || getDescription($d) || item.title, + pubDate: pubDate || extractPubDate($d), + }; + } catch (error) { + logger.error(`获取详情失败: ${item.link}`, error); + return item; + } + }) + ) + ); + return { + title: '手机搜狐新闻', + link: 'https://m.sohu.com/limit', + item: items.filter(Boolean), + }; +} + +function extractPlateBlockNewsLists(jsonData: any) { + const result: any[] = []; + for (const key of Object.keys(jsonData)) { + if (key.startsWith('PlateBlock')) { + const plateBlock = jsonData[key]; + // 处理新闻列表 + if (plateBlock?.param?.newsData?.list) { + result.push(...plateBlock.param.newsData.list); + } + // 处理焦点图数据 + if (plateBlock?.param?.focusData?.list) { + result.push(...plateBlock.param.focusData.list); + } + if (plateBlock?.param?.feedData0?.list) { + result.push(...plateBlock.param.feedData0.list); + } + if (plateBlock?.param?.feedData1?.list) { + result.push(...plateBlock.param.feedData1.list); + } + } + } + return result; +} + +function extractPubDate($: cheerio.CheerioAPI): string { + const timeElements = ['.time', '#videoPublicTime']; + let date; + for (const selector of timeElements) { + const text = $(selector).first().text().trim(); + if (!text) { + continue; + } + + date = parseDate(text); + if (date) { + return date; + } + } + + const img = $('meta[name="share_img"]') + .toArray() + .map((i) => $(i).attr('src')) + .find((i) => i.includes('images01')); + date = img ? parseDate(img?.match(/images01\/(\d{8})\//i)?.[1]) : ''; + if (date) { + return date; + } +} + + +function getDescription($: cheerio.CheerioAPI) : string | null { + const content = $('#articleContent'); + if (content.length) { + return content.first().html()?.trim(); + } + const video = $('#videoPlayer div'); + if (video.length) { + return ``; + } + const imageList = $('script:contains("imageList")').text(); + if (imageList) { + const list = JSON.parse(imageList.match(/(\[.*\])/s)?.[1].replaceAll(/("description": ".*"),/g, '$1') || '[]'); + return list.map((item: any) => `${item.description || ''}`).join(''); + } + return ''; +}