chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
217 lines (216 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SearchResultAnalyzer = void 0;
class SearchResultAnalyzer {
page;
constructor(page) {
this.page = page;
}
async analyzeSearchResults() {
return await this.page.evaluate(() => {
const patterns = [];
// Common search result containers
const containerSelectors = [
'[data-testid*="result"]',
'[class*="search-result"]',
'[class*="result-item"]',
'[class*="SearchResult"]',
'.g', // Google specific
'article',
'[role="article"]',
'li[class*="result"]'
];
// Analyze each container type
containerSelectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
if (elements.length < 2)
return; // Need multiple to be a pattern
// Sample first 3 elements
const samples = Array.from(elements).slice(0, 3);
const sampleTexts = samples.map(el => el.innerText.substring(0, 200));
// Analyze characteristics inline (since we're in evaluate context)
const characteristics = {};
// Check for common patterns
const datePatterns = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|\d{1,2}\/\d{1,2}|\d{4}|ago|yesterday|today)\b/i;
const sourcePatterns = /\b(CNN|BBC|Reuters|Times|Post|Journal|News|Blog)\b/i;
const pricePatterns = /\$\d+|\d+\.\d{2}|USD|EUR|£/;
const durationPatterns = /\d+:\d+|\d+ min|\d+ hour/i;
samples.forEach(el => {
const text = el.innerText;
if (datePatterns.test(text))
characteristics.hasDate = true;
if (sourcePatterns.test(text))
characteristics.hasSource = true;
if (pricePatterns.test(text))
characteristics.hasPrice = true;
if (durationPatterns.test(text))
characteristics.hasDuration = true;
// Check position - top results often have special styling
const rect = el.getBoundingClientRect();
if (rect.top < 300)
characteristics.isTopResult = true;
// Store container class for reference
if (!characteristics.containerClass && el.className) {
characteristics.containerClass = el.className.split(' ')[0];
}
});
// Determine type inline
const combinedText = sampleTexts.join(' ').toLowerCase();
let type = 'generic';
if (characteristics.hasDate && characteristics.hasSource) {
type = 'news';
}
else if (characteristics.hasPrice) {
type = 'product';
}
else if (characteristics.hasDuration) {
type = 'video';
}
else if (characteristics.isTopResult &&
(combinedText.includes('biography') ||
combinedText.includes('profile') ||
combinedText.includes('official') ||
combinedText.includes('wikipedia'))) {
type = 'bio';
}
// Calculate confidence inline
let confidence = 0.5;
if (elements.length >= 10)
confidence += 0.2;
else if (elements.length >= 5)
confidence += 0.1;
if (type === 'news' && characteristics.hasDate && characteristics.hasSource) {
confidence += 0.2;
}
else if (type === 'product' && characteristics.hasPrice) {
confidence += 0.2;
}
else if (type === 'video' && characteristics.hasDuration) {
confidence += 0.2;
}
if (characteristics.isTopResult && type !== 'news') {
confidence -= 0.2;
}
confidence = Math.max(0.1, Math.min(1, confidence));
patterns.push({
selector,
type,
confidence,
sampleTexts,
characteristics
});
});
// Sort by confidence
patterns.sort((a, b) => b.confidence - a.confidence);
// Generate recommendations inline
const recommendations = [];
const newsPatterns = patterns.filter(p => p.type === 'news' && p.confidence > 0.5);
const genericPatterns = patterns.filter(p => p.type === 'generic' && p.confidence > 0.5);
if (newsPatterns.length > 0) {
recommendations.push(`For news articles, use selector: ${newsPatterns[0].selector}`, 'These elements contain dates and news sources');
}
if (genericPatterns.length > 0 && newsPatterns.length === 0) {
recommendations.push(`Main search results found at: ${genericPatterns[0].selector}`, 'Consider filtering by text content to find specific result types');
}
// Warn about bio results
const bioPatterns = patterns.filter(p => p.type === 'bio');
if (bioPatterns.length > 0) {
recommendations.push('Note: Top results appear to be biography/profile links', 'For news articles, look further down the page');
}
return { patterns, recommendations };
});
}
analyzeElements(elements) {
const characteristics = {};
// Check for common patterns
const datePatterns = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|\d{1,2}\/\d{1,2}|\d{4}|ago|yesterday|today)\b/i;
const sourcePatterns = /\b(CNN|BBC|Reuters|Times|Post|Journal|News|Blog)\b/i;
const pricePatterns = /\$\d+|\d+\.\d{2}|USD|EUR|£/;
const durationPatterns = /\d+:\d+|\d+ min|\d+ hour/i;
elements.forEach(el => {
const text = el.innerText;
if (datePatterns.test(text))
characteristics.hasDate = true;
if (sourcePatterns.test(text))
characteristics.hasSource = true;
if (pricePatterns.test(text))
characteristics.hasPrice = true;
if (durationPatterns.test(text))
characteristics.hasDuration = true;
// Check position - top results often have special styling
const rect = el.getBoundingClientRect();
if (rect.top < 300)
characteristics.isTopResult = true;
// Store container class for reference
if (!characteristics.containerClass && el.className) {
characteristics.containerClass = el.className.split(' ')[0];
}
});
return characteristics;
}
determineResultType(characteristics, sampleTexts) {
const combinedText = sampleTexts.join(' ').toLowerCase();
// News articles typically have dates and sources
if (characteristics.hasDate && characteristics.hasSource) {
return 'news';
}
// Product results have prices
if (characteristics.hasPrice) {
return 'product';
}
// Video results have durations
if (characteristics.hasDuration) {
return 'video';
}
// Bio/profile results often appear at top and have specific keywords
if (characteristics.isTopResult &&
(combinedText.includes('biography') ||
combinedText.includes('profile') ||
combinedText.includes('official') ||
combinedText.includes('wikipedia'))) {
return 'bio';
}
return 'generic';
}
calculateConfidence(count, characteristics, type) {
let confidence = 0.5;
// More elements = higher confidence
if (count >= 10)
confidence += 0.2;
else if (count >= 5)
confidence += 0.1;
// Type-specific confidence boosts
if (type === 'news' && characteristics.hasDate && characteristics.hasSource) {
confidence += 0.2;
}
else if (type === 'product' && characteristics.hasPrice) {
confidence += 0.2;
}
else if (type === 'video' && characteristics.hasDuration) {
confidence += 0.2;
}
// Lower confidence for top results (often not what user wants)
if (characteristics.isTopResult && type !== 'news') {
confidence -= 0.2;
}
return Math.max(0.1, Math.min(1, confidence));
}
generateRecommendations(patterns) {
const recommendations = [];
const newsPatterns = patterns.filter(p => p.type === 'news' && p.confidence > 0.5);
const genericPatterns = patterns.filter(p => p.type === 'generic' && p.confidence > 0.5);
if (newsPatterns.length > 0) {
recommendations.push(`For news articles, use selector: ${newsPatterns[0].selector}`, 'These elements contain dates and news sources');
}
if (genericPatterns.length > 0 && newsPatterns.length === 0) {
recommendations.push(`Main search results found at: ${genericPatterns[0].selector}`, 'Consider filtering by text content to find specific result types');
}
// Warn about bio results
const bioPatterns = patterns.filter(p => p.type === 'bio');
if (bioPatterns.length > 0) {
recommendations.push('Note: Top results appear to be biography/profile links', 'For news articles, look further down the page');
}
return recommendations;
}
}
exports.SearchResultAnalyzer = SearchResultAnalyzer;