跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
JavaScriptNode.js大前端

动态网站爬虫实战:SpiderFlow 可视化编排与自定义函数

综述由AI生成介绍使用 SpiderFlow 可视化工具结合自定义 JavaScript 函数爬取动态加载网站(Coveris)的方法。通过 F12 分析 XHR 请求定位数据接口,模拟滚动触发加载,利用正则解析 JSON 响应提取 URL。流程包含分页抓取、去重判断、详情提取及数据库存储。解决了传统静态解析无法获取异步内容的问题,实现了新闻数据的自动化采集。

imJackJia发布于 2026/3/21更新于 2026/5/614 浏览
动态网站爬虫实战:SpiderFlow 可视化编排与自定义函数

前言

在爬虫开发过程中,我们经常会遇到动态加载的网站,这类网站采用了现代前端框架(如 React、Vue 或 Angular)构建,数据是通过 JavaScript 异步加载的。传统的基于静态 HTML 解析的爬虫框架往往无法直接定位元素,因为它们只能获取初始 HTML 文档,而无法执行 JavaScript 代码来获取动态生成的内容。

本文将分享一个实际案例,展示如何通过自定义函数 + 可视化爬虫编排的方式,成功爬取动态加载的 Coveris 新闻网站。Coveris 是一家国际包装解决方案提供商,其新闻中心页面采用了典型的 AJAX 动态加载技术,新闻内容通过异步请求加载,页面 URL 保持不变,这给传统爬虫带来了很大挑战。

具体解决方案包括以下几个关键步骤:

  1. 使用浏览器开发者工具(F12)分析 XHR 请求,定位到新闻数据的 API 接口
  2. 通过自定义 JavaScript 函数模拟滚动事件,触发更多数据的加载
  3. 利用可视化编排工具设置合理的请求间隔,避免触发反爬机制
  4. 设计数据解析逻辑,处理嵌套的 JSON 数据结构

在实现过程中,我们特别注意到:

  • 需要设置合适的 User-Agent 头部信息模拟浏览器访问
  • 要处理分页逻辑,通常通过观察 API 请求参数中的 offset 或 page 参数变化
  • 对于图片等媒体资源,需要额外处理相对路径转绝对路径的问题
  • 要考虑异常处理机制,如请求失败时的重试策略

通过这种方法,我们最终成功获取了 Coveris 网站的新闻数据,包括标题、发布日期、正文内容和相关图片链接,为后续的数据分析和商业情报收集提供了可靠的数据源。

问题背景

目标网站:http://www.coveris.com

挑战:

  • 网站内容采用动态加载技术,新闻列表通过 Ajax 请求获取
  • 无法直接使用 SpiderFlow 的选择器功能定位元素
  • 需要处理分页、去重、数据提取等一系列复杂逻辑

整体架构设计

[开始] → [获取时间范围] → [查询已爬取 URL] → [分页抓取列表] → [解析 URL 列表] → [循环处理] → [去重判断] → [抓取详情] → [提取内容] → [存储数据] → [结束]

思路点拨

列表接口 https://www.coveris.com/en/news/press-releases

列表中拿到内容标题和详情链接:https://www.coveris.com/en/news/coveris-and-tipa-enter-exclusive-agreement-to-deliver-home-compostable-produce-labels

关键技术点详解

1. 动态内容抓取策略

对于动态加载的网站,我们不能直接解析页面 HTML,而是需要找到真实的数据接口。

核心配置:自定义函数 match_coveris
// 自定义函数:match_coveris
// 功能:从 Ajax 响应中提取新闻 URL
// 输入:htmlStr (Ajax 返回的 JSON 字符串)
// 输出:新闻 URL 数组
// 注册位置:系统管理 → 自定义函数 → 添加函数
function match_coveris(htmlStr) {
    // 1. 获取 html 字段中的内容
    var htmlContent = htmlStr;
    if (!htmlContent) return [];
    
    // 2. 正则匹配:提取 "url" 字段的值
    // 正则说明:匹配 "url": "具体 URL" 格式
    var regex = /"url"\s*:\s*"(.*?)"/g;
    
    // 3. 执行匹配并提取结果
    var matches;
    var results = [];
    while ((matches = regex.exec(htmlContent)) !== null) {
        var sentence = matches[1].trim();
        if (sentence) { results.push(sentence); }
    }
    
    // 4. 返回 URL 数组
    return results;
}
2. 初始化节点配置
2.1 开始节点
<!-- 爬虫基础配置 -->
<startNode>
    <spiderName>000227_抓取 coveris 新闻</spiderName>
    <submit-strategy>random</submit-strategy>
    <!-- 随机提交策略,避免被识别 -->
    <threadCount></threadCount>
    <!-- 线程数留空,使用默认值 -->
