瀑布模式:分层策略实现可靠的数据提取

发布: (2026年2月15日 GMT+8 04:05)
10 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将按照要求保留原始链接、格式和技术术语,仅翻译文本部分。

瀑布方法 – 构建弹性爬虫

现在是凌晨3点,你的生产爬虫刚刚崩溃了。日志显示了一个常见的罪魁祸首:目标网站的开发者将一个 CSS 类从 product-price 重命名为 price‑v2‑red。这只是一次耗时五秒的表面改动,却导致你的整个数据管道中断。

如果你仅仅依赖视觉 CSS 选择器,就像在流沙上建房。网站不断变化,每一次重新设计都可能成为维护噩梦。要 构建弹性爬虫,请使用 “瀑布” 方法——一种分层优先级系统,在放弃之前会在多种提取方式之间回退。

稳定性层级

网页不仅仅是一个视觉文档;它由不同层次的数据组成,每一层的稳定性程度各不相同。瀑布方法按照从最稳定到最不稳定的顺序对这些层进行优先级排序。

层级稳定原因
Tier 1隐藏数据(JSON‑LD / Script 标签)用于 SEO 或内部 JavaScript 框架的结构化数据是为机器而设计的,而非人为阅读。界面重新设计时它很少会改变。
Tier 2语义锚点(ID / Data 属性)id="product-123"data-testid="price-display" 这样的唯一标识符通常与数据库键或自动化测试套件绑定。开发者很少更改它们,因为这会破坏自己的内部工具。
Tier 3关系 XPath如果缺少特定的 ID,就寻找标签。虽然 CSS 类会变化,但 “Price:” 这类文字通常保持不变。XPath 可以定位该文本并获取其相邻的元素。
Tier 4视觉选择器(CSS 类)这是最后的手段。像 .blue-text 这样的 CSS 类会在设计师想要新外观时随时更改。只有在所有其他方法都失效时才使用它们。

通过从 Tier 1 开始并按瀑布顺序向下进行,你可以在最大化成功率的同时最小化维护成本。

设置环境

我们将使用 parsel,这是为 Scrapy 提供动力的库,因为它允许在同一个对象中使用 CSS、XPath 和正则表达式。

pip install parsel requests

模拟 HTML 片段

以下 HTML 片段将在整个指南中使用。它代表一个典型的电子商务页面,包含多个数据层:

html_content = """

    
        
        {
            "@context": "https://schema.org/",
            "@type": "Product",
            "name": "Ultimate Coffee Grinder",
            "sku": "GRND-99",
            "offers": {
                "price": "89.99",
                "priceCurrency": "USD"
            }
        }
        
    
    
        
            

Ultimate Coffee Grinder

            Price:
            $89.99
        
    
"""

Tier 1 – 金牌标准(隐藏 JSON)

现代网站通常在 <script> 标签中嵌入结构化数据(通常是用于 SEO 的 JSON‑LD,或像 Next.js 之类框架的 “window state” 对象)。这种来源非常稳定,因为它独立于 HTML 布局。

import json
from parsel import Selector

def extract_tier_1(selector):
    # 定位包含 JSON‑LD 的 script 标签
    json_data = selector.css('script[type="application/ld+json"]::text').get()
    if json_data:
        data = json.loads(json_data)
        # 安全地遍历字典
        return data.get('offers', {}).get('price')
    return None

sel = Selector(text=html_content)
print(f"Tier 1 Result: {extract_tier_1(sel)}")

Tier 2 – 语义锚点(ID 与 Data 属性)

如果没有 JSON‑LD,可寻找 语义锚点。这些属性描述 数据是什么,而不是 它的外观。ID 和 data‑* 属性常用于状态管理或端到端测试,且比样式类更少变化。

def extract_tier_2(selector):
    # 先尝试 ID。如果 ID 是动态的,使用 “starts‑with” 选择器。
    price = selector.css('[id^="price-id-"]::text').get()

    # 回退到现代框架常用的 data 属性
    if not price:
        price = selector.css('[data-testid="product-price"]::text').get()

    return price.replace('$', '').strip() if price else None

Tier 3 – 基于文本的关系逻辑(XPath)

当缺少干净的 ID 时,依赖可见的文本标签。 在电商站点上,“Price:” 几乎总是出现在实际数值旁边。

使用 XPath 轴,可以定位包含标签 “Price:” 的元素,然后跳转到其相邻的元素。 这种标签‑值关系通常在标签类型改变时仍然保持。

