从零到接单 10:动态网页爬取——Selenium 与 Playwright
系列目录:本文是「从零到接单:Python 自动化与爬虫实战」系列的第 10 篇。上一篇我们用 BeautifulSoup 爬静态网页,但很多网站用 JavaScript 动态渲染内容——BeautifulSoup 拿到的 HTML 里根本没有数据。这篇我们用 Selenium 和 Playwright 控制真实浏览器,所见即所得。
一、静态 vs 动态网页
# 静态网页:HTML 里直接有数据
# requests.get(url) → HTML 里有 → BeautifulSoup 直接提取 ✅
# 动态网页:JavaScript 运行后才生成数据
# requests.get(url) → HTML 里只有 <div id="app"></div>
# 数据是 JS 渲染的,BS4 看不到!❌
判断一个网站是否动态:右键 → 查看网页源代码,看看数据在不在 HTML 里。不在的话就需要 Selenium 或 Playwright。
二、Selenium:老牌浏览器自动化
安装
pip install selenium webdriver-manager
webdriver-manager 帮你自动下载和管理浏览器驱动,不需要手动配置。
第一个 Selenium 脚本
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 自动下载并配置 ChromeDriver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
# 打开网页
driver.get("https://httpbin.org/html")
# 获取页面源码(此时 JS 已执行完毕)
html = driver.page_source
print(html[:500])
# 获取标题
print(driver.title)
# 查找元素
element = driver.find_element(By.TAG_NAME, "h1")
print(element.text)
# 截图
driver.save_screenshot("screenshot.png")
# 关闭浏览器
driver.quit()
常用元素查找方式
from selenium.webdriver.common.by import By
# 8 种定位策略
driver.find_element(By.ID, "username")
driver.find_element(By.CLASS_NAME, "submit-btn")
driver.find_element(By.NAME, "email")
driver.find_element(By.TAG_NAME, "input")
driver.find_element(By.CSS_SELECTOR, ".container > .item")
driver.find_element(By.XPATH, "//div[@class='article']/h2")
driver.find_element(By.LINK_TEXT, "下一页")
driver.find_element(By.PARTIAL_LINK_TEXT, "下一")
# 找多个元素
elements = driver.find_elements(By.CLASS_NAME, "article")
for el in elements:
print(el.text)
页面交互
from selenium.webdriver.common.keys import Keys
# 输入文字
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Python 爬虫")
# 点击按钮
search_button = driver.find_element(By.NAME, "btnK")
search_button.click()
# 清空输入框
search_box.clear()
# 键盘操作
search_box.send_keys(Keys.RETURN) # 回车
search_box.send_keys(Keys.ESCAPE) # ESC
# 下拉选择框
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element(By.ID, "category"))
select.select_by_value("python")
select.select_by_visible_text("Python")
select.select_by_index(1)
等待元素加载(最重要!)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# ❌ 不等待——元素可能还没加载出来
element = driver.find_element(By.ID, "content")
# ✅ 显式等待——等元素出现再操作
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "content"))
)
# 其他等待条件
EC.visibility_of_element_located(...) # 元素可见
EC.element_to_be_clickable(...) # 可点击
EC.text_to_be_present_in_element(...) # 文本出现
EC.alert_is_present() # 弹窗出现
EC.invisibility_of_element_located(...) # 元素消失
三、Selenium 实战:登录并爬取数据
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 webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
import time
import csv
class SeleniumCrawler:
def __init__(self, headless=False):
"""初始化浏览器"""
options = webdriver.ChromeOptions()
if headless:
options.add_argument("--headless") # 无头模式(不显示浏览器窗口)
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
self.driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
self.wait = WebDriverWait(self.driver, 10)
def login(self, url, username, password):
"""登录网站"""
self.driver.get(url)
# 等待登录表单加载
username_input = self.wait.until(
EC.presence_of_element_located((By.NAME, "username"))
)
password_input = self.driver.find_element(By.NAME, "password")
submit_btn = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
# 填写并提交
username_input.send_keys(username)
password_input.send_keys(password)
submit_btn.click()
# 等待登录成功
time.sleep(2)
print(f"登录完成,当前 URL:{self.driver.current_url}")
def scroll_to_bottom(self):
"""滚动到页面底部(加载更多内容)"""
last_height = self.driver.execute_script("return document.body.scrollHeight")
while True:
# 滚动到底部
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
# 检查是否有新内容加载
new_height = self.driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
print("已滚动到底部")
def extract_items(self, item_selector, fields):
"""提取列表项"""
items = []
elements = self.driver.find_elements(By.CSS_SELECTOR, item_selector)
for el in elements:
item = {}
for field_name, css_selector in fields.items():
try:
item[field_name] = el.find_element(By.CSS_SELECTOR, css_selector).text
except:
item[field_name] = ""
items.append(item)
return items
def save_csv(self, data, filename="data.csv"):
"""保存为 CSV"""
if not data:
print("无数据")
return
with open(filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=data[0].keys())
writer.writeheader()
writer.writerows(data)
print(f"已保存 {len(data)} 条数据到 {filename}")
def close(self):
self.driver.quit()
# 使用示例
if __name__ == "__main__":
crawler = SeleniumCrawler(headless=False)
try:
crawler.driver.get("https://httpbin.org/html")
print(f"页面标题:{crawler.driver.title}")
crawler.driver.save_screenshot("page.png")
print("截图已保存")
finally:
crawler.close()
四、Playwright:新一代浏览器自动化
Playwright 是微软出品的现代化方案,比 Selenium 更快、更稳定。
安装
pip install playwright
playwright install chromium # 安装 Chromium 浏览器
第一个 Playwright 脚本
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# 启动浏览器
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# 打开网页
page.goto("https://httpbin.org/html")
# 等待元素
page.wait_for_selector("h1")
# 获取内容
print(page.title())
print(page.content()[:500])
# 截图
page.screenshot(path="screenshot.png", full_page=True)
browser.close()
Playwright 常用操作
# ── 查找元素 ──
page.locator(".article") # CSS 选择器
page.locator("text=Python 爬虫") # 文本内容
page.locator("button:has-text('提交')") # 包含文本
page.locator("input[name='username']") # 属性选择
page.get_by_role("button", name="提交") # 按角色和名称
# ── 获取元素列表 ──
articles = page.locator(".article").all()
for article in articles:
title = article.locator("h2").inner_text()
print(title)
# ── 交互 ──
page.fill("input[name='q']", "Python") # 输入
page.click("button[type='submit']") # 点击
page.select_option("select#category", "python") # 选择下拉
# ── 等待 ──
page.wait_for_selector(".result", timeout=10000) # 等待元素出现
page.wait_for_load_state("networkidle") # 等待网络空闲
page.wait_for_timeout(2000) # 等待 2 秒(不推荐过度使用)
# ── 提取数据 ──
title = page.locator("h1").inner_text()
html = page.content()
url = page.url
# ── 反反爬 ──
page.set_extra_http_headers({
"User-Agent": "Mozilla/5.0 ...",
})
五、Playwright 实战:滚动加载爬虫
from playwright.sync_api import sync_playwright
import csv
def crawl_infinite_scroll(url, max_items=100):
"""爬取无限滚动页面"""
results = []
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle")
while len(results) < max_items:
# 获取当前页面的所有项目
items = page.locator(".item-card").all()
for item in items[len(results):]: # 只处理新增的
results.append({
"title": item.locator(".title").inner_text(),
"desc": item.locator(".desc").inner_text(),
"price": item.locator(".price").inner_text(),
})
print(f"已收集 {len(results)} 条数据...")
if len(results) >= max_items:
break
# 滚动到底部
page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(2000) # 等待新内容加载
# 检查是否到底了(没有新内容加载)
before = len(results)
page.wait_for_timeout(1000)
after = page.evaluate("""() => {
return document.querySelectorAll('.item-card').length;
}""")
if after <= before:
print("已到页面底部")
break
browser.close()
return results
# 使用(实际使用时替换 URL 和选择器)
if __name__ == "__main__":
# data = crawl_infinite_scroll("https://example.com/products")
# for item in data:
# print(f"{item['title']} - {item['price']}")
print("请替换 URL 和选择器后使用")
六、Selenium vs Playwright:怎么选?
| 维度 | Selenium | Playwright | |------|----------|------------| | 浏览器支持 | Chrome/Firefox/Edge/Safari | Chromium/Firefox/WebKit | | 速度 | 较慢 | 更快 | | API 设计 | 传统 | 现代化,更简洁 | | 自动等待 | 需手动设置 | 内置自动等待 | | 社区生态 | 非常成熟 | 快速增长 | | 屏幕录制 | ❌ | ✅ 内置 trace viewer | | 对爬虫友好度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
建议:新项目优先选 Playwright,维护老项目或需要 Safari 时用 Selenium。
七、动态爬虫最佳实践
# 1. 设置合理的视窗大小(像真人)
page.set_viewport_size({"width": 1920, "height": 1080})
# 2. 随机延时
import random
import time
time.sleep(random.uniform(1, 3))
# 3. 随机鼠标移动(Playwright)
page.mouse.move(x, y)
# 4. 循环翻页而非暴力抓取
while True:
# ... 抓取当前页 ...
next_btn = page.locator("a.next-page")
if not next_btn.is_visible():
break
next_btn.click()
page.wait_for_load_state("networkidle")
time.sleep(random.uniform(1, 3))
总结
| 工具 | 适用场景 | 安装 |
|------|----------|------|
| requests + BS4 | 静态 HTML | pip install requests beautifulsoup4 |
| Selenium | 动态网页、兼容老项目 | pip install selenium webdriver-manager |
| Playwright | 动态网页、新项目首选 | pip install playwright |
下一篇,我们学习数据存储——抓回来的数据怎么存到 CSV、Excel、数据库里,形成可交付的成果。
练习:用 Playwright 打开一个带搜索功能的网站(如百度),输入搜索关键词,等待搜索结果出现,提取前 10 条结果的标题和链接。截图保存整个页面。
Comments
Sign in to leave a comment.