</startNode>
2.2 获取时间范围节点
<!-- 变量节点:动态计算时间范围 -->
<variableNode name="获取时间范围">
    <!-- 变量配置 -->
    <variable name="start_date" value="${date.format(date.addDays(date.now(),-30),'yyyy-MM-dd')}" description="开始日期:30 天前"/>
    <variable name="end_date" value="${date.format(date.addDays(date.now(),1),'yyyy-MM-dd')}" description="结束日期:明天"/>
</variableNode>
<!-- 输出节点:查看时间范围(用于调试) -->
<outputNode name="输出时间范围">
    <output-name>["开始时间","结束时间"]</output-name>
    <output-value>["${start_date}","${end_date}"]</output-value>
</outputNode>
3. 数据查询节点配置
3.1 查询已爬取 URL
<!-- 执行 SQL 节点:查询指定时间范围内的已爬取 URL -->
<executeSqlNode name="查询已爬取 URL">
    <!-- 数据源配置 -->
    <datasourceId>ee975ab73415f54e7872e57ed0031ce9</datasourceId>
    <statementType>select</statementType>
    <!-- SQL 语句:查询已存在的 URL 用于去重 -->
    <sql> SELECT url FROM news WHERE url like '%https://www.coveris.com%' AND (insert_date BETWEEN '${start_date}' AND '${end_date}') </sql>
    <!-- 查询结果将存入 rs 变量 -->
</executeSqlNode>
<!-- 输出节点:查看查询结果 -->
<outputNode name="输出查询结果">
    <output-name>["开始时间","结束时间","获取结果","结果数量"]</output-name>
    <output-value>["${start_date}","${end_date}","${rs}","${list.length(rs)}"]</output-value>
</outputNode>
4. 分页抓取配置
4.1 分页请求节点
<!-- 请求节点:抓取列表页 -->
<requestNode name="开始抓取">
    <method>GET</method>
    <sleep>5000</sleep>
    <!-- 请求间隔 5 秒 -->
    <timeout>30000</timeout>
    <!-- 超时时间 30 秒 -->
    <retryCount>3</retryCount>
    <!-- 失败重试 3 次 -->
    <retryInterval>5000</retryInterval>
    <!-- 重试间隔 5 秒 -->
    <!-- 请求头配置 -->
    <header-name>["User-Agent"]</header-name>
    <header-value>["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"]</header-value>
    <!-- 动态 URL:使用 page 变量 -->
    <url>https://www.coveris.com/app/article/list?page=${page}</url>
    <!-- 其他配置 -->
    <follow-redirect>1</follow-redirect>
    <!-- 自动跟随重定向 -->
    <cookie-auto-set>1</cookie-auto-set>
    <!-- 自动管理 Cookie -->
</requestNode>
4.2 解析 URL 列表节点
<!-- 变量节点:解析响应内容 -->
<variableNode name="定义变量">
    <!-- 分页控制:自动累加页码 -->
    <variable name="page" value="${page==null?1:page+1}" description="当前页码"/>
    <!-- 调用自定义函数解析 URL 列表 -->
    <variable name="news_urllist" value="${match_coveris(resp.html)}" description="新闻 URL 列表"/>
</variableNode>
<!-- 条件分支:判断是否继续分页 -->
<conditionNode name="分页判断">
    <!-- 条件:当页码小于等于 2 且 URL 列表不为空时继续 -->
    <condition expression="${news_urllist!=null &amp;&amp; page<=2}">
        <target>开始抓取</target>
        <!-- 继续下一页 -->
    </condition>
    <condition expression="${news_urllist==null || page>2}">
        <target>循环处理</target>
        <!-- 进入详情处理 -->
    </condition>
