攻克多级导航循环与class属性ID提取:基于双层循环架构的精准爬虫设计

一、引言

在实际爬虫开发中,最复杂的场景莫过于:网站有多个导航分类,每个分类下又有分页,需要先遍历导航分类,再遍历每个分类下的分页,最后从每个文章页提取数据。这种"多级嵌套循环"的结构,对爬虫的流程控制能力提出了极高要求。

本文将深入分析一个针对 resource-recycling.com 新闻站点的爬虫设计案例。该爬虫创新性地解决了 "多级导航循环""class属性ID提取" 两大核心难题,实现了对多分类新闻网站的高效采集。

与之前案例的核心差异:

  • 导航层级差异:先提取导航分类,再遍历每个分类(之前多为单一路径)
  • ID存储差异:ID存储在article的class属性中,格式如"post-12345"
  • 循环嵌套差异:三层循环(导航→分页→文章列表)
  • 导航提取差异:从首页提取所有导航链接
  • URL构建差异:导航路径 + 分类路径 + 分页路径

二、系统架构与核心流程

2.1 整体架构设计

该爬虫采用 "三层嵌套循环架构",整体流程如下:

2.2 数据流向图


三、关键技术难点与解决方案

难点一:多级导航循环架构

问题描述:

该网站有多个新闻分类(如e-scrap、plastics等),需要先从首页提取所有导航链接,然后对每个导航分类分别进行分页抓取。这是一个典型的多级循环问题。

解决方案:

构建 "三层嵌套循环" 架构:

javascript 复制代码
// 第一层:提取导航链接
{
    "variable-name": ["navigation"],
    "variable-value": [
        "${extract.selectors(resp.html, '.vc_figure>a', 'attr', 'href')}"
    ]
}

// 第二层:导航循环(固定3个分类)
{
    "value": "循环",
    "loopVariableName": "i",
    "loopCount": "3",
    "loopStart": "0"
}

// 在循环内构建分类URL
"url": "https://resource-recycling.com/${navigation[i]}/category/news/page/${page}/"

// 第三层:分页循环
{
    "variable-name": ["news_urllist", "page", "news_idlist"],
    "variable-value": [
        "${page==null?null:extract.selectors(resp.html, '#rwh_content_margin h1>a', 'attr', 'href')}",
        "${page==null?1:page+1}",
        "${page==null?null:extract.selectors(resp.html, '#rwh_content_margin>article', 'attr', 'class')}"
    ]
}

多级循环原理图:
文章层循环 index
分页层循环 page=1
导航层循环 i=0 to 2
i=0: e-scrap
分页循环
i=1: plastics
分页循环
i=2: recycling
分页循环
page=1

提取文章列表
page=1

提取文章列表
page=1

提取文章列表
遍历每篇文章
遍历每篇文章
遍历每篇文章

URL构建示例:

导航i navigation[i] page 完整URL
0 e-scrap 1 /e-scrap/category/news/page/1/
1 plastics 1 /plastics/category/news/page/1/
2 recycling 1 /recycling/category/news/page/1/

难点二:class属性中的ID提取

问题描述:

新闻ID存储在article标签的class属性中,格式为"post-12345 post-xxx",需要从中提取数字ID。

解决方案:

使用正则表达式从class字符串中提取数字:

javascript 复制代码
// 第一层:提取class属性
"${extract.selectors(resp.html, '#rwh_content_margin>article', 'attr', 'class')}"
// 返回: ["post-12345 post-xxx", "post-12346 post-xxx", ...]

// 第二层:正则提取数字
"${news_idlist[index].regx('(?<=post-)(\\d+)').toInt()}"
// 正则解释: (?<=post-) 匹配前面有"post-"的位置
//           (\d+)      提取连续数字

class属性提取原理图:
正则提取
提取class
HTML结构

标题

标题

post-12345 post-news
post-12346 post-news
(?<=post-)(\d+)
12345
12346

正则分解说明:

正则部分 含义 作用
(?<=post-) 正向零宽断言 匹配位置:前面必须是"post-"
(\d+) 捕获组 匹配连续数字并捕获

难点三:双列表协同提取

问题描述:

需要同时提取URL列表(从h1>a)和ID列表(从article class),两者通过索引关联。

解决方案:

采用双列表并行提取,通过索引关联:

javascript 复制代码
// 定义变量节点 - 双列表提取
{
    "variable-name": ["news_urllist", "page", "news_idlist"],
    "variable-value": [
        // URL列表
        "${page==null?null:extract.selectors(resp.html, '#rwh_content_margin h1>a', 'attr', 'href')}",
        // 分页控制
        "${page==null?1:page+1}",
        // ID列表(从class属性)
        "${page==null?null:extract.selectors(resp.html, '#rwh_content_margin>article', 'attr', 'class')}"
    ]
}

