传统的 HTML 解析器在面对现代动态网页时几乎束手无策。像 BeautifulSoup 这类工具本质上是静态 DOM 解析,遇到 JavaScript 渲染的内容直接返回空壳。更棘手的是图片资源,很多站点通过懒加载或响应式按需注入 src,纯 HTTP 请求拿到的只是占位符。


我花了一些时间去完成动态网页图片下载的这个功能。在这里讲讲 Selenium 方案。


说到 Selenium,还有一个容易被忽略的背景。以前做 Office 自动化的人习惯用 VBA 调 InternetExplorer 对象来抓网页,但这个对象在 2022 年已经被微软正式停掉了。在现在的系统上直接跑会报错,别说 JS 渲染,连基本的页面加载都成问题。所以不管是 Python 还是 VBA 场景,Selenium 都是当前最实际的替代路径。


Selenium 的核心思路并不复杂,它通过 WebDriver 协议直接驱动真实浏览器,Chrome 或 Edge 都行。页面上的 JS 照常执行,DOM 完整渲染后再提取数据。这比模拟请求链路要靠谱得多,因为你拿到的是和用户看到的完全一致的 DOM 树。如果是 VBA 场景,用 SeleniumBasic 做绑定,还能把采集结果直接写进 Excel 单元格,从数据抓取到报表展现一条链路打穿。


核心代码如下:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(
service = Service(ChromeDriverManager().install()),
    options=options
)

这里有个细节值得展开。webdriver_manager 会自动下载与当前浏览器版本匹配的驱动,省去了手动管理版本的麻烦。如果你用的是 SeleniumBasic(VBA 方案),就没有这个便利了,它自带的驱动通常已经过时,必须手动去下载最新的 msedgedriver.exe 覆盖到安装目录,否则连新版浏览器都启动不了。


跑起来之后,第一个要踩的坑就是图片属性的选取。直觉上会去抓 img 标签的 src 属性,但在现代响应式网站中,src 里往往只放了一张低分辨率的缩略图。真正的高清版本藏在 srcset 属性里,这个属性按视口宽度列出了多个分辨率的图片 URL。如果只取 src,爬回来的图全是模糊的,排查半天才发现是属性取错了。


再一个坑是动态内容迭代时的元素失效。在 Unsplash 这类内容实时更新的页面上遍历元素,很容易触发 StaleElementReferenceException。原因是浏览器端的 DOM 更新了你手里的引用对象,原来的 element 引用指向的节点已经不在当前 DOM 树里了。解决办法就是做异常捕获,捕获到之后重新用选择器定位元素再继续。


这里有几个我们踩到的坑,建议你配置一下。

先用 driver.maximize_window() 把窗口撑到最大,这不是为了看着舒服,而是因为响应式布局会根据视口宽度切换不同的 DOM 结构,窗口太小会导致你的选择器突然失效。


在用 urllib.request 下载文件时如果碰到 403,大概率是服务端检测到请求头里没有合法的 User-Agent,手动塞一个浏览器的 UA 进去就行。
备注:现代反爬不仅看 UA,还会看 Referer 和 Cookies。


生产环境还有几条硬性要求。始终开启 --headless 模式跑,省掉图形界面那部分 CPU 和内存开销。下载前检查本地是否已存在同名文件,跳过重复资源以节省带宽。最关键的一点,脚本结尾必须显式调用 driver.quit()。如果忘了这行,浏览器进程不会自己退出,跑个几十次之后机器上全是孤儿进程,内存直接拉满。


如果目标站点上了高级反爬体系,比如浏览器指纹识别或者验证码拦截,单靠 Selenium 本身很难绕过去。我们在生产环境接了蜻蜓代理做请求链路的 IP 轮换和自动重试,把反爬对抗从业务脚本里剥离出去。


转载请注明