作为一名常年跟数据抓取打交道的人,我太清楚那种"信心满满写好代码,结果跑出来一片空白"的感受了。尤其是当你面对一个用现代前端框架(Vue、React)搭建的网站时,用requests能拿到的往往只是一个空壳HTML,真正的数据全是JavaScript在浏览器里动态渲染出来的。这个场景下,Selenium就不再是"可选工具",而是"救命工具"。这篇内容没有教科书式的废话,全是基于实际项目踩坑后的经验总结,围绕JavaScript渲染这个核心痛点,把Selenium从环境搭建到风控对抗的完整链路说透,希望能帮你少走那些我走过的弯路。
1. 为什么requests会扑空:JavaScript渲染页面的数据流变化
先从一个最基础但很多人没想透的问题说起。为什么你用requests请求一个网页,拿到的HTML里明明有<div id="app"></div>这种标签,但里面的内容却空空如也?
因为现代网站的开发模式变了。十年前的服务端渲染,是服务器把数据库里的数据直接拼进HTML字符串再返回给浏览器,所以curl看到一个页面什么样,用户在浏览器里看到的就什么样。现在的前端工程化项目完全不是这个逻辑,服务器返回的HTML通常只是一个"骨架",里面可能只有一个挂载点,剩下的所有列表、商品、用户信息,都要靠浏览器执行JavaScript代码,触发Ajax请求、再通过DOM操作把数据渲染到页面上。
这个过程涉及两个关键角色:
- Ajax异步请求:页面加载完成后,JS代码会向后端接口发起HTTP请求,后端返回的往往是JSON格式的纯数据。
- DOM动态渲染:拿到JSON数据后,JS再把数据一个个塞进页面的各个节点。
所以当requests去请求这个页面时,它只有能力把服务器最初返回的那个"空骨架"拿回来,根本没有执行JS这一步,自然也就拿不到任何有价值的内容。
1.1 Selenium到底解决了什么问题
Selenium做的事情,简单说就是"替你把浏览器跑起来"。它不是直接去抓HTML字符串,而是通过WebDriver协议,驱动一个真实的浏览器实例去加载页面。既然是真实浏览器,那它就会像正常用户一样:解析HTML、执行JavaScript、触发网络请求、渲染DOM。整个过程跑完之后,你再从浏览器实例里提取已经渲染完整的页面。
这个"驱动真实浏览器"的思路,和requests这类纯HTTP库有本质区别。
| 对比项 | requests | Selenium |
|---|---|---|
| 请求载体 | Python代码直接发HTTP请求 | 驱动真实浏览器发请求 |
| JavaScript执行 | 不执行,只拿静态HTML | 完整执行,拿到渲染结果 |
| 数据提取 | 解析HTML或直接调接口 | 可解析DOM,也可拦截网络响应 |
| 反爬识别风险 | 请求头特征容易被识别 | 浏览器指纹相对自然 |
| 效率 | 高,毫秒级 | 低,秒级甚至更慢 |
用Selenium处理JS渲染页面的逻辑在于,你不再和"接口签名、加密参数、请求头顺序"这些细节纠缠,而是直接站在用户视角拿到最终结果。它像是一个笨重但可靠的兜底方案,当requests完全无解的时候,Selenium往往还能打开局面。
1.2 什么时候该用Selenium,什么时候不该用
很重要的一点:Selenium不是万能的,而且它效率很低。如果你面对的是一个接口可以直接返回JSON且没有复杂加密的网站,优先用requests就能解决。非要硬上Selenium,反而会陷入性能瓶颈。
我一般这样判断:
- 页面数据通过JS异步加载,且接口加密逻辑复杂、短时间无法逆向,直接上Selenium。
- 网站有严格的行为检测,纯HTTP请求很快被识别为爬虫,上Selenium配合模拟操作。
- 页面需要交互(点击、滚动、翻页)才能加载出数据,也适合用Selenium。
但反过来,如果目标网站是传统的服务端渲染页面,或者能直接从包管理器里定位到数据接口,直接用requests一把梭,不要给自己找麻烦。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建Selenium环境时最容易被卡的三个环节
真正开始用Selenium之后,你会发现难点根本不在"写代码爬数据",而在环境搭建。这个环节我前前后后踩了不少坑,尤其新手时期,经常在安装和驱动配置上卡一整天。按照下面的流程走,可以最大程度避免折腾。
2.1 安装Selenium库
Python的安装没什么好说的,直接pip搞定:
bash复制pip install selenium
如果你用的是Anaconda,也可以用conda装,不过pip一般就够用了。这里稍微提醒一下,尽量在虚拟环境里装,别把全局环境搞乱了,尤其是你手头同时维护多个爬虫项目时,依赖隔离能省掉很多麻烦。
2.2 浏览器驱动的坑:版本必须严格匹配
这一步是最多人翻车的地方。Selenium本质是通过一个中间层(WebDriver)与浏览器进行通信,Chrome浏览器有ChromeDriver,Firefox有GeckoDriver,Edge有EdgeDriver。如果驱动版本和浏览器版本对不上,启动时会直接报错,常见的错误是SessionNotCreatedException。
关于驱动下载和对应关系,现在有两种方式:
- 手动管理:去Chrome官网查看当前浏览器版本,然后到ChromeDriver镜像站下载对应版本的driver,把可执行文件放到PATH路径下或直接在代码里指定路径。
- Selenium Manager自动管理:Selenium 4.6之后内置了Selenium Manager,只要你本地装了Chrome,它会自动检测浏览器版本并下载匹配的driver,基本不需要手动配置。
如果你还在用Selenium 3.x,我强烈建议升级到4.x,就为了这个自动驱动机制也值得。手动下载driver还有个常见坑:不少人把driver放在项目目录下,然后代码里没加路径,程序找不到文件然后抛异常。最稳妥的做法是用Service对象显式指定路径,或者干脆把driver目录加进系统PATH。
2.3 第一次启动浏览器的错误排查
代码写好了,环境也装好了,结果一跑发现浏览器根本弹不出来,或者一闪而过。这种情况多半是以下几个原因:
- 浏览器没有安装在默认位置,Selenium找不到可执行文件。
- 驱动权限有问题,Linux/macOS下可执行权限未开启。
- Chrome和driver版本严重不匹配。
- 部分系统下还需要安装额外的依赖库(比如Linux下缺少
libX11等)。
验证环境是否OK,可以用最简单的启动代码试:
python复制from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://example.com")
print(driver.title)
driver.quit()
能正常打印出页面标题,说明环境没问题,可以继续往下走。如果这一步都报错,先别急着上网搜一堆莫名其妙的问题,优先确认driver版本和浏览器版本。
2.4 无头模式与反检测的取舍
很多人一开始喜欢用headless模式跑,因为不弹浏览器看起来更炫酷,也省资源。但无头模式在部分网站前面会直接暴露爬虫身份,因为它的浏览器指纹和正常浏览器有细微差异,比如一些Canvas指纹特征、WebGL渲染特征、字体列表等。
如果遇到目标网站检测到了你的无头浏览器,可以尝试去掉headless改成有头模式,或者用一些参数来伪装:
python复制from selenium import webdriver
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--headless=new") # Chrome 112+的支持
opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
opts.add_experimental_option("useAutomationExtension", False)
这样处理之后,浏览器会把navigator.webdriver属性伪装成undefined,降低被识别为自动化的概率。
3. 定位元素与显式等待:把"猜时间"变成"等条件"
环境搭好之后,进入核心问题:怎么从渲染完成的页面里把目标数据抠出来?这里有两个重点:元素定位和加载等待。很多人拿不到数据,不是定位写错了,而是页面还没加载完就去提取,结果自然扑空。
3.1 等待策略的选择:隐式等待vs显式等待
新手最容易犯的错误是统一用sleep(5),让程序硬等5秒。这种方式简单粗暴,但问题很大:如果页面2秒就加载完了,你白白多等了3秒;如果页面加载超过5秒,程序照样报错。所以更科学的方案是使用智能等待。
Selenium提供了隐式等待和显式等待两种机制。隐式等待是全局性的,告诉WebDriver每次找元素时如果没找到,最多等多久再抛异常。写起来很简单:
python复制driver.implicitly_wait(10)
但它有个坑:只能解决"元素存在"的状态,没法解决"元素存在但内容还没渲染完"的问题。比如一个Ajax请求需要3秒才返回数据,而DOM节点提前就在页面上了,此时只用隐式等待,拿到的还是空数据。
显式等待则精准匹配"某个条件满足后再继续"的需求。它会反复轮询页面,直到条件满足或超时为止:
python复制from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 20)
element = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "product-list"))
)
这里WebDriverWait的第一个参数是driver实例,第二个是超时时间。until()里传入一个条件函数,presence_of_element_located表示"元素出现在DOM树中",还有visibility_of_element_located(元素可见)、element_to_be_clickable(元素可点击)等不同条件,按需取用。
3.2 定位方式的优先级选择
Selenium定位元素有很多种方式:By.ID、By.CLASS_NAME、By.XPATH、By.CSS_SELECTOR等。我的判断顺序是这样的:
- 优先用
By.ID,因为ID在页面中通常唯一,定位速度最快。 - 其次用
By.CSS_SELECTOR,性能好,而且比XPATH简洁。 - 再次用
By.XPATH,适合复杂层级和文本定位,但效率略低,且XPath表达式写不好很容易出错。 - 用
find_elements,一次性拿到一组相同类型的元素,做列表页抓取。
实际工作中界面结构变化频繁,靠classname容易因为样式调整而失效,而CSS_SELECTOR和XPATH更灵活,能够通过层级关系唯一定位。
举例,抓取一个排行榜列表,每个排名项都在div.list-item里,里面子节点有个span.rank和a.title,用CSS选择器可以这样写:
python复制items = driver.find_elements(By.CSS_SELECTOR, "div.list-item")
for item in items:
rank = item.find_element(By.CSS_SELECTOR, "span.rank").text
title = item.find_element(By.CSS_SELECTOR, "a.title").text
print(rank, title)
3.3 页面滚动与懒加载处理
现在很多网站为了优化首屏速度,采用懒加载策略,即页面滚动到某个位置时才去加载此区域的内容。如果你直接提取全部链接或图片地址,会发现只能拿到首屏那几条。解决办法是模拟滚动,让浏览器反复往下滑,直到数据不再增长。
python复制def scroll_to_bottom(driver, max_scrolls=20):
last_height = driver.execute_script("return document.body.scrollHeight")
for _ in range(max_scrolls):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
这种方法在无限滚动页面中屡试不爽。注意sleep的时间没必要太长,2秒足够触发第二次数据加载了,太长会影响整体效率。
4. execute_script的妙用:把Selenium当成"带浏览器的requests"
Selenium最容易被低估的功能是execute_script,它允许你直接在浏览器当前页面的上下文里执行任意JavaScript代码。这个能力意味着你不仅能操作DOM,还能在页面内部调用一些原本被保护起来的变量、函数,甚至绕过一些点击限制。
4.1 用JS直接获取渲染后的完整数据
有时候用Selenium的find_element方式一层层取文本非常麻烦。比如一个表格有100行数据,每行有10列,你固然可以用循环去定位,但代码会变得又臭又长。换一种思路,用JS在页面里直接把整个表格转成二维数组返回:
python复制js_code = """
var table = document.querySelector('#data-table');
var rows = Array.from(table.rows);
return rows.map(row => Array.from(row.cells).map(cell => cell.innerText));
"""
data = driver.execute_script(js_code)
这样拿到的是一个干净的Python二维列表,后续处理数据非常方便。同理,如果你希望提取页面上所有图片的真实地址,可以:
python复制imgs = driver.execute_script("""
return Array.from(document.images).map(img => img.src);
""")
比一个个find_element再取属性高效很多。
4.2 修改元素属性,突破输入限制
有些输入框设置了readonly或disabled属性,正常模拟用户输入时会失败。此时可以用JS直接移除属性:
python复制driver.execute_script("""
var input = document.querySelector('#date-input');
input.removeAttribute('readonly');
""")
然后再用Selenium的send_keys往里填充内容。这类技巧在处理日期选择器、只读选择器等场景时候特别有用。
4.3 替换接口返回值,实现数据导出
另外一个进阶玩法是:通过execute_script改写页面上Ajax回调的逻辑,或者直接调用页面内部的全局函数,把该网站的底层数据源截取出来。比如有的管理后台列表,前端逻辑会把接口返回的原始JSON再加工展示,此时你直接在浏览器环境里执行一段重写fetch或XMLHttpRequest的代码,就能拦路截获原始数据。这个操作本质上是在模拟"前端调试工具"的行为,非常实用。
python复制driver.execute_script("""
window.__rawData = [];
var originalFetch = window.fetch;
window.fetch = function(...args) {
return originalFetch.apply(this, args).then(response => {
var clone = response.clone();
clone.json().then(data => {
window.__rawData.push(data);
});
return response;
});
};
""")
driver.get("https://example.com/data-page")
time.sleep(5)
raw_data = driver.execute_script("return window.__rawData;")
这种方式比解析DOM稳定得多,因为接口返回的是结构化数据,不用再去HTML里猜每个字段的含义。
5. 风控对抗:滑块验证、webdriver特征与伪装边界
处理JS渲染页面的爬虫,遇到的终极难题往往是风控。动态渲染只是技术屏障,真正的运营布防在风控体系上。Selenium虽然用的是真实浏览器,但依然有让它暴露的地方。这些年我碰到比较多的是两类问题:webdriver特征被检测和滑块验证。
5.1 webdriver特征被检测的常见原因
很多网站的反爬会在前端脚本里检查navigator.webdriver这个属性,自动化环境下它的值是true,普通浏览器是undefined或false。这是最直接的一项检测。此外还可能检测:
window.chrome对象的完整性(自动化浏览器里这个对象可能是空的)。navigator.languages是否包含常见语言项。navigator.plugins数组长度是否为零。- 是否存在
cdc_开头的DOM元素或属性(老版本ChromeDriver引入的特征)。
针对这些特征的伪装,代码层面可以做的处理有:
python复制opts.add_argument("--disable-blink-features=AutomationControlled")
opts.add_experimental_option("excludeSwitches", ["enable-automation"])
opts.add_experimental_option("useAutomationExtension", False)
然后再配合JS注入隐藏webdriver:
python复制driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
但这些措施只对检测逻辑相对简单的网站有效。遇到强风控平台,这种"打补丁"式的伪装很快会被识别出来,因为网页还会检查脚本执行顺序、事件触发顺序、鼠标移动轨迹等更多指标。
5.2 使用开发者工具协议绕过检测(以Chrome为例)
如果你用的是Chrome,且对检测机制有一些了解,可以考虑通过DevTools Protocol(CDP)来禁止一些自动化特征的注入。Selenium 4里的add_cdp_listener或直接使用driver.execute_cdp_cmd,可以让你在浏览器层面操作。例如:
python复制driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
"""
})
在每次导航前预先注入这段脚本,可以让新打开的页面里不再有webdriver特征。此外还可以通过CDP修改浏览器的User-Agent、屏幕尺寸、时区等环境信息,更接近于一个真实用户。
5.3 滑块验证的实操思路
滑块验证几乎是爬虫圈绕不开的话题。遇到滑块之后,Selenium的思路一般是:定位滑块按钮,模拟按住并拖动的过程。但难点在于移动轨迹的模拟要足够自然,不能用了固定速率的匀速拖动,那样后台一检测就知道是脚本。
一个粗糙但有效的实现思路如下:
python复制from selenium.webdriver.common.action_chains import ActionChains
slider = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "slider")))
ActionChains(driver) \
.click_and_hold(slider) \
.pause(0.2) \
.move_by_offset(100, 0) \
.pause(0.1) \
.move_by_offset(80, 0) \
.release() \
.perform()
这种分段移动轨迹比一步到位拖到目标位置要自然一些,但在更严格的滑块验证(比如需要根据缺口像素精确计算偏移、带轨迹加密的)面前还是不够用。那种级别的滑块,光靠Selenium硬刚并不现实,更有效的路径是介入机器学习模型识别缺口位置,配合更精细的拖动轨迹算法。涉及的内容量很大,这里不展开聊,核心提醒是:Selenium能解决一部分风控,但越是商业级的风控体系,越需要综合方案。
5.4 合理设置并发与请求频率
风控体系里,行为频率比技术特征更好检测。如果你用Selenium高频访问,再好的伪装也会触发封禁。我在实战中的做法是单机控制并发数为1到2个浏览器实例,每个实例在操作过程中加入随机化的等待时间。建议把常规操作时间设定在2到6秒之间,偶尔加一次较长停顿模拟读内容的行为,让请求曲线更像人。
另外,要时刻注意目标网站Robots协议和使用条款,技术上可行不等于合规上可取,数据采集需要在合法合规的边界内进行。
6. 跑出来只显示"proceed finished with exit code 0"?从头排查一遍
不少人在PyCharm里运行爬虫代码时会遇到一个很迷惑的现象:程序没有报错,也没有任何输出,控制台只显示一行proceed finished with exit code 0。程序正常退出,但什么都没爬下来。第一次遇到这个情况,我也愣了很久,因为代码逻辑看起来没问题,requests也能正常拿到响应,但程序就是不打印数据。
6.1 问题出在哪里:为什么没有报错也没有输出
exit code 0表示程序运行结束且没有抛出异常。所以问题的核心不是程序崩溃,而是你没有把数据正确输出出来。常见的原因有:
- 代码里没有
print或日志,某个分支被跳过,程序走完了但什么都没打印。 - 目标页面结构和预期不一样,定位逻辑返回了空列表。
- 网站做了反爬,返回了一个验证页面,里面根本不包含你需要的数据。
- 使用了无头模式,代码里又有弹窗、alert没处理,导致浏览器卡住,后续代码没执行。
- 页面加载策略设置不当,
driver.get()提前返回,而数据还没渲染好。
以Selenium为例,代码里如果用了find_elements(By.CLASS_NAME, "item")但页面中实际根本不存在这个class,那返回的就是空列表。空列表不报错,你遍历空列表自然什么都不打印。
6.2 从哪几个方向一步步排查
遇到这种问题,不要急着改代码,按顺序做下面几件事:
- 去掉headless模式运行一次,用肉眼看浏览器到底发生了什么。很多时候屏幕上会弹出"该页面无法访问"或"验证您是真人"之类的提示,这些信息比代码内的任何日志都直接。
- 加中间调试输出,打印页面标题、当前URL、页面源码长度等信息,判断是否跳转到了其他页面。
- 把HTML源码保存到本地,用浏览器打开分析:
python复制with open("debug.html", "w", encoding="utf-8") as f:
f.write(driver.page_source)
- 检查是否有iframe,很多页面数据在iframe中,而Selenium默认只能定位当前frame里的元素。如果目标数据藏在iframe里,需要先
switch_to.frame()切换进去。
6.3 Selenium版本导致的隐性坑
Selenium 3升4之后,很多API发生了变化,例如find_element_by_class_name被废弃,改用find_element(By.CLASS_NAME, "...")。如果老代码直接跑在Selenium 4上,可能会有DeprecationWarning,但一般不报错。真正坑的是有些方法在4.x里行为变了,例如driver.get已经默认等待页面load事件,如果目标网站的某个资源一直不返回,就会一直卡着不执行后续代码。
这时可以调整页面加载策略:
python复制opts = Options()
opts.page_load_strategy = "eager"
driver = webdriver.Chrome(options=opts)
eager模式表示等DOM加载完成即返回,不用等所有图片和样式表都加载完毕,能有效避免图片拖慢整个程序。
6.4 日志与异常捕获,让问题浮出水面
最后给一个非常实用的建议:在爬虫代码里加好日志和异常捕获,让问题在第一时间暴露,而不是干瞪眼看exit code 0。一个最小化的日志配置如下:
python复制import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
try:
logger.info("开始访问目标页面")
driver.get("https://example.com")
logger.info("页面标题: %s", driver.title)
except Exception as e:
logger.error("访问失败: %s", e, exc_info=True)
有了清晰的日志,即使爬虫某个环节出了问题,你也能快速定位到是哪一步异常、页面状态是什么,再也不用靠猜。
7. 让Selenium爬虫不翻车的几点私房经验
前面讲了不少具体操作,这里再把我在多个项目中反复验证过的一些非显性经验整理一下。它们不太会在官方文档或教程里出现,但对实际项目的稳定性和效率影响非常大。
7.1 尽量复用浏览器会话而不是反复启动
启动一个浏览器实例通常要消耗几百MB内存,如果每爬一个页面重新启动一次浏览器,资源的开销会大到你怀疑人生。正确做法是:一次会话内完成多个页面抓取,需要批量抓取时用循环去访问不同URL,或者通过driver.get()跳转,而不是反复webdriver.Chrome()。
如果爬虫任务周期较长,建议把浏览器驱动单例化,做个简单的连接池,每次爬取任务结束不必立即退出浏览器,留着待命,下次任务继续用,速度会快很多。
7.2 合理设置代理IP池
Selenium无头模式下代理配置比requests麻烦一些,但并非无法做到。Chrome下可以通过Options设置代理:
python复制opts.add_argument("--proxy-server=http://127.0.0.1:7890")
如果代理需要认证,可以通过ProxyExtension等方式注入,这块配置涉及不少细节。需要特别提醒的是,代理Ip的质量比数量重要得多,高匿代理和高频代理差别很大,如果你爬的是高风控网站,买便宜代理基本会被秒封。
7.3 使用Cookie池实现免登录
有些站点数据需要登录后才能看到,用Selenium每次走一遍登录流程又慢又容易触发验证码。我常用的思路是:先用Selenium手动登录一次,登录完成后用driver.get_cookies()把cookie保存到本地。下次爬虫启动时,通过driver.add_cookie()把cookie逐个加入会话,就能直接跳过登录环节。
保存到文件的代码大致是:
python复制import json
cookies = driver.get_cookies()
with open("cookies.json", "w", encoding="utf-8") as f:
json.dump(cookies, f)
下次使用时:
python复制with open("cookies.json", "r", encoding="utf-8") as f:
cookies = json.load(f)
for cookie in cookies:
driver.add_cookie(cookie)
注意必须先访问一次目标域名的任意页面(建立起会话),然后才能添加cookie,否则会报InvalidCookieDomainException。
7.4 组合运用多种技术栈
最后想说的是,Selenium虽然能解决JavaScript渲染问题,但它不应该成为你唯一的武器。真正高效的爬虫体系,是多种技术手段的综合运用。
对于简单网站,用requests和BeautifulSoup就能解决。对于中等复杂程度的动态页面,可以试试Cloud Scraping这样的工具,把浏览器渲染能力提供给更轻量的接口。只有当网站前端过于复杂、加密难以破解时,才需要Selenium或Playwright这样的完整浏览器自动化方案兜底。
还有一点,requests + 浏览器抓包拦截的混搭路线也值得考虑。有时候Selenium只负责触发页面的真实Ajax接口请求,你在driver.get()访问页面后,不一定非要去解析DOM,而是直接在当前会话里拦截网络请求、读取响应体,用类似requests请求接口的方式去拿结构化数据,比解析HTML更高效、更稳定。
8. 写在最后的几个提示
爬虫这个圈子进进出出的人很多,技术更新也很快。Selenium面世这么多年,至今依然是处理JavaScript渲染页面的主力选手之一,但同时也在不断被新工具挑战。现在Playwright、Puppeteer等替代方案也越来越成熟,尤其在无头浏览器性能和反检测方面,各有优势。如果你有精力,建议在项目里做一次对比,找到最适合自己场景的方案。
我个人的实际感受是:Selenium真正费精力的不是框架本身,而是它和网站的"对抗"过程。前端框架在迭代,风控策略在升级,今天的绕过方式明天可能就失效了,所以保持持续调试的心态比掌握某几个具体API更重要。代码这行有一个不变的规律,你不会永远顺风顺水,但只要肯在一次又一次的排查中积累经验,很多难题最终都会变成你工具箱里的一部分。
希望这篇内容能帮你绕过一些明显的坑,也欢迎在实践中多测试、多总结,形成属于自己的一套Selenium使用心得。数据抓取是一件越做越有意思的事情,保持好奇、保持耐心,成果自然会慢慢积累起来。