// 新闻地址节点 - 索引关联
{
    "variable-name": ["news_url", "news_id", "news_urlmap", "query_result"],
    "variable-value": [
        "${news_urllist[index]}",                          // 通过索引获取URL
        "${news_idlist[index].regx('(?<=post-)(\\d+)').toInt()}", // 通过索引获取ID
        "${{'url': news_url}}",
        "${!rs.contains(news_urlmap)}"
    ]
}

难点四:固定导航循环次数

问题描述:

导航分类数量固定为3个(e-scrap、plastics、recycling),可以直接写死循环次数。

解决方案:

固定循环次数为3:

javascript 复制代码
// 循环节点配置
{
    "value": "循环",
    "loopVariableName": "i",
    "loopCount": "3",  // 固定循环3次
    "loopStart": "0"
}

难点五:7天时间窗口

问题描述:

需要抓取最近7天的新闻,反映该网站更新频率较高。

解决方案:

动态时间范围计算:

javascript 复制代码
// 获取时间范围节点
{
    "variable-name": ["start_date", "end_date"],
    "variable-value": [
        "${date.format(date.addDays(date.now(),-7),'yyyy-MM-dd')}",  // 7天前
        "${date.format(date.addDays(date.now(),1),'yyyy-MM-dd')}"    // 明天
    ]
}

四、核心代码实现解析

4.1 多级导航循环控制器

javascript 复制代码
// 伪代码:多级导航循环控制器
class MultiLevelNavigationController {
    constructor(navigationUrls, maxPagePerCategory = 1) {
        this.navigationUrls = navigationUrls; // 导航链接数组
        this.maxPagePerCategory = maxPagePerCategory;
        this.currentNavIndex = 0;
        this.currentPage = null;
    }
    
    async run() {
        // 第一层:导航循环
        for (let i = 0; i < this.navigationUrls.length; i++) {
            const navUrl = this.navigationUrls[i];
            
            // 第二层:分页循环
            let page = 1;
            while (page <= this.maxPagePerCategory) {
                const listUrl = this.buildListUrl(navUrl, page);
                await this.processListPage(listUrl);
                page++;
            }
        }
    }
    
    buildListUrl(navUrl, page) {
        // 从导航URL提取路径
        const path = navUrl.replace('https://resource-recycling.com/', '');
        return `https://resource-recycling.com/${path}/category/news/page/${page}/`;
    }
    
    async processListPage(url) {
        // 处理列表页,提取URL和ID
        const html = await this.fetch(url);
        const urls = this.extractUrls(html);
        const ids = this.extractIds(html);
        
        // 第三层:文章循环
        for (let j = 0; j < urls.length; j++) {
            await this.processArticle(urls[j], ids[j]);
        }
    }
}

4.2 class属性ID提取器

javascript 复制代码
// 伪代码:class属性ID提取器
class ClassAttributeIdExtractor {
    constructor(pattern = /post-(\d+)/) {
        this.pattern = pattern;
    }
    
    extractIdFromClass(classString) {
        const match = classString.match(this.pattern);
        return match ? parseInt(match[1]) : null;
    }
    
    extractIdsFromClassList(classList) {
        return classList.map(cls => this.extractIdFromClass(cls))
                       .filter(id => id !== null);
    }
    
    // 批量处理
    process(html) {
        // 提取所有article的class
        const classes = extract.selectors(html, 'article', 'attr', 'class');
        return this.extractIdsFromClassList(classes);
    }
}

// 使用示例
const extractor = new ClassAttributeIdExtractor();
const ids = extractor.process(html); // [12345, 12346, 12347]

4.3 双列表关联器

javascript 复制代码
// 伪代码:双列表关联器
class DualListAssociator {
    constructor(urlList, idList) {
        this.urls = urlList;
        this.ids = idList;
        this.count = Math.min(urlList.length, idList.length);
    }
    
    getAll() {
        const results = [];
        for (let i = 0; i < this.count; i++) {
            results.push({
                url: this.urls[i],
                id: this.ids[i],
                urlMap: { 'url': this.urls[i] }
            });
        }
        return results;
    }
    
    getOne(index) {
        if (index >= this.count) return null;
        return {
            url: this.urls[index],
            id: this.ids[index],
            urlMap: { 'url': this.urls[index] }
        };
    }
}

五、与前八个案例的对比分析

5.1 核心差异点对比

维度 resource-recycling plastikmedia packaging-gateway 雅式橡塑网 icis新闻 polymerupdate博客 bioplasticsnews polymerupdate新闻 chemanalyst
核心架构 三层嵌套循环 单层循环 单层循环+容错 单层循环 单层循环 单层循环 单层循环 单层循环 单层循环
导航层级 首页→导航→分页 直接列表页 直接列表页 直接列表页 直接列表页 直接列表页 直接列表页 直接列表页 直接列表页
ID存储 article class data-id URL中 对象属性 article id URL中 URL中
ID格式 post-12345 - - 纯数字 URL中数字 对象属性 post-数字 URL中数字 URL中数字
循环嵌套 3层 2层 2层 2层 2层 2层 2层 2层 2层
导航提取 ✅ .vc_figure>a
代理策略 全流程 全流程 详情页 全流程 详情页