</conditionNode>
5. 循环处理节点配置
5.1 循环节点
<!-- 循环节点:遍历新闻 URL 列表 -->
<loopNode name="循环">
    <!-- 循环配置 -->
    <loopCount>${news_urllist.length}</loopCount>
    <!-- 循环次数 = URL 数量 -->
    <loopStart>0</loopStart>
    <!-- 起始索引 -->
    <loopEnd>-1</loopEnd>
    <!-- 结束索引,-1 表示到最后 -->
    <loopVariableName>index</loopVariableName>
    <!-- 循环变量名,从 0 开始 -->
</loopNode>
5.2 构造详情页 URL
<!-- 变量节点:处理当前新闻 URL -->
<variableNode name="新闻地址">
    <!-- 获取当前循环的 URL,并处理转义字符 -->
    <variable name="news_url" value="${news_urllist[index].replace('\\', '')}" description="原始 URL 路径"/>
    <!-- 构造完整的新闻详情页 URL -->
    <variable name="news_urlmap" value="${'https://www.coveris.com' + news_url}" description="完整 URL"/>
    <!-- 去重判断:检查 URL 是否已存在 -->
    <variable name="query_result" value="${!rs.contains(news_urlmap)}" description="是否为新 URL"/>
</variableNode>
<!-- 输出节点:查看处理结果 -->
<outputNode name="输出 URL 信息">
    <output-name>["原始路径","完整 URL","是否为新"]</output-name>
    <output-value>["${news_url}","${news_urlmap}","${query_result}"]</output-value>
</outputNode>
<!-- 条件分支:基于去重结果分流 -->
<conditionNode name="去重判断">
    <!-- 新 URL:进入详情抓取 -->
    <condition expression="${query_result}">
        <target>抓取新闻详情</target>
    </condition>
    <!-- 已存在 URL:跳过 -->
    <condition expression="${!query_result}">
        <target>输出调试信息</target>
    </condition>
</conditionNode>
6. 详情页抓取配置
6.1 抓取详情页
<!-- 请求节点:抓取新闻详情 -->
<requestNode name="抓取新闻详情">
    <method>GET</method>
    <sleep>5000</sleep>
    <!-- 请求间隔 5 秒 -->
    <timeout>30000</timeout>
    <!-- 超时时间 30 秒 -->
    <retryCount>3</retryCount>
    <!-- 失败重试 3 次 -->
    <retryInterval>5000</retryInterval>
    <!-- 重试间隔 5 秒 -->
    <!-- 动态 URL:使用构造好的完整 URL -->
    <url>${news_urlmap}</url>
    <!-- 其他配置 -->
    <follow-redirect>1</follow-redirect>
    <cookie-auto-set>1</cookie-auto-set>
</requestNode>
6.2 提取新闻内容
<!-- 变量节点:提取详情页内容 -->
<variableNode name="内容提取">
    <!-- 使用选择器提取标题 -->
    <variable name="title" value="${extract.selector(resp.html, 'h1')}" description="新闻标题"/>
    <!-- 作者字段(可根据实际页面调整选择器) -->
    <variable name="author" value="${extract.selector(resp.html, '.author')}" description="作者"/>
    <!-- 发布日期(可根据实际页面调整选择器) -->
    <variable name="release_date" value="${extract.selector(resp.html, '.date')}" description="发布日期"/>
    <!-- 提取正文内容 -->
    <variable name="content" value="${extract.selector(resp.html, '.text-column.theme-wysiwyg', 'text')}" description="新闻正文"/>
</variableNode>
<!-- 条件判断:内容不为空才保存 -->
<conditionNode name="内容判断">
    <condition expression="${content!=null}">
        <target>执行 SQL 保存</target>
    </condition>
    <condition expression="${content==null}">
        <target>输出错误日志</target>
    </condition>
