通过Warp+Puppeteer绕过L站rss反爬虫机制,n8n每日AI总结L站热门贴并推送电报

最后更新于 2025-07-26 56 次阅读


AI 摘要

摘要:本文详细介绍了如何通过Warp和Puppeteer技术绕过L站的RSS反爬虫机制,实现使用n8n自动抓取L站每日热门贴并推送至Telegram的功能。首先,在服务器上通过安装Warp和WireProxy,将VPS的请求伪装,以避免被Cloudflare拦截。接着,利用Puppeteer模拟浏览器环境,抓取RSS数据,并通过定时脚本转发至n8n进行处理。最后,介绍了如何在n8n中配置Webhook与其他节点,以实现对数据的提取与处理,并利用DeepSeek进行文本总结,最终将结果发送至Telegram。整体工作流程清晰易懂,为希望实现类似功能的用户提供了实用的参考。

本文是直接复制粘贴我在Linux.do发布的文章

在服务器上部署了一个n8n,就想找几个项目部署学习一下,找到了L站的一个自动推送每日热门贴的教程
【n8n系列教程6】十分钟完成:每日AI总结L站热门贴,并自动推送到你的邮箱
觉得非常不错,但是复制下来后发现和很多帖子下最近的回复遇到的问题一样,rss请求失败了,状态403,问了chatgpt说是cloudflare给拦截了,于是尝试解决

首先介绍环境,我的服务器是部署在美东,Debian10,然后用1panel管理后台,n8n和其他与本文无关的服务都部署在docker中
在经过一天的上班摸鱼之后,为了实现原帖基本一致的功能,我的解决思路和步骤如下(注册了几个月第一次发帖,另外原理部分有很多chatgpt生成内容,难免不准确,大佬多指正)

安装warp,启动wireproxy

第一步利用脚本在服务器上安装warp,然后使用它的wireproxy功能,将VPS的请求伪装成来自 Cloudflare 信任区域,避免了 VPS 原始 IP 被 Cloudflare 检测出为 “机器流量” 而封锁。

wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh && bash menu.sh [option] [lisence/url/token]

warp
13 #选择使用公用账号启动wireproxy

Warp会在我们的服务器上打开一个代理端口给本地服务使用,注意这里最好把vps的各种服务都绑好域名访问然后关闭所有ip访问,不然所有人都可以通过ip:wireproxy端口来使用你的代理服务

但是这还不够,仅仅通过这个代理去获取,依然会返回403,chatgpt教我要用Puppeteer 启动一个Chromium无头浏览器模拟真实用户。puppeteer是 Node.js 的浏览器控制库,它能模拟完整的浏览器环境(包括执行 JavaScript、加载 DOM、cookie/session 等)。启动无头 Chrome,通过设置 user-agent 伪装成桌面用户。能真正渲染页面内容,绕过 Cloudflare 的 JS Challenge、token 校验等。最终从页面 DOM 中提取完整 RSS XML。

编写定时脚本,借助puppeteer抓取rss

这一部分,我的方案不算太优雅但算是易于理解,我在服务器一个目录下新建一个Puppeteer环境,使用puppeteer库的功能再通过wireproxy的代理端口去请求L站的rss,然后编写一个定时脚本获取到信息后转发给我们的n8n,n8n可以通过webhook功能提供一个用于接收请求的url,一旦n8n收到请求就执行原有的处理流程

经过我的测试,二者缺一不可,至少在我的服务器上,必须同时配置wireproxy和puppeteer才能在一个大约十多分钟一次的频率下稳定获取内容

以下是我使用的脚本示例和操作流程
首先如果没有,先安装npm和nodejs

# Debian/Ubuntu 系统示例
apt update
apt install -y curl
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - 
apt install -y nodejs build-essential

然后编写抓取脚本

mkdir -p /home/rss-collector # 随便找个地方
cd /home/rss-collector
npm init -y
npm install puppeteer

来自gpt的一些注释:执行 npm install puppeteer 会自动下载一个 Chromium 二进制,体积大约在 150MB 左右。如果你想使用系统自带的 Chrome/Chromium,也可以安装 puppeteer-core,并手动指定可执行路径,但为了演示简便,这里直接用默认的 Puppeteer。

