写爬虫的人几乎都遇到过同一个尴尬局面:脚本能正常打开目标页面,但页面内容全是登录提示,真正的数据藏在登录之后。手动在代码里模拟登录又常常被验证码、扫码、风控拦截,折腾半天不如直接在浏览器里点一次登录来得省事。本文介绍的方案正是基于这个思路:先用普通方式启动一个浏览器并完成登录,然后让Python通过Selenium接管这个浏览器,直接提取其中的页面内容。

为什么选择复用已登录的浏览器会话
传统的Selenium用法是让脚本从零启动一个全新的浏览器实例,这个浏览器没有任何历史Cookie和登录态,对目标网站来说就是一个全新访客。如果网站需要登录才能查看内容,脚本就得模拟整个登录流程,包括输入账号密码、处理验证码、通过滑块验证等环节,每一个环节都可能失败,而且一旦网站改版,登录代码就要跟着重写。
复用已登录会话的思路则完全绕开了这些麻烦。用户先手动打开浏览器,正常完成登录(包括扫码、短信验证等任何复杂操作),登录成功后,这个浏览器实例中保存了完整的会话Cookie。此时让Selenium通过远程调试端口连接到这个浏览器,就相当于操作一个已经登录的账号,可以直接访问所有需要认证的页面,提取真实内容。
这种方式还有一个隐藏的好处:浏览器保持运行状态,Cookie持续有效,脚本可以反复连接、断开,不需要每次都重新登录。对于需要长时间、分批次采集数据的任务来说,稳定性和可维护性都远胜于模拟登录方案。
启动可被连接的浏览器实例
要实现这个方案,第一步是让浏览器以远程调试模式启动,并监听一个指定端口。以Chrome为例,需要在启动命令中加上--remote-debugging-port参数。下面是Windows平台下的启动命令:
chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\selenium_profile"
这里的9222是调试端口号,可以自定义,但要确保端口没有被占用。--user-data-dir参数指定一个独立的用户数据目录,这一点非常重要。如果省略该参数,浏览器会使用默认配置目录,而已有的Chrome进程可能正占用它,导致远程调试端口实际上没有生效,后续连接时就会报错。指定一个专用目录可以保证这是一个全新的、干净的浏览器实例。
执行这条命令后,浏览器窗口会正常打开。此时手动访问目标网站并完成登录,登录状态会保存在C:\selenium_profile目录下的Cookie文件中。如果希望下次启动时仍然保持登录态,只要继续使用同一个user-data-dir即可,浏览器会自动恢复上次的Cookie和会话数据。
macOS和Linux平台的启动方式类似,只是Chrome可执行文件路径不同,例如macOS下路径为/Applications/Google Chrome.app/Contents/MacOS/Google Chrome,参数部分保持一致即可。
用Python通过Selenium连接浏览器并提取内容
浏览器准备就绪后,就可以编写Python代码建立连接。关键在于配置debuggerAddress选项,让ChromeDriver不去新启浏览器,而是连接到已存在的实例:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 指向已启动浏览器的调试地址,格式为 127.0.0.1:端口
options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
driver = webdriver.Chrome(options=options)
# 打开需要提取内容的目标页面(浏览器已处于登录状态)
driver.get("https://example.ipipp.com/dashboard")
print(driver.title)
print(driver.page_source)连接成功后,driver对象的行为与常规Selenium完全一致,可以使用find_element系列方法定位元素,也可以执行JavaScript、切换窗口、处理iframe。下面的例子演示了如何提取页面中的正文段落和表格数据:
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, 10)
container = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.article-content"))
)
# 提取所有段落文本
for p in container.find_elements(By.TAG_NAME, "p"):
print(p.text)
# 提取表格数据并整理为二维列表
table = driver.find_element(By.CSS_SELECTOR, "table.data")
rows = []
for tr in table.find_elements(By.TAG_NAME, "tr"):
cells = [td.text.strip() for td in tr.find_elements(By.TAG_NAME, "td")]
if cells:
rows.append(cells)
for row in rows:
print(row)由于页面处于登录态,动态加载的数据通常需要在请求后等待片刻。推荐统一使用WebDriverWait做显式等待,而不是time.sleep硬性延时,前者既不会浪费时间,也更稳定。如果数据是通过接口异步返回的,还可以考虑结合浏览器的网络面板分析接口地址,直接用requests携带浏览器Cookie去请求接口,效率会更高。
常见问题与排查思路
连接失败最常见的原因是浏览器没有以调试模式启动。有些人直接双击Chrome图标打开浏览器,再试图用Selenium连接,结果报出无法连接到目标的错误。排查方法是访问http://127.0.0.1:9222/json,如果能看到一串JSON格式的页面列表,说明调试端口正常工作;如果打不开,说明浏览器启动参数有问题,需要检查是否指定了独立的--user-data-dir。
另一个常见问题是ChromeDriver版本与浏览器版本不匹配。使用Selenium 4.x及以上版本时,Selenium Manager通常会自动下载匹配的驱动,问题不大;但如果环境中残留了旧版驱动,就可能抛出会话无法建立的异常,删除旧的chromedriver可执行文件即可解决。
最后提醒一点,如果希望脚本自动完成整个流程,也可以不在命令行手动启动浏览器,而是用subprocess模块在Python内部拉起Chrome进程,再执行连接操作。这样整套流程只需要运行一个Python脚本,登录仍然可以人工介入一次,之后Cookie长期有效,脚本即可无人值守地反复提取数据。这种方式在采集需要登录的内部系统、后台报表等场景中非常实用。