</conditionNode>
7. 数据存储配置
7.1 SQL 插入节点
<!-- 执行 SQL 节点:保存新闻数据 -->
<executeSqlNode name="保存新闻数据">
    <datasourceId>ee975ab73415f54e7872e57ed0031ce9</datasourceId>
    <statementType>insert</statementType>
    <!-- SQL 语句:插入新闻数据 -->
    <sql> INSERT INTO news ( url, -- 新闻 URL
        news_id, -- 新闻 ID
        author, -- 作者
        title, -- 标题
        release_date, -- 发布日期
        content, -- 内容
        source, -- 来源
        insert_date -- 插入时间
    ) VALUES (
        '#${news_urlmap}#', -- URL
        '#${news_id}#', -- ID(如果有)
        '#${author}#', -- 作者
        '#${title}#', -- 标题
        '#${release_date}#', -- 发布日期
        '#${content}#', -- 内容
        '#www.coveris.com#', -- 来源
        NOW() -- 当前时间
    ) </sql>
    <!-- 说明:使用#号包裹变量,可防止 SQL 注入 -->
</executeSqlNode>
<!-- 输出节点:保存确认 -->
<outputNode name="保存结果">
    <output-name>["URL","标题","保存状态"]</output-name>
    <output-value>["${news_urlmap}","${title}","保存成功"]</output-value>
</outputNode>
8. 合并与结束节点
<!-- ForkJoin 节点:同步多个并行分支 -->
<forkJoinNode name="执行结束">
    <!-- 等待所有分支执行完毕 -->
    <shape>forkJoin</shape>
</forkJoinNode>

完整配置示例

