跳到主要内容JavaScriptNode.js大前端
动态网站爬虫实战:SpiderFlow 可视化编排与自定义函数
综述由AI生成介绍使用 SpiderFlow 可视化工具结合自定义 JavaScript 函数爬取动态加载网站(Coveris)的方法。通过 F12 分析 XHR 请求定位数据接口,模拟滚动触发加载,利用正则解析 JSON 响应提取 URL。流程包含分页抓取、去重判断、详情提取及数据库存储。解决了传统静态解析无法获取异步内容的问题,实现了新闻数据的自动化采集。
imJackJia14 浏览 前言
在爬虫开发过程中,我们经常会遇到动态加载的网站,这类网站采用了现代前端框架(如 React、Vue 或 Angular)构建,数据是通过 JavaScript 异步加载的。传统的基于静态 HTML 解析的爬虫框架往往无法直接定位元素,因为它们只能获取初始 HTML 文档,而无法执行 JavaScript 代码来获取动态生成的内容。
本文将分享一个实际案例,展示如何通过自定义函数 + 可视化爬虫编排的方式,成功爬取动态加载的 Coveris 新闻网站。Coveris 是一家国际包装解决方案提供商,其新闻中心页面采用了典型的 AJAX 动态加载技术,新闻内容通过异步请求加载,页面 URL 保持不变,这给传统爬虫带来了很大挑战。
具体解决方案包括以下几个关键步骤:
- 使用浏览器开发者工具(F12)分析 XHR 请求,定位到新闻数据的 API 接口
- 通过自定义 JavaScript 函数模拟滚动事件,触发更多数据的加载
- 利用可视化编排工具设置合理的请求间隔,避免触发反爬机制
- 设计数据解析逻辑,处理嵌套的 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
function match_coveris(htmlStr) {
var htmlContent = htmlStr;
if (!htmlContent) return [];
var regex = /"url"\s*:\s*"(.*?)"/g;
var matches;
var results = [];
while ((matches = regex.exec(htmlContent)) !== null) {
var sentence = matches[1].trim();
if (sentence) { results.push(sentence); }
}
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
<executeSqlNode name="查询已爬取 URL">
<datasourceId>ee975ab73415f54e7872e57ed0031ce9</datasourceId>
<statementType>select</statementType>
<sql> SELECT url FROM news WHERE url like '%https://www.coveris.com%' AND (insert_date BETWEEN '${start_date}' AND '${end_date}') </sql>
</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>
<timeout>30000</timeout>
<retryCount>3</retryCount>
<retryInterval>5000</retryInterval>
<header-name>["User-Agent"]</header-name>
<header-value>["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"]</header-value>
<url>https://www.coveris.com/app/article/list?page=${page}</url>
<follow-redirect>1</follow-redirect>
<cookie-auto-set>1</cookie-auto-set>
</requestNode>
4.2 解析 URL 列表节点
<variableNode name="定义变量">
<variable name="page" value="${page==null?1:page+1}" description="当前页码"/>
<variable name="news_urllist" value="${match_coveris(resp.html)}" description="新闻 URL 列表"/>
</variableNode>
<conditionNode name="分页判断">
<condition expression="${news_urllist!=null && page<=2}">
<target>开始抓取</target>
</condition>
<condition expression="${news_urllist==null || page>2}">
<target>循环处理</target>
</condition>
</conditionNode>
5. 循环处理节点配置
5.1 循环节点
<loopNode name="循环">
<loopCount>${news_urllist.length}</loopCount>
<loopStart>0</loopStart>
<loopEnd>-1</loopEnd>
<loopVariableName>index</loopVariableName>
</loopNode>
5.2 构造详情页 URL
<variableNode name="新闻地址">
<variable name="news_url" value="${news_urllist[index].replace('\\', '')}" description="原始 URL 路径"/>
<variable name="news_urlmap" value="${'https://www.coveris.com' + news_url}" description="完整 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="去重判断">
<condition expression="${query_result}">
<target>抓取新闻详情</target>
</condition>
<condition expression="${!query_result}">
<target>输出调试信息</target>
</condition>
</conditionNode>
6. 详情页抓取配置
6.1 抓取详情页
<requestNode name="抓取新闻详情">
<method>GET</method>
<sleep>5000</sleep>
<timeout>30000</timeout>
<retryCount>3</retryCount>
<retryInterval>5000</retryInterval>
<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 插入节点
<executeSqlNode name="保存新闻数据">
<datasourceId>ee975ab73415f54e7872e57ed0031ce9</datasourceId>
<statementType>insert</statementType>
<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>
</executeSqlNode>
<outputNode name="保存结果">
<output-name>["URL","标题","保存状态"]</output-name>
<output-value>["${news_urlmap}","${title}","保存成功"]</output-value>
</outputNode>
8. 合并与结束节点
<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;
数据源配置
<datasource>
<url>jdbc:mysql://localhost:3306/spider?useSSL=false&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-Agent | Mozilla/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 计算逻辑 |
| 请求超时 | 网络问题 | 增加超时时间,启用重试机制 |
性能优化建议
- 线程数配置
- 单线程执行(避免被封)
- 如需并发,建议不超过 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>
结语
本案例完整展示了如何通过自定义函数 + 可视化编排的方式,优雅地解决动态网站的爬取难题。关键不在于工具本身,而在于对网站加载机制的理解和灵活运用各种技术手段。通过本文的详细配置说明,相信读者能够快速上手类似的动态网站爬取任务。
相关免费在线工具
- 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