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

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(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&nbsp;/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function truncate(str: string, maxLen: number): string {
if (str.length <= maxLen) return str;
return str.slice(0, maxLen) + "...";
}