爬虫实战:如何优雅地抓取网页中隐藏在伪元素(::before)里的文本?

简介: 本文揭秘爬虫中“页面可见但代码抓不到”的元凶——CSS伪元素(::before/::after)。它生成的文本不在DOM中,导致requests+BS4失效。文章剖析content四种来源,提供接口直采、CSS正则解析、渲染引擎+代理IP三阶方案,并附Selenium实战代码与避坑指南。(239字)
相信很多写过一段时间爬虫的同学大概率撞过这堵“隐形墙”:在浏览器里明明白白显示着"¥9.9"的价格,或者一段验证码文本,但当你切换到 DevTools 时,发现那个数字安安静静地挂在某个标签里。然而,当你用 requests 抓下来,再用 BeautifulSoup 或 XPath 去提取得时候,拿到的却是一个空列表。即便你认为是 JS 动态加载,一通 Selenium 渲染操作后,DOM 里依旧是一无所有。 这其实并非 JS 的锅,而是 CSS 伪元素(Pseudo-elements)在作祟。 今天这篇文章,我们就从技术根源出发,手把手教你如何破除这层“伪装”,并结合强大的代理工具实现稳定抓取。

根因剖析:文本去哪了?

问题的根源在于,你想要抓取的文本 根本就不在 DOM 树里 这些文本通常是 CSS 引擎根据 ::before ::after 伪元素的 content 属性在运行时凭空生成的。因为伪元素并不属于 DOM 节点集合,传统的 getElementById querySelector 或者 XPath 自然全都失效了。 在动手写代码前,我们必须先分清 content 的四种常见取值来源:
  • 写死的字符串:例如 content: "¥9.9";,值直接硬编码在 CSS 规则中。
  • attr()attr() 函数引用:例如 content: attr(data-price);,真实数据其实藏在 HTML 元素的 data-price 属性里。
  • url()url() 函数引入:插入的是一张图片而不是文字,遇到这种情况无法当做文本抓取,只能走结构化接口或 OCR。
  • counter()counter() 计数器:通常用于自动编号,值是渲染引擎在运行时算出来的,必须依赖浏览器渲染才能读取。

破局方案与实战演示

应对这种反爬,我们的决策优先级永远是: 能拿接口绝不渲染,能抓静态绝不上自动化
  • 首选方案:直采接口。绝大数伪元素只是装饰,背后都有明文 JSON 接口支撑。找到接口直接发纯 HTTP 请求,速度快且反爬压力小。
  • 次选方案:正则解析 CSS。如果 content 是写死的字符串或来自 attr(),可以直接把 CSS 抓下来用正则提取,不需要启动浏览器。
  • 终极方案:渲染引擎 + 代理 IP。当 CSS 是动态注入的,或者强制使用 counter() 渲染时,我们就必须祭出 Selenium / Playwright 并调用 window.getComputedStyle 来读取最终样式。

渲染抓取实战(配合爬虫代理)

走到终极方案时,一个无法回避的问题是:单个 IP 使用 Selenium 频繁访问,其极其明显的自动化特征会迅速触发验证码甚至封禁。浏览器需要加载完整资源、停留时间长,IP 暴露面极大。 在这类渲染型抓取中,因为会话连续性(如保持登录态、Cookie 验证、表单提交)的要求,一旦中途跳变 IP,会导致之前的验证全部失效。此时,我们需要使用代理厂商提供的 固定转发模式 爬虫代理 为例,其固定转发模式能够在一个特定时间窗口(通常 1 到 3 分钟)内保持出口 IP 一致,完美契合了渲染型会话的需要。 以下是基于 Selenium 接入固定转发代理,并抓取伪元素数据的完整示例代码:
# 推荐使用 selenium-wire 以支持原生不支持的代理账密认证
from seleniumwire 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
import time

# 1. 配置亿牛云代理(固定转发模式,保持IP在时间窗口内连续)
proxyHost = "t.16yun.cn"
proxyPort = "31111"
proxyUser = "username"      # 替换成你的账号
proxyPass = "password"      # 替换成你的密码

options = {
   
    "proxy": {
   
        "http":  f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
        "https": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}",
        "no_proxy": "localhost,127.0.0.1" # 绕过本地请求
    }
}

# 2. 启动浏览器
driver = webdriver.Chrome(seleniumwire_options=options)

try:
    url = "https://example.com/product/123"
    driver.get(url)

    # 3. 避免 headless 时序陷阱,不能仅等待元素存在,要轮询判断 content 是否渲染
    # 定义一个自定义的等待函数,轮询获取 ::before 的内容
    deadline = time.time() + 10 # 10秒超时
    extracted_price = None

    while time.time() < deadline:
        # 执行 JS 获取 getComputedStyle
        val = driver.execute_script(
            "return getComputedStyle(document.querySelector('.price'), '::before').getPropertyValue('content');"
        )

        # 伪元素存在时,有效内容不会是空或 'none'
        if val and val != "none":
            # getPropertyValue('content') 返回的结果通常会包裹着额外的双引号或单引号
            extracted_price = val.strip('"').strip("'")
            break
        time.sleep(0.3)

    if extracted_price:
        print(f"成功抓取伪元素价格: {extracted_price}")
    else:
        print("伪元素内容未能在规定时间内渲染。")