def extract_tier_3(selector):
    # 找到包含 "Price:" 的 <span>,然后获取下一个兄弟 <span> 的文本
    xpath_query = "//span[contains(text(), 'Price:')]/following-sibling::span/text()"
    price = selector.xpath(xpath_query).get()
    return price.replace('$', '').strip() if price else None

Tier 4 – 最后手段(正则表达式)

有时 DOM 乱成一团:类名被混淆、没有 ID、结构深层嵌套。 在这种情况下,把 HTML 当作普通字符串,使用 正则表达式。 正则表达式完全忽略 DOM 树,允许你基于模式提取值。

import re

def extract_tier_4(html):
    # 查找前面可能有空白和 "$" 的金额
    match = re.search(r'\$?\s*([0-9]+(?:\.[0-9]{2})?)', html)
    return match.group(1) if match else None

综合示例

def waterfall_extract(html):
    selector = Selector(text=html)

    # Tier 1
    price = extract_tier_1(selector)
    if price:
        return price

    # Tier 2
    price = extract_tier_2(selector)
    if price:
        return price

    # Tier 3
    price = extract_tier_3(selector)
    if price:
        return price

    # Tier 4
    return extract_tier_4(html)

print("Final price:", waterfall_extract(html_content))

对模拟 HTML 运行脚本的结果是:

Final price: 89.99

回顾

  1. 从隐藏的、机器可读的数据(JSON‑LD,API 负载)开始
  2. 回退到语义锚点iddata‑*)。
  3. 使用基于稳定文本标签的关系 XPath
  4. 仅在 DOM 没有可靠挂钩时才使用正则表达式

通过遵循 Waterfall Method,你的爬虫对重新设计、类名更改以及其他表面变化的适应性大大提升——为你节省无数深夜调试时间。祝爬取愉快!

附加正则回退(第 4 层 – 替代方案)

当所有其他方法都失败时,您可以在 JavaScript 变量或深层嵌套的字符串中搜索隐藏的价格模式。

import re

def extract_tier_4(html_string):
    # Search for a pattern like price: "89.99" anywhere in the raw HTML
    match = re.search(r'price":\s*"([\d.]+)"', html_string)
    if match:
        return match.group(1)
    return None

综合瀑布函数(带日志)

将分层方法合并为一个函数。优先使用最稳定的方法,并在需要回退到更低层时记录警告。该警报系统可以在网站发生变化、爬虫真正失效之前提醒你。

import logging
from parsel import Selector   # or any selector library you use

logging.basicConfig(level=logging.INFO)

def get_product_price(html):
    sel = Selector(text=html)

    # Tier 1: JSON‑LD
    price = extract_tier_1(sel)
    if price:
        return price
    logging.warning("Tier 1 failed. Falling back to Tier 2 (Attributes).")

    # Tier 2: Semantic Attributes
    price = extract_tier_2(sel)
    if price:
        return price
    logging.warning("Tier 2 failed. Falling back to Tier 3 (XPath Relational).")

    # Tier 3: XPath Relational
    price = extract_tier_3(sel)
    if price:
        return price
    logging.error("Tier 1‑3 failed. Attempting Tier 4 (Regex) as last resort.")

    # Tier 4: Regex on raw string
    return extract_tier_4(html)

final_price = get_product_price(html_content)
print(f"Final Extracted Price: {final_price}")

为什么这很重要

想象一下网站所有者更新他们的网站:他们删除了 JSON‑LD(Tier 1)并更改了所有 CSS 类(Tier 2)。

在传统的爬虫中,你的代码会返回 None 并崩溃。使用 Waterfall Method,你的 Tier 3 逻辑仍然能够找到数据。你会在日志中收到警告,让你在工作时间更新主要选择器,而不是在午夜处理紧急情况。

总结

弹性爬取需要接受网站是动态变化的事实。瀑布模型为您的数据提取提供了安全网。

  • 优先获取机器可读的数据: 首先检查 JSON‑LD 或 <script> 标签。
  • 使用语义锚点: 更倾向于使用 data- 属性和 id 标签,而不是 CSS 类。
  • 利用 XPath 关系: 使用人类可读的标签作为锚点来查找相邻数据。
  • 监控回退机制: 当爬虫降至较低层级时记录日志,以主动应对选择器的更改。

通过摆脱脆弱的基于类的选择器,您可以减少修复破损代码的时间,更多地专注于使用数据。欲了解更高级的示例,请参阅 Homedepot.com Scrapers repository

0 浏览
Back to Blog

相关文章

阅读更多 »