在文件夹中创建fetch_rss.js

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: true,
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--proxy-server=socks5://127.0.0.1:54321', // 修改为你的 SOCKS5 代理端口
    ],
  });

  try {
    const page = await browser.newPage();

    await page.setUserAgent(
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
      'AppleWebKit/537.36 (KHTML, like Gecko) ' +
      'Chrome/114.0.0.0 Safari/537.36'
    );

    await page.goto('https://linux.do/top.rss?period=daily', {
      waitUntil: 'domcontentloaded',
      timeout: 60000,
    });

    const maxWait = 30000;
    const start = Date.now();

    let xmlText = '';
    while (Date.now() - start < maxWait) {
      await new Promise(res => setTimeout(res, 1000));

      const text = await page.evaluate(() => document.documentElement.textContent);

      if (text.includes('<rss')) {
        xmlText = text.trim();
        break;
      }
    }

    if (!xmlText) {
      console.error('Error: 未能在 30 秒内拿到 RSS XML,请检查网络或 WARP 配置');
      process.exit(1);
    }

    console.log(xmlText);
  } catch (err) {
    console.error('抓取 RSS 失败:', err.message);
    process.exit(1);
  } finally {
    await browser.close();
  }
})();

接下来使用crontab或者或者我是用1Panel侧栏的计划任务功能创建了一个定时的脚本,运行刚才的代码,然后把抓取到的信息发送到webhook url,这个我们在之后的n8n配置中会方便的获取到

#!/bin/bash

WEBHOOK_URL="https://heiheihei/workflow/12345"
TEMP_FILE="(mktemp)"
cd /home/rss-collector
# 执行 JS 脚本并写入临时文件
node fetch_rss.js>"TEMP_FILE"

# 如果输出为空或文件太小(小于 5000 字节),则退出
if [[ ! -s "TEMP_FILE" ||(stat -c%s "TEMP_FILE") -lt 5000 ]]; then
  echo "[WARN] RSS 内容可能不完整或为空,跳过发送"
  cat "TEMP_FILE"
  rm "TEMP_FILE"
  exit 1
fi

# 调用 Webhook
curl -X POST \
  -H "Content-Type: application/xml" \
  --data-binary "@{TEMP_FILE}" \
  "{WEBHOOK_URL}"

rm "TEMP_FILE"

然后我们去n8n上,修改一下原有的流程
首先就是要把原来的定时启动+rss获取改成webhook启动

双击webhook节点可以找到要填写给服务器脚本的url,记得选择production的url,这样设置就全部结束了,定时脚本在服务器启动时,会运行js程序,使用puppeteer库的功能,在wireproxy的代理端口下获取rss内容,然后通过webhook全部发送到n8n,接受进一步的处理和推送

要注意,rss获取间隔时间不要过短,我的测试下,服务器两次请求的间隔要至少超过10分钟,不然怎么都会被拒绝

修改workflow,处理数据


以上是原帖workflow和我当前使用的workflow对比

我对工作流程做了一些修改,首先原贴使用的触发器被我们换成了webhook,然后获取到的数据因为不再是rss自动分好类的json格式,所以我们加一个code节点只保留我们需要的信息

return $input.first().json.body.rss.channel.item

本来以为处理会复杂一点就用了code节点,然后发现好像完全不用处理,总之已经动起来了,不想再改了

接下来原贴筛选了前20个帖子然后扔给Gemini去做总结,我换成了deepseek但是经常会碰到输出上限,所以我用了循环节点,每10个为一组交给deepseek处理一遍,然后直接发送到telegram节点让它发送文本消息给我

以下是我的workflow配置

