攻克俄语编码适配与无ID字段去重:基于纯URL拼接的国际化爬虫设计

一、引言

在实际爬虫开发中,我们经常会遇到两种特殊场景:一是目标网站使用非英语语言(如俄语),虽然对爬虫本身影响不大,但需要注意编码问题;二是网站没有独立的新闻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去重

plastics.ru


六、性能优化与最佳实践

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 核心收获

  1. 俄语站点适配:语言不影响爬虫逻辑,但需注意数据库字符集
  2. 相对路径拼接:从相对路径构建完整URL
  3. 纯URL去重:无ID字段时的替代方案
  4. 分页起始处理:page从0开始的分页控制

7.2 可复用经验

  1. 语言无关性:爬虫逻辑与页面语言无关,只关心HTML结构
  2. URL标准化:提高纯URL去重的准确性
  3. 域名拼接:处理相对路径的统一方法
  4. 数据库编码:确保支持目标语言字符集

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字段网站的爬虫开发具有很高的参考价值。

相关推荐
JackSparrow4144 小时前
前端安全之JS混淆+请求加密+请求签名以提升爬虫难度
前端·javascript·后端·爬虫·python·安全
kisloy17 小时前
【爬虫入门第5讲】Python爬虫文本解析详解
开发语言·爬虫·python
上海云盾-小余19 小时前
CC 高频请求 + DDoS 流量攻击实战防御:全链路 WAF 流量清洗部署完整流程
网络·爬虫·安全·小程序·ddos
喜欢吃豆1 天前
Playwright 深度解析:从浏览器自动化到动态爬虫、自动化测试与 AI Agent
运维·爬虫·自动化
kisloy1 天前
【爬虫入门第10讲】Scrapy 框架深度解析(一):五大组件与中间件
爬虫·scrapy·中间件
IPdodo_1 天前
Python 接入代理IP:爬虫网络优化实战
爬虫·python·网络代理·代理ip·ipdodo
kisloy1 天前
【爬虫入门第9讲】Python爬虫浏览器自动化
爬虫·python·自动化
独行侠影a1 天前
Selenium 爬虫反爬实战:Python 模拟浏览器点击动态页面采集
爬虫·python·selenium
q567315232 天前
企业级 HTTP 代理采购选型:技术评估清单 15 项
开发语言·网络·爬虫·网络协议·http·隧道ip·代理ip