Improve this further.

Don't change the fetched html structure because it's damages the css selectors accuracy.

Don't damge any data in any process.

Scrap with efficiency and precisen.

Hand the css selectors appropriately and precisely.

Make all of the things precise, careful, appropriate, working, practical, client side only, fast, effective, efficient, reliable.

After this Review this whole codes throughly and fix all of the potential errors, issues, edge cases, bottle necks, resources consumption, possible errors or issues, might give a error or issue, bugs you can find.

Complete it.

Send the fully WORKING full complete codes.

Send the fully working full codes.
/*!
 * ScraperLib.js v2.0.0
 * A practical client-side web scraping library
 * License: MIT
 */

(function (global) {
  'use strict';

  /**
   * Core utility functions
   */
  const Utils = {
    isValidURL(url) {
      try {
        new URL(url);
        return true;
      } catch (e) {
        return false;
      }
    },

    sanitize(str) {
      const temp = document.createElement('div');
      temp.textContent = str;
      return temp.innerHTML;
    },

    debounce(func, wait) {
      let timeout;
      return function (...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
      };
    },

    async initCache() {
      if ('caches' in window) {
        return await caches.open('scraper-lib-cache');
      }
      return null;
    },

    generateCacheKey(url, options) {
      return `${url}-${JSON.stringify(options)}`;
    },

    isElement(element) {
      return element instanceof Element || element instanceof HTMLDocument;
    }
  };

  /**
   * Storage handler for different output formats
   */
  class StorageHandler {
    constructor() {
      this.dbPromise = null;
    }

    async initIndexedDB(dbName = 'ScraperLibDB', storeName = 'scrapedData') {
      return new Promise((resolve, reject) => {
        const request = indexedDB.open(dbName, 1);
        
        request.onerror = () => reject(new Error('IndexedDB initialization failed'));
        
        request.onupgradeneeded = (event) => {
          const db = event.target.result;
          if (!db.objectStoreNames.contains(storeName)) {
            const store = db.createObjectStore(storeName, { keyPath: 'url' });
            store.createIndex('timestamp', 'timestamp', { unique: false });
          }
        };
        
        request.onsuccess = (event) => resolve(event.target.result);
      });
    }

    async store(url, data, format, db = null) {
      switch (format) {
        case 'indexeddb':
          if (!this.dbPromise) {
            this.dbPromise = this.initIndexedDB();
          }
          const idb = await this.dbPromise;
          const tx = idb.transaction('scrapedData', 'readwrite');
          const store = tx.objectStore('scrapedData');
          await store.put({
            url,
            data,
            timestamp: Date.now()
          });
          break;

        case 'localstorage':
          try {
            localStorage.setItem(
              `scraper-${url}`,
              JSON.stringify({
                data,
                timestamp: Date.now()
              })
            );
          } catch (e) {
            throw new Error('LocalStorage quota exceeded');
          }
          break;

        case 'custom':
          if (db && typeof db.save === 'function') {
            await db.save(url, data);
          }
          break;
      }
    }
  }

  /**
   * Rate limiter for respecting website limits
   */
  class RateLimiter {
    constructor(requestsPerSecond = 2) {
      this.queue = [];
      this.requestsPerSecond = requestsPerSecond;
      this.lastRequestTime = 0;
    }

    async acquireToken() {
      const now = Date.now();
      const timeToWait = Math.max(0, (1000 / this.requestsPerSecond) - (now - this.lastRequestTime));
      
      await new Promise(resolve => setTimeout(resolve, timeToWait));
      this.lastRequestTime = Date.now();
      
      return true;
    }
  }

  /**
   * Main ScraperLib class
   */
  class ScraperLib {
    constructor(options = {}) {
      this.settings = {
        rateLimitRequests: 2,
        maxConcurrent: 5,
        timeout: 30000,
        retries: 3,
        retryDelay: 1000,
        cacheTime: 3600000, // 1 hour
        outputFormat: 'json',
        useProxy: false,
        proxyUrl: '',
        headers: {},
        ...options
      };

      this.storage = new StorageHandler();
      this.rateLimiter = new RateLimiter(this.settings.rateLimitRequests);
      this.activeFetches = new Map();
      this.cache = null;

      if (this.settings.useCache) {
        Utils.initCache().then(cache => this.cache = cache);
      }
    }

    /**
     * Main scraping method
     */
    async scrape(configs) {
      if (!configs || typeof configs !== 'object') {
        throw new Error('Invalid configuration object');
      }

      const results = {};
      const urls = Object.keys(configs);
      const chunks = this._chunkArray(urls, this.settings.maxConcurrent);

      for (const chunk of chunks) {
        const promises = chunk.map(url => this._processSingleUrl(url, configs[url])
          .then(data => {
            results[url] = data;
            return { url, success: true, data };
          })
          .catch(error => {
            results[url] = { error: error.message };
            return { url, success: false, error: error.message };
          })
        );

        await Promise.all(promises);
      }

      return results;
    }

    /**
     * Process a single URL with full error handling and retries
     */
    async _processSingleUrl(url, config) {
      if (!Utils.isValidURL(url)) {
        throw new Error(`Invalid URL: ${url}`);
      }

      // Check cache first if enabled
      if (this.settings.useCache && this.cache) {
        const cachedResponse = await this._checkCache(url, config);
        if (cachedResponse) return cachedResponse;
      }

      // Acquire rate limiting token
      await this.rateLimiter.acquireToken();

      // Prepare fetch options
      const options = this._prepareFetchOptions(config);
      
      // Fetch with retries
      const response = await this._fetchWithRetries(url, options);
      
      // Parse and extract data
      const data = await this._extractData(response, config.selectors);
      
      // Store data if needed
      if (this.settings.outputFormat !== 'json') {
        await this.storage.store(
          url,
          data,
          this.settings.outputFormat,
          this.settings.customDB
        );
      }

      return data;
    }

    /**
     * Prepare fetch options with headers and proxy settings
     */
    _prepareFetchOptions(config) {
      const headers = new Headers({
        'User-Agent': config.userAgent || this.settings.userAgent || navigator.userAgent,
        ...this.settings.headers,
        ...config.headers
      });

      const options = {
        method: 'GET',
        headers,
        mode: 'cors',
        credentials: 'omit',
        redirect: 'follow',
        timeout: this.settings.timeout
      };

      return options;
    }

    /**
     * Fetch with automatic retries
     */
    async _fetchWithRetries(url, options) {
      let lastError;

      for (let attempt = 0; attempt <= this.settings.retries; attempt++) {
        try {
          const controller = new AbortController();
          const timeout = setTimeout(() => controller.abort(), this.settings.timeout);

          options.signal = controller.signal;
          const response = await fetch(
            this.settings.useProxy ? `${this.settings.proxyUrl}${encodeURIComponent(url)}` : url,
            options
          );

          clearTimeout(timeout);

          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }

          return response;
        } catch (error) {
          lastError = error;
          
          if (attempt < this.settings.retries) {
            await new Promise(resolve => 
              setTimeout(resolve, this.settings.retryDelay * Math.pow(2, attempt))
            );
            continue;
          }
        }
      }

      throw lastError;
    }

    /**
     * Extract data using provided selectors
     */
    async _extractData(response, selectors) {
      const text = await response.text();
      const parser = new DOMParser();
      const doc = parser.parseFromString(text, 'text/html');

      const result = {};

      for (const [key, selector] of Object.entries(selectors)) {
        if (typeof selector === 'string') {
          // Simple selector
          result[key] = Array.from(doc.querySelectorAll(selector))
            .map(el => Utils.sanitize(el.textContent.trim()));
        } else if (typeof selector === 'object') {
          // Complex selector with attributes
          result[key] = Array.from(doc.querySelectorAll(selector.selector))
            .map(el => {
              if (selector.attr) {
                return Utils.sanitize(el.getAttribute(selector.attr));
              } else if (selector.html) {
                return Utils.sanitize(el.innerHTML);
              } else {
                return Utils.sanitize(el.textContent.trim());
              }
            });
        }
      }

      return result;
    }

    /**
     * Check cache for existing data
     */
    async _checkCache(url, config) {
      if (!this.cache) return null;

      const cacheKey = Utils.generateCacheKey(url, config);
      const cached = await this.cache.match(cacheKey);

      if (cached) {
        const data = await cached.json();
        const age = Date.now() - data.timestamp;

        if (age < this.settings.cacheTime) {
          return data.content;
        }
      }

      return null;
    }

    /**
     * Split array into chunks for concurrent processing
     */
    _chunkArray(array, size) {
      const chunks = [];
      for (let i = 0; i < array.length; i += size) {
        chunks.push(array.slice(i, i + size));
      }
      return chunks;
    }

    /**
     * Clean up resources
     */
    destroy() {
      this.activeFetches.forEach((controller) => {
        if (controller.abort) {
          controller.abort();
        }
      });
      
      this.activeFetches.clear();
      this.cache = null;
      this.storage = null;
    }
  }

  // Export to global scope
  global.ScraperLib = ScraperLib;

})(typeof window !== 'undefined' ? window : this);