aardio实战篇) 下载微信公众号文章为pdf和html

news2024/12/25 13:41:49

首发地址: https://mp.weixin.qq.com/s/w6v3RhqN0hJlWYlqTzGCxA

前言

之前在PC微信逆向) 定位微信浏览器打开链接的call提过要写一个保存公众号历史文章的工具。这篇文章先写一个将文章保存成pdf和html的工具,后面再补充一个采集历史的工具,搭配使用就能保存所有历史文章到本地。

如果是在浏览器打开文章,想保存成pdf和html很简单,右键打印(pdf)和另存为(html)就可以了。想在程序里实现则需要一些自动化工具,例如playwright、puppeteer等,但这些都没有移植到aardio。

cdp

先科普一个知识:大部分自动化工具都是基于chromium内核浏览器自带的一个叫Chrome DevTools Protocol[1]的协议(后面简称cdp),它涵盖了对谷歌浏览器的所有自动化操作。

cdp协议使用jsonrpc和谷歌浏览器通信,所以完全可以在aardio也实现一个类似drissionpage的库,但是工程量不小,我没那么多时间去实现。所以只在用到哪部分的时候完善哪部分接口,不会去完整实现一个drissionpage。

用到的cdp接口

保存成html

cdp协议里并没有直接获取页面html的接口,但是可以通过获取页面document.body.outerHTML的值来得到。而获取该值则是通过Runtime.evaluate[2]接口执行js表达式并返回结果。

不过这样保存的html打开之后,会显示一直转圈,并且图片无法加载。这是因为有些图片用的相对链接,解决方法就是替换相对链接为绝对链接。不过我更推荐保存成mhtml,这样图片就会被嵌入到html里,不需要从网络加载。

保存成mhtml

cdp协议里保存成mhtml的接口是Page.captureSnapshot[3]

保存成pdf

接口是Page.printToPDF[4]

简单使用

aardio其实提供了cdp协议的封装库web.socket.chrome,用法可以在案例里搜索这个。

保存成mhtml
import win.ui;
import console
import web.view;
import web.socket.chrome;
/*DSG{{*/
var winform = win.form(text="测试";right=759;bottom=469;bgcolor=16777215)
winform.add()
/*}}*/

var wb = web.view(winform,,"--remote-debugging-port=29999");
winform.text = "正在打开网页,请稍候 ……"
winform.show();

var ws = wb.openRemoteDebugging();

ws.Page.navigate(
    url = "https://mp.weixin.qq.com/s/Nik8fBF3hxH5FPMGNx3JFw";
);

wb.wait("Nik8fBF3hxH5FPMGNx3JFw");
win.delay(3000)
import crypt;
ws.Page.captureSnapshot().end = function(result,err){
   if(result[["data"]]){
       string.save("示例.mhtml", result.data)
       winform.text = "保存mhtml成功"
   } 
} 

win.loopMessage();

虽然保存了,但是图片并没有显示,应该是图片还没加载就已经开始保存了,并且有些图片只有滑动到底部时才会加载。所以还需要先下拉到底部,让页面把图片全部加载出来再进行保存。

异步改同步

这是个异步库,上面的写法看起来不太顺眼,可以将它稍微封装一下改为同步库使用。

callWait = function(ws, method,params,timeout,interval){
    if(!ws) return;
    var done = null;
    var t = ..string.split(method,".");
    var func = ws;
    for(i=1;#t;1){
        func = func[t[i]];
    }
    var result;
    func(params).end = function(r,err){
        if(!err) {
            done = true;
            result = r;
        }
    };
    ..win.wait(lambda() done,winform,timeout:15000,interval);
    return result;
}

这样调用就顺眼多了,当然习惯了异步的话也可以不改。

var result = callWait(ws, "Page.captureSnapshot", {});
string.save("示例.mhtml", result.data)
滑动到底部

滑动操作用JavaScript比cdp接口要简单的多,所以先找gpt写一段JavaScript滑动到底部的代码(需要多调教几次,最初版本肯定是有错误的)。

