You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
4.2 KiB
119 lines
4.2 KiB
export interface SearchResultItem {
|
|
title: string;
|
|
url: string;
|
|
snippet: string;
|
|
displayUrl?: string;
|
|
}
|
|
|
|
export interface ParsedSearchResults {
|
|
items: SearchResultItem[];
|
|
totalResults?: string;
|
|
engine: string;
|
|
}
|
|
|
|
export function parseBingResults(html: string, maxResults: number, snippetLength: number): ParsedSearchResults {
|
|
const items: SearchResultItem[] = [];
|
|
|
|
// Bing 结果项在 <li class="b_algo"> 中
|
|
const itemRegex = /<li[^>]*class="[^"]*b_algo[^"]*"[^>]*>([\s\S]*?)<\/li>/gi;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = itemRegex.exec(html)) !== null && items.length < maxResults) {
|
|
const block = match[1];
|
|
|
|
// 标题和链接在 <h2><a href="..." ...>标题</a></h2>
|
|
const linkMatch = block.match(/<a[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
|
|
if (!linkMatch) continue;
|
|
|
|
const url = linkMatch[1];
|
|
const title = stripTags(linkMatch[2]).trim();
|
|
if (!title || !url) continue;
|
|
|
|
// 摘要在 <p class="b_lineclamp..."> 或 <div class="b_caption"><p>
|
|
const snippetMatch =
|
|
block.match(/<p[^>]*class="[^"]*b_lineclamp[^"]*"[^>]*>([\s\S]*?)<\/p>/i) ||
|
|
block.match(/<div[^>]*class="[^"]*b_caption[^"]*"[^>]*>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i);
|
|
const snippet = snippetMatch ? truncate(stripTags(snippetMatch[1]).trim(), snippetLength) : "";
|
|
|
|
// 显示URL
|
|
const displayMatch = block.match(/<cite[^>]*>([\s\S]*?)<\/cite>/i);
|
|
const displayUrl = displayMatch ? stripTags(displayMatch[1]).trim() : undefined;
|
|
|
|
items.push({ title, url, snippet, displayUrl });
|
|
}
|
|
|
|
return { items, engine: "bing" };
|
|
}
|
|
|
|
export function parseBaiduResults(html: string, maxResults: number, snippetLength: number): ParsedSearchResults {
|
|
const items: SearchResultItem[] = [];
|
|
|
|
// 百度结果项在 <div class="result ..."> 或 <div class="c-container ...">
|
|
const itemRegex = /<div[^>]*class="[^"]*(?:result|c-container)[^"]*"[^>]*>([\s\S]*?)(?=<div[^>]*class="[^"]*(?:result|c-container)|$)/gi;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = itemRegex.exec(html)) !== null && items.length < maxResults) {
|
|
const block = match[1];
|
|
|
|
const linkMatch = block.match(/<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
|
|
if (!linkMatch) continue;
|
|
|
|
const url = linkMatch[1];
|
|
const title = stripTags(linkMatch[2]).trim();
|
|
if (!title || !url) continue;
|
|
|
|
const snippetMatch = block.match(/<span[^>]*class="[^"]*content-right[^"]*"[^>]*>([\s\S]*?)<\/span>/i) ||
|
|
block.match(/<div[^>]*class="[^"]*c-abstract[^"]*"[^>]*>([\s\S]*?)<\/div>/i) ||
|
|
block.match(/<span[^>]*>([\s\S]*?)<\/span>/i);
|
|
const snippet = snippetMatch ? truncate(stripTags(snippetMatch[1]).trim(), snippetLength) : "";
|
|
|
|
items.push({ title, url, snippet });
|
|
}
|
|
|
|
return { items, engine: "baidu" };
|
|
}
|
|
|
|
export function parseGoogleResults(html: string, maxResults: number, snippetLength: number): ParsedSearchResults {
|
|
const items: SearchResultItem[] = [];
|
|
|
|
// Google 结果项在 <div class="g"> 中
|
|
const itemRegex = /<div[^>]*class="[^"]*\bg\b[^"]*"[^>]*>([\s\S]*?)<\/div>\s*(?=<div[^>]*class="[^"]*\bg\b|<div[^>]*id="foot"|$)/gi;
|
|
let match: RegExpExecArray | null;
|
|
|
|
while ((match = itemRegex.exec(html)) !== null && items.length < maxResults) {
|
|
const block = match[1];
|
|
|
|
const linkMatch = block.match(/<a[^>]*href="\/url\?q=(https?:\/\/[^&"]+)&[^"]*"[^>]*>([\s\S]*?)<\/a>/i) ||
|
|
block.match(/<a[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
|
|
if (!linkMatch) continue;
|
|
|
|
const url = decodeURIComponent(linkMatch[1]);
|
|
const title = stripTags(linkMatch[2]).trim();
|
|
if (!title || !url) continue;
|
|
|
|
const snippetMatch = block.match(/<span[^>]*>([\s\S]*?)<\/span>/i);
|
|
const snippet = snippetMatch ? truncate(stripTags(snippetMatch[1]).trim(), snippetLength) : "";
|
|
|
|
items.push({ title, url, snippet });
|
|
}
|
|
|
|
return { items, engine: "google" };
|
|
}
|
|
|
|
function stripTags(html: string): string {
|
|
return html
|
|
.replace(/<[^>]+>/g, "")
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, "'")
|
|
.replace(/ /g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
}
|
|
|
|
function truncate(str: string, maxLen: number): string {
|
|
if (str.length <= maxLen) return str;
|
|
return str.slice(0, maxLen) + "...";
|
|
}
|
|
|