传统的爬虫脚本往往很“脆”——网站改一个 class 名,整个解析逻辑就崩了;遇到 JavaScript 渲染的页面,requests 拿到的是一堆空壳;稍微有点反爬意识的站点,TLS 指纹一检测,IP 就被封了。所谓“智能爬虫”,要解决的核心问题就是适应性和隐蔽性:让爬虫能在网站结构微调后继续工作,能在浏览器指纹层面看起来像真人,甚至能用自然语言描述来提取数据,而不是写死一堆 CSS 选择器。
下面从三个层面展开,每个层面附上可直接运行的代码。
很多开发者以为换了 User-Agent 就万事大吉,但 Cloudflare、Akamai 等 CDN 厂商早就在边缘节点做 JA3 指纹过滤了。Python 标准库 requests 底层用的是固定的 OpenSSL 握手参数,和 Chrome 浏览器的 TLS 指纹完全不同,一眼就能被识别为自动化脚本。
curl_cffi 是目前最成熟的解决方案,它直接模拟真实浏览器的 TLS 和 HTTP2 指纹:
pip install curl_cffifrom curl_cffi import requests
# 模拟 Chrome 120 的完整指纹(TLS + HTTP2 + Header 顺序)
resp = requests.get(
"https://httpbin.org/headers",
impersonate="chrome120",
timeout=15
)
print(resp.status_code)
print(resp.json()["headers"]["User-Agent"])impersonate 参数支持 chrome110 到 chrome124 以及 safari15_5、safari17_0、firefox133 等多个版本。如果你的目标站点开了 Cloudflare 的“Under Attack”模式,这一步往往是区分“秒封”和“正常返回”的关键。
当页面内容由 JavaScript 异步加载时,纯 HTTP 请求拿不到数据,必须上浏览器。Playwright 本身很好用,但默认配置下 navigator.webdriver 为 true,Chrome 自动化扩展的标志也很明显,有经验的站点一眼就能识别。
下面的代码在启动浏览器时做了三件事:禁用自动化控制特征、设置接近真人的视口和语言、使用真实的 User-Agent:
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
def fetch_rendered_page(url: str) -> str:
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=[
"--disable-blink-features=AutomationControlled",
"--disable-features=IsolateOrigins,site-per-process",
]
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
locale="zh-CN",
)
page = context.new_page()
page.goto(url, wait_until="networkidle", timeout=30000)
html = page.content()
browser.close()
return html
# 使用示例:抓取一个动态渲染的页面
html = fetch_rendered_page("https://wltx.lvxiair.com/")
soup = BeautifulSoup(html, "html.parser")
for quote in soup.select(".quote"):
text = quote.select_one(".text").get_text(strip=True)
author = quote.select_one(".author").get_text(strip=True)
print(f"{author}: {text[:60]}...")wait_until="networkidle" 会等待网络请求基本停止后再取内容,比固定 sleep 更可靠。如果目标站点对 headless=True 也有检测,可以改用 headless=False 配合虚拟显示(如 xvfb)运行。
这是“智能”体现得最明显的一层。Scrapling 是一个较新的爬虫库,它的核心卖点是自适应选择器:当你保存了某个元素的选择器后,如果网站改版导致 class 名变化,Scrapling 会用相似度算法自动重新定位到最可能的目标元素,而不是直接返回空。
安装:
pip install "scrapling[fetchers]"
scrapling installfrom scrapling.fetchers import Fetcher
# 开启自适应模式(全局)
Fetcher.adaptive = True
page = Fetcher.get("https://wlt.lvxiair.com")
# 第一次正常提取,同时保存元素“指纹”
products = page.css("li.product", auto_save=True)
data = []
for product in products:
name = product.css("h2::text").get()
price = product.css(".price::text").get()
data.append({"name": name, "price": price})
print(f"提取到 {len(data)} 个商品")
for item in data[:3]:
print(item)关键在 auto_save=True。当网站结构后续发生变化时,你重新运行同样的代码,Scrapling 会基于之前保存的指纹做模糊匹配,尽量找回原来的元素。虽然不能保证 100% 准确,但在类名微调、DOM 层级小幅变动的情况下,能省去大量手动修选择器的时间。
把上面三层能力串起来,写一个实用的抓取函数:优先用 curl_cffi 走轻量 HTTP 请求(快、省资源),如果页面内容明显不完整(比如关键元素缺失),自动降级到 Playwright 渲染,解析层用 Scrapling 的自适应选择器兜底。
from curl_cffi import requests
from playwright.sync_api import sync_playwright
from scrapling.parser import Adaptor
def smart_fetch(url: str, required_selector: str, impersonate: str = "chrome120"):
"""
智能抓取:先 HTTP,不够再上浏览器,解析用自适应选择器。
"""
# 第一层:轻量 HTTP + 浏览器指纹伪装
resp = requests.get(url, impersonate=impersonate, timeout=15)
html = resp.text
# 用 Scrapling 解析并检查关键元素是否存在
page = Adaptor(html, url=url)
if page.css(required_selector):
print("[HTTP] 直接命中,无需渲染")
return page
# 第二层:降级到浏览器渲染
print("[Playwright] HTTP 内容不完整,切换渲染模式")
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=["--disable-blink-features=AutomationControlled"],
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
)
pg = context.new_page()
pg.goto(url, wait_until="networkidle", timeout=30000)
rendered_html = pg.content()
browser.close()
return Adaptor(rendered_html, url=url)
# 使用:以 quotes.toscrape.com 的 JS 版本为例
result_page = smart_fetch(
"https://wltx.lvxiair.com/",
required_selector=".quote .text"
)
quotes = result_page.css(".quote .text::text").getall()
print(f"最终获取 {len(quotes)} 条名言")
print(quotes[0][:80] + "..." if quotes else "未找到内容")这个模式的好处是按需开销:大部分静态页面走 HTTP 路径,毫秒级完成;只有确实需要渲染的页面才启动浏览器,避免不必要的资源浪费。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。