scrollPageBottom = function(ws){
    ..win.delay(1000);
    var scrollToEnd = `(async function scrollPage() {
        return new Promise(async (resolve) => {
            var distance = 500; 
            var count = 0;
            window.scrollTo(0, 0);
            window.scrollTo(0, 0);
            var scroll = async () => {
                var lastScrollTop = document.documentElement.scrollTop;
                window.scrollBy(lastScrollTop, distance);
                await new Promise(r => setTimeout(r, 500)); 
                var newScrollTop = document.documentElement.scrollTop;
                var scrollHeight = document.body.scrollHeight;
                console.log(lastScrollTop, newScrollTop, scrollHeight);
                if(lastScrollTop === newScrollTop) count += 1;
                if ((lastScrollTop === newScrollTop && newScrollTop/scrollHeight > 0.8) || count > 2) {
                    resolve(); 
                } else {
                    await scroll(); 
                }
            };
            await scroll();
        });
    })();`;
    var params = {
        "expression": scrollToEnd,
        "awaitPromise": true,
        "returnByValue": true
    }
    // 开始滑动
    callWait(ws, "Runtime.evaluate", params);
    // 有时候滑动还未结束,上面的代码就返回了,所以继续等待
    ..win.wait(function(){
        var r= callWait(ws, "Runtime.evaluate", {
            expression="document.documentElement.scrollTop/document.body.scrollHeight > 0.8";
            awaitPromise=true;
            returnByValue=true
        });
        return r;
    },,15000,500)
}
封装成库

全部放出来代码会太多,所以将代码封装成了库(cdpdriver),放到了之前写的aardio教程) 搭建自己的扩展库仓库里,有兴趣的可以去github自己看怎么实现的。

封装的库使用示例如下:

import cdpdriver;
import web.view;
import win.ui;
import console
/*DSG{{*/
var winform = win.form(text="cdp协议";right=759;bottom=469)
winform.add()
/*}}*/

var initWebView = function(){
    var cmdArgs = `--remote-debugging-port=29999`;
    winform.webView = web.view(winform,,cmdArgs);
    if(!_STUDIO_INVOKED) winform.webView.enableDevTools(false);
    winform.show();

    winform.stateTable = {
        pageReady=null;//页面加载完成
    }
    var ws = winform.webView.openRemoteDebugging();
    var cdpClient = cdpdriver(ws);
    // 启用Page事件
    ws.Page.enable();
    // Page.domContentEventFired和Page.loadEventFired事件触发表示页面加载完成
    ws.on("Page.domContentEventFired",function(param){
        winform.stateTable.pageReady = true;
    })
    ws.on("Page.loadEventFired",function(param){
        winform.stateTable.pageReady = true;
    })
    winform.stateTable.pageReady = null;
    var url = "https://mp.weixin.qq.com/s/Nik8fBF3hxH5FPMGNx3JFw";
    winform.webView.go(url);
    win.wait(lambda() winform.stateTable.pageReady, winform.hwnd, 15000, 50);  
    win.delay(1000) 
    if(winform.stateTable.pageReady){
        cdpClient.scrollPageBottom();
        var mhtml = cdpClient.outerMHTML;
        string.save("测试.mhtml", mhtml)
    }
}

initWebView()

winform.show();
win.loopMessage();

这样保存的mhtml图片显示也正常

pdf也是正常的

严重bug

当某个网页的图片特别多的时候,保存的mhtml文件特别大的时候(比如八九十兆),这时候控制台就会出现no enough memory的错误,经过多天的排查,没有找到具体原因,不过我猜测是aardio异步传输数据时,申请的内存空间小于这个文件大小,所以当传输文件的数据时就会出错。

解决方法

这个解决不了只能不用这个异步库,自己基于官方扩展库里的hpsocket实现一个jsonrpc。

但是官方扩展库的hpsocket使用的dll还是2017年的版本,为了避免之前版本有未修复的bug,去github更新一下hpsocket的dll。

hpsocket的dll下载地址: https://github.com/ldcsaa/HP-Socket/releases

hpsocket封装后的使用案例
import win.ui;
import web.view;
/*DSG{{*/
mainForm = win.form(text="hpsocket cdp协议";right=757;bottom=467)
mainForm.add()
/*}}*/