5.2 差异化技术难点

5.3 循环架构演进

单层循环

chemanalyst
双层循环

bioplasticsnews
三层嵌套循环

resource-recycling


六、性能优化与最佳实践

6.1 多级循环优化

javascript 复制代码
// 控制循环深度
const MAX_NAV_ITEMS = 3;
const MAX_PAGES = 1;

for (let navIndex = 0; navIndex < MAX_NAV_ITEMS; navIndex++) {
    for (let page = 1; page <= MAX_PAGES; page++) {
        // 处理列表页
        for (let articleIndex = 0; articleIndex < urls.length; articleIndex++) {
            // 处理文章
        }
    }
}

6.2 class属性提取优化

class格式 正则表达式 提取结果
post-12345 post-(\d+) 12345
post-12345 post-news (?<=post-)(\d+) 12345
article-12345 article-(\d+) 12345

6.3 导航提取优化

javascript 复制代码
// 提取导航路径(而非完整URL)
function extractNavPath(href) {
    // href: https://resource-recycling.com/e-scrap/
    // 返回: e-scrap
    return href.replace('https://resource-recycling.com/', '')
               .replace(/\/$/, '');
}

七、总结与经验分享

7.1 核心收获

  1. 多级嵌套循环:处理需要先遍历导航、再遍历分页的复杂场景
  2. class属性ID提取:从article的class属性中提取数字ID
  3. 动态URL构建:根据导航路径动态构建分类URL
  4. 三层循环架构:导航循环 + 分页循环 + 文章循环

7.2 可复用经验

  1. 分析网站结构:识别是否有多个导航分类需要分别处理
  2. 多级循环设计:根据实际层级设计相应层数的嵌套循环
  3. 属性ID提取:注意class属性可能包含多个类名,需要精确定位
  4. 导航路径提取:从完整URL中提取有用的路径部分

7.3 适用场景

该爬虫设计模式适用于:

  • 有多个新闻分类的网站
  • ID存储在class属性中的网站
  • 需要多层遍历的复杂网站
  • 分类+分页组合的网站结构

八、附录:核心配置对照表

节点类型 核心作用 关键技术点
获取时间范围 动态计算7天窗口 date.addDays(now,-7)
执行SQL(查询) 获取已抓取URL like '%resource-recycling.com%'
抓取网站 获取首页 提取导航链接
定义变量(导航) 提取导航列表 .vc_figure>a href
导航循环 遍历3个分类 loopCount=3
抓取新闻列表 获取分类列表页 / n a v / c a t e g o r y / n e w s / p a g e / {nav}/category/news/page/ nav/category/news/page/{page}/
定义变量(列表) 双列表提取 URL + class属性
分页循环 page<=1 只抓第一页
文章循环 遍历文章 list.length(news_urllist)
新闻地址 正则提取ID (?<=post-)(\d+)
抓取新闻详情页 获取详情页 全流程代理
定义变量(详情) 提取字段 标题/作者/时间/内容
执行SQL(插入) 存储数据 source='resource-recycling'

通过以上设计,该爬虫成功应对了多级导航循环和class属性ID提取的双重挑战,实现了对resource-recycling.com新闻网站的高效稳定采集。其中的三层嵌套循环架构、class属性正则提取等思路,对于处理多分类复杂网站的爬虫开发具有很高的参考价值。

相关推荐
沐风___8 小时前
解锁协作式 AI:Claude Agent Teams 架构与实战完全指南
人工智能·架构
小程故事多_808 小时前
无 GitAI 依赖|企业 AI 编码合规管控 + 全生命周期追溯,实现效率与安全双向破局
人工智能·安全·架构·aigc·ai编程·harness
任我坤9 小时前
状态驱动的图计算架构解析
架构
Agent产品评测局9 小时前
汽车行业智能自动化平台选型,生产与供应链全优化:2026企业级智能体(Agent)实测与架构解析
java·人工智能·ai·chatgpt·架构·自动化
Joy T9 小时前
【Web3】深度解析 NFT 跨链智能合约开发:原生资产与衍生包装合约架构实战
git·架构·web3·区块链·node·智能合约·hardhat
ZC跨境爬虫10 小时前
Playwright核心操作实战精讲(QQ空间+百度+iframe,含等待_键盘_iframe操作)
前端·爬虫·python·计算机外设
小陈工10 小时前
2026年4月4日技术资讯洞察:异步编程范式重塑、架构理性回归与开发者体验革命
开发语言·人工智能·python·机器学习·架构·数据挖掘·回归
GISer_Jing10 小时前
GeoFlow-AI:智能三维地理空间处理平台
前端·人工智能·架构
ZC跨境爬虫10 小时前
对称加密算法详解(DES篇):特点、实现与逆向实操
前端·javascript·爬虫