活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Python与PhantomJS结合使用后如何正确释放资源避免内存泄漏的实用指南

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-23 23:10:01 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1. 引言:Python与PhantomJS的结合使用

PhantomJS是一个无头浏览器,它提供了一个可编程的浏览器环境,但没有图形用户界面。Python开发者经常使用PhantomJS进行网页自动化测试、屏幕截图、网页数据抓取等任务。通常,我们通过Selenium WebDriver来控制PhantomJS。

然而,Python与PhantomJS结合使用时,如果不正确管理资源,很容易导致内存泄漏问题。内存泄漏不仅会影响程序的性能,还可能导致系统不稳定甚至崩溃。本文将详细介绍如何在使用Python和PhantomJS时正确释放资源,避免内存泄漏。

2. 理解内存泄漏的根本原因

在Python与PhantomJS结合使用时,内存泄漏主要由以下几个原因造成:

2.1 PhantomJS进程未正确关闭

当PhantomJS实例创建后,如果没有正确关闭,进程会继续在后台运行,占用系统资源。

2.2 WebDriver对象未正确释放

Selenium WebDriver对象与PhantomJS进程相连,如果未正确释放,会导致PhantomJS进程无法正常关闭。

2.3 循环引用

Python中的循环引用可能导致垃圾回收器无法及时回收对象,从而造成内存泄漏。

2.4 资源未及时释放

如文件句柄、网络连接等资源未及时释放,也会导致内存泄漏。

3. 正确使用PhantomJS与Python的最佳实践

3.1 使用上下文管理器(with语句)

