爬虫实战:如何优雅地抓取网页中隐藏在伪元素(::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 属性值。
一句话总结这套打法: 先判断来源,能拿接口绝不渲染;真要渲染,记得上稳定代理(如爬虫代理)保底 。希望这篇文章能帮你打破“看见抓不着”的窘境,让你的爬虫更上一层楼!
相关文章
|
27天前
|
人工智能 自然语言处理 API
阿里云海外重磅发布 Qwen Cloud
Qwen Cloud,正是为AI Agent 而生的全新服务方式。
2373 61
|
19小时前
|
存储 JSON 搜索推荐
Python的字典合并坑了我三天,原来是踩了这个坑
本文揭秘Python字典合并的五大陷阱:`update()`浅层覆盖导致嵌套键丢失、原地修改污染原始数据、解包`{**d1,**d2}`和`|`运算符同样不递归、`ChainMap`仅为逻辑视图。附深度合并方案与对比表格,助你避开配置系统翻车。
40 0
|
19小时前
|
数据采集 JavaScript 前端开发
技术拆解:单页面应用(SPA)路由跳转后的数据抓取策略
这篇文档讨论了单页应用(SPA)数据抓取的挑战和策略。SPA与传统服务端渲染不同,数据由JS生成,导致传统爬虫失效。难点包括路由切换时机、动态接口定位、鉴权、反爬等。解决方案包括直接复现接口和浏览器自动化渲染。核心是保持会话和IP一致性。文档提供了技术解决方案和代码示例。
|
3月前
|
数据采集 JavaScript 前端开发
告别空壳HTML!Node.js + Playwright + 代理IP 优雅抓取动态网页实战
本文详解Node.js+Playwright抓取动态网页的实战方案:针对React/Vue等框架渲染的SPA页面,结合代理IP(支持动态/固定转发模式)突破采集限制,并提供BrowserContext级代理配置、IP有效性验证、健壮重试机制及常见报错(407/429/403)应对策略,助你构建高可用工业级爬虫。
487 0
|
27天前
|
数据采集 机器学习/深度学习 运维
从“秒封”到“日爬十万”:谈谈5个风控机制
这篇文档讨论了Python爬虫常见问题和反爬策略。作者提出五个关键点:1. 控制请求频率;2. 轮换IP;3. 伪装请求头;4. 模拟真实访问路径;5. 使用高匿名代理。这些策略需综合运用,提高爬虫生存率。
290 5
|
8月前
|
机器学习/深度学习 人工智能 数据挖掘
不是简单搜索,而是理解:让获取的论文会“语义对话”
本文探讨如何用向量数据库实现学术文献的语义检索。传统搜索依赖关键词匹配,效果有限;而通过将论文内容向量化并存入FAISS等库,可实现“理解式”检索——如搜“ECG deep learning”,也能找到“心律失常CNN分类”相关内容。借助PubMed合法API获取数据,结合Sentence-BERT生成嵌入,构建可复现的智能检索原型,提升科研效率。
305 2
|
3月前
|
数据采集 Rust NoSQL
架构视角下的千万级分布式爬虫:Rust + Reqwest 与代理网关的全局设计
本文探讨如何用Rust重构分布式爬虫Worker节点,解决高并发下的内存泄漏、CPU瓶颈与代理调度难题;结合Tokio、Reqwest与企业级隧道代理,实现千万级实时抓取的稳定、安全与高效。
296 2
|
4天前
|
人工智能 Kubernetes 调度
AgentTeams 和 Claude Tag 都进入群聊模式,是新范式还是新叙事?
AgentTeams 和 AgentLoop 均处于邀测期,欢迎感兴趣的朋友申请测试。
|
7月前
|
数据采集 Java 调度
从10个协程到1000个协程:性能下降的背后究竟发生了什么?
本文探讨了异步程序中常见的误解“协程越多越快”,并通过一个实际的异步抓取学术论文元数据的例子来阐明这一点。文章首先解释了协程过多可能导致的效率低下的原因,包括事件循环的调度限制、网络瓶颈、代理并发限制以及Python协程切换的成本。接着,文章提供了一个使用代理、从DOAJ抓取开放论文元数据并存入SQLite数据库的完整异步代码示例,并强调了合理设置并发量的重要性。最后,文章总结了初学者在编写异步抓取程序时容易遇到的几个陷阱,并提供了相应的解决方案。
319 2
|
7月前
|
消息中间件 NoSQL Kafka
强一致性时代,Kafka、Redis、Celery 谁才是那块短板
这篇文章讨论了一个金融级实时Tick数据项目的失败与修复。项目最初使用Celery、Kafka和Redis,但因缺乏重试、幂等和安全策略导致问题。文章提出了四个关键改进方向,修复后的系统满足了金融级要求,总结了五点教训。
654 1