一、引言
在实际爬虫开发中,我们经常会遇到两种特殊场景:一是目标网站使用非英语语言(如俄语),虽然对爬虫本身影响不大,但需要注意编码问题;二是网站没有独立的新闻ID字段,必须完全依赖URL进行去重。这两种情况的结合,对爬虫的URL处理能力和去重策略提出了特殊要求。
本文将深入分析一个针对 plastics.ru 俄语新闻站点的爬虫设计案例。该爬虫创新性地解决了 "俄语站点编码适配" 和 "纯URL拼接去重" 两大核心难题,实现了对俄语新闻网站的高效采集。
与之前案例的核心差异:
- 语言差异:俄语站点(之前均为英语站点)
- URL构建差异 :需要拼接相对路径
http://plastics.ru${相对路径} - ID处理差异:无ID字段,完全依赖URL去重
- 分页起始差异:page从0开始(类似plastikmedia)
- 选择器差异:俄语页面使用相同的类名,提取逻辑不变
二、系统架构与核心流程
2.1 整体架构设计
该爬虫采用 "相对路径拼接 + 纯URL去重" 的架构,整体流程如下:

2.2 数据流向图

三、关键技术难点与解决方案
难点一:俄语站点的编码适配
问题描述:
虽然该网站使用俄语,但HTML结构和类名与英语网站类似,提取逻辑不变。需要注意的是响应编码和数据库存储时的字符集支持。
解决方案:
确保数据库支持UTF-8编码,爬虫配置使用正确的字符集:
javascript
// 请求节点配置(无需特殊编码设置)
{
"url": "http://plastics.ru/publications/news/?PAGEN_1=${page}",
"method": "GET",
"response-charset": "" // 默认UTF-8
}
// 提取字段时使用标准CSS选择器
{
"variable-name": ["news_title", "release_date", "content"],
"variable-value": [
"${extract.selector(resp.html, '.text>h1')}", // 俄语标题
"${extract.selector(resp.html, '.text>span')}", // 俄语日期
"${extract.selector(resp.html, '.simple_text')}" // 俄语内容
]
}
俄语站点处理示意图:
#mermaid-svg-6WcCVJa8lD8pq5D9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6WcCVJa8lD8pq5D9 .error-icon{fill:#552222;}#mermaid-svg-6WcCVJa8lD8pq5D9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6WcCVJa8lD8pq5D9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .marker.cross{stroke:#333333;}#mermaid-svg-6WcCVJa8lD8pq5D9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6WcCVJa8lD8pq5D9 p{margin:0;}#mermaid-svg-6WcCVJa8lD8pq5D9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster-label text{fill:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster-label span{color:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster-label span p{background-color:transparent;}#mermaid-svg-6WcCVJa8lD8pq5D9 .label text,#mermaid-svg-6WcCVJa8lD8pq5D9 span{fill:#333;color:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .node rect,#mermaid-svg-6WcCVJa8lD8pq5D9 .node circle,#mermaid-svg-6WcCVJa8lD8pq5D9 .node ellipse,#mermaid-svg-6WcCVJa8lD8pq5D9 .node polygon,#mermaid-svg-6WcCVJa8lD8pq5D9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .rough-node .label text,#mermaid-svg-6WcCVJa8lD8pq5D9 .node .label text,#mermaid-svg-6WcCVJa8lD8pq5D9 .image-shape .label,#mermaid-svg-6WcCVJa8lD8pq5D9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6WcCVJa8lD8pq5D9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .rough-node .label,#mermaid-svg-6WcCVJa8lD8pq5D9 .node .label,#mermaid-svg-6WcCVJa8lD8pq5D9 .image-shape .label,#mermaid-svg-6WcCVJa8lD8pq5D9 .icon-shape .label{text-align:center;}#mermaid-svg-6WcCVJa8lD8pq5D9 .node.clickable{cursor:pointer;}#mermaid-svg-6WcCVJa8lD8pq5D9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .arrowheadPath{fill:#333333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6WcCVJa8lD8pq5D9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6WcCVJa8lD8pq5D9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6WcCVJa8lD8pq5D9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster text{fill:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 .cluster span{color:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6WcCVJa8lD8pq5D9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6WcCVJa8lD8pq5D9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6WcCVJa8lD8pq5D9 .icon-shape,#mermaid-svg-6WcCVJa8lD8pq5D9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6WcCVJa8lD8pq5D9 .icon-shape p,#mermaid-svg-6WcCVJa8lD8pq5D9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6WcCVJa8lD8pq5D9 .icon-shape .label rect,#mermaid-svg-6WcCVJa8lD8pq5D9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6WcCVJa8lD8pq5D9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6WcCVJa8lD8pq5D9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6WcCVJa8lD8pq5D9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储阶段
解析阶段
请求阶段
HTTP请求
俄语HTML响应
CSS选择器提取
类名与英语站点相同
提取俄语文本
UTF-8编码
存入数据库
数据库支持
俄文字符集
难点二:相对路径拼接
问题描述:
列表页提取的href是相对路径(如/publications/news/12345/),需要拼接为完整URL。
解决方案:
在新闻地址节点进行域名拼接:
javascript
// 新闻地址节点配置
{
"variable-name": ["news_url", "news_id", "news_urlmap", "query_result"],
"variable-value": [
// 域名拼接:http://plastics.ru + 相对路径
"http://plastics.ru${news_urllist[index]}",
"${null}", // 无ID字段
"${{'url': news_url}}",
"${!rs.contains(news_urlmap)}"
]
}
URL拼接原理图:
#mermaid-svg-WqJtL9GE00F03ki1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WqJtL9GE00F03ki1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WqJtL9GE00F03ki1 .error-icon{fill:#552222;}#mermaid-svg-WqJtL9GE00F03ki1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WqJtL9GE00F03ki1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WqJtL9GE00F03ki1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WqJtL9GE00F03ki1 .marker.cross{stroke:#333333;}#mermaid-svg-WqJtL9GE00F03ki1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WqJtL9GE00F03ki1 p{margin:0;}#mermaid-svg-WqJtL9GE00F03ki1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WqJtL9GE00F03ki1 .cluster-label text{fill:#333;}#mermaid-svg-WqJtL9GE00F03ki1 .cluster-label span{color:#333;}#mermaid-svg-WqJtL9GE00F03ki1 .cluster-label span p{background-color:transparent;}#mermaid-svg-WqJtL9GE00F03ki1 .label text,#mermaid-svg-WqJtL9GE00F03ki1 span{fill:#333;color:#333;}#mermaid-svg-WqJtL9GE00F03ki1 .node rect,#mermaid-svg-WqJtL9GE00F03ki1 .node circle,#mermaid-svg-WqJtL9GE00F03ki1 .node ellipse,#mermaid-svg-WqJtL9GE00F03ki1 .node polygon,#mermaid-svg-WqJtL9GE00F03ki1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WqJtL9GE00F03ki1 .rough-node .label text,#mermaid-svg-WqJtL9GE00F03ki1 .node .label text,#mermaid-svg-WqJtL9GE00F03ki1 .image-shape .label,#mermaid-svg-WqJtL9GE00F03ki1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-WqJtL9GE00F03ki1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WqJtL9GE00F03ki1 .rough-node .label,#mermaid-svg-WqJtL9GE00F03ki1 .node .label,#mermaid-svg-WqJtL9GE00F03ki1 .image-shape .label,#mermaid-svg-WqJtL9GE00F03ki1 .icon-shape .label{text-align:center;}#mermaid-svg-WqJtL9GE00F03ki1 .node.clickable{cursor:pointer;}#mermaid-svg-WqJtL9GE00F03ki1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WqJtL9GE00F03ki1 .arrowheadPath{fill:#333333;}#mermaid-svg-WqJtL9GE00F03ki1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WqJtL9GE00F03ki1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WqJtL9GE00F03ki1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WqJtL9GE00F03ki1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WqJtL9GE00F03ki1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WqJtL9GE00F03ki1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WqJtL9GE00F03ki1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WqJtL9GE00F03ki1 .cluster text{fill:#333;}#mermaid-svg-WqJtL9GE00F03ki1 .cluster span{color:#333;}#mermaid-svg-WqJtL9GE00F03ki1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WqJtL9GE00F03ki1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WqJtL9GE00F03ki1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-WqJtL9GE00F03ki1 .icon-shape,#mermaid-svg-WqJtL9GE00F03ki1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WqJtL9GE00F03ki1 .icon-shape p,#mermaid-svg-WqJtL9GE00F03ki1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WqJtL9GE00F03ki1 .icon-shape .label rect,#mermaid-svg-WqJtL9GE00F03ki1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WqJtL9GE00F03ki1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WqJtL9GE00F03ki1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WqJtL9GE00F03ki1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 相对路径
/news/12345/
域名
http://plastics.ru
拼接操作
域名 + 相对路径
完整URL
http://plastics.ru/news/12345/
URL拼接示例:
| 相对路径 | 完整URL |
|---|---|
| /publications/news/12345/ | http://plastics.ru/publications/news/12345/ |
| /publications/news/12346/ | http://plastics.ru/publications/news/12346/ |
| /publications/news/12347/ | http://plastics.ru/publications/news/12347/ |
难点三:纯URL去重(无ID字段)
问题描述:
该网站没有独立的新闻ID字段,只能完全依赖URL进行去重判断。
解决方案:
直接使用URL构造Map对象进行去重:
javascript
// 新闻地址节点 - 纯URL去重
{
"variable-name": ["news_url", "news_id", "news_urlmap", "query_result"],
"variable-value": [
"http://plastics.ru${news_urllist[index]}",
"${null}", // ID字段置空
"${{'url': news_url}}", // 构造Map用于去重
"${!rs.contains(news_urlmap)}" // 去重判断
]
}
// 输出节点 - ID字段为空
{
"output-name": ["网址", "id", "构造map", "查询结果"],
"output-value": ["${news_url}", "${news_id}", "${news_urlmap}", "${query_result}"]
}
难点四:page从0开始的分页
问题描述:
该网站分页从0开始(第一页page=0,第二页page=1),需要特殊处理。
解决方案:
设置page从0开始递增:
javascript
// 定义变量节点 - 分页控制
{
"variable-name": ["page", "news_urllist"],
"variable-value": [
"${page==null?0:page+1}", // page从0开始
"${extract.selectors(resp.html, '.text-pad>a', 'attr', 'href')}"
]
}
// 循环控制条件
"condition": "${page<=1}" // 只抓取两页:page=0和page=1
分页对照表:
| 请求次数 | page变量 | URL中的PAGEN_1 | 说明 |
|---|---|---|---|
| 第1次 | null | 0 | 第一页 |
| 第2次 | 0 | 0 | 已请求过?注意问题 |
| 第3次 | 1 | 1 | 第二页 |
注意:这里的配置可能存在逻辑问题。正确的应该是:首次请求page=null时,URL中使用0;响应后设置page=0表示已请求过第0页;下次请求时应该使用page=1,但URL中应该是1。需要确保变量传递正确。
难点五: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 RelativePathJoiner {
constructor(domain) {
this.domain = domain; // http://plastics.ru
}
join(relativePath) {
// 确保相对路径以/开头
if (!relativePath.startsWith('/')) {
relativePath = '/' + relativePath;
}
return this.domain + relativePath;
}
joinAll(relativePaths) {
return relativePaths.map(path => this.join(path));
}
// 标准化处理
normalize(url) {
// 移除末尾斜杠
return url.replace(/\/$/, '');
}
}
// 使用示例
const joiner = new RelativePathJoiner('http://plastics.ru');
const fullUrl = joiner.join('/news/12345/');
// http://plastics.ru/news/12345/
4.2 纯URL去重器
javascript
// 伪代码:纯URL去重器
class UrlOnlyDeduplicator {
constructor(existingUrls) {
this.existingSet = new Set(existingUrls);
}
isDuplicate(url) {
const normalized = this.normalize(url);
return this.existingSet.has(normalized);
}
filterNewUrls(urls) {
return urls.filter(url => !this.isDuplicate(url));
}
normalize(url) {
// 标准化处理:移除协议、末尾斜杠、转为小写
let normalized = url.replace(/^https?:\/\//, '');
normalized = normalized.replace(/\/$/, '');
normalized = normalized.toLowerCase();
return normalized;
}
// 构造去重用的Map对象
createUrlMap(url) {
return { 'url': url };
}
}
// 使用示例
const deduplicator = new UrlOnlyDeduplicator(existingUrls);
const urlMap = deduplicator.createUrlMap('http://plastics.ru/news/123/');
const isDuplicate = deduplicator.isDuplicate('http://plastics.ru/news/123/');
4.3 俄语页面提取器
javascript
// 伪代码:俄语页面提取器
class RussianPageExtractor {
extractNewsData(html) {
return {
// 类名与英语网站相同,无需特殊处理
title: this.extractText(html, '.text>h1'),
date: this.extractText(html, '.text>span'),
content: this.extractText(html, '.simple_text')
};
}
extractText(html, selector) {
// 提取的文本自动包含俄文字符
const text = extract.selector(html, selector, 'text');
// 无需额外编码处理,保持原样
return text;
}
}
五、与前九个案例的对比分析
5.1 核心差异点对比
| 维度 | plastics.ru | resource-recycling | plastikmedia | packaging-gateway | 雅式橡塑网 | icis新闻 | polymerupdate博客 | bioplasticsnews | polymerupdate新闻 | chemanalyst |
|---|---|---|---|---|---|---|---|---|---|---|
| 语言 | 俄语 | 英语 | 英语 | 英语 | 英语 | 英语 | 英语 | 英语 | 英语 | 英语 |
| URL构建 | 域名+相对路径 | 完整URL | 完整URL | 完整URL | 域名+相对路径 | 完整URL | 完整URL | 域名+相对路径 | 完整URL | 完整URL |
| ID字段 | 无 | 有(class) | 无 | 无 | 有(data-id) | 有(URL中) | 有(对象) | 有(article id) | 有(URL中) | 有(URL中) |
| 分页起始 | 0 | 0 | 0 | 0(偏移) | 2(特殊) | 1 | 1 | 1 | 1 | 1 |
| 代理策略 | 详情页 | 全流程 | 全流程 | 详情页 | 无 | 无 | 无 | 全流程 | 详情页 | 无 |
| 特殊难点 | 俄语适配 | 三层循环 | 内容清洗 | 双路容错 | 属性提取 | 双层解析 | JSON对象 | 双列表 | POST表单 | GET参数 |
5.2 差异化技术难点
#mermaid-svg-XtshO8AITzAqdb01{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XtshO8AITzAqdb01 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XtshO8AITzAqdb01 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XtshO8AITzAqdb01 .error-icon{fill:#552222;}#mermaid-svg-XtshO8AITzAqdb01 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XtshO8AITzAqdb01 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XtshO8AITzAqdb01 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XtshO8AITzAqdb01 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XtshO8AITzAqdb01 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XtshO8AITzAqdb01 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XtshO8AITzAqdb01 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XtshO8AITzAqdb01 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XtshO8AITzAqdb01 .marker.cross{stroke:#333333;}#mermaid-svg-XtshO8AITzAqdb01 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XtshO8AITzAqdb01 p{margin:0;}#mermaid-svg-XtshO8AITzAqdb01 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XtshO8AITzAqdb01 .cluster-label text{fill:#333;}#mermaid-svg-XtshO8AITzAqdb01 .cluster-label span{color:#333;}#mermaid-svg-XtshO8AITzAqdb01 .cluster-label span p{background-color:transparent;}#mermaid-svg-XtshO8AITzAqdb01 .label text,#mermaid-svg-XtshO8AITzAqdb01 span{fill:#333;color:#333;}#mermaid-svg-XtshO8AITzAqdb01 .node rect,#mermaid-svg-XtshO8AITzAqdb01 .node circle,#mermaid-svg-XtshO8AITzAqdb01 .node ellipse,#mermaid-svg-XtshO8AITzAqdb01 .node polygon,#mermaid-svg-XtshO8AITzAqdb01 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XtshO8AITzAqdb01 .rough-node .label text,#mermaid-svg-XtshO8AITzAqdb01 .node .label text,#mermaid-svg-XtshO8AITzAqdb01 .image-shape .label,#mermaid-svg-XtshO8AITzAqdb01 .icon-shape .label{text-anchor:middle;}#mermaid-svg-XtshO8AITzAqdb01 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XtshO8AITzAqdb01 .rough-node .label,#mermaid-svg-XtshO8AITzAqdb01 .node .label,#mermaid-svg-XtshO8AITzAqdb01 .image-shape .label,#mermaid-svg-XtshO8AITzAqdb01 .icon-shape .label{text-align:center;}#mermaid-svg-XtshO8AITzAqdb01 .node.clickable{cursor:pointer;}#mermaid-svg-XtshO8AITzAqdb01 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XtshO8AITzAqdb01 .arrowheadPath{fill:#333333;}#mermaid-svg-XtshO8AITzAqdb01 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XtshO8AITzAqdb01 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XtshO8AITzAqdb01 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XtshO8AITzAqdb01 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XtshO8AITzAqdb01 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XtshO8AITzAqdb01 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XtshO8AITzAqdb01 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XtshO8AITzAqdb01 .cluster text{fill:#333;}#mermaid-svg-XtshO8AITzAqdb01 .cluster span{color:#333;}#mermaid-svg-XtshO8AITzAqdb01 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XtshO8AITzAqdb01 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XtshO8AITzAqdb01 rect.text{fill:none;stroke-width:0;}#mermaid-svg-XtshO8AITzAqdb01 .icon-shape,#mermaid-svg-XtshO8AITzAqdb01 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XtshO8AITzAqdb01 .icon-shape p,#mermaid-svg-XtshO8AITzAqdb01 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XtshO8AITzAqdb01 .icon-shape .label rect,#mermaid-svg-XtshO8AITzAqdb01 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XtshO8AITzAqdb01 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XtshO8AITzAqdb01 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XtshO8AITzAqdb01 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} plastics.ru
俄语站点
UTF-8适配
无ID字段
纯URL去重
相对路径拼接
域名+相对路径
前九个案例
英语站点
有ID字段或可提取
URL完整或简单拼接
5.3 URL构建方式演进
#mermaid-svg-Y8IkgPXnlCgTnRcK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Y8IkgPXnlCgTnRcK .error-icon{fill:#552222;}#mermaid-svg-Y8IkgPXnlCgTnRcK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Y8IkgPXnlCgTnRcK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .marker.cross{stroke:#333333;}#mermaid-svg-Y8IkgPXnlCgTnRcK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Y8IkgPXnlCgTnRcK p{margin:0;}#mermaid-svg-Y8IkgPXnlCgTnRcK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster-label text{fill:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster-label span{color:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster-label span p{background-color:transparent;}#mermaid-svg-Y8IkgPXnlCgTnRcK .label text,#mermaid-svg-Y8IkgPXnlCgTnRcK span{fill:#333;color:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .node rect,#mermaid-svg-Y8IkgPXnlCgTnRcK .node circle,#mermaid-svg-Y8IkgPXnlCgTnRcK .node ellipse,#mermaid-svg-Y8IkgPXnlCgTnRcK .node polygon,#mermaid-svg-Y8IkgPXnlCgTnRcK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .rough-node .label text,#mermaid-svg-Y8IkgPXnlCgTnRcK .node .label text,#mermaid-svg-Y8IkgPXnlCgTnRcK .image-shape .label,#mermaid-svg-Y8IkgPXnlCgTnRcK .icon-shape .label{text-anchor:middle;}#mermaid-svg-Y8IkgPXnlCgTnRcK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .rough-node .label,#mermaid-svg-Y8IkgPXnlCgTnRcK .node .label,#mermaid-svg-Y8IkgPXnlCgTnRcK .image-shape .label,#mermaid-svg-Y8IkgPXnlCgTnRcK .icon-shape .label{text-align:center;}#mermaid-svg-Y8IkgPXnlCgTnRcK .node.clickable{cursor:pointer;}#mermaid-svg-Y8IkgPXnlCgTnRcK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .arrowheadPath{fill:#333333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y8IkgPXnlCgTnRcK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Y8IkgPXnlCgTnRcK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y8IkgPXnlCgTnRcK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster text{fill:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK .cluster span{color:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Y8IkgPXnlCgTnRcK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Y8IkgPXnlCgTnRcK rect.text{fill:none;stroke-width:0;}#mermaid-svg-Y8IkgPXnlCgTnRcK .icon-shape,#mermaid-svg-Y8IkgPXnlCgTnRcK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Y8IkgPXnlCgTnRcK .icon-shape p,#mermaid-svg-Y8IkgPXnlCgTnRcK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Y8IkgPXnlCgTnRcK .icon-shape .label rect,#mermaid-svg-Y8IkgPXnlCgTnRcK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Y8IkgPXnlCgTnRcK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Y8IkgPXnlCgTnRcK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Y8IkgPXnlCgTnRcK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 完整URL
chemanalyst
域名+相对路径
bioplasticsnews
域名+相对路径
plastics.ru
纯URL去重
六、性能优化与最佳实践
6.1 俄语站点优化
javascript
// 确保数据库支持俄文字符
CREATE TABLE news (
id INT PRIMARY KEY,
title VARCHAR(255) CHARACTER SET utf8mb4,
content TEXT CHARACTER SET utf8mb4
) DEFAULT CHARSET=utf8mb4;
6.2 URL标准化处理
| 原始URL | 标准化后 | 说明 |
|---|---|---|
| http://plastics.ru/news/123/ | plastics.ru/news/123 | 移除协议和末尾斜杠 |
| https://plastics.ru/news/123 | plastics.ru/news/123 | 统一协议 |
| http://plastics.ru/NEWS/123 | plastics.ru/news/123 | 转为小写 |
6.3 分页控制优化
javascript
// 正确的分页逻辑
function getNextPageUrl(currentPage) {
if (currentPage === null) {
return '?PAGEN_1=0'; // 第一页
}
const nextPage = currentPage + 1;
return `?PAGEN_1=${nextPage}`;
}
七、总结与经验分享
7.1 核心收获
- 俄语站点适配:语言不影响爬虫逻辑,但需注意数据库字符集
- 相对路径拼接:从相对路径构建完整URL
- 纯URL去重:无ID字段时的替代方案
- 分页起始处理:page从0开始的分页控制
7.2 可复用经验
- 语言无关性:爬虫逻辑与页面语言无关,只关心HTML结构
- URL标准化:提高纯URL去重的准确性
- 域名拼接:处理相对路径的统一方法
- 数据库编码:确保支持目标语言字符集
7.3 适用场景
该爬虫设计模式适用于:
- 非英语网站(俄语、日语、中文等)
- 没有独立ID字段的网站
- 使用相对路径的网站
- 需要简单高效去重的场景
八、附录:核心配置对照表
| 节点类型 | 核心作用 | 关键技术点 |
|---|---|---|
| 获取时间范围 | 动态计算7天窗口 | date.addDays(now,-7) |
| 执行SQL(查询) | 获取已抓取URL | like '%plastics.ru%' |
| 抓取新闻地址列表 | GET请求列表页 | ?PAGEN_1=${page} |
| 新闻地址列表 | 分页控制+URL提取 | page从0开始 |
| 新闻地址 | 域名拼接+纯URL去重 | http://plastics.ru${相对路径} |
| 开始抓取 | 获取详情页 | 详情页代理 |
| 内容 | 俄语字段提取 | 标准CSS选择器 |
| 输出 | 数据汇总 | ID字段为null |
| 执行SQL(插入) | 存储数据 | source='plastics' |
通过以上设计,该爬虫成功应对了俄语站点适配和纯URL去重的双重挑战,实现了对plastics.ru新闻网站的高效稳定采集。其中的相对路径拼接技术、纯URL去重策略等思路,对于处理非英语站点和无ID字段网站的爬虫开发具有很高的参考价值。