| | import axios from 'axios'; |
| |
|
| | |
| | |
| | |
| | class ProxyPool { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | constructor(options = {}) { |
| | |
| | this.targetCount = options.targetCount || 20; |
| | this.batchSize = options.batchSize || 20; |
| | this.testTimeout = options.testTimeout || 5000; |
| | this.requestTimeout = options.requestTimeout || 10000; |
| | this.targetUrl = options.targetUrl || 'https://www.notion.so'; |
| | this.concurrentRequests = options.concurrentRequests || 10; |
| | this.minThreshold = options.minThreshold || 5; |
| | this.checkInterval = options.checkInterval || 30000; |
| | this.proxyProtocol = options.proxyProtocol || 'http'; |
| | this.maxRefillAttempts = options.maxRefillAttempts || 20; |
| | this.retryDelay = options.retryDelay || 1000; |
| | this.useCache = options.useCache !== undefined ? options.useCache : true; |
| | this.cacheExpiry = options.cacheExpiry || 3600000; |
| | |
| | |
| | this.availableProxies = []; |
| | this.currentIndex = 0; |
| | this.isInitialized = false; |
| | this.isRefilling = false; |
| | this.checkTimer = null; |
| | this.proxyCache = new Map(); |
| | |
| | |
| | this.getProxy = this.getProxy.bind(this); |
| | this.removeProxy = this.removeProxy.bind(this); |
| | this.checkAndRefill = this.checkAndRefill.bind(this); |
| | } |
| | |
| | |
| | |
| | |
| | |
| | async initialize() { |
| | if (this.isInitialized) return; |
| | |
| | console.log(`初始化代理池,目标数量: ${this.targetCount}`); |
| | await this.refillProxies(); |
| | |
| | |
| | this.checkTimer = setInterval(this.checkAndRefill, this.checkInterval); |
| | |
| | this.isInitialized = true; |
| | console.log(`代理池初始化完成,当前可用代理数量: ${this.availableProxies.length}`); |
| | } |
| | |
| | |
| | |
| | |
| | stop() { |
| | if (this.checkTimer) { |
| | clearInterval(this.checkTimer); |
| | this.checkTimer = null; |
| | } |
| | console.log('代理池服务已停止'); |
| | } |
| | |
| | |
| | |
| | |
| | async checkAndRefill() { |
| | if (this.availableProxies.length <= this.minThreshold && !this.isRefilling) { |
| | console.log(`可用代理数量(${this.availableProxies.length})低于阈值(${this.minThreshold}),开始补充代理`); |
| | await this.refillProxies(); |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | async refillProxies() { |
| | if (this.isRefilling) return; |
| | |
| | this.isRefilling = true; |
| | console.log(`开始补充代理,当前数量: ${this.availableProxies.length},目标数量: ${this.targetCount}`); |
| | |
| | let attempts = 0; |
| | |
| | try { |
| | |
| | const neededProxies = this.targetCount - this.availableProxies.length; |
| | |
| | |
| | if (this.useCache && this.proxyCache.size > 0) { |
| | await this.tryUsingCachedProxies(neededProxies); |
| | } |
| | |
| | |
| | while (this.availableProxies.length < this.targetCount && attempts < this.maxRefillAttempts) { |
| | attempts++; |
| | |
| | console.log(`补充尝试 #${attempts},当前可用代理: ${this.availableProxies.length}/${this.targetCount}`); |
| | |
| | |
| | const remainingNeeded = this.targetCount - this.availableProxies.length; |
| | const batchSizeNeeded = Math.max(this.batchSize, remainingNeeded * 2); |
| | |
| | |
| | const proxies = await this.getProxiesFromProvider(batchSizeNeeded); |
| | |
| | if (proxies.length === 0) { |
| | console.log(`没有获取到代理,等待${this.retryDelay/1000}秒后重试...`); |
| | await new Promise(resolve => setTimeout(resolve, this.retryDelay)); |
| | continue; |
| | } |
| | |
| | |
| | const newProxies = this.filterExistingProxies(proxies); |
| | |
| | if (newProxies.length === 0) { |
| | console.log('所有获取的代理都已存在,继续获取新代理...'); |
| | continue; |
| | } |
| | |
| | |
| | const results = await this.testProxiesConcurrently(newProxies); |
| | |
| | |
| | this.addValidProxies(results); |
| | |
| | |
| | if (this.availableProxies.length >= this.targetCount) { |
| | break; |
| | } |
| | |
| | |
| | if (this.availableProxies.length < this.targetCount) { |
| | await new Promise(resolve => setTimeout(resolve, this.retryDelay)); |
| | } |
| | } |
| | } catch (error) { |
| | console.error('补充代理过程中出错:', error); |
| | } finally { |
| | this.isRefilling = false; |
| | |
| | if (this.availableProxies.length >= this.targetCount) { |
| | console.log(`代理补充完成,当前可用代理: ${this.availableProxies.length}/${this.targetCount}`); |
| | } else { |
| | console.log(`已达到最大尝试次数 ${this.maxRefillAttempts},当前可用代理: ${this.availableProxies.length}/${this.targetCount}`); |
| | } |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | async tryUsingCachedProxies(neededProxies) { |
| | const now = Date.now(); |
| | const cachedProxies = []; |
| | |
| | |
| | for (const [proxyKey, data] of this.proxyCache.entries()) { |
| | if (now - data.timestamp < this.cacheExpiry && data.valid) { |
| | cachedProxies.push(proxyKey); |
| | |
| | if (cachedProxies.length >= neededProxies) { |
| | break; |
| | } |
| | } |
| | } |
| | |
| | if (cachedProxies.length > 0) { |
| | console.log(`从缓存中找到 ${cachedProxies.length} 个可能可用的代理`); |
| | |
| | |
| | const results = await this.testProxiesConcurrently(cachedProxies); |
| | this.addValidProxies(results); |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | filterExistingProxies(proxies) { |
| | return proxies.filter(proxy => { |
| | const [ip, port] = proxy.split(':'); |
| | return !this.availableProxies.some(p => p.ip === ip && p.port === port); |
| | }); |
| | } |
| | |
| | |
| | |
| | |
| | |
| | addValidProxies(results) { |
| | for (const { proxy, result } of results) { |
| | if (result) { |
| | const [ip, port] = proxy.split(':'); |
| | |
| | |
| | if (!this.availableProxies.some(p => p.ip === ip && p.port === port)) { |
| | const proxyObj = { |
| | ip, |
| | port, |
| | protocol: this.proxyProtocol, |
| | full: `${this.proxyProtocol}://${proxy}`, |
| | addedAt: new Date().toISOString() |
| | }; |
| | |
| | this.availableProxies.push(proxyObj); |
| | |
| | |
| | if (this.useCache) { |
| | this.proxyCache.set(proxy, { |
| | valid: true, |
| | timestamp: Date.now() |
| | }); |
| | } |
| | |
| | console.log(`成功添加代理: ${proxyObj.full},当前可用代理: ${this.availableProxies.length}/${this.targetCount}`); |
| | |
| | if (this.availableProxies.length >= this.targetCount) { |
| | break; |
| | } |
| | } |
| | } else if (this.useCache) { |
| | |
| | this.proxyCache.set(proxy, { |
| | valid: false, |
| | timestamp: Date.now() |
| | }); |
| | } |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | async getProxiesFromProvider(count = null) { |
| | try { |
| | const requestCount = count || this.batchSize; |
| | const url = `https://proxy.scdn.io/api/get_proxy.php?protocol=${this.proxyProtocol}&count=${requestCount}`; |
| | console.log(`正在获取代理,URL: ${url}`); |
| | |
| | const response = await axios.get(url, { |
| | timeout: 10000, |
| | validateStatus: status => true |
| | }); |
| | |
| | if (response.data && response.data.code === 200) { |
| | console.log(`成功获取 ${response.data.data.count} 个代理`); |
| | return response.data.data.proxies; |
| | } else { |
| | console.error('获取代理失败:', response.data ? response.data.message : '未知错误'); |
| | return []; |
| | } |
| | } catch (error) { |
| | console.error('获取代理出错:', error.message); |
| | return []; |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | async testProxiesConcurrently(proxies) { |
| | const results = []; |
| | const remainingNeeded = this.targetCount - this.availableProxies.length; |
| | |
| | |
| | const concurrentRequests = Math.min(this.concurrentRequests * 2, 20); |
| | |
| | |
| | for (let i = 0; i < proxies.length; i += concurrentRequests) { |
| | const batch = proxies.slice(i, i + concurrentRequests); |
| | const promises = batch.map(proxy => { |
| | |
| | if (this.useCache && this.proxyCache.has(proxy)) { |
| | const cachedResult = this.proxyCache.get(proxy); |
| | const isFresh = (Date.now() - cachedResult.timestamp) < this.cacheExpiry; |
| | |
| | if (isFresh) { |
| | |
| | return Promise.resolve({ proxy, result: cachedResult.valid }); |
| | } |
| | } |
| | |
| | return this.testProxy(proxy) |
| | .then(result => ({ proxy, result })) |
| | .catch(() => ({ proxy, result: false })); |
| | }); |
| | |
| | const batchResults = await Promise.all(promises); |
| | results.push(...batchResults); |
| | |
| | |
| | const successCount = results.filter(item => item.result).length; |
| | if (successCount >= remainingNeeded) { |
| | break; |
| | } |
| | } |
| | |
| | return results; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | async testProxy(proxyUrl) { |
| | try { |
| | |
| | const proxyConfig = { |
| | host: proxyUrl.split(':')[0], |
| | port: parseInt(proxyUrl.split(':')[1]), |
| | protocol: this.proxyProtocol |
| | }; |
| | |
| | |
| | const response = await axios.get(this.targetUrl, { |
| | proxy: proxyConfig, |
| | headers: { |
| | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', |
| | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', |
| | 'Accept-Language': 'en-US,en;q=0.5', |
| | 'Connection': 'keep-alive', |
| | 'Upgrade-Insecure-Requests': '1' |
| | }, |
| | timeout: this.requestTimeout, |
| | validateStatus: status => true, |
| | maxRedirects: 10, |
| | followRedirect: true |
| | }); |
| | |
| | |
| | const isTargetContent = response.data && |
| | (typeof response.data === 'string') && |
| | (response.data.includes('notion') || |
| | response.data.includes('Notion')); |
| | |
| | const isValid = response.status === 200 && isTargetContent; |
| | |
| | if (isValid) { |
| | console.log(`代理 ${proxyUrl} 请求目标网站成功,状态码: ${response.status}`); |
| | } else { |
| | console.log(`代理 ${proxyUrl} 请求目标网站失败,状态码: ${response.status}`); |
| | } |
| | |
| | return isValid; |
| | } catch (error) { |
| | console.log(`代理 ${proxyUrl} 请求出错: ${error.message}`); |
| | return false; |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | getProxy() { |
| | if (this.availableProxies.length === 0) { |
| | console.log('没有可用代理'); |
| | return null; |
| | } |
| | |
| | |
| | const proxy = this.availableProxies[this.currentIndex]; |
| | this.currentIndex = (this.currentIndex + 1) % this.availableProxies.length; |
| | |
| | return proxy; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | removeProxy(ip, port) { |
| | const portStr = port.toString(); |
| | const initialLength = this.availableProxies.length; |
| | |
| | |
| | const proxyToRemove = this.availableProxies.find( |
| | proxy => proxy.ip === ip && proxy.port === portStr |
| | ); |
| | |
| | if (proxyToRemove) { |
| | |
| | if (this.useCache) { |
| | const proxyKey = `${ip}:${portStr}`; |
| | this.proxyCache.set(proxyKey, { valid: false, timestamp: Date.now() }); |
| | } |
| | } |
| | |
| | this.availableProxies = this.availableProxies.filter( |
| | proxy => !(proxy.ip === ip && proxy.port === portStr) |
| | ); |
| | |
| | |
| | if (this.currentIndex >= this.availableProxies.length && this.availableProxies.length > 0) { |
| | this.currentIndex = 0; |
| | } |
| | |
| | const removed = initialLength > this.availableProxies.length; |
| | |
| | if (removed) { |
| | console.log(`已移除代理 ${ip}:${port},当前可用代理: ${this.availableProxies.length}`); |
| | } else { |
| | console.log(`未找到要移除的代理 ${ip}:${port}`); |
| | } |
| | |
| | |
| | this.checkAndRefill(); |
| | |
| | return removed; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | getAllProxies() { |
| | return [...this.availableProxies]; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | getCount() { |
| | return this.availableProxies.length; |
| | } |
| | |
| | |
| | |
| | |
| | cleanupCache() { |
| | if (!this.useCache) return; |
| | |
| | const now = Date.now(); |
| | let cleanupCount = 0; |
| | |
| | for (const [key, data] of this.proxyCache.entries()) { |
| | if (now - data.timestamp > this.cacheExpiry) { |
| | this.proxyCache.delete(key); |
| | cleanupCount++; |
| | } |
| | } |
| | |
| | if (cleanupCount > 0) { |
| | console.log(`清理了 ${cleanupCount} 个过期的缓存代理`); |
| | } |
| | } |
| | } |
| |
|
| | |
| | async function example() { |
| | |
| | const proxyPool = new ProxyPool({ |
| | targetCount: 10, |
| | minThreshold: 3, |
| | checkInterval: 60000, |
| | targetUrl: 'https://www.notion.so', |
| | concurrentRequests: 15, |
| | useCache: true, |
| | maxRefillAttempts: 15, |
| | retryDelay: 1000 |
| | }); |
| | |
| | |
| | await proxyPool.initialize(); |
| | |
| | |
| | const proxy = proxyPool.getProxy(); |
| | console.log('获取到代理:', proxy); |
| | |
| | |
| | setTimeout(() => { |
| | if (proxy) { |
| | proxyPool.removeProxy(proxy.ip, proxy.port); |
| | } |
| | |
| | |
| | const allProxies = proxyPool.getAllProxies(); |
| | console.log(`当前所有代理(${allProxies.length}):`, allProxies); |
| | |
| | |
| | setTimeout(() => { |
| | proxyPool.stop(); |
| | console.log('代理池示例运行完毕'); |
| | }, 5000); |
| | }, 5000); |
| | } |
| |
|
| | |
| | if (typeof require !== 'undefined' && require.main === module) { |
| | example().catch(err => console.error('示例运行出错:', err)); |
| | } |
| |
|
| | |
| | export default ProxyPool; |
| | export const proxyPool = new ProxyPool(); |
| |
|