var threadMain = function(debugPort){
    import win;
    import cdpdriver.hpcdp;
    import cdpdriver.jsonrpc;
    import kilogging;

    var logger = kilogging();
    ..cdpdriver.jsonrpc.waitDebuggingPages(debugPort);
    var wsClient = ..cdpdriver.jsonrpc();
    wsClient.connect(debugPort);
    wsClient.send("Page.enable");
    wsClient.on("Page.domContentEventFired", function(){
        ..thread.set("pageReady" + owner.guid, true);
    })
    wsClient.on("Page.loadEventFired", function(){
        ..thread.set("pageReady" + owner.guid, true);
    })
    var cdpClient = ..cdpdriver.hpcdp(wsClient);
    var url = "https://mp.weixin.qq.com/s/Nik8fBF3hxH5FPMGNx3JFw";
    var pageReadyFlag = "pageReady" + wsClient.guid;
    ..thread.set(pageReadyFlag, null);
    logger.info("开始下载 (%s) pdf和html", url);
    wsClient.send("Page.navigate",{"url":url})
    win.wait(function(){
        return thread.get(pageReadyFlag);
    },, 10000, 100);
    if(!thread.get(pageReadyFlag)) {
        logger.info("页面(%s)访问失败", url);
        return;
    }
    cdpClient.scrollPageBottom();
    // 计算网页图片的数量
    var imgCount = cdpClient.runJsCode('document.querySelectorAll("#img-content img").length;')
    // 如果获取数量失败,则默认是40
    imgCount := 40;
    // 每张图片会多等待300毫秒
    ..win.delay(imgCount * 300);
    var mhtmlData = cdpClient.getOuterMHTML();
    var mhtml = mhtmlData ? mhtmlData.data;
    var pdfData = cdpClient.getPdf();
    var pdf = pdfData ? pdfData.data;
    logger.info("获取到的文件大小,pdf(%s), mhtml(%s)",tostring(#pdf), tostring(#mhtml));
    if(pdf) {
        var pdfBytes = ..crypt.bin.decodeBase64(pdf);
        ..string.save("测试.pdf", pdfBytes);
        logger.info("保存pdf成功,路径:%s", io.fullpath("测试.pdf"));
    }
    if(mhtml) {
        ..string.save("测试.mhtml", mhtml);
        logger.info("保存mhtml成功,路径:%s", io.fullpath("测试.mhtml"));
    }    
}

var initWebView = function(){
    var cmdArgs = `--remote-debugging-port=29999`;
    mainForm.webView = web.view(mainForm,,cmdArgs);
    mainForm.show();

    var debugPort = mainForm.webView.remoteDebuggingPort;
    thread.invoke(threadMain,debugPort)    
}

initWebView()

mainForm.show();
return win.loopMessage();

很明显,hpsocket写代码要比web.socket.chrome麻烦的多,因为它是基于多线程的,所以正常情况下推荐使用web.socket.chrome,只有当你遇到不能使用的情况,才换hpsocket

引用链接
  • [1] https://chromedevtools.github.io/devtools-protocol/
  • [2] https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate
  • [3] https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureSnapshot
  • [4] https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1831026.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cweek6

C语言学习 十六.程序环境和预处理 1.翻译环境和运行环境 编译又分为三个阶段: 预编译(文本操作):将include引入的头文件展开成代码,并把注释删除,使用空格代替注释,替换#define的文本编译&a…

基于改进字典学习的旋转机械故障诊断方法(MATLAB)

在过去的二十年里,稀疏表示在各个领域引起了广泛的关注。它的核心思想是将信号描述为尽量少的字典原子,在计算机视觉、生物学、特征提取和机械故障诊断方面显示出强大而可靠的能力。SR通常分为两个步骤:构建字典和学习稀疏系数。对于稀疏系数…

U盘文件夹变exe:现象解析与数据恢复策略

一、U盘文件夹变exe现象描述 在日常使用U盘进行数据传输和存储的过程中,部分用户可能会遭遇一种异常现象:原本正常的文件夹突然变成了可执行文件(即后缀为.exe的文件)。这种变化不仅影响了用户对文件的正常访问和管理&#xff0c…

怎么加密U盘数据?U盘加密软件哪个好?

U盘是我们在生活和工作中最常用的移动存储设备,而为了避免U盘数据泄露,我们需要使用U盘加密软件来加密保护U盘数据。那么,U盘加密软件哪个好呢?下面我们就一起来了解一下吧。 BitLocker加密 BitLocker是Windows系统提供的磁盘加密…

【一步一步了解Java系列】:认识String类

看到这句话的时候证明:此刻你我都在努力 加油陌生人 个人主页:Gu Gu Study专栏:一步一步了解Java 喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者:小闭…

MS1112驱动开发(iio框架)

作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…

揭秘未来:用线性回归模型预测一切的秘密武器!

线性回归模型 1. 引言2. 理论基础2.1 线性回归模型的定义与原理原理与关键假设模型参数估计 2.2 模型评估指标2.2.1 残差分析2.2.2 拟合优度指标2.2.3 统计检验 3. 应用场景3.1. 金融领域中的应用3.2. 医疗健康领域中的应用3.3. 其他领域的应用 4. 实例分析4.1、数据集选择4.2、…

企业三要素核验-公司三要素核验-企业三要素核验接口

接口简介:企业三要素验证,输入公司名称、统一社会信用代码、法人姓名验证是否一致 1.输入公司名称、统一社会信用代码、法人姓名验证是否一致。 2.查询结果仅供参考,不作法定证明使用。 3.不返回其它信息 接口地址:https://www.wa…

【基于 PyTorch 的 Python 深度学习】8 注意力机制(4):PyTorch 实现(上)

前言 文章性质:学习笔记 📖 学习资料:吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容:根据学习资料撰写的学习笔记,该篇主要介绍了如何使用 PyTorch 实现 Transformer 。 代…

数据库系统概念(第八周 第一堂)(规范化关系数据库设计)(强推学习!!!)

目录 前言 E-R模型质量低的深层原因 数据依赖 函数依赖 主属性/非主属性 逻辑蕴含与闭包 Armstrongs Axiom 求解F闭包算法 求解属性集闭包算法 属性集闭包的作用 候选码求解理论和算法 候选码求解理论 无关属性 检验方法 正则覆盖 关系模式的设计 关系…

vcpkg安装opencv中的特殊问题记录(无法找到opencv_corexd.dll)

我是按照网上的vcpkg安装opencv方法进行的(比如这篇:从0开始在visual studio上安装opencv(超详细,针对小白)),但是中间出现了一些别人没有遇到的问题,虽然原因没有找到,但…

[自动驾驶 SoC]-3 英伟达Orin

NVIDIA Jetson AGX OrinTM series (资料来源:nvidia-jetson-agx-orin-technical-brief.pdf) 1 整体介绍 1) Orin SoC结构 Orin SoC,如下图所示,由一个NVIDIA Ampere architecture GPU, Arm Cortex-A78AE CPU, 下一代深度学习核视觉处理加速…

MicroPython+ESP32 C3开发上云

传感器PinI/O状态D412输出1开0关D513输出1开0关 概述 MicroPython是python3编程语言的精简实现,能够在资源非常有限的硬件上运行,如MCU微控制器Micropython的网络功能和计算功能很强大,有非常多的库可以使用,它为嵌入式开发带来了…

Windows NT 3.5程序员讲述微软标志性“3D管道”屏幕保护程序的起源故事

人们使用屏保程序来防止 CRT 显示器"烧毁",因为静态图像会永久损坏屏幕。像 3D Pipes 这样的屏保程序能在显示器处于非活动状态时为其提供动画效果,从而保护屏幕并延长其使用寿命。此外,它们还能在用户不使用电脑时为其提供可定制的…

盘点有趣的人工智能开源项目一

字幕导出 zh_recogn是一个专注于中文语音识别的字幕生成工具,基于魔塔社区Paraformer模型。它不仅支持音频文件,还能处理视频文件,输出标准的SRT字幕格式。这个项目提供了API接口和简单的用户界面,使得用户可以根据自己的需求灵活…

值得推荐的品牌维权控价方法

数据调查 全面了解线上各渠道(如淘宝、天猫、拼多多、京东、抖音、快手等)的低价情况,包括哪些是授权店低价、窜货或假货,为后续针对性治理提供依据。人工排查适用于链接不多的情况,链接数量庞大时利用系统监测更高效…

睿烨蜘蛛池福建官网下载

baidu搜索:如何联系八爪鱼SEO? baidu搜索:如何联系八爪鱼SEO? baidu搜索:如何联系八爪鱼SEO? 现在做站群程序的时候,由于百度、搜狗蜘蛛越来越少了,所以缓存也跟着减少,很多人都降低了服务器的配置,这个时候google蜘蛛却疯狂涌入,烦不胜烦…

Pulsar 社区周报 | No.2024-06-14 | 增强 Pulsar Broker 级别的监控指标

“ 各位热爱 Pulsar 的小伙伴们,Pulsar 社区周报更新啦!这里将记录 Pulsar 社区每周的重要更新,每周发布。 ” 本期主题:增强 Pulsar Broker 级别的监控指标 在 Pulsar 的当前度量指标框架中, pulsar_out_bytes_total …

AbMole带你探索细胞的“铁”门:Piezo1通道在椎间盘退变中的关键角色

在生物医学领域,铁是细胞功能不可或缺的元素,但铁的异常积累却可能成为细胞的“隐形杀手”。最近,一项发表在《Bone Research》上的研究,为我们揭开了铁代谢与椎间盘退变之间神秘联系的一角。这项研究不仅深化了我们对铁离子通道P…

[机器学习] Stable Diffusion初体验——基于深度学习通过神经网络的强大AI平台

文章目录 前言平台介绍 一.创建应用 Stable Diffusion WebUI初始化上传模型,VAE,lora 介绍sd模型,vae,lora模型进入应用文生图工作区调参区图生图 结语 前言 在这个信息爆炸的时代,AI技术正以前所未有的速度发展着。图…