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

首发地址: 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;')// 如果获取数量失败,则默认是40imgCount := 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.mzph.cn/diannao/28827.shtml

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

相关文章

HTTP协议版本历程

HTTP协议的发展历程 版本推出年份当前状态HTTP/0.91991年已过时HTTP/1.01996年已过时HTTP/1.11997年标准HTTP/2.02015年标准HTTP/3.02022年标准 HTTP/0.9 HTTP/0.9非常简单,并不涉及数据包传输,通过请求和响应的交换达成通信,请求由单行指…

SmartEDA、Multisim、Proteus大比拼:电路设计王者之争?

在电路设计领域,SmartEDA、Multisim和Proteus无疑是三款备受瞩目的软件工具。它们各自拥有独特的功能和优势,但在这场电路设计王者的竞争中,谁才是真正的领跑者?让我们深入探究这三款软件的异同,揭示它们各自的魅力所在…

图像处理与视觉感知复习--图像特征描述图像生成

文章目录 角点(关键点)的特点图像分类的流程梯度方向直方图(HOG)流程平移、旋转和尺度特征(SIFT)流程常用的图像生成模型GAN的原理Diffusion Model的原理mAP计算方法 角点(关键点)的…

Vue48-ref属性

一、需求:操作DOM元素 1-1、使用原生的id属性 不太好! 1-2、使用 ref属性 原生HTML中,用id属性给元素打标识,vue里面用ref属性。 给哪个元素加了ref属性,vc实例对象就收集哪个元素!!&#xff0…

HTML初体验

可参考jd.com官网&#xff0c;ctrlu查看当前页面源代码 找到你的项目&#xff0c;在项目中创建html类型的网页文件 标准的HTML正确书写格式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title&…

仅靠独立网站也能赚到100万,真的太牛了

你听说过 Photopea 吗&#xff1f;这是一个免费的类似 Photoshop 的图像编辑器。 这个项目&#xff1a; 每月1300万访问量每月150万用户使用小时每月10万美元的广告收入 Photopea 项目的天才创造者是 Ivan Kutskir。 令人惊讶的是&#xff0c;他独自处理了每日50万用户&…

Tomcat配置详解

文章目录 一、配置文件介绍配置文件日志文件 二、组件组件分层和分类核心组件Tomcat处理请求过程URL对应关系 三、部署java程序手动部署搭建博客状态页 四、常见配置详解tomcat端口号安全配置管理虚拟主机配置Context配置 四、Tomcat Nginx动静分离 一、配置文件介绍 配置好环…

区间DP——AcWing 282. 石子合并

区间DP 定义 区间 DP 是动态规划的一种特殊形式&#xff0c;主要是在一段区间上进行动态规划计算。 运用情况 通常用于解决涉及在一段区间内进行操作、计算最优值等问题。比如计算一个区间内的最大子段和、最小分割代价等。一些常见的场景包括合并操作、划分操作等在区间上…

夏季河湖防溺水新举措:青犀AI视频智能监控系统保障水域安全

近日一则新闻引起大众关注&#xff0c;有网友发布视频称&#xff0c;假期在逛西湖时&#xff0c;发现水面上“平躺”漂浮着一名游客在等待救援。在事发3分钟内&#xff0c;沿湖救生员成功将落水游客救到了岸边。 随着夏季的到来&#xff0c;雨水增多&#xff0c;各危险水域水位…

如何下载GoldWave 6.80软件及详细安装步骤

GoldWave功能介绍&#xff1a; GoldWave是一款很强大多功能数字音频编辑软件&#xff0c;可以用来消除某些音乐里边的噪音&#xff0c;可以用来声音编缉、播放、录制和转换还是多功能。它的音频特效有很多种可供选择。 GoldWave音频编辑软件与Windows其它应用软件一样&#x…

GaussDB技术解读——GaussDB架构介绍(四)

目录 11 GaussDB云原生架构 11.1 云原生关键技术架构 11.2 关键技术方案 11.2.1 通信组件 11.2.2 集群管理组件 11.2.3 多租组件 GaussDB架构介绍&#xff08;三&#xff09;从智能关键技术方案、驱动接口关键技术方案等方面对GaussDB架构进行了解读&#xff0c;本篇将…

SpringCloud:Feign远程调用

程序员老茶 &#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; P   S : 点赞是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#…

驾驭未来:智能网关如何革新车联网体验

车联网&#xff08;Internet of Vehicles&#xff09;是一个跨领域的技术综合体&#xff0c;它基于物联网&#xff0c;利用先进的信息通信技术实现车与车、车与路、车与人、车与服务平台等的全方位网络连接。 龙兴物联智能网关是集成了多协议、多接口&#xff0c;具有综合数据采…

[图解]建模相关的基础知识-11

1 00:00:00,700 --> 00:00:05,090 下一个知识点就是函数在集合上的限制 2 00:00:08,290 --> 00:00:10,200 符号可以这样来 3 00:00:10,210 --> 00:00:16,640 F然后一个往下的箭头A 4 00:00:16,650 --> 00:00:19,520 意思就是说F里面的元素 5 00:00:20,120 --&…

闭包表(Closure Table)

设计血缘关系&#xff08;data-lineage&#xff09;时&#xff0c;想到要使用的表模型。 表设计 节点记录表 - node CREATE TABLE lineages_node (name varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 节点名称,id bigint(20) unsigned NOT NULL AUTO_INCREM…

element--el-table合计换行显示

el-table合计换行显示 效果图实现1、使用到的参数2、代码演示 效果图 实现 1、使用到的参数 官网链接&#xff1a;element-table 将show-summary设置为true就会在表格尾部展示合计行。默认情况下&#xff0c;对于合计行&#xff0c;第一列不进行数据求合操作&#xff0c;而是…

【Python/Pytorch - 网络模型】-- SVD算法

文章目录 文章目录 00 写在前面01 基于Pytorch版本的SVD算代码02 理论知识 00 写在前面 &#xff08;1&#xff09;矩阵的奇异值分解在最优化问题、特征值问题、最小二乘方问题、广义逆矩阵问题及统计学等方面都有重要应用&#xff1b; &#xff08;2&#xff09;应用&#…

Sora和快手可灵背后的核心技术 | 3DVAE:通过小批量特征交换实现身体和面部的三维形状变分自动编码器

【摘要】学习3D脸部和身体生成模型中一个解开的、可解释的和结构化的潜在表示仍然是一个开放的问题。当需要控制身份特征时,这个问题尤其突出。在本文中,论文提出了一种直观而有效的自监督方法来训练一个3D形状变分自动编码器(VAE),以鼓励身份特征的解开潜在表示。通过交换不同…

数据结构(DS)C语言版:学习笔记(4):线性表

参考教材&#xff1a;数据结构C语言版&#xff08;严蔚敏&#xff0c;吴伟民编著&#xff09; 工具&#xff1a;XMind、幕布、公式编译器 正在备考&#xff0c;结合自身空闲时间&#xff0c;不定时更新&#xff0c;会在里面加入一些真题帮助理解数据结构 目录 2.1线性…

eNSP由于Cloud网卡设置错误引起的STP环路机制问题

现象&#xff1a;SW1和SW2之间直连&#xff08;vlan13&#xff09;不可达&#xff0c;但是断开左边的Cloud云的虚拟之后可达&#xff08;设置g0/0/1口为down) ,接口协议均up&#xff0c;配置正确。 查看生成树状态&#xff1a; 发现&#xff0c;SW2的g0/0/4接口为阻塞状态&…