惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
C
Check Point Blog
Hacker News: Ask HN
Hacker News: Ask HN
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
WordPress大学
WordPress大学
P
Proofpoint News Feed
V
Visual Studio Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 叶小钗
Cisco Talos Blog
Cisco Talos Blog
S
Schneier on Security
T
Threat Research - Cisco Blogs
腾讯CDC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Hacker News
The Hacker News
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
N
News | PayPal Newsroom
L
LINUX DO - 最新话题
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Palo Alto Networks Blog
T
Tenable Blog
S
Secure Thoughts
T
Threatpost
V2EX - 技术
V2EX - 技术
大猫的无限游戏
大猫的无限游戏
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
罗磊的独立博客
P
Privacy & Cybersecurity Law Blog
Engineering at Meta
Engineering at Meta
小众软件
小众软件
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
Y
Y Combinator Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cybersecurity and Infrastructure Security Agency CISA
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
P
Privacy International News Feed
H
Heimdal Security Blog
量子位
B
Blog

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
实现网页长截图的常见思路总结 - 元视角
飞鸿踏雪 · 2021-01-10 · via 元视角

作为一个经常写博客的人,我有时会在微博上分享博客内容,可不知道从什么时候开始,国内互联网越来越丧失信仰,所有的厂商都在试图打造一个**“只进不出”的信息孤岛,进而达到增强“用户黏度”的目的。以微博为例,微博中的外链永远都会被转化为短地址,并且无法通过微博内置的浏览器进行跳转。即使你通过手动复制链接的方式打开链接,你依然需要至少两个步骤方能见到“庐山真面目”。借鉴/抄袭这一陋习的还有简书,花时间做了一个第三方链接跳转提示页面,唯独不愿意在上面加一个 a 标签,你还是要手动复制黏贴。坦白说,我觉得国内互联网正在丧失着信仰,看起来电商、物流、外卖、打车、支付……此起彼伏逐渐渗透到我们生活的方方面面,成为名副其实的“互联网+”,可在信息泛滥的今天,我们越来越难找到真正有价值的信息……既然外链注定要被屏蔽掉,那我就勉为其难地顺应潮流发“长截图”咯,所以,接下来我会为大家分享实现网页“长截图”**的常见思路,希望对有类似烦恼或者需求的小伙伴们有所帮助。

通过浏览器实现

要实现网页长截图,显然是和网页打交道,而和网页打交道最多的是谁呢?自然是我们每天都要用的浏览器啦!值得庆幸的是,不管是 Chrome 还是 Firefox ,我们都可以通过它们来是实现这个想法。

Chrome

对于 Chrome 来说,我们只需要“F12”打开开发者工具,并在其中找到“控制台”选项卡,在平时输入 JavaScript 脚本的地方(即 Console 选项卡)输入Ctrl + Shift + P命令,然后你会得到一个类似 VSCode 命令行体验的输入窗口,接下来,输入:Capture full size screenshot并回车。此时,我们就可以得到完整的页面截图。而如果你希望截取网页中的一部分,则可以在选中指定 DOM 元素后采用相同的方式输入命令:Capture node screenshot。此外,更常用的截取浏览器可见范围内的内容,可以使用:Capture screenshot。可能相对于一般可以进行拖拽截图的工具而言,这个方案显得有点笨拙且简陋,可它真的可以完美地实现我们的想法,而且不需要安装任何扩展或者插件。

使用 Chrome 的截图功能 使用 Chrome 的截图功能

Firefox

对于 Firefox 而言,它本身自带截图功能,并且支持拖拽截图,对于我们这些需要长截图的人而言,唯一需要做的就是点击几下数据,确实要比敲命令行要简单一点、友好一点,我个人更喜欢用 Firefox 一点,因为 Chrome 正在从屠龙少年变成恶龙,为了让这个世界上不是只有 Chrome 一种浏览器内核,我决定支持一下 Firefox ,2020 年因为疫情的原因, Mozila 裁员 25%约 250 人,这家几乎靠着理想主义在维护 Gecko 内核的公司,之后可能再无法和 Google 的 Chrome 抗衡,而这个世界只有一种浏览器的时代我们都曾经经历过,它的名字叫做 IE6 ,不禁令人感慨,简直是开放 Web 的罗曼蒂克消亡史。

使用 Firefox 的截图功能 使用 Firefox 的截图功能

通过 Selenium 实现

