Selenium动态网页爬虫实战:从环境搭建到反反爬策略

📅 2026/7/18 9:47:02
Selenium动态网页爬虫实战:从环境搭建到反反爬策略
1. 项目概述为什么选择Selenium处理动态网页如果你尝试过用传统的requests库去抓取一个现代的单页应用SPA比如一个电商网站的商品列表或者一个社交媒体的信息流大概率会碰壁。你拿到的HTML源码里除了一个div idroot/div和一些基础的骨架代码几乎找不到你想要的数据。因为这些数据都是页面加载后由JavaScript通过Ajax请求动态填充进去的。这就是我们常说的“动态网页”。面对这种情况很多人的第一反应是去分析XHR/Fetch请求直接模拟接口调用。这确实是最快、最轻量的方式。但这条路有时候走不通接口参数被复杂加密、请求头需要动态令牌、或者数据本身就是通过WebSocket这类长连接推送的。这时候一个能“模拟真人操作浏览器”的工具就成了刚需。Selenium就是这个领域的“瑞士军刀”。我选择SeleniumChromeDriver这个组合不仅仅因为它能解决动态渲染问题。更重要的是它提供了一个近乎真实的浏览器环境。这意味着所见即所得你看到什么就能获取到什么。无需关心背后是React、Vue还是Angular。绕过复杂交互对于需要登录、点击按钮、滚动加载、甚至处理验证码虽然复杂的场景Selenium可以模拟完整的用户操作流。调试直观脚本运行时你可以亲眼看到浏览器窗口在自动操作哪里卡住了、元素没找到一目了然调试体验非常友好。当然它也有代价慢。启动浏览器、加载页面、执行脚本都需要时间资源消耗也远大于纯HTTP请求。所以它更适合作为“攻坚武器”用在那些反爬策略严密、动态交互复杂的特定场景而不是大规模、高并发的数据采集任务。2. 环境搭建与核心组件解析工欲善其事必先利其器。搭建一个稳定可用的Selenium环境是后续一切操作的基础。这里面的坑我几乎都踩过一遍。2.1 Python环境与Selenium库安装首先确保你有一个Python环境建议3.7以上。安装Selenium库非常简单一条命令搞定pip install selenium注意强烈建议使用虚拟环境如venv或conda来管理项目依赖避免不同项目间的包版本冲突。这是保持环境纯净的第一步。安装后你可以通过import selenium来验证。但光有这个库还不够Selenium只是一个发送指令的“指挥官”它需要一个“士兵”——WebDriver来实际驱动浏览器。2.2 ChromeDriver连接Selenium与Chrome的桥梁这是新手最容易卡住的地方。ChromeDriver的版本必须与你电脑上已安装的Chrome浏览器主版本号完全匹配查看Chrome版本打开Chrome浏览器点击右上角三个点 - 帮助 - 关于Google Chrome。记下版本号例如128.0.6613.138。下载对应Driver访问ChromeDriver的官方下载站或国内镜像站。找到与你Chrome主版本号如128一致的Driver版本进行下载。如果版本是128.x.x就找主版本为128的Driver。配置Driver路径下载的是一个可执行文件Windows是chromedriver.exemacOS/Linux是chromedriver。你有两种方式让Selenium找到它方法A推荐项目内管理将chromedriver文件放在你的项目根目录下然后在代码中指定路径。方法B系统路径将chromedriver所在目录添加到系统的PATH环境变量中。我强烈推荐方法A。将Driver放在项目里意味着你的项目环境是自包含的换一台机器或者和别人协作时只需要同步这个文件避免了“在我机器上好好的”这类问题。2.3 编写你的第一个Selenium脚本验证环境环境搭好了我们来写一个最简单的脚本打开百度并搜索关键词以此验证整个链路是否通畅。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 1. 创建浏览器驱动实例指定chromedriver路径 # 假设chromedriver.exe放在当前脚本同目录下 driver webdriver.Chrome(executable_path./chromedriver) # 如果已加入PATH可省略executable_path参数 # 2. 打开目标网页 driver.get(https://www.baidu.com) # 3. 找到搜索框输入关键词 # 通过F12开发者工具查看搜索框的HTML元素属性这里用它的id search_box driver.find_element(By.ID, kw) search_box.send_keys(Selenium自动化测试) # 输入文字 search_box.send_keys(Keys.RETURN) # 模拟按下回车键 # 4. 等待一下看看结果 time.sleep(3) # 强制等待简单演示用实际项目应避免 # 5. 获取当前页面标题并打印 print(当前页面标题是, driver.title) # 6. 关闭浏览器 driver.quit()运行这段代码你应该会看到一个Chrome浏览器窗口自动打开访问百度输入搜索词跳转到结果页然后在控制台打印出标题最后关闭。实操心得第一次运行可能会遇到各种报错。最常见的是WebDriverException提示“无法找到Chrome二进制文件”或“ChromeDriver版本不匹配”。请严格按照上述步骤核对Chrome版本和Driver版本。另一个常见错误是NoSuchElementException这通常是因为页面还没加载完你就去查找元素我们后面会详细讲等待策略。3. Selenium核心操作与动态数据抓取实战环境跑通了我们就进入核心环节如何用Selenium模拟人的操作并从中提取出我们需要的数据。我们以一个模拟抓取动态新闻列表的场景为例。3.1 元素定位八种武器Selenium要操作页面上的任何东西输入框、按钮、链接、列表项首先得找到它。这是自动化测试和爬虫的基石。Selenium提供了8种主要的定位方式通过By类来调用from selenium.webdriver.common.by import By # 假设 driver 已经创建 # 1. ID定位 (最优先选择通常唯一) element driver.find_element(By.ID, “login-button”) # 2. Name定位 element driver.find_element(By.NAME, “username”) # 3. Class Name定位 (注意class可能有多个用空格分隔) element driver.find_element(By.CLASS_NAME, “btn-primary”) # 4. Tag Name定位 (如 div, input, a) elements driver.find_elements(By.TAG_NAME, “a”) # 查找所有链接 # 5. Link Text定位 (精确匹配链接的可见文本) element driver.find_element(By.LINK_TEXT, “忘记密码”) # 6. Partial Link Text定位 (部分匹配链接文本) element driver.find_element(By.PARTIAL_LINK_TEXT, “忘记”) # 7. CSS Selector定位 (功能强大灵活) element driver.find_element(By.CSS_SELECTOR, “#content .list-item:first-child”) # 8. XPath定位 (功能最强大但可能复杂) element driver.find_element(By.XPATH, “//div[id‘content’]//h1”)如何选择有ID必用IDID在标准HTML中应该是唯一的定位最快最准。其次用CSS SelectorCSS选择器在现代前端开发中广泛使用语法简洁浏览器原生支持定位效率高。对于复杂的层级关系CSS选择器通常比XPath更易读。XPath作为备用当元素没有ID、Class也不独特或者需要根据文本内容定位时XPath是终极武器。但尽量避免使用绝对路径以/html开头因为它极其脆弱页面结构微调就会失效。多用相对路径和属性组合。find_elementvsfind_elements前者返回第一个匹配的元素找不到则抛出异常后者返回一个列表找不到则返回空列表。根据你的需求选择。3.2 页面等待告别time.sleep的笨办法在动态网页中元素不是瞬间出现的。使用time.sleep(10)这种“强制等待”是最低效的方法因为你永远在等最慢的那个点浪费了大量时间。Selenium提供了两种智能等待方式隐式等待 (Implicit Wait)为整个driver的生命周期设置一个全局的等待时间。在查找任何元素时如果元素没有立即出现WebDriver会轮询DOM一段时间你设定的时长直到找到它或超时。driver.implicitly_wait(10) # 单位秒 # 后续所有的 find_element 操作都会最多等待10秒注意隐式等待只需设置一次。但它有个缺点它只对find_element*方法有效对于元素的“可点击”、“可见”等条件无效。显式等待 (Explicit Wait)这是生产环境推荐的最佳实践。它为某个特定的操作或条件设置等待更加灵活和精确。你需要配合WebDriverWait和expected_conditions简称EC使用。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待直到ID为“dynamic-content”的元素出现在DOM中 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, “dynamic-content”)) ) # 等待直到某个元素可被点击 button WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, “.load-more”)) ) button.click() # 等待直到页面标题包含特定文字 WebDriverWait(driver, 10).until( EC.title_contains(“搜索结果”) )常用EC条件presence_of_element_located: 元素存在于DOM树不一定可见。visibility_of_element_located: 元素存在且可见宽高大于0。element_to_be_clickable: 元素存在、可见且可点击。text_to_be_present_in_element: 元素的文本包含特定文字。我的策略是全局设置一个较短的隐式等待如5秒作为兜底然后在关键交互步骤如点击按钮后等待新内容加载使用显式等待。这样既能保证脚本的健壮性又不会因为某个非关键元素加载慢而卡死整个流程。3.3 模拟用户交互点击、输入、滚动定位和等待都是为了最终的交互。Selenium的WebElement对象提供了丰富的交互方法。# 假设我们已经定位到了一个输入框元素 input_ele 和一个按钮元素 button_ele # 1. 输入文本 (支持中文) input_ele.send_keys(“你好世界”) # 2. 清除输入框 input_ele.clear() # 3. 点击元素 button_ele.click() # 4. 提交表单 (如果元素在form内) input_ele.submit() # 5. 获取元素文本、属性 text button_ele.text class_name button_ele.get_attribute(“class”) href button_ele.get_attribute(“href”) # 6. 模拟键盘操作 (需要 from selenium.webdriver.common.keys import Keys) input_ele.send_keys(Keys.CONTROL, ‘a’) # 全选 (CtrlA) input_ele.send_keys(Keys.BACKSPACE) # 删除 input_ele.send_keys(Keys.ENTER) # 回车 # 7. 模拟鼠标悬停 (需要 ActionChains) from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) actions.move_to_element(button_ele).perform() # 将鼠标移动到按钮上 # 8. 执行JavaScript (终极武器用于复杂操作或获取原生JS属性) # 滚动到页面底部 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) # 滚动到元素所在位置 driver.execute_script(“arguments[0].scrollIntoView(true);”, element) # 修改元素属性慎用可能被网站检测 driver.execute_script(“arguments[0].setAttribute(‘style’, ‘border: 2px solid red;’);”, element)3.4 实战案例抓取一个动态加载的新闻列表假设我们要抓取一个新闻网站它的列表是滚动到底部后自动加载更多。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.keys import Keys import time import json def scrape_news_website(url, max_pages5): 抓取动态加载的新闻列表 driver webdriver.Chrome(‘./chromedriver’) driver.implicitly_wait(5) # 设置隐式等待 driver.get(url) news_items [] last_height driver.execute_script(“return document.body.scrollHeight”) for page in range(max_pages): print(f“正在抓取第 {page 1} 页...”) # 1. 提取当前视窗内的新闻条目 # 假设每条新闻都在一个 class‘news-item’ 的div里 items driver.find_elements(By.CSS_SELECTOR, “.news-item”) for item in items: # 避免重复抓取可以用一些唯一标识判断比如链接 try: title_elem item.find_element(By.CSS_SELECTOR, “.title a”) title title_elem.text link title_elem.get_attribute(“href”) summary item.find_element(By.CSS_SELECTOR, “.summary”).text publish_time item.find_element(By.CSS_SELECTOR, “.time”).get_attribute(“datetime”) # 假设时间在datetime属性里 news_items.append({ “title”: title, “link”: link, “summary”: summary, “publish_time”: publish_time }) except Exception as e: # 某条新闻解析失败记录日志继续下一条 print(f“解析一条新闻时出错{e}”) continue # 2. 滚动触发加载更多 driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);”) time.sleep(2) # 给新内容加载一点时间 # 3. 检查新内容是否已加载 new_height driver.execute_script(“return document.body.scrollHeight”) if new_height last_height: print(“已滚动到底部没有新内容加载停止。”) break last_height new_height # 4. 可选等待“加载中” spinner 消失如果存在 # try: # WebDriverWait(driver, 5).until( # EC.invisibility_of_element_located((By.CLASS_NAME, “loading-spinner”)) # ) # except: # pass driver.quit() # 去重根据链接去重 unique_news {} for item in news_items: unique_news[item[‘link’]] item final_list list(unique_news.values()) print(f“共抓取到 {len(final_list)} 条不重复新闻。”) # 保存为JSON文件 with open(‘news_data.json’, ‘w’, encoding‘utf-8’) as f: json.dump(final_list, f, ensure_asciiFalse, indent2) return final_list # 使用示例 if __name__ “__main__”: # 替换成目标网站URL target_url “https://example-news-site.com/latest” data scrape_news_website(target_url, max_pages3)这个案例涵盖了动态抓取的几个核心要点滚动触发加载、循环提取数据、基础的去重逻辑以及适当的异常处理。4. 高级技巧与反反爬策略当你的爬虫开始规律性地访问网站时很可能会触发反爬机制。Selenium本身因为模拟真实浏览器指纹比纯请求库更难识别但并非无懈可击。4.1 浏览器指纹伪装一个干净的、默认配置的Selenium驱动浏览器有一些特征可以被网站检测到例如navigator.webdriver属性为true。我们可以通过添加ChromeOptions来修改这些特征。from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options Options() # 1. 添加常规伪装选项 chrome_options.add_argument(‘--disable-blink-featuresAutomationControlled’) # 禁用自动化控制标志 chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) # 移除“正在受自动化软件控制”提示 chrome_options.add_experimental_option(‘useAutomationExtension’, False) # 禁用自动化扩展 # 2. 修改 navigator.webdriver 属性 (关键) chrome_options.add_argument(“--disable-blink-features”) chrome_options.add_argument(“--disable-blink-featuresAutomationControlled”) # 3. 使用非无头模式并设置一个正常的用户代理 chrome_options.add_argument(‘user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36’) # 4. 其他实用选项 # chrome_options.add_argument(‘--headless’) # 无头模式不显示GUI节省资源。但有些网站能检测无头模式。 chrome_options.add_argument(‘--no-sandbox’) # 在Linux服务器上运行时可能需要 chrome_options.add_argument(‘--disable-dev-shm-usage’) # 解决Linux下共享内存问题 chrome_options.add_argument(‘--window-size1920,1080’) # 设置初始窗口大小 # 5. 启动时执行JS覆盖webdriver属性 driver webdriver.Chrome(‘./chromedriver’, optionschrome_options) driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘ }) # 现在 driver 的指纹更接近普通浏览器注意事项反检测是一场“军备竞赛”上述方法可能随着浏览器和检测技术的更新而失效。无头模式--headless容易被一些高级反爬系统识别在关键任务中慎用。最好的策略是节制访问频率模拟人类的不规律操作。4.2 处理复杂交互下拉框、弹窗、文件上传下拉选择框 (Select)Selenium提供了专门的Select类。from selenium.webdriver.support.ui import Select select_element driver.find_element(By.ID, “country-select”) select Select(select_element) select.select_by_visible_text(“中国”) # 根据文本选择 select.select_by_value(“CN”) # 根据value属性选择 select.select_by_index(1) # 根据索引选择从0开始弹窗/Alert# 等待alert出现并获取文本 WebDriverWait(driver, 5).until(EC.alert_is_present()) alert driver.switch_to.alert print(alert.text) alert.accept() # 点击“确定” # alert.dismiss() # 点击“取消”文件上传如果上传按钮是input type“file”直接对其使用send_keys传入文件本地绝对路径即可。upload_element driver.find_element(By.CSS_SELECTOR, “input[type‘file’]”) upload_element.send_keys(“/Users/yourname/Desktop/test_image.jpg”)如果是一个需要点击后打开系统文件选择框的复杂组件则可能需要借助pyautogui等桌面自动化工具或者与前端开发沟通寻找隐藏的input元素。iframe/Frame如果元素位于iframe内必须先切换到对应的frame中才能操作。# 通过id或name切换 driver.switch_to.frame(“iframe-id”) # 或者通过元素切换 iframe_element driver.find_element(By.TAG_NAME, “iframe”) driver.switch_to.frame(iframe_element) # 操作frame内的元素... # 操作完毕后切回主文档 driver.switch_to.default_content()4.3 Cookies与会话管理Selenium可以方便地获取、添加和删除Cookies这对于需要登录状态的爬虫至关重要。# 1. 登录操作假设通过表单登录 driver.get(“https://example.com/login”) driver.find_element(By.ID, “username”).send_keys(“your_username”) driver.find_element(By.ID, “password”).send_keys(“your_password”) driver.find_element(By.ID, “submit-btn”).click() # 等待登录成功... # 2. 获取当前所有的cookies cookies driver.get_cookies() print(cookies) # 是一个字典列表 # 3. 将cookies保存到文件 (例如JSON)供后续使用 import json with open(‘cookies.json’, ‘w’) as f: json.dump(cookies, f) # 4. 在新的浏览器会话中加载cookies以恢复登录状态 driver2 webdriver.Chrome(‘./chromedriver’) driver2.get(“https://example.com/”) # 先访问域名才能设置该域下的cookie with open(‘cookies.json’, ‘r’) as f: cookies_list json.load(f) for cookie in cookies_list: # 添加cookie时可能需要删除‘expiry’字段因为它可能是浮点数 if ‘expiry’ in cookie: # 有时需要将expiry转换为整数 cookie[‘expiry’] int(cookie[‘expiry’]) driver2.add_cookie(cookie) # 刷新页面使cookies生效 driver2.refresh() # 此时应该处于登录状态5. 性能优化与常见问题排查当脚本运行起来后你会发现它可能很慢或者不稳定。下面是一些优化和解决问题的思路。5.1 提升脚本执行效率减少不必要的浏览器操作find_element、click、get_attribute都是与浏览器交互的耗时操作。尽量批量获取元素用find_elements将数据存储在变量中避免重复查找。优化等待策略如前所述用显式等待替代固定sleep。只为必要的元素加载等待而不是每个步骤都等。禁用不必要的浏览器功能加载图片、CSS、字体甚至JavaScript都会消耗时间。如果只关心文本数据可以禁用它们。chrome_options Options() prefs { “profile.managed_default_content_settings.images”: 2, # 禁用图片 “profile.default_content_setting_values.stylesheets”: 2, # 禁用CSS } chrome_options.add_experimental_option(“prefs”, prefs)警告禁用CSS和JS可能导致页面布局错乱影响元素定位。请根据目标网站情况谨慎使用。使用无头模式在服务器或不需要观察界面的场景启用--headless可以节省大量GUI渲染资源。考虑并发对于大量独立页面的抓取可以使用concurrent.futures的ThreadPoolExecutor启动多个driver实例并行工作。但务必注意每个driver实例都是一个独立的浏览器进程非常消耗内存和CPU。并发数不宜过高通常2-4个并且要做好异常处理和资源清理。5.2 常见错误与解决方案速查表错误信息/现象可能原因解决方案NoSuchElementException1. 元素定位器写错了。2. 页面还没加载出来就去查找。3. 元素在iframe或Shadow DOM里。4. 元素被动态移除了。1. 用浏览器开发者工具F12的Elements面板和Console面板验证定位器如$x(‘your-xpath’),$$(‘your-css’)。2. 添加显式等待WebDriverWaitEC。3. 使用driver.switch_to.frame()切换到对应frame。4. 检查脚本逻辑确保操作顺序正确。ElementNotInteractableException1. 元素不可见被遮挡、display:none。2. 元素不可点击disabled属性。3. 另一个元素覆盖了目标元素。1. 等待元素可见EC.visibility_of...。2. 检查元素属性。3. 尝试用ActionChains移动鼠标再点击或用JS直接点击driver.execute_script(“arguments[0].click();”, element)。TimeoutException显式等待超时。1. 增加等待时间。2. 检查等待条件是否合理例如等待的元素可能永远不会出现。3. 检查网络或目标网站是否正常。StaleElementReferenceException之前找到的元素因为页面刷新或重新渲染已经“过时”了。这是Selenium常见坑解决方案是重新查找元素。避免在页面可能刷新的操作如click()、refresh()后继续使用旧的元素对象。脚本运行慢1. 使用了大量time.sleep()。2. 网络慢或网站响应慢。3. 浏览器加载了太多资源。1. 用显式等待替代固定等待。2. 考虑增加超时时间或检查自身网络。3. 通过ChromeOptions禁用图片、CSS等。浏览器被检测为自动化工具网站检测到了Selenium/WebDriver特征。使用ChromeOptions进行反检测配置见4.1节。降低访问频率模拟人类行为。ChromeDriver版本不匹配浏览器升级后Driver未更新。检查并更新ChromeDriver至与Chrome浏览器匹配的版本。5.3 调试技巧活用print和日志在关键步骤打印状态信息如“开始登录”、“找到列表元素X个”、“正在滚动”等。截图当脚本出错或行为异常时立即截图能帮你快速定位问题。driver.save_screenshot(‘error_screenshot.png’)保存页面源码有时元素定位问题需要分析当时的HTML结构。with open(‘page_source.html’, ‘w’, encoding‘utf-8’) as f: f.write(driver.page_source)手动干预模式在调试时可以在关键步骤前加入input(“按回车继续...”)然后手动操作浏览器观察页面状态再让脚本继续。6. 项目结构与代码封装建议当你的爬虫脚本越来越复杂时良好的代码结构能让你事半功倍。一个建议的项目结构your_project/ ├── chromedriver # ChromeDriver可执行文件 ├── config.py # 配置文件URL、关键词、等待时间等 ├── utils/ │ ├── __init__.py │ ├── browser.py # 封装浏览器启动、配置、关闭 │ ├── logger.py # 日志配置 │ └── helpers.py # 通用辅助函数如保存数据、处理字符串 ├── spiders/ │ ├── __init__.py │ └── news_spider.py # 具体的爬虫类 ├── main.py # 主程序入口 ├── requirements.txt # 项目依赖 └── data/ # 存放爬取的数据 └── news_data.json封装浏览器操作示例 (utils/browser.py)from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait import logging class Browser: def __init__(self, driver_path‘./chromedriver’, headlessFalse): self.driver_path driver_path self.headless headless self.driver None self.logger logging.getLogger(__name__) def start(self): “”“启动并配置浏览器”“” chrome_options Options() if self.headless: chrome_options.add_argument(‘--headless’) chrome_options.add_argument(‘--disable-blink-featuresAutomationControlled’) chrome_options.add_experimental_option(“excludeSwitches”, [“enable-automation”]) chrome_options.add_argument(‘user-agentMozilla/5.0 ...’) try: self.driver webdriver.Chrome(executable_pathself.driver_path, optionschrome_options) self.driver.implicitly_wait(5) self.driver.execute_cdp_cmd(‘Page.addScriptToEvaluateOnNewDocument’, { ‘source’: ‘ Object.defineProperty(navigator, ‘webdriver’, { get: () undefined }); ‘ }) self.logger.info(“浏览器启动成功。”) return self.driver except Exception as e: self.logger.error(f“启动浏览器失败{e}”) raise def wait_for_element(self, by, locator, timeout10): “”“显式等待元素的便捷方法”“” wait WebDriverWait(self.driver, timeout) return wait.until(EC.presence_of_element_located((by, locator))) def quit(self): “”“安全关闭浏览器”“” if self.driver: self.driver.quit() self.logger.info(“浏览器已关闭。”)这样在你的主爬虫脚本中代码会非常清晰from utils.browser import Browser from spiders.news_spider import NewsSpider def main(): browser Browser(headlessFalse) # 调试时关闭无头模式 try: driver browser.start() spider NewsSpider(driver) data spider.crawl(“https://news.example.com”) spider.save_to_json(data, ‘data/news.json’) finally: browser.quit() # 确保无论如何都会关闭浏览器 if __name__ “__main__”: main()7. 伦理、法律与最佳实践最后也是最重要的一部分。技术是中立的但使用技术的人需要负责。遵守robots.txt在爬取任何网站前先访问https://目标网站/robots.txt查看该网站是否允许爬虫访问你感兴趣的路径。这是网络爬虫的基本礼仪。尊重网站负载在你的脚本中添加随机延迟例如time.sleep(random.uniform(1, 3))避免在短时间内发起大量请求对目标服务器造成DDoS攻击般的压力。一个友好的爬虫应该模拟人类浏览的速度。识别并遵守使用条款许多网站的服务条款中明确禁止未经授权的数据抓取。在开始大规模爬取前请仔细阅读相关条款。数据用途爬取的数据应用于个人学习、研究或合法的商业分析。切勿用于侵犯他人隐私、进行诈骗、传播虚假信息等非法活动。版权与隐私注意数据的版权归属。对于明确标注了版权声明的个人或敏感信息如用户手机号、地址应避免爬取和传播。设置合理的User-Agent使用一个真实的浏览器User-Agent字符串并在请求头中表明你的爬虫身份如果网站允许例如可以在User-Agent末尾加上你的联系方式如YourBot/1.0 (contactexample.com)以示友好。Selenium是一个强大的工具它打开了处理复杂动态网页的大门。但它也是一把双刃剑效率较低且资源消耗大。在实际项目中我的经验是能走接口API就别用Selenium。只有当目标网站接口难以逆向、交互逻辑极其复杂、且数据价值足够高时才祭出Selenium这套“重型装备”。掌握它不是为了所有场景都用它而是为了在遇到那些“硬骨头”时你手里有能敲开它的工具。