finally:
    driver.quit()

进阶避坑指南

在实操过程中,再点出几个很容易踩坑的地方:
  1. 返回值自带引号:就像代码中展示的一样,使用 getPropertyValue('content') 取出来的如果是字符串,它会原样返回 ""¥9.9""。一定要用 strip('"').strip("'") 进行清洗,不然你的数据库里全都是脏数据。
  2. 警惕图片“文字”:如果 content 返回的是 url(...),请立刻停止尝试提取文本,那实质上是一张图片。你要么去找其他数据源接口,要么只能引入复杂的 OCR 链路。
  3. 不要轻信 attr()attr():如果通过静态解析 CSS 发现 content attr(data-price),千万别以为大功告成了,你拿到的只是一个表达式。你必须回到那个真实的 DOM 节点上,去提取它自身的 data-price 属性值。
一句话总结这套打法: 先判断来源,能拿接口绝不渲染;真要渲染,记得上稳定代理(如爬虫代理)保底 。希望这篇文章能帮你打破“看见抓不着”的窘境,让你的爬虫更上一层楼!
相关文章
|
10月前
|
Cloud Native IDE Go
升级 GoLand 2025.1 = 云原生开发提速 80%!微服务 + 容器化一键适配+安装包
GoLand 2025.1 是 JetBrains 推出的 Go 语言旗舰 IDE,全面支持 Go 1.23 新特性,强化泛型、模块化与并发开发体验。内置 AI 辅助、多版本切换、云原生工具链,深度优化性能,提升编码、调试、测试全流程效率,助力开发者高效构建现代 Go 应用。
476 2
|
8月前
|
机器学习/深度学习 存储 编解码
阿里云企业级云服务器:c9i、g9i、r9i实例的极致性能与适用场景解析
阿里云第九代企业级云服务器c9i、g9i、r9i实例基于CIPU架构与英特尔®至强®6处理器,分别针对计算密集型、通用型及内存密集型场景优化。c9i实例(1:2配比)支持AMX矩阵加速,适用于机器学习推理、视频编码;g9i实例(1:4配比)平衡计算、存储与网络性能,适合Web应用、数据库;r9i实例(1:8配比)提供大内存容量,适配内存数据库、大数据处理。
|
9月前
|
人工智能 运维 机器人
3000万人问AI什么?千问这份榜单让老金看懂了2025年真相
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
|
9月前
|
UED 开发者
Axure Web端元件库:设计师的高效利器
Axure Web端元件库融合高保真、丰富交互与海量案例,支持Axure 8-10版本,涵盖500+高质量组件,助力设计师高效打造接近真实产品的原型,提升协作效率与创意表达。
330 0
|
11月前
|
Docker 容器
创空间什么时候支持docker?
创空间目前尚未正式支持Docker,尽管界面已显示Beta选项,但暂不可用。具体上线时间请关注官方更新公告。
224 1
|
JSON API 开发者
天猫商品详情 API 接口:功能、调用与实战攻略
天猫商品详情API为电商从业者、开发者和数据分析人员提供高效的商品数据获取途径。通过商品ID,该接口可返回包括基本信息、价格、库存及图片等详细内容,具有高准确性、易集成和功能丰富的特点。示例代码展示了如何用Python调用此API,生成签名确保请求安全,助力用户优化定价策略、开发应用或分析市场趋势。
923 10
|
数据采集 安全 调度
如何用Python搭建一个好用的海外代理IP池子?
本文介绍了如何通过Python构建一个高质量的海外代理IP池,解决全球化数据采集中的IP访问限制、速度瓶颈及资源稳定性问题。文章详细讲解了代理IP池的重要性,包括绕过地理限制、提高访问成功率和保障爬取效率,并推荐了青果网络作为可靠的海外代理IP服务提供商。随后,文章分步骤指导读者完成从安装必要库到整合爬虫任务的全过程,包括获取、验证海外代理IP以及构建动态调度管理的IP池。最后,作者建议使用企业级服务以简化基础设施搭建,专注于采集逻辑与数据分析,助力更高效的爬虫项目开发。
如何用Python搭建一个好用的海外代理IP池子?
|
人工智能 自然语言处理 架构师
Praison AI:LangChain危险了!这个低代码框架让AI智能体「自主协作」,1行代码搞定任务编排
Praison AI 是一个开源的多智能体框架,支持低代码创建和管理AI代理,提供多种流程类型和集成选项,适用于企业流程自动化、智能客服等场景。
925 18
|
存储 监控 安全
网络安全第1章课后题 网络安全概论
网络安全第1章课后题 网络安全概论
2168 1