在我的认知中,有浏览器的地方就有爬虫,而有爬虫的地方就有 Selenium 。原本好端端的 UI 自动化测试框架,怎么就助纣为虐做起爬虫来了呢?其实,主要原因是它提供了一个可以和浏览器交互的环境,从某种意义上来讲,SeleniumPhantomJS 以及 Playwright 都可以认为是类似的技术,这里我们以 Selenium 为例,而通过 Selenium 实现网页长截图则主要有两种方式:其一,是构造一个足够“大”的浏览器,然后调用save_screenshot()方法进行截图;其二,是通过“拖拽”滚动条来滚动截图,然后再通过PIL进行拼接,下面来看具体的代码实现:

def save_screenshot(url, fp_pic):
    fireFoxOptions = webdriver.FirefoxOptions()
    fireFoxOptions.set_headless()
    driver = webdriver.Firefox(firefox_options=fireFoxOptions)
    driver.get(url)
    time.sleep(1)
    # 设置浏览器宽度和高度
    width = driver.execute_script(
      "return document.documentElement.scrollWidth"
    )
    height = driver.execute_script(
      "return document.documentElement.scrollHeight"
    )
    driver.set_window_size(width, height)
    time.sleep(1)
    # 截图
    driver.save_screenshot(fp_pic)
    driver.close()

这里我使用的是 Firefox 的驱动,喜欢 Chrome 的按个人喜好即可,这里我假设你已经掌握了 Python 和 Selenium,如果需要一点辅助知识,可以参考博主的这篇文章:作为技术宅的我,是这样追鬼滅の刃的 。这种方式的“长截图”实现起来非常简单,可是因为需要构造一个非常“大”的浏览器,所以,如果页面适配没有做好的话,可能会出现页面元素变形的问题,其次,这种方式生成的图片体积普遍比较大,所以,从总体上看主要就是这两个缺点。而“滚动截图”实现起来会稍微复杂一点,因为里面会涉及到一小部分计算:

def save_screenshot2(url, fp_pic):
    fireFoxOptions = webdriver.FirefoxOptions()
    fireFoxOptions.set_headless()
    driver = webdriver.Firefox(firefox_options=fireFoxOptions)
    driver.fullscreen_window() # 全屏窗口
    driver.get(url)
    window_height = driver.get_window_size()['height'] # 窗口高度
 
    page_height = driver.execute_script(
      'return document.documentElement.scrollHeight'
    ) # 页面高度
    driver.save_screenshot('temp.png')
 
    if page_height > window_height:
        n = page_height // window_height # 需要滚动的次数
        base_mat = np.atleast_2d(Image.open('temp.png')) # 打开截图并转为二维矩阵
    
    for i in range(n):
        driver.execute_script(
          f'document.documentElement.scrollTop={window_height * (i+1)};'
        )
        time.sleep(.5)
        driver.save_screenshot(f'temp_{i}.png') # 保存截图
        mat = np.atleast_2d(Image.open(f'temp_{i}.png')) # 打开截图并转为二维矩阵
        base_mat = np.append(base_mat, mat, axis=0) # 拼接图片的二维矩阵
        Image.fromarray(base_mat).save(fp_pic, format='PNG')
        os.remove(f'temp_{i}.png')

    os.remove('temp.png')
    driver.quit()

这个方案本身没有太大的问题,可如果你的网页是那种页面滚动时头部固定的设计,譬如类似博主的博客这样的风格,此时这种方案就会有一点问题,每次截取都会包含头部这部分,和我们最后想要实现的效果有一点出入,如果可以计算出头部的高度,截图或者拼接的时候把这个高度考虑进去,就可以彻底解决这个问题,可这样这个问题就从一个通用型问题变成一个局部型问题啦,果然,世上没有完美的解决方案呢……

通过 JavaScript 实现

有人可能要说,博主你好偏心,为什么 Python 都出来了,作为前端三剑客之一的 JavaScript 还没有出现?嗯,对此我想说——你不用说,我知道不就是**“人生苦短,我用 Python”**吗?人家前端世界里有个叫做 html2canvas 的库,博主你可有耳闻?我笑了笑,我并没有看了看我的劳力士,因为我没有劳力士。好吧,既然这里提到了这个库,那就来说说这个库的实现思路吧,人家不是说了嘛?一切可以实现的东西,最终可以用 JavaScript 来实现,我们来看看具体的代码实现,这里,首先准备一个 HTML 文件:

