103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
import { Page } from 'playwright';
|
|
import { getLogger } from '@stock-bot/logger';
|
|
import { Browser } from './browser';
|
|
import type { ScrapingResult } from './types';
|
|
|
|
interface TabInfo {
|
|
page: Page;
|
|
contextId: string;
|
|
}
|
|
|
|
export class BrowserTabManager {
|
|
private tabs: Map<string, TabInfo> = new Map();
|
|
private logger = getLogger('browser-tab-manager');
|
|
|
|
async createTab(url?: string): Promise<{ page: Page; tabId: string }> {
|
|
const tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
const { page, contextId } = await Browser.createPageWithProxy(url || 'about:blank');
|
|
|
|
this.tabs.set(tabId, { page, contextId });
|
|
this.logger.debug('Tab created', { tabId, url });
|
|
|
|
return { page, tabId };
|
|
}
|
|
|
|
async createTabWithProxy(
|
|
url: string,
|
|
proxy: string
|
|
): Promise<{ page: Page; tabId: string; contextId: string }> {
|
|
const tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
const { page, contextId } = await Browser.createPageWithProxy(url, proxy);
|
|
|
|
this.tabs.set(tabId, { page, contextId });
|
|
this.logger.debug('Tab with proxy created', { tabId, url, proxy });
|
|
|
|
return { page, tabId, contextId };
|
|
}
|
|
|
|
async scrapeUrlsWithProxies<T>(
|
|
urlProxyPairs: Array<{ url: string; proxy: string }>,
|
|
extractor: (page: Page) => Promise<T>,
|
|
options: { concurrency?: number } = {}
|
|
): Promise<ScrapingResult<T>[]> {
|
|
const { concurrency = 3 } = options;
|
|
const results: ScrapingResult<T>[] = [];
|
|
|
|
for (let i = 0; i < urlProxyPairs.length; i += concurrency) {
|
|
const batch = urlProxyPairs.slice(i, i + concurrency);
|
|
|
|
const batchPromises = batch.map(async ({ url, proxy }) => {
|
|
let tabId: string | undefined;
|
|
|
|
try {
|
|
const result = await this.createTabWithProxy(url, proxy);
|
|
tabId = result.tabId;
|
|
|
|
const data = await extractor(result.page);
|
|
|
|
return {
|
|
data,
|
|
url,
|
|
success: true,
|
|
} as ScrapingResult<T>;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
|
|
return {
|
|
data: null as T,
|
|
url,
|
|
success: false,
|
|
error: errorMessage,
|
|
} as ScrapingResult<T>;
|
|
} finally {
|
|
if (tabId) {
|
|
await this.closeTab(tabId);
|
|
}
|
|
}
|
|
});
|
|
|
|
const batchResults = await Promise.all(batchPromises);
|
|
results.push(...batchResults);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async closeTab(tabId: string): Promise<void> {
|
|
const tab = this.tabs.get(tabId);
|
|
if (tab) {
|
|
await tab.page.close();
|
|
await Browser.closeContext(tab.contextId);
|
|
this.tabs.delete(tabId);
|
|
this.logger.debug('Tab closed', { tabId });
|
|
}
|
|
}
|
|
|
|
getTabCount(): number {
|
|
return this.tabs.size;
|
|
}
|
|
|
|
getAllTabIds(): string[] {
|
|
return Array.from(this.tabs.keys());
|
|
}
|
|
}
|