资讯动态

批量网页截图实战:EyeWitness与Selenium组合处理动态页面

发布时间:2026/10/21 23:50:08 来源:尧图企业网站定制
1. 从需求说起为什么要给网站批量截图做过Web取证、站点归档或者安全审计的朋友应该都遇到过这个场景领导或者客户丢过来一个域名列表要求“把这些网站当前的样子都截下来整理成报告”。几十个站点还好说要是几百个上千个手工打开浏览器一张张截图光想想就头皮发麻。这就是EyeWitness这类工具存在的意义。严格说起来EyeWitness的核心能力是“针对一批URL自动访问并生成带截图的HTML报告”最初在渗透测试里被广泛用来做目标站点的快速指纹采集和可视化记录。而Selenium则是浏览器自动化的事实标准能驱动真实浏览器执行JavaScript、模拟用户操作、处理动态渲染的页面。把两者放在一起本质上是解决一个很现实的问题越来越多的网站是SPA单页应用或者重度依赖Ajax渲染直接用EyeWitness默认方式去抓很可能拿到一张白屏或者半加载状态的截图等于是白干。所以这篇内容不是单纯讲EyeWitness怎么安装、参数怎么填而是讲清楚一套组合打法什么时候用EyeWitness快速出图什么时候用Selenium做精细化的动态页面抓取以及怎样把两者的优势合并到同一条流水线里。无论你是在做合规审计的记录留存还是给甲方交付站点巡检报告这套思路都能直接用上。适合谁来读如果你已经接触过命令行工具但还没系统梳理过批量截图流程这篇文章能帮你省掉大量试错时间。如果你正被动态加载页面、登录后才能看到的内容、懒加载图片这些难题卡住那下面的内容会更对胃口。2. 整体思路EyeWitness与Selenium的分工逻辑2.1 两条路线各自的擅长与短板EyeWitness的设计哲学很明确简单、批量、快。给它一个文本文件里面每行一个URL它就能自动并发访问、截图、收集响应头信息最后生成一个索引页面左侧是站点链接列表右侧是缩略图墙点开还能看大图。这个报告形式在展示交付成果时非常讨喜客户打开一个HTML文件就能浏览所有站点状态。但EyeWitness的短板也很致命它对页面的等待策略比较粗糙基本依赖内置的超时和少数可调参数遇到需要长时间渲染的图表、需要滚动加载的列表页、必须等待特定元素出现的后台页面经常截出来是“半个页面”甚至空白。另一个问题是它对需要登录的站点支持有限虽然可以通过浏览器会话操作但配置起来麻烦而且不稳定。Selenium的优势正好弥补这些缺口。通过WebDriver我们能精确控制浏览器窗口大小、等待某个CSS选择器出现、模拟滚动、处理Cookies、执行任意JavaScript甚至能在截图前做必要的交互——点击展开菜单、关闭弹窗等。代价是配置成本更高、并发处理需要更多工程化工作、运行速度相对慢。2.2 组合打法的核心原则我的建议是分层处理。第一层用EyeWitness对所有目标URL做一次快速扫描生成完整的基线报告。这一步的目的是快速发现哪些站点能正常访问、哪些超时、哪些重定向到了意外位置。第二层从EyeWitness结果里筛出那些“截图像素是纯色”“页面源码含大量Script标签但截图没有内容渲染”的可疑项再用Selenium脚本针对这些页面做定向重截。这样做的好处是显而易见的既不想放弃EyeWitness那套漂亮的报告骨架又不想被它的渲染短板限制住。还有一种更彻底的做法——完全抛弃EyeWitness的截图模块只保留它的报告生成逻辑截图全部交给Selenium完成后按约定目录存放最后通过脚本把Selenium生成的图片注入到EyeWitness的HTML结构中。这个玩法适合对报告样式有定制要求的场景后续章节我会给出思路。2.3 环境层面的考量需要提前说明的是EyeWitness最初是在Linux环境下开发使用的依赖项里有Python、Selenium、PhantomJS老版本等组件。新版本已经默认支持Chrome和Firefox的远程驱动模式。如果你在Windows上使用建议用Docker方式跑EyeWitness减少依赖冲突的烦恼同时也方便和Selenium脚本共享同一套浏览器环境。Selenium这边语言选择建议用Python生态最成熟写起来也快。浏览器驱动统一使用ChromeDriver配合无头模式运行既能满足截图需求又不会在服务器上弹出一个个浏览器窗口。3. 环境搭建与基础配置3.1 快速部署EyeWitness在Debian系或Ubuntu系统上常规操作步骤如下git clone https://github.com/FortyNorthSecurity/EyeWitness.git cd EyeWitness ./setup/setup.sh脚本会自动处理大部分依赖。如果遇到安装失败检查是否缺少Python开发头文件和编译工具这两类问题在干净服务器上最常碰到。安装完成后先小规模测试一遍python3 EyeWitness.py --help能看到完整的参数列表就算基本成功。用文本文件传入URL列表的方式最简单python3 EyeWitness.py -f urls.txt --web其中--web参数表示生成Web报告格式。执行完毕后输出目录下会生成report.html和一堆截图文件。3.2 配置Selenium与WebDriverPython环境准备pip install seleniumChromeDriver的版本必须和本机Chrome主版本号对应否则启动时会直接报错。检查版本方式google-chrome --version然后去对应地址下载匹配的驱动放进/usr/local/bin或者项目目录里再通过Service对象指定路径加载。一个稳定的最小化启动模板是from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) service Service(/usr/local/bin/chromedriver) driver webdriver.Chrome(serviceservice, optionsoptions)这里有几个参数要解释清楚。--no-sandbox是在容器或root环境下必须加的否则Chrome拒绝启动--disable-dev-shm-usage解决容器内/dev/shm空间不足导致的崩溃这两个是重灾区。--window-size直接决定截图像素尺寸要在这一步就定好不去依赖后续的set_window_size方法。3.3 验证联通性配置完成后写个最简单的小脚本打开一个页面比如已知的某公开页面截图后看看文件大小和内容。如果截出来是空白或者报错“SessionNotCreated”十有八九是驱动版本问题。先不要急着调其他参数把所有错误堆栈读一遍再动手。4. 核心技巧拆解从入门到进阶的十个关键点4.1 技巧一统一窗口尺寸与设备缩放比截图的尺寸统一是基本功。不同屏幕尺寸下网站的响应式布局会让页面结构变化同一批报告里如果截图忽宽忽窄后期浏览体验很差。建议统一设置为1920,1080这个尺寸在大多数PC端网站下展示效果最稳。不要忽略设备缩放比问题。有些高分屏环境下如果Chrome启动参数没指定缩放截下来的图片可能实际宽度只有1600甚至更小。固定加一个参数options.add_argument(--force-device-scale-factor1)这样能确保截图CSS像素和物理像素比例是1:1。4.2 技巧二显式等待取代固定睡眠刚接触Selenium的人最容易写time.sleep(5)这种代码但这种方式在页面加载较慢时依然截不全在页面加载较快时又白白浪费时间。更专业的做法是使用显式等待让脚本等待某个关键元素出现后再截图。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait WebDriverWait(driver, 20) wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, main)))选择的等待元素很讲究。优先选页面主体容器比如#app、main、.content这类结构性的标签不要等一个广告位或者底部版权信息。有些网站的核心内容不是首屏就加载的这时候需要看网络请求在做什么或者直接结合技巧三处理。4.3 技巧三处理懒加载与滚动加载现在的门户网站、电商列表页、资讯流大量使用懒加载——图片和列表数据在用户滚动到可视区域时才请求。Selenium默认截图的区域只是当前视口即使设置了窗口高度为1080也超不出浏览器视口范围。想要截取完整页面有两种做法。第一种是单页长截图。先获取页面总高度再把窗口高度临时设置为总高度最后调用截图接口total_height driver.execute_script(return document.body.scrollHeight) driver.set_window_size(1920, total_height) driver.save_screenshot(fullpage.png)这种方法适用于页面高度不是特别夸张的场景缺点是如果页面本身有几万像素高生成的图片会非常大而且部分网站的某些区块在超长视口下渲染异常。第二种是分段滚动截图后拼接。做法是循环执行滚动并截图再用PIL拼接。这种方案工程量更大但效果最接近用户真实浏览所见。就我个人经验非必要不拼图除非客户明确要一整张长图。4.4 技巧四无头模式与有头模式的权衡无头模式--headlessnew的优势是无界面、资源占用小、在服务器上不会弹出窗口适合批量无人值守的自动化。但有头模式在排查问题时有不可替代的作用——能看到脚本实际操作时页面到底发生了什么。我的建议是给脚本留一个全局变量控制headless开关排查问题时切到有头模式观察WebDriver执行点击、滚动时的实际表现。等逻辑稳定后再切回无头模式跑全量任务。这个切换成本很低但能省下大量“为什么脚本在这里点不到”的排查时间。4.5 技巧五Cookies注入与登录态保持批量截图经常遇到需要登录的站点——后台管理系统、内部数据看板、个人中心。如果每次都靠Selenium现场登录不仅要处理验证码、二次验证等不确定因素还会拖慢整体速度。更快的方式是先在普通浏览器里手动登录一次然后用Selenium读取并保存Cookies到本地文件批量截图时加载这个Cookie文件再刷新页面登录态就自动恢复了。保存Cookiescookies driver.get_cookies() import json with open(cookies.json, w) as f: json.dump(cookies, f)加载Cookies时要注意一点必须先访问目标域名的任意页面才能向该域名写入Cookies否则会报“Cookies must be set for the current domain”异常。典型顺序是driver.get(https://example.com/login) for cookie in cookies: driver.add_cookie(cookie) driver.get(https://example.com/dashboard)这里还有个容易踩的坑某些网站的认证信息不仅存在Cookies里还依赖LocalStorage或SessionStorage。针对这类站点简单的Cookie注入就不够用了只能走完整的登录流程或者用Chrome用户数据目录方式启动浏览器options.add_argument(--user-data-dir/path/to/chrome-profile)把这个路径指向你手动登录过该站点的Chrome配置目录登录态就完全保留下来了。这个方法虽然方便但要小心不要在多任务时共享同一个目录容易产生配置文件锁冲突。4.6 技巧六处理弹窗、通知与遮挡物站点截图最烦人的就是各种弹窗遮住关键内容。常见的有Cookie同意横幅、新用户引导蒙层、订阅弹窗、浏览器通知权限请求。处理策略按优先级排通知权限请求最好在浏览器层面解决加参数--disable-notifications直接禁止。Cookie同意横幅和引导蒙层一般属于页面内元素建议通过JavaScript强行移除或隐藏driver.execute_script( document.querySelectorAll(.cookie-banner, .modal, .overlay).forEach(el el.remove()); )如果是广告遮罩优先尝试关闭按钮。需要注意移除元素可能导致页面布局跳动所以执行完移除脚本后最好再等1秒让浏览器重新排版再进行截图。4.7 技巧七图片格式与文件体积控制EyeWitness默认生成的PNG截图清晰度高但体积往往偏大。一个上千站点的项目截图文件累积下来可能占用数GB磁盘空间。如果报告只要在浏览器里查看没有印刷需求建议转成JPEG或WebP能显著减小体积。Selenium的save_screenshot方法只支持PNG想要JPEG输出需要加一层转换from PIL import Image driver.save_screenshot(temp.png) image Image.open(temp.png) rgb_image image.convert(RGB) rgb_image.save(output.jpg, quality85)质量参数设为85是清晰度和体积的平衡点。纯文本页面设70就够含图表和数据可视化的页面建议不低于90。如果你后续有图像拼接、对比的需求JPEG的压缩伪影会影响像素级比较那种场景保留PNG更合适。4.8 技巧八错误重试与超时管理批量截图跑起来网络抖动、目标站点防火墙拦截、偶发的证书错误都是常态。不加重试机制的脚本往往跑一半就停下来等人工介入。我习惯在脚本里封装一个截图函数内置三级重试逻辑第一次失败后等待5秒重试一次第二次失败后切换User-Agent再试一次第三次仍失败就跳过该URL并在日志里记录失败原因。整个流程结束后单独输出一份失败清单方便人工复核。超时管理同样重要页面的加载超时设为40秒比较合理。WebDriver设置方式driver.set_page_load_timeout(40)有些网站会无限挂起某些资源请求导致页面迟迟不触发加载完成事件这时候需要单独设置脚本执行超时driver.set_script_timeout(10)4.9 技巧九多线程并发控制Selenium脚本跑单线程一个页面等10秒1000个页面就是近3小时。实际使用中不同站点响应速度差异很大单线程等待严重浪费资源。建议使用concurrent.futures的ThreadPoolExecutor做并发控制每个线程启动独立的WebDriver实例。注意几个关键点Chrome单实例内存占用约300-500MB并发数要结合服务器内存设置一般8到16线程比较稳每个线程必须有独立的临时用户数据目录防止配置锁冲突。from concurrent.futures import ThreadPoolExecutor, as_completed def capture(url): driver create_driver() try: result capture_page(driver, url) finally: driver.quit() return result with ThreadPoolExecutor(max_workers8) as executor: futures [executor.submit(capture, url) for url in urls] for future in as_completed(futures): # 收集结果并写入进度日志4.10 技巧十与EyeWitness报告体系融合这是一个我更偏爱的进阶玩法截图主力交给Selenium但报告依然用EyeWitness生成。步骤是先用EyeWitness跑一遍完整流程再对比Selenium的高质量截图找出EyeWitness报告中截图质量差的URL用Selenium重截后替换到对应目录。EyeWitness的目录结构中有个习惯每个URL的截图文件名规范且路径稳定。找到对应文件直接覆盖即可。如果批量替换数量大写个脚本按URL映射关系自动完成文件覆盖再重新生成一次报告。这个混合模式的好处是既保住了EyeWitness报告的专业外观又让截图质量完全可控。特别是在做横向对比报告时能确保所有站点都在同一渲染标准下截取。5. 实操过程一个完整的批量截图脚本演示5.1 脚本结构设计在实际项目中我从不会把代码写成一个巨大的单一脚本。合理的拆法是config.py放配置信息比如URL列表路径、输出目录、并发数。driver_factory.py负责WebDriver创建、参数设置、无头模式切换。capture_worker.py封装单个URL的截图逻辑、重试机制、日志记录。scheduler.py负责线程池调度和任务进度汇总。report_merge.py负责和EyeWitness报告做文件合并。5.2 核心代码片段一个带显式等待、重试机制和JPEG压缩的最小示例import os import time import json from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from PIL import Image def create_driver(profile_dirNone): options Options() options.add_argument(--headlessnew) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) options.add_argument(--disable-gpu) options.add_argument(--force-device-scale-factor1) options.add_argument(--disable-notifications) options.add_argument(--log-level3) if profile_dir: options.add_argument(f--user-data-dir{profile_dir}) service Service(/usr/local/bin/chromedriver) return webdriver.Chrome(serviceservice, optionsoptions) def capture_with_retry(url, output_path, max_retries3): for attempt in range(1, max_retries 1): driver None try: driver create_driver() driver.set_page_load_timeout(40) driver.get(url) # 等待页面主体出现 WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CSS_SELECTOR, body)) ) # 执行页面美化移除遮挡元素 driver.execute_script( document.querySelectorAll(.cookie-banner, .modal-backdrop, .overlay).forEach(el el.remove()); ) time.sleep(2) total_height driver.execute_script(return document.body.scrollHeight) driver.set_window_size(1920, max(total_height, 1080)) time.sleep(1) driver.save_screenshot(/tmp/temp_screenshot.png) image Image.open(/tmp/temp_screenshot.png) rgb_image image.convert(RGB) rgb_image.save(output_path, quality88) return True except Exception as e: print(f[失败] {url} 第{attempt}次尝试: {type(e).__name__}: {e}) finally: if driver: driver.quit() time.sleep(5) return False有几点值得说明。WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.CSS_SELECTOR, body)))这一步看起来很基础实际作用是确保页面HTML骨架已经返回。仅靠这一层等待还不够很多SPA应用body标签秒出但内容在几秒后才渲染。所以要结合前面说的业务元素等待才能更稳地保证内容就绪。document.querySelectorAll选择器列表里写的那些常见遮挡类名不同站点类名差异很大需要根据目标站点灵活调整。更稳妥的方式是把这些选择器放配置文件里按站点分组维护。非核心的图片资源加载失败的截图往往不够完善可以在截图尝试之前用监测网络请求返回状态的方式做更精细的判定这部分工作量较大需要结合具体需求来权衡。5.3 调度模块与进度管理并发调度模块的核心是控制线程数量同时汇总结果、管理日志。from concurrent.futures import ThreadPoolExecutor, as_completed def run_batch(urls, output_dir, workers8): os.makedirs(output_dir, exist_okTrue) total len(urls) finished 0 success_count 0 failure_list [] with ThreadPoolExecutor(max_workersworkers) as executor: future_map {} for url in urls: safe_name url.replace(https://, ).replace(http://, ).replace(/, _) .jpg output_path os.path.join(output_dir, safe_name) future executor.submit(capture_with_retry, url, output_path) future_map[future] (url, output_path) for future in as_completed(future_map): url, output_path future_map[future] finished 1 try: ok future.result() if ok: success_count 1 else: failure_list.append(url) except Exception as e: failure_list.append(url) print(f[异常] {url}: {e}) print(f[进度] {finished}/{total} 成功: {success_count} 失败: {len(failure_list)}) with open(os.path.join(output_dir, failure_list.txt), w) as f: f.write(\n.join(failure_list))这段代码里有个小技巧在提交任务时同时保存输出路径回到主线程时直接拿路径处理结果避免在线程内操作共享列表产生的竞态问题。5.4 运行实测数据以一组200个企业内部站点页面为样本包含大量图表类后台页面单线程平均每个页面耗时约22秒8线程并发总耗时约24分钟成功率约92%失败集中在证书过期和网络超时两类。这个数据供大家参考实际效率取决于目标站点响应速度和服务器配置。6. 常见故障排查与避坑记录6.1 典型异常对照表症状可能原因解决办法启动Chrome时报错无法找到二进制系统未安装Chrome或路径不对安装Chrome指定binary_location参数SessionNotCreatedExceptionWebDriver版本和浏览器版本不匹配核对版本号重新下载驱动自动化测试提示无法点击网站检测到WebDriver标识加--disable-blink-featuresAutomationControlled参数截图全白但页面正常打开等待时间不足或页面渲染在canvas中延长显式等待或延迟2-3秒再截截图内容不完整底部空白窗口设置了高度但内容未触底加载先滚动到底再回顶部或分段滚动截屏脚本中途无响应页面有未停掉的对话框或无限循环动画增加脚本执行超时必要时强制刷新输出空间不足截图保存失败磁盘被大量PNG占满改用JPEG输出定期清理临时缓存6.2 WebDriver标识被检测的问题很多网站现在会通过navigator.webdriver属性识别自动化浏览器并因此不渲染某些内容。处理办法是添加参数options.add_argument(--disable-blink-featuresAutomationControlled)同时通过执行脚本伪装特征driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) })这个方法不能保证百分百绕过所有检测但应付多数站点足够。6.3 Chrome崩溃与僵尸进程治理批量长时间运行后系统里容易残留僵尸Chrome进程占用内存又不退出。建议每次driver.quit()后主动检查并清理pkill -f chrome.*user-data-dir也可以在调度循环里周期性检查内存占用超过阈值就重启一组Worker线程。6.4 证书错误页面的处理遇到使用自签名证书的站点WebDriver默认会拦截并显示警告页截图自然就没内容了。在启动参数中加上options.add_argument(--ignore-certificate-errors)这个参数在抓取测试环境站点时几乎是必加的。6.5 外网访问稳定性问题批量抓取外部站点时网络波动和不同地域的响应速度差异会显著影响耗时。建议配置合理的代理池每个请求分配不同出口同时控制同一目标站点域名的请求频率避免被限流。6.6 浏览器崩溃后的进程残留长时间运行后即便主脚本正常结束Chrome子进程也可能残留。建议在批处理结束后统一执行一次清理命令释放服务器资源。如果你把脚本放在定时任务里跑这一步尤其重要。7. 实战心得与扩展建议7.1 一个被低估的参数User-Agent切换不同网站的适配策略差别很大有些站点对爬虫类User-Agent直接返回403页面。建议在轮询时切换User-Agent模拟常见浏览器环境能大幅降低被拦截概率。7.2 报告审美不要忽略社交媒体项目管理经验告诉我客户对截图的直观感受会直接影响对整个交付物的评价。建议截图输出前统一做一轮图像处理确保亮度正常、页面关键区块尽量在图片中心区域、文件命名规范包含站点名和抓取日期。这些细节看似琐碎实际是提升专业感的关键。7.3 数据比对与巡检的场景延伸这套方案不仅能用于单次截图。把批处理脚本接上定时任务每天固定时间对一组关键站点截图积累一段时间后就能做可视化差异对比。通过对比图的像素差异可以快速发现站点页面的改动位置这在合规审计和变更监控里非常实用。7.4 从截图到证据链的扩展如果你的截图项目需要和日志、响应头信息合并形成完整证据链可以改造脚本在截图的同时利用Selenium的execute_script获取页面响应数据或通过浏览器开发者工具协议监听网络请求。把这些信息和截图放在同一个数据目录下报告的证明力会强很多。我个人在维护一套长期巡检系统时把每天抓取的截图按日期归档每周自动生成一次趋势报告效果很好。企业做站群管理的运维人员、做竞品页面监控的产品同事都可以采用这种方案核心流程不必大改只需把输出对接上自己的数据存储就好。批量截图这件事做多了你会发现它本质上是一个“稳定性工程”——不是一次性跑通就完事而是要在长期无人值守运行中持续稳健地输出结果。希望这篇文章里记录的技巧和踩坑经验能让你少走几段弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