Python的上下文管理器(with语句)是管理资源的最佳方式,它可以确保资源在使用后被正确释放,即使在发生异常的情况下也是如此。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. def phantomjs_context_manager():
  4.     # 设置PhantomJS的路径
  5.     service_args = [
  6.         '--proxy-type=http',
  7.         '--proxy=proxy.example.com:8080',
  8.     ]
  9.    
  10.     # 创建Desired Capabilities
  11.     dcap = dict(DesiredCapabilities.PHANTOMJS)
  12.     dcap["phantomjs.page.settings.userAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  13.    
  14.     # 使用with语句创建WebDriver
  15.     with webdriver.PhantomJS(
  16.         desired_capabilities=dcap,
  17.         service_args=service_args,
  18.         executable_path='/path/to/phantomjs'
  19.     ) as driver:
  20.         # 使用driver进行操作
  21.         driver.get("https://www.example.com")
  22.         print(driver.title)
  23.         # 其他操作...
  24.         
  25.     # 当with块结束时,driver会自动调用quit()方法,释放资源
复制代码

3.2 显式调用quit()方法

如果不使用上下文管理器,应该显式调用quit()方法来关闭WebDriver和PhantomJS进程。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. def explicit_quit_example():
  4.     # 初始化PhantomJS WebDriver
  5.     dcap = dict(DesiredCapabilities.PHANTOMJS)
  6.     dcap["phantomjs.page.settings.userAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  7.    
  8.     driver = webdriver.PhantomJS(
  9.         desired_capabilities=dcap,
  10.         executable_path='/path/to/phantomjs'
  11.     )
  12.    
  13.     try:
  14.         # 使用driver进行操作
  15.         driver.get("https://www.example.com")
  16.         print(driver.title)
  17.         # 其他操作...
  18.     finally:
  19.         # 确保driver被正确关闭
  20.         driver.quit()
复制代码

3.3 使用try-finally块确保资源释放

即使在操作过程中发生异常,try-finally块也能确保资源被正确释放。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. def try_finally_example():
  4.     driver = None
  5.     try:
  6.         # 初始化PhantomJS WebDriver
  7.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  8.         driver = webdriver.PhantomJS(
  9.             desired_capabilities=dcap,
  10.             executable_path='/path/to/phantomjs'
  11.         )
  12.         
  13.         # 使用driver进行操作
  14.         driver.get("https://www.example.com")
  15.         print(driver.title)
  16.         # 其他操作...
  17.         
  18.     except Exception as e:
  19.         print(f"发生错误: {e}")
  20.     finally:
  21.         # 确保driver被正确关闭
  22.         if driver is not None:
  23.             driver.quit()
复制代码

3.4 使用装饰器管理资源

创建一个装饰器来自动管理WebDriver的生命周期。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import functools
  4. def webdriver_manager(func):
  5.     @functools.wraps(func)
  6.     def wrapper(*args, **kwargs):
  7.         driver = None
  8.         try:
  9.             # 初始化PhantomJS WebDriver
  10.             dcap = dict(DesiredCapabilities.PHANTOMJS)
  11.             driver = webdriver.PhantomJS(
  12.                 desired_capabilities=dcap,
  13.                 executable_path='/path/to/phantomjs'
  14.             )
  15.             
  16.             # 将driver作为参数传递给被装饰的函数
  17.             return func(driver, *args, **kwargs)
  18.         except Exception as e:
  19.             print(f"发生错误: {e}")
  20.             raise
  21.         finally:
  22.             # 确保driver被正确关闭
  23.             if driver is not None:
  24.                 driver.quit()
  25.     return wrapper
  26. # 使用装饰器
  27. @webdriver_manager
  28. def scrape_website(driver, url):
  29.     driver.get(url)
  30.     print(driver.title)
  31.     # 其他操作...
  32.     return driver.page_source
  33. # 调用函数
  34. html_content = scrape_website("https://www.example.com")
复制代码

4. 高级资源管理技术

4.1 使用对象池管理WebDriver实例

对于需要频繁创建和销毁WebDriver实例的应用程序,可以使用对象池技术来重用实例,减少资源创建和销毁的开销。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import queue
  4. import threading
  5. import time
  6. class WebDriverPool:
  7.     def __init__(self, max_size=5):
  8.         self.max_size = max_size
  9.         self.pool = queue.Queue(max_size)
  10.         self.lock = threading.Lock()
  11.         self.current_size = 0
  12.         
  13.     def get_driver(self):
  14.         with self.lock:
  15.             if not self.pool.empty() or self.current_size >= self.max_size:
  16.                 return self.pool.get()
  17.             
  18.             # 创建新的WebDriver实例
  19.             dcap = dict(DesiredCapabilities.PHANTOMJS)
  20.             dcap["phantomjs.page.settings.userAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  21.             
  22.             driver = webdriver.PhantomJS(
  23.                 desired_capabilities=dcap,
  24.                 executable_path='/path/to/phantomjs'
  25.             )
  26.             self.current_size += 1
  27.             return driver
  28.    
  29.     def return_driver(self, driver):
  30.         try:
  31.             # 重置driver状态
  32.             driver.delete_all_cookies()
  33.             driver.get("about:blank")
  34.             
  35.             # 将driver返回到池中
  36.             self.pool.put(driver, block=False)
  37.         except queue.Full:
  38.             # 如果池已满,关闭driver
  39.             driver.quit()
  40.             with self.lock:
  41.                 self.current_size -= 1
  42.    
  43.     def close_all(self):
  44.         with self.lock:
  45.             while not self.pool.empty():
  46.                 driver = self.pool.get()
  47.                 driver.quit()
  48.             self.current_size = 0
  49. # 使用对象池
  50. def use_webdriver_pool():
  51.     pool = WebDriverPool(max_size=3)
  52.    
  53.     try:
  54.         # 从池中获取driver
  55.         driver = pool.get_driver()
  56.         
  57.         try:
  58.             # 使用driver进行操作
  59.             driver.get("https://www.example.com")
  60.             print(driver.title)
  61.             # 其他操作...
  62.         finally:
  63.             # 将driver返回到池中
  64.             pool.return_driver(driver)
  65.             
  66.     finally:
  67.         # 关闭池中的所有driver
  68.         pool.close_all()
复制代码

4.2 使用弱引用管理资源

Python的weakref模块可以创建弱引用,这些引用不会增加对象的引用计数,从而允许垃圾回收器在需要时回收对象。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import weakref
  4. import gc
  5. class WebDriverManager:
  6.     _instances = weakref.WeakSet()
  7.    
  8.     def __init__(self):
  9.         # 初始化PhantomJS WebDriver
  10.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  11.         self.driver = webdriver.PhantomJS(
  12.             desired_capabilities=dcap,
  13.             executable_path='/path/to/phantomjs'
  14.         )
  15.         WebDriverManager._instances.add(self)
  16.    
  17.     def __del__(self):
  18.         if hasattr(self, 'driver') and self.driver is not None:
  19.             self.driver.quit()
  20.    
  21.     @classmethod
  22.     def cleanup(cls):
  23.         # 强制垃圾回收
  24.         gc.collect()
  25.         
  26.         # 关闭所有剩余的driver
  27.         for instance in list(cls._instances):
  28.             if hasattr(instance, 'driver') and instance.driver is not None:
  29.                 instance.driver.quit()
  30. # 使用弱引用管理
  31. def use_weakref_example():
  32.     manager = WebDriverManager()
  33.     driver = manager.driver
  34.    
  35.     try:
  36.         # 使用driver进行操作
  37.         driver.get("https://www.example.com")
  38.         print(driver.title)
  39.         # 其他操作...
  40.     finally:
  41.         # 显式清理
  42.         WebDriverManager.cleanup()
复制代码

4.3 使用进程隔离

对于复杂的爬虫或自动化任务,可以考虑使用进程隔离,将每个任务放在单独的进程中执行,这样即使一个任务发生内存泄漏,也不会影响其他任务或主进程。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import multiprocessing
  4. import time
  5. def phantomjs_task(url):
  6.     """在单独的进程中执行的任务"""
  7.     driver = None
  8.     try:
  9.         # 初始化PhantomJS WebDriver
  10.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  11.         driver = webdriver.PhantomJS(
  12.             desired_capabilities=dcap,
  13.             executable_path='/path/to/phantomjs'
  14.         )
  15.         
  16.         # 使用driver进行操作
  17.         driver.get(url)
  18.         title = driver.title
  19.         print(f"页面标题: {title}")
  20.         
  21.         # 返回结果
  22.         return {
  23.             'url': url,
  24.             'title': title,
  25.             'success': True
  26.         }
  27.     except Exception as e:
  28.         print(f"处理 {url} 时发生错误: {e}")
  29.         return {
  30.             'url': url,
  31.             'error': str(e),
  32.             'success': False
  33.         }
  34.     finally:
  35.         # 确保driver被正确关闭
  36.         if driver is not None:
  37.             driver.quit()
  38. def run_with_process_isolation(urls):
  39.     """使用进程隔离运行任务"""
  40.     # 创建进程池
  41.     pool = multiprocessing.Pool(processes=min(len(urls), multiprocessing.cpu_count()))
  42.    
  43.     try:
  44.         # 提交任务到进程池
  45.         results = pool.map(phantomjs_task, urls)
  46.         
  47.         # 处理结果
  48.         for result in results:
  49.             if result['success']:
  50.                 print(f"成功处理 {result['url']}: {result['title']}")
  51.             else:
  52.                 print(f"处理 {result['url']} 失败: {result['error']}")
  53.                
  54.         return results
  55.     finally:
  56.         # 关闭进程池
  57.         pool.close()
  58.         pool.join()
  59. # 使用进程隔离
  60. if __name__ == '__main__':
  61.     urls = [
  62.         "https://www.example.com",
  63.         "https://www.example.org",
  64.         "https://www.example.net"
  65.     ]
  66.     results = run_with_process_isolation(urls)
复制代码

5. 监控和诊断内存泄漏

5.1 使用内存分析工具

Python提供了多种工具来监控和诊断内存泄漏,如memory_profiler、objgraph等。
  1. # 安装memory_profiler: pip install memory_profiler
  2. # 安装objgraph: pip install objgraph
  3. from selenium import webdriver
  4. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  5. import time
  6. import objgraph
  7. import gc
  8. def analyze_memory():
  9.     # 初始化PhantomJS WebDriver
  10.     dcap = dict(DesiredCapabilities.PHANTOMJS)
  11.     driver = webdriver.PhantomJS(
  12.         desired_capabilities=dcap,
  13.         executable_path='/path/to/phantomjs'
  14.     )
  15.    
  16.     try:
  17.         # 使用driver进行操作
  18.         for i in range(5):
  19.             driver.get(f"https://www.example.com?page={i}")
  20.             print(driver.title)
  21.             
  22.             # 强制垃圾回收
  23.             gc.collect()
  24.             
  25.             # 显示内存中的对象
  26.             print(f"\n=== 第 {i+1} 次循环后的对象统计 ===")
  27.             objgraph.show_most_common_types(limit=10)
  28.             
  29.             # 如果发现WebDriver对象数量增加,可能存在内存泄漏
  30.             webdriver_count = len(objgraph.by_type('webdriver.PhantomJS'))
  31.             print(f"PhantomJS WebDriver对象数量: {webdriver_count}")
  32.             
  33.             time.sleep(1)
  34.     finally:
  35.         # 确保driver被正确关闭
  36.         driver.quit()
  37.         
  38.         # 再次检查对象
  39.         gc.collect()
  40.         print("\n=== 关闭driver后的对象统计 ===")
  41.         objgraph.show_most_common_types(limit=10)
  42.         webdriver_count = len(objgraph.by_type('webdriver.PhantomJS'))
  43.         print(f"PhantomJS WebDriver对象数量: {webdriver_count}")
  44. # 运行内存分析
  45. analyze_memory()
复制代码

5.2 使用日志记录资源使用情况

通过记录资源的使用情况,可以帮助识别内存泄漏的模式。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import logging
  4. import psutil
  5. import os
  6. import time
  7. # 配置日志
  8. logging.basicConfig(
  9.     level=logging.INFO,
  10.     format='%(asctime)s - %(levelname)s - %(message)s',
  11.     handlers=[
  12.         logging.FileHandler('resource_usage.log'),
  13.         logging.StreamHandler()
  14.     ]
  15. )
  16. def log_resource_usage():
  17.     """记录当前进程的资源使用情况"""
  18.     process = psutil.Process(os.getpid())
  19.     memory_info = process.memory_info()
  20.     logging.info(f"内存使用: RSS={memory_info.rss / 1024 / 1024:.2f}MB, VMS={memory_info.vms / 1024 / 1024:.2f}MB")
  21.     logging.info(f"CPU使用率: {process.cpu_percent()}%")
  22.     logging.info(f"线程数: {process.num_threads()}")
  23. def monitored_phantomjs_usage():
  24.     """监控资源使用情况的PhantomJS示例"""
  25.     driver = None
  26.     try:
  27.         logging.info("初始化PhantomJS WebDriver")
  28.         log_resource_usage()
  29.         
  30.         # 初始化PhantomJS WebDriver
  31.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  32.         driver = webdriver.PhantomJS(
  33.             desired_capabilities=dcap,
  34.             executable_path='/path/to/phantomjs'
  35.         )
  36.         
  37.         logging.info("PhantomJS WebDriver初始化完成")
  38.         log_resource_usage()
  39.         
  40.         # 使用driver进行操作
  41.         for i in range(5):
  42.             logging.info(f"加载页面 {i+1}")
  43.             driver.get(f"https://www.example.com?page={i}")
  44.             print(driver.title)
  45.             
  46.             # 记录资源使用情况
  47.             log_resource_usage()
  48.             
  49.             time.sleep(1)
  50.     except Exception as e:
  51.         logging.error(f"发生错误: {e}")
  52.     finally:
  53.         if driver is not None:
  54.             logging.info("关闭PhantomJS WebDriver")
  55.             driver.quit()
  56.             log_resource_usage()
  57. # 运行监控示例
  58. monitored_phantomjs_usage()
复制代码

6. 常见陷阱和解决方案

6.1 循环引用导致的内存泄漏

当对象之间存在循环引用时,Python的垃圾回收器可能无法及时回收这些对象。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. class PageScraper:
  4.     def __init__(self):
  5.         # 初始化PhantomJS WebDriver
  6.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  7.         self.driver = webdriver.PhantomJS(
  8.             desired_capabilities=dcap,
  9.             executable_path='/path/to/phantomjs'
  10.         )
  11.         self.pages = []
  12.    
  13.     def scrape_page(self, url):
  14.         self.driver.get(url)
  15.         page_data = {
  16.             'url': url,
  17.             'title': self.driver.title,
  18.             'scraper': self  # 创建循环引用
  19.         }
  20.         self.pages.append(page_data)
  21.         return page_data
  22.    
  23.     def close(self):
  24.         if hasattr(self, 'driver') and self.driver is not None:
  25.             self.driver.quit()
  26.             self.driver = None
  27.    
  28.     def __del__(self):
  29.         self.close()
  30. # 错误示例:循环引用导致内存泄漏
  31. def circular_reference_example():
  32.     scraper = PageScraper()
  33.     try:
  34.         scraper.scrape_page("https://www.example.com")
  35.         scraper.scrape_page("https://www.example.org")
  36.         
  37.         # 即使调用close,由于循环引用,对象可能不会被垃圾回收
  38.         scraper.close()
  39.     finally:
  40.         # 显式删除引用
  41.         del scraper
  42. # 正确示例:避免循环引用
  43. def no_circular_reference_example():
  44.     scraper = PageScraper()
  45.     try:
  46.         # 使用弱引用避免循环引用
  47.         import weakref
  48.         scraper_ref = weakref.ref(scraper)
  49.         
  50.         scraper.scrape_page("https://www.example.com")
  51.         scraper.scrape_page("https://www.example.org")
  52.         
  53.         # 在存储页面数据时,不存储对scraper的引用
  54.         for page in scraper.pages:
  55.             if 'scraper' in page:
  56.                 del page['scraper']
  57.         
  58.         scraper.close()
  59.     finally:
  60.         del scraper
复制代码

6.2 全局变量导致的内存泄漏

全局变量会一直存在于内存中,直到程序结束,如果不正确管理,可能导致内存泄漏。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. # 全局变量
  4. GLOBAL_DRIVER = None
  5. def init_global_driver():
  6.     global GLOBAL_DRIVER
  7.     if GLOBAL_DRIVER is None:
  8.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  9.         GLOBAL_DRIVER = webdriver.PhantomJS(
  10.             desired_capabilities=dcap,
  11.             executable_path='/path/to/phantomjs'
  12.         )
  13. def use_global_driver():
  14.     init_global_driver()
  15.     GLOBAL_DRIVER.get("https://www.example.com")
  16.     print(GLOBAL_DRIVER.title)
  17. # 错误示例:全局变量未正确释放
  18. def bad_global_example():
  19.     use_global_driver()
  20.     # GLOBAL_DRIVER仍然存在,占用资源
  21.     # 如果多次调用此函数,可能会导致资源浪费
  22. # 正确示例:正确管理全局变量
  23. def good_global_example():
  24.     try:
  25.         use_global_driver()
  26.     finally:
  27.         # 显式释放全局变量
  28.         global GLOBAL_DRIVER
  29.         if GLOBAL_DRIVER is not None:
  30.             GLOBAL_DRIVER.quit()
  31.             GLOBAL_DRIVER = None
复制代码

6.3 异常处理不当导致的资源泄漏

异常处理不当可能导致资源无法正确释放。
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. # 错误示例:异常处理不当
  4. def bad_exception_handling():
  5.     driver = None
  6.     try:
  7.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  8.         driver = webdriver.PhantomJS(
  9.             desired_capabilities=dcap,
  10.             executable_path='/path/to/phantomjs'
  11.         )
  12.         
  13.         driver.get("https://www.example.com")
  14.         
  15.         # 模拟一个错误
  16.         if True:
  17.             raise ValueError("模拟错误")
  18.             
  19.         print(driver.title)
  20.     except ValueError as e:
  21.         print(f"捕获到错误: {e}")
  22.         # 错误:没有释放driver资源
  23.    
  24. # 正确示例:正确处理异常
  25. def good_exception_handling():
  26.     driver = None
  27.     try:
  28.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  29.         driver = webdriver.PhantomJS(
  30.             desired_capabilities=dcap,
  31.             executable_path='/path/to/phantomjs'
  32.         )
  33.         
  34.         driver.get("https://www.example.com")
  35.         
  36.         # 模拟一个错误
  37.         if True:
  38.             raise ValueError("模拟错误")
  39.             
  40.         print(driver.title)
  41.     except ValueError as e:
  42.         print(f"捕获到错误: {e}")
  43.     finally:
  44.         # 确保driver被正确释放
  45.         if driver is not None:
  46.             driver.quit()
复制代码

7. 实际应用案例

7.1 网页爬虫中的资源管理
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import time
  4. import random
  5. import logging
  6. from urllib.parse import urljoin, urlparse
  7. import os
  8. # 配置日志
  9. logging.basicConfig(
  10.     level=logging.INFO,
  11.     format='%(asctime)s - %(levelname)s - %(message)s'
  12. )
  13. class WebCrawler:
  14.     def __init__(self, base_url, max_pages=10, output_dir='output'):
  15.         self.base_url = base_url
  16.         self.max_pages = max_pages
  17.         self.output_dir = output_dir
  18.         self.visited_urls = set()
  19.         
  20.         # 创建输出目录
  21.         os.makedirs(self.output_dir, exist_ok=True)
  22.         
  23.         # 初始化PhantomJS WebDriver
  24.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  25.         dcap["phantomjs.page.settings.userAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  26.         
  27.         self.driver = webdriver.PhantomJS(
  28.             desired_capabilities=dcap,
  29.             executable_path='/path/to/phantomjs'
  30.         )
  31.         
  32.         # 设置页面加载超时
  33.         self.driver.set_page_load_timeout(30)
  34.         
  35.         logging.info("Web爬虫初始化完成")
  36.    
  37.     def crawl(self):
  38.         try:
  39.             self._crawl_page(self.base_url)
  40.         except Exception as e:
  41.             logging.error(f"爬取过程中发生错误: {e}")
  42.         finally:
  43.             self.close()
  44.    
  45.     def _crawl_page(self, url):
  46.         if len(self.visited_urls) >= self.max_pages:
  47.             return
  48.             
  49.         if url in self.visited_urls:
  50.             return
  51.             
  52.         # 添加到已访问URL集合
  53.         self.visited_urls.add(url)
  54.         logging.info(f"爬取页面 ({len(self.visited_urls)}/{self.max_pages}): {url}")
  55.         
  56.         try:
  57.             # 加载页面
  58.             self.driver.get(url)
  59.             
  60.             # 保存页面截图
  61.             parsed_url = urlparse(url)
  62.             screenshot_path = os.path.join(
  63.                 self.output_dir,
  64.                 f"{parsed_url.netloc}_{parsed_url.path.replace('/', '_')}.png"
  65.             )
  66.             self.driver.save_screenshot(screenshot_path)
  67.             
  68.             # 保存页面HTML
  69.             html_path = os.path.join(
  70.                 self.output_dir,
  71.                 f"{parsed_url.netloc}_{parsed_url.path.replace('/', '_')}.html"
  72.             )
  73.             with open(html_path, 'w', encoding='utf-8') as f:
  74.                 f.write(self.driver.page_source)
  75.             
  76.             # 随机延迟,避免过于频繁的请求
  77.             time.sleep(random.uniform(1, 3))
  78.             
  79.             # 查找页面上的链接并继续爬取
  80.             links = self.driver.find_elements_by_tag_name('a')
  81.             for link in links:
  82.                 try:
  83.                     href = link.get_attribute('href')
  84.                     if href and self._is_valid_link(href):
  85.                         absolute_url = urljoin(url, href)
  86.                         self._crawl_page(absolute_url)
  87.                 except Exception as e:
  88.                     logging.warning(f"处理链接时发生错误: {e}")
  89.                     continue
  90.                     
  91.         except Exception as e:
  92.             logging.error(f"爬取页面 {url} 时发生错误: {e}")
  93.    
  94.     def _is_valid_link(self, url):
  95.         """检查链接是否有效"""
  96.         parsed_url = urlparse(url)
  97.         
  98.         # 只爬取HTTP和HTTPS链接
  99.         if parsed_url.scheme not in ('http', 'https'):
  100.             return False
  101.             
  102.         # 只爬取同一域名下的链接
  103.         base_domain = urlparse(self.base_url).netloc
  104.         if parsed_url.netloc != base_domain:
  105.             return False
  106.             
  107.         return True
  108.    
  109.     def close(self):
  110.         """关闭爬虫并释放资源"""
  111.         if hasattr(self, 'driver') and self.driver is not None:
  112.             logging.info("关闭PhantomJS WebDriver")
  113.             self.driver.quit()
  114.             self.driver = None
  115. # 使用爬虫
  116. if __name__ == '__main__':
  117.     crawler = WebCrawler(
  118.         base_url='https://www.example.com',
  119.         max_pages=10,
  120.         output_dir='crawled_pages'
  121.     )
  122.     crawler.crawl()
复制代码

7.2 自动化测试中的资源管理
  1. from selenium import webdriver
  2. from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
  3. import unittest
  4. import time
  5. import sys
  6. import os
  7. class TestWebsite(unittest.TestCase):
  8.     @classmethod
  9.     def setUpClass(cls):
  10.         """初始化测试环境"""
  11.         # 创建输出目录
  12.         cls.output_dir = 'test_results'
  13.         os.makedirs(cls.output_dir, exist_ok=True)
  14.         
  15.         # 初始化PhantomJS WebDriver
  16.         dcap = dict(DesiredCapabilities.PHANTOMJS)
  17.         dcap["phantomjs.page.settings.userAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
  18.         
  19.         cls.driver = webdriver.PhantomJS(
  20.             desired_capabilities=dcap,
  21.             executable_path='/path/to/phantomjs'
  22.         )
  23.         
  24.         # 设置隐式等待
  25.         cls.driver.implicitly_wait(10)
  26.         
  27.         print("测试环境初始化完成")
  28.    
  29.     def setUp(self):
  30.         """每个测试用例执行前的设置"""
  31.         self.test_start_time = time.time()
  32.         self.test_name = self.id().split('.')[-1]
  33.         print(f"\n开始测试: {self.test_name}")
  34.    
  35.     def test_home_page(self):
  36.         """测试首页加载"""
  37.         self.driver.get("https://www.example.com")
  38.         
  39.         # 验证页面标题
  40.         self.assertIn("Example", self.driver.title)
  41.         
  42.         # 截图保存
  43.         screenshot_path = os.path.join(self.output_dir, f"{self.test_name}.png")
  44.         self.driver.save_screenshot(screenshot_path)
  45.         
  46.         print("首页测试通过")
  47.    
  48.     def test_search_functionality(self):
  49.         """测试搜索功能"""
  50.         self.driver.get("https://www.example.com")
  51.         
  52.         # 假设有一个搜索框和搜索按钮
  53.         search_box = self.driver.find_element_by_name("q")
  54.         search_button = self.driver.find_element_by_name("btnK")
  55.         
  56.         # 输入搜索词并提交
  57.         search_box.send_keys("test")
  58.         search_button.click()
  59.         
  60.         # 等待搜索结果加载
  61.         time.sleep(2)
  62.         
  63.         # 验证搜索结果页面
  64.         self.assertIn("test", self.driver.title.lower())
  65.         
  66.         # 截图保存
  67.         screenshot_path = os.path.join(self.output_dir, f"{self.test_name}.png")
  68.         self.driver.save_screenshot(screenshot_path)
  69.         
  70.         print("搜索功能测试通过")
  71.    
  72.     def tearDown(self):
  73.         """每个测试用例执行后的清理"""
  74.         test_duration = time.time() - self.test_start_time
  75.         print(f"测试 {self.test_name} 完成,耗时: {test_duration:.2f}秒")
  76.    
  77.     @classmethod
  78.     def tearDownClass(cls):
  79.         """清理测试环境"""
  80.         if hasattr(cls, 'driver') and cls.driver is not None:
  81.             print("关闭PhantomJS WebDriver")
  82.             cls.driver.quit()
  83.             cls.driver = None
  84.         
  85.         print("测试环境清理完成")
  86. # 运行测试
  87. if __name__ == '__main__':
  88.     # 创建测试套件
  89.     suite = unittest.TestLoader().loadTestsFromTestCase(TestWebsite)
  90.    
  91.     # 运行测试
  92.     runner = unittest.TextTestRunner(verbosity=2, stream=sys.stdout)
  93.     result = runner.run(suite)
  94.    
  95.     # 输出测试结果摘要
  96.     print("\n测试结果摘要:")
  97.     print(f"运行测试: {result.testsRun}")
  98.     print(f"失败: {len(result.failures)}")
  99.     print(f"错误: {len(result.errors)}")
  100.     print(f"成功率: {(result.testsRun - len(result.failures) - len(result.errors)) / result.testsRun * 100:.2f}%")
复制代码

8. 结论与最佳实践总结

在使用Python与PhantomJS结合时,正确管理资源和避免内存泄漏是非常重要的。以下是一些关键的最佳实践:

1. 使用上下文管理器:尽可能使用with语句来管理WebDriver的生命周期,确保资源被正确释放。
2. 显式调用quit()方法:如果不使用上下文管理器,确保在finally块中显式调用quit()方法。
3. 避免循环引用:注意对象之间的引用关系,避免创建循环引用,特别是当对象持有WebDriver实例时。
4. 谨慎使用全局变量:全局变量会一直存在于内存中,确保在不需要时正确释放它们。
5. 使用对象池:对于需要频繁创建和销毁WebDriver实例的应用程序,考虑使用对象池来重用实例。
6. 进程隔离:对于复杂的任务,考虑使用进程隔离,将每个任务放在单独的进程中执行。
7. 监控资源使用:使用内存分析工具和日志记录来监控资源使用情况,及时发现和解决内存泄漏问题。
8. 正确处理异常:确保在异常情况下也能正确释放资源,使用try-finally块来保证资源的释放。

使用上下文管理器:尽可能使用with语句来管理WebDriver的生命周期,确保资源被正确释放。

显式调用quit()方法:如果不使用上下文管理器,确保在finally块中显式调用quit()方法。

避免循环引用:注意对象之间的引用关系,避免创建循环引用,特别是当对象持有WebDriver实例时。

谨慎使用全局变量:全局变量会一直存在于内存中,确保在不需要时正确释放它们。

使用对象池:对于需要频繁创建和销毁WebDriver实例的应用程序,考虑使用对象池来重用实例。

进程隔离:对于复杂的任务,考虑使用进程隔离,将每个任务放在单独的进程中执行。

监控资源使用:使用内存分析工具和日志记录来监控资源使用情况,及时发现和解决内存泄漏问题。

正确处理异常:确保在异常情况下也能正确释放资源,使用try-finally块来保证资源的释放。

通过遵循这些最佳实践,可以有效地避免Python与PhantomJS结合使用时的内存泄漏问题,提高应用程序的稳定性和性能。

9. 参考资料

1. Selenium WebDriver文档:https://www.selenium.dev/documentation/
2. PhantomJS文档:https://phantomjs.org/documentation/
3. Python内存管理:https://docs.python.org/3/memorymanagement.html
4. Python weakref模块:https://docs.python.org/3/library/weakref.html
5. Python上下文管理器:https://docs.python.org/3/reference/datamodel.html#context-managers
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则