{
  "name": "My workflow 3",
  "nodes": [
    {
      "parameters": {
        "text": "={{ json.text }}",
        "additionalFields": {}
      },
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1220,
        -60
      ],
      "id": "1409a08f-10a1-481f-a272-107ef5ab31f1",
      "name": "Telegram",
      "webhookId": "7e9fead9-3c29-4701-8b7b-007ba59ff550",
      "credentials": {
        "telegramApi": {
          "id": "vr462TaGdGAshRnc",
          "name": "Telegram account"
        }
      }
    },
    {
      "parameters": {
        "batchSize": 10,
        "options": {}
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        440,
        -240
      ],
      "id": "3ec88aae-cc26-4db4-8450-22dff234a42d",
      "name": "Loop Over Items"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=以下为L站的帖子的内容\n{{json.text }}",
        "messages": {
          "messageValues": [
            {
              "message": "⸻\n\n你是论坛内容分析助手。针对每篇帖子,请严格按以下格式输出:\n\n📄 <序号>. 原标题:<帖子标题>  \n🖋️ 作者:<作者名>  \n🔗 链接:<原链接>  \n\n📊 内容分析  \n📌 核心内容:  \n- 用简洁的几个句子全面概括相应帖子内容  \n\n🔑 重要信息:  \n- 关键点1  \n- 关键点2(如有可加第3点)\n\n💡 实用价值:  \n- 提取可操作的信息(如有,无则写“暂无”)  \n\n\n⸻\n\n处理原则:\n\t•\t保持简洁,避免冗长\n\t•\t突出实用性内容\n\t•\t如涉及资源,标注使用条件\n\t•\t如涉及技术,标注关键参数\n\t•\t输出格式统一,便于Telegram中阅读\n\t•\t不添加 Markdown 或 HTML 代码标识(如 ```)\n"
            }
          ]
        }
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.4,
      "position": [
        860,
        -240
      ],
      "id": "74e73921-1ae4-4052-b768-bc9d06669b32",
      "name": "总结",
      "retryOnFail": true,
      "notesInFlow": true
    },
    {
      "parameters": {
        "jsCode": "return input.first().json.body.rss.channel.item"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        200,
        -240
      ],
      "id": "7551e1e5-7885-42a4-814b-669c56dc41d3",
      "name": "Code",
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "jsCode": "const items =input.all();\nlet text = \"\";\n\nitems.forEach((item, index) => {\n    const creator = item.json['dc:creator'];\n    const title = item.json.title;\n    const link = item.json.link;\n\n    const rawDescription = item.json.description || \"\";\n    const plainText = rawDescription.replace(/<[^>]+>/g, '');\n    const snippet = plainText.match(/[\\u4e00-\\u9fa5,。!?、;:“”‘’《》【】()——…]/g)?.slice(0, 150).join('') || '';\n\n    const contentSnippet = snippet;\n\n    text += `第{index + 1}篇帖子标题:{title} \\n 第{index + 1}篇帖子作者:{creator} \\n 第{index + 1}篇帖子链接:{link} \\n 第{index + 1}篇帖子内容:{contentSnippet} \\n\\n`;\n});\n\nreturn {\n    text: text.trim()\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        -240
      ],
      "id": "c7f37a68-f22b-4fe7-938f-60ef6a0b0fd8",
      "name": "提取有效内容",
      "retryOnFail": true
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        880,
        -40
      ],
      "id": "2cc954cc-eacf-4b21-84ae-497e39cfeebc",
      "name": "DeepSeek Chat Model",
      "credentials": {
        "deepSeekApi": {
          "id": "jdhbDrz31BQ43DZT",
          "name": "DeepSeek account"
        }
      }
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "get_linux_do",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -20,
        -240
      ],
      "id": "5256dfa1-7ee8-428b-b9c9-fbf17a45ede3",
      "name": "Webhook",
      "webhookId": "8a525c47-db6a-40e0-b5d5-fad89f13096e"
    }
  ],
  "pinData": {},
  "connections": {
    "Telegram": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "提取有效内容",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "总结": {
      "main": [
        [
          {
            "node": "Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "提取有效内容": {
      "main": [
        [
          {
            "node": "总结",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "总结",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bcb435f2-3786-4868-a45f-377d1393e35c",
  "meta": {
    "instanceId": "d2e350b020eeb7d7ff5013e930bd912332ba24b773f76a1d9c1c49cd85ec83c2"
  },
  "id": "LYZXNsuh0UGuVaR0",
  "tags": []
}

另外说一下,telegram的credential

简单说一下流程供参考
先找@BotFather去注册一个机器人,拿到token填在n8n获取到credential,然后要创建一个公开频道,把这个bot拉进来作为管理员,在n8n的telegram节点会需要一个chat id,是你的机器人要发言的频道或群组的chat id,获取方式是进入网页端telegram,打开那个频道,地址栏最后的包括‘-’符号的一串数字填写进去

debug思路

我觉得过程中不遇到bug是不太现实的,我把每一步可能遇到的问题和解决思路简单总结一下
- 安装Warp:SSH服务器控制台输入warp,确认wireproxy已经开启别记错端口
- 编写完js代码后可以直接运行一下,node ./fetch_rss.js,如果有依赖库没装记得装一下
- 脚本记得两次运行之间至少间隔10分钟,不然一定抓不到(至少我的服务器上
- 如果怀疑已经请求成功,是n8n接受不到消息,可以本地或服务器本地用curl工具测试一下,当然也别忘记打开workflow开关