数据库表结构
-- 创建新闻表
CREATE TABLE `news`(
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `news_id` bigint(15) DEFAULT NULL,
    `url` mediumtext COLLATE utf8mb4_unicode_ci,
    `title` mediumtext COLLATE utf8mb4_unicode_ci,
    `release_date` mediumtext COLLATE utf8mb4_unicode_ci,
    `author` mediumtext COLLATE utf8mb4_unicode_ci,
    `content` longtext COLLATE utf8mb4_unicode_ci,
    `insert_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `format_release_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '标准格式发布时间',
    `source` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT '海外媒体' COMMENT '来源',
    `hq_web_release_status` tinyint(2) DEFAULT '0' COMMENT '行情取数状态 (1=取数,0=未取数)',
    `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    `relate_rate` int(2) DEFAULT NULL COMMENT '相关度',
    PRIMARY KEY (`id`),
    KEY `news_id_index` (`id`),
    KEY `news_insert_date_index` (`insert_date`)
) ENGINE=InnoDB AUTO_INCREMENT=107498 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
数据源配置
<!-- 数据源 ID:ee975ab73415f54e7872e57ed0031ce9 -->
<!-- 对应数据库连接配置 -->
<datasource>
    <url>jdbc:mysql://localhost:3306/spider?useSSL=false&amp;characterEncoding=utf8</url>
    <username>spider_user</username>
    <password>********</password>
    <driver>com.mysql.jdbc.Driver</driver>
</datasource>

技术要点总结

1. 动态网站爬取核心技巧
  • 抓包分析:找到真实的数据接口 /app/article/list
  • 直接请求 API:绕过 JavaScript 渲染
  • 正则解析:使用自定义函数处理 JSON 响应
2. 防封禁策略
策略配置值说明
请求间隔sleep=5000每个请求后等待 5 秒
重试机制retryCount=3, retryInterval=5000失败重试 3 次,间隔 5 秒
User-AgentMozilla/5.0 …模拟浏览器访问
随机策略submit-strategy=random随机化请求特征
3. 数据质量控制
控制点实现方式目的
去重查询SELECT url FROM news WHERE …避免重复爬取
实时判断${!rs.contains(news_urlmap)}动态去重
内容验证${content!=null}确保数据完整性
时间范围start_date/end_date精确控制爬取范围
4. 变量命名规范
变量名类型用途
page循环变量分页控制
news_urllist数组存储 URL 列表
news_url字符串当前处理的 URL 路径
news_urlmap字符串完整的详情页 URL
query_result布尔值去重判断结果
rs结果集SQL 查询结果

调试技巧

1. 输出节点调试
<!-- 在每个关键节点后添加输出节点,便于跟踪变量值 -->
<outputNode name="调试输出">
    <output-all>1</output-all>
    <!-- 输出所有变量 -->
</outputNode>
2. 日志查看
  • 查看执行日志:日志管理 → 执行日志
  • 查看错误信息:日志管理 → 错误日志
  • 监控实时执行:任务监控 → 实时日志
3. 常见问题解决
问题可能原因解决方案
URL 列表为空正则匹配失败检查响应格式,调整正则表达式
内容提取失败选择器错误使用浏览器开发者工具验证选择器
去重失效时间范围错误检查 start_date/end_date 计算逻辑
请求超时网络问题增加超时时间,启用重试机制

性能优化建议

  1. 线程数配置
    • 单线程执行(避免被封)
    • 如需并发,建议不超过 3 个线程

批量处理

<!-- 批量插入优化 -->
INSERT INTO news (url, title, content)
VALUES
<foreach collection="list" item="item" separator=",">(#{item.url}, #{item.title}, #{item.content})</foreach>

请求间隔优化

<!-- 动态间隔:根据响应时间调整 -->
<sleep>${random.nextInt(3000,8000)}</sleep>

结语

本案例完整展示了如何通过自定义函数 + 可视化编排的方式,优雅地解决动态网站的爬取难题。关键不在于工具本身,而在于对网站加载机制的理解和灵活运用各种技术手段。通过本文的详细配置说明,相信读者能够快速上手类似的动态网站爬取任务。

目录

  1. 前言
  2. 问题背景
  3. 整体架构设计
  4. 思路点拨
  5. 关键技术点详解
  6. 1. 动态内容抓取策略
  7. 核心配置:自定义函数 match_coveris
  8. 2. 初始化节点配置
  9. 2.1 开始节点
  10. 2.2 获取时间范围节点
  11. 3. 数据查询节点配置
  12. 3.1 查询已爬取 URL
  13. 4. 分页抓取配置
  14. 4.1 分页请求节点
  15. 4.2 解析 URL 列表节点
  16. 5. 循环处理节点配置
  17. 5.1 循环节点
  18. 5.2 构造详情页 URL
  19. 6. 详情页抓取配置
  20. 6.1 抓取详情页
  21. 6.2 提取新闻内容
  22. 7. 数据存储配置
  23. 7.1 SQL 插入节点
  24. 8. 合并与结束节点
  25. 完整配置示例
  26. 数据库表结构
  27. 数据源配置
  28. 技术要点总结
  29. 1. 动态网站爬取核心技巧
  30. 2. 防封禁策略
  31. 3. 数据质量控制
  32. 4. 变量命名规范
  33. 调试技巧
  34. 1. 输出节点调试
  35. 2. 日志查看
  36. 3. 常见问题解决
  37. 性能优化建议
  38. 结语
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Cursor Chat Browser:浏览和管理 Cursor AI 聊天历史的 Web 应用
  • 基于 Higress 将 REST API 转换为 MCP Server 实战指南
  • 2026 年 AI 编程工具对比:GitHub Copilot、Cursor 与 Codeium 选型指南
  • RTX 4090 本地部署腾讯混元与阿里通义万相视频模型
  • C++ 继承进阶:友元、静态成员与菱形继承解析
  • Stable Diffusion 3.5 FP8 模型在消费级显卡上的部署与优化
  • 大模型应用开发:高级 RAG 技术实践
  • 量化、算子融合与内存映射:C 语言实现 AI 推理优化
  • Metric3D v2: 零样本单目度量深度与表面法线估计基础模型
  • Spring Boot 日志实战指南:从入门到高级配置
  • 开源 AI 编程工具对比:Superpowers 技能库与 OpenSpec 规范驱动
  • Python Web 自动化测试实战:常用函数全解析与场景化应用指南
  • Hash 校验 MD5 值及 SHA1 值
  • Sublime Text 3 在 Windows 10 上的安装与配置指南
  • OpenClaw:自托管无代码 AI Agent 框架技术剖析
  • Transformer 模型架构与核心原理详解
  • Python PySerial 串口通信库入门与实战
  • Python 面向对象学生管理系统设计与实现
  • 2026主流AI大模型横评与选型指南
  • OpenClaw 安装百度网页搜索技能指南

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online