<!DOCTYPE html>
<head>
    <script src='./html2canvas.min.js'></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body style="overflow: hidden;">
    <div id="app" style="height: 768px; overflow: hidden;">
        请输入URL: <input type="text" v-model="url">
        <button v-on:click="capture">截取</button>
        <hr>
        <iframe 
          id="view" 
          v-bind:src='url' 
          width="100%" 
          height="100%" 
          frameborder="0" 
          ref="view">
        </iframe>
    </div>
</body>

非常简单,输入一个地址,然后通过一个 iframe 进行预览,点击按钮进行截图。下面给出 JavaScript 代码:

<script>
    var vm = new Vue({
        el: '#app',
        data: {
            url: 'https://regularly-archive.github.io/2020/Matrix/',
        },
        methods: {
            capture: function() {
                var self = this;
                var iframe = self.$refs.view.contentWindow;
                var iframeBody = iframe.document.getElementsByTagName('body')[0]
                html2canvas(iframeBody).then(canvas => {
                    document.body.appendChild(canvas);
                    //canvas转图片
                    let canvasImg = canvas.toDataURL("image/png");
                    //模拟下载
                    var a = document.createElement('a')
                    a.href = canvasImg;
                    a.download = self.url;
                    let event = document.createEvent("MouseEvents")
                    event.initMouseEvent("click", true, false,
                       window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
                    a.dispatchEvent(event)
                });
            }
        }
    });
</script>

效果如下,你可以点击 这里 访问在线演示 DEMO:

使用html2canvas实现的长截图 使用html2canvas实现的长截图

这里使用 iframe 可能会引入跨域的问题,大家可以参考我的这篇文章:聊聊前端跨域的爱恨情仇 ,而 html2canvas 本身就提供了关于跨域问题的解决方案,大家可以参考这里:http://html2canvas.hertzen.com/configuration

通过第三方工具实现

我知道程序员都喜欢自己去折腾,如果是前无古人、后无来者的东西,我建议去折腾,因为梦想还是要有的,万一实现了呢?而我们这个圈子里同样有一句经典的话,叫做“不要重复制造轮子”,所以,博主这里找到了几个轮子供大家参考,不喜欢在冬天动手写代码的人,可以收藏下这几个工具,这个冬天实在是太冷了,冷到什么程度呢?大概听见笑话都不大愿意笑,用罗翔老师的话说这叫做搞笑未遂。

wkhtml2image

wkhtml 系列,一个命令行工具,可以将本地 HTML 文件或者远程 URL 指向的网页转化为图片,该系列产品中还有 wkhtml2pdf,顾名思义,网页转 PDF,实际使用过程中基本没什么问题,输出的图片 1:1 还原网页,唯一的缺点是偶尔会丢失样式,尤其是页面中引入了第三方的 JavaScript 或者 CSS 的时候,整体上远程 URL 比本地 HTML 要稳定一点,推荐系数:4 星。

长截图03.png 长截图03.png

PickFrom

PickFrom,一个在线的网页转图片的服务,填写 URL 然后点击按钮即可,提供免费预览一部分图片的功能,完整图片的查看、下载均需要支付一定费用,服务质量还可以,但不适合我们这种被迫“白嫖”的穷人家的孩子,土豪们随意,推荐系数:4 星

PickFrom PickFrom

Tiomg

Tiomg,接下来是博主要重点推荐,它和 PickFrom 提供着相同的服务,唯一不同的是,它是完全免费的,我现在主要用这个来工具来生成“长截图”,不错,我背叛了上面我写的那些代码,为什么要重复造轮子呢?有时候我想不明白,为什么国内公司都喜欢那种“大而全”的软件,恨不得要拥有竞争对手所有的特性,可明明大家都“卷”成这样了,为什么不试试差异化的路线呢?可能,是因为低端竞争太多吧!推荐系数:5 星

Tiomg Tiomg

冬天实在是没有动力去写有技术含量的东西啊!关于“长截图”这个话题,差不多是从一周前开始关注、做实验的,所以,请允许在下偶尔写这样一篇“水文”吧!关于“视频是不能 P 的系列”,因为 Dlib 安装起来实在讨厌,而 OpenCV 提供的 68 特征点算法目前只支持 C++ ,研究起来难免要花一点时间,好了,这篇博客暂时先写到这里吧,博主要先去冬眠啦,再见!