php 抓取网页生成图片(流量之于互联网:微信内做分享和传播的形式)

优采云 发布时间: 2021-10-29 21:01

  php 抓取网页生成图片(流量之于互联网:微信内做分享和传播的形式)

  前言

  流量对于互联网公司来说就像水之于一切一样重要。那么目前国内移动互联网流量集中在哪里呢?答案很明显,那就是我们每天都在使用的微信。

  2018年初,微信月活跃用户数突破10亿,成为国内首个月活跃用户突破10亿的产品。“三季度大战”后,腾讯逐渐从封闭走向开放,而微信作为腾讯在移动互联网时代最重要、最成功的战略产品,也在践行腾讯的开放战略。在这样的背景下,如何利用好微信的流量引导用户分享和传播,成为摆在我们面前的重要课题。但是,基于自身利益和用户体验等考虑,微信对在微信中分享和传播的内容和形式有严格的规定和诸多限制。如果你不注意违反这些规则,你可能会受到惩罚。严重的甚至被微信封杀死。

  然而,风险和回报总是成正比的。有时为了达到更好的传播效果,我们不得不“合理”地采取一些措施。目前,微信中的分享传播方式不外乎以下五种:文字、图片、H5链接、小程序、短视频。从技术角度来看,微信、文字、H5链接、小程序三种形式,相对容易控制,而图片和短视频相对更容易绕过微信监控。虽然现在短视频很流行,但是我们公司用的不多(虽然我觉得应该充分利用),所以还是把重点放在图片上吧。

  商业背景

  在我们的业务中,我们经常需要引导用户将图片分享到微信。目前,我们生成图片的方式主要有两种。一种是通过APP中自研的Autumn系统生成图片,另一种是通过PHP后端生成图片,将图片上传到CDN再将图片链接返回给前端。. 这两种方法都有其局限性。第一种方法的局限是只能在APP中使用,不能在微信环境或手机浏览器中使用。第二种方法的问题在于它需要后端学生。完成共享海报图片的版面开发,需要占用CDN资源(需要花钱),如果生成的图片只是临时使用,这种方式的弊端就很明显了。所以,

  技术选型

  如果要在前端生成图片,自然会想到使用Canvas技术来做,但是团队中如何使用Canvas有两个思路:第一个是完全封装Canvas API进行绘图,以及二是直接使用开源库,比如流行的html2canvas库。我个人提倡第二种方法。一方面,它可以直接将 DOM 转换为 Canvas。用一个我们比较熟悉的方法也不太方便。如果我们封装它,很多基础的任务,比如界面布局,各种元素的绘制等等,都得重新做,开发和维护的风险和成本太高了。另一方面,html2canvas库是一个长期的开源项目,应该是比较成熟和稳定的。然而,团队中还有老司机发出警告,提到他们之前也尝试过使用html2canvas库做项目,但是在绘制一些比较复杂的页面时遇到了很多问题,所以他们决定放弃这个方案,改用A采用完全独立的开发方式,即上述第一种方式。但是一方面,我们需要生成的海报图片并不复杂,另一方面,直接使用DOM来绘制也是非常有诱惑力的,所以想了想,还是决定使用html2canvas的绘制方案。所以他们决定放弃这个方案,转而采用完全独立的开发方式,也就是上面的第一种方式。但是一方面,我们需要生成的海报图片并不复杂,另一方面,直接使用DOM来绘制也是非常有诱惑力的,所以想了想,还是决定使用html2canvas的绘制方案。所以他们决定放弃这个方案,转而采用完全独立的开发方式,也就是上面的第一种方式。但是一方面,我们需要生成的海报图片并不复杂,另一方面,直接使用DOM来绘制也是非常有诱惑力的,所以想了想,还是决定使用html2canvas的绘制方案。

  html2canvas 基本介绍

  根据html2canvas官方文档的介绍,html2canvas库的工作原理并不是真正的“截图”,而是读取网页上目标DOM节点的信息来绘制画布,所以不支持所有的css属性(详见这里),并且期望使用的图片与当前域名相同,但是官方也提供了一些方法来解决加载跨域图片的问题。

  它的用法非常简单。引入html2canvas库后,获取目标dom,调用html2canvas方法生成canvas对象。由于我们的目标是生成图片,所以需要调用canvas.toDataURL()方法来生成。

  标签的可用数据:

  html2canvas(targetDom).then(canvas => {// 将画布附加到页面});

  接下来我列举一下我在这个过程中遇到的一些问题以及解决方法。

  问题一:生成的图片很模糊

  当我写了代码,准备按照官网的介绍查看效果的时候,发现生成的图片很模糊。您可以查看下图的比较以了解详细信息。海报图片DOM在右边,左边是html2canvas库生成的Canvas。如果下面没有特别说明, , 都是左 Canvas 和右 DOM。

  

  问题一:画面模糊

  如您所见,图中红色圆圈标记的部分非常模糊。遇到这个问题后,我自然而然地去谷歌解决了这个问题。不说了,分分钟搜了一堆结果(毕竟是成熟的开源软件),还天真地以为这个问题很快就解决了。.

  

  谷歌大法好!

  我随意点了几个链接,看了看里面的解决方案。大体思路是将画布放大n倍,然后制作图形。奇怪的是,我试了之后,发现很多人说这个有效的方法对我完全没用。即使我放大200倍,画出来的画面还是一样的模糊,所以我只能遗憾地向我宣布这个方法。它不再有效。

  所以想知道这是不是html2canvas库本身的bug,于是换了一个类似的库,dom-to-image,试了一下。结果出乎我的意料......这个产品不仅仅是一张模糊的图片,它是整个画布。很模糊!而且它恢复得非常糟糕,我吓得差点把手机扔掉。截图来了,大家自己感受一下吧……

  

  你是认真的吗?

  说到这里,我有点泄气了,因为从这种情况来看,很有可能是某些底层逻辑存在问题,也就是说,这个问题可能无解……不过还好,没有路可走完美。当我们几个人一起讨论这个问题时,转折点出现了。有同学突然发现,生成的海报图片中并不是所有的图片都是模糊的,一张小图片还是很清晰的!这个发现让我欣喜若狂。经过一段时间的测试,我发现只有用作背景图像的背景图像才会模糊,并且

  图片标签没有这个问题。那么解决方法很简单,直接用

  达到background-image的效果,问题就解决了。

  问题二:删除线(text-decoration:line-through)在线条下方

  这是一个小问题,但是文字有点下划线,如图:

  

  文本中的下水平线

  解决方法也很简单,将文本元素设置为相对相对定位,然后使用伪元素进行模拟。较少的代码如下:

  &:after {.text-decoration-line-through();}

  .text-decoration-line-through(@color: #fff) {位置:绝对;宽度:100%;高度:1px;顶部:50%;左:0;内容:''; 背景色:@color; }

  问题三:带省略号的多行文字无法正确渲染

  实测发现,使用省略号样式的多行文字时,会直接导致文字消失,如下图:

  

  溢出:隐藏;显示:-webkit-box;-webkit-box-orient:垂直;-webkit-line-clamp:2;

  这也是一个小问题。可能是html2canvas不支持上述某些样式(虽然我没有从文档中找到证据)。我的解决方案是简单地使用 overflow: hidden 来截断文本。不怕麻烦的话就得用js计算加省略号了。

  问题四(Big Boss登场):图片无法渲染

  嗯,其实这个问题出现在图片模糊问题之前。因为需要加载的图片都在CDN上,而且我们知道html2canvas的工作原理是使用js解析目标dom节点生成canvas,所以我们需要使用跨域的方式来加载图片。一开始为了让图片正常显示,我添加了allowTaint属性,设置为true。代码和截图如下:

  

  添加allowTaint属性之前,图片无法显示

  html2canvas(targetDom, {allowTaint: true}).then(canvas => {// 将画布附加到页面});

  

  添加allowTaint属性后,图片显示正常

  注意!这里我们只是转换成canvas,还没有生成图片。那么接下来,我们尝试通过调用canvas.toDataURL()来生成图片数据,并将其设置为目标图片dom的src属性:

  html2canvas(targetDom, {allowTaint: true}).then(canvas => {targetImage.setAttribute('src', canvas.toDataURL() };);

  这时候发现代码报错了,报错信息如下:

  

  从错误信息的意思来看,添加allowTaint:true属性生成的canvas会导致toDataURL方法失败。所以我只能去掉allowTaint:true再试,结果是图片没有直接渲染:

  

  去掉allowTaint:true属性后图片消失

  我查看了官方文档,找到了问题的答案:

  为什么我的图像没有渲染?

  html2canvas 无法绕过浏览器设置的内容策略限制。位于当前页面原点之外的绘制图像会污染它们被绘制的画布。如果画布被污染,则无法再读取。因此, html2canvas 实现了在应用图像之前检查图像是否会污染画布的方法。如果您已将 allowTaintoption 设置为false,则不会绘制图像。

  如果您希望加载位于页面原点之外的图像,您可以使用代理加载图像。

  根据官方的说法,跨域加载的图片会污染画布,导致画布无法导出数据。也建议我们设置一个节点代理服务器来解决这个问题。什么?这么麻烦还需要架设节点服务器吗?我们本来想在H5端独立做这个,不想服务器参与!坚决寻找任何其他解决方案。果然,我在配置文件中找到了另一个选项:

  

  使用CORS

  这一刻,我微微一笑,淡定的加上了useCORS:true属性,然后优雅地等待着胜利果实的到来。一切看起来都很完美~

  

  哦,该死的!发生了什么?好的胜利果实呢?♀️?此错误消息表示我的图像已跨域加载。我不是已经将useCORS设置为true了吗?♀️?这时候,我和世界杯上的梅西一样惊慌失措,于是赶紧上谷歌找原因。

  

  很遗憾,我把我发现的所有相关问题都看了一遍,发现没有人真正解决过这个问题。这些老外长着一张倔强树上的顽固*敏*感*词*。晃晃悠悠的发呆……(其实现在回想起来,上面截图最后一个中文写的解决方案最接近问题的本质,可惜他还没有完全弄清楚问题的根源问题)。没有办法,只能另辟蹊径,继续探讨这个问题。

  查了一些跨域的相关信息,然后尝试去掉useCORS属性,发现虽然报错信息没了,但是图片还是无法渲染。这时候怀疑是html2canvas库本身的bug,因为根据它,useCORS属性是用来解决图片跨域加载问题的。为什么添加后还是报错?而且,官网上的大量类似问题没有得到处理,更加深了我的怀疑。于是我查看了它的源码,发现图片加载部分的代码是这样的:

  

  为了验证是否是库中的bug,我精简了代码,然后复制了错误图片的链接,手动执行。测试代码和控制台输出如下:

  

  

  令我惊讶的是图片加载成功!我快速测试了几种情况,发现在渲染弹出DOM节点之前测试代码可以成功执行,但是在渲染弹出DOM之后就会失败。直到这时我才开始意识到,这个问题很可能与图片的浏览器缓存有关。发现这个现象后,查了很多相关资料,迷雾终于散去。

  首先是MDN上介绍CORS的文章文章:跨域资源共享(CORS)。有几个重要的知识点:

  当 Web 应用程序请求与自己的来源具有不同来源(域、协议和端口)的资源时,它会发出跨源 HTTP 请求。

  出于安全原因,浏览器会限制从脚本内发起的跨源 HTTP 请求。

  请注意,在任何访问控制请求中,始终发送 Origin 标头。(跨域请求必须带上带有当前域名值的Origin请求头)

  我们先来看看启用CORS的图片介绍:

  什么是“污染”画布?

  尽管您可以在画布中使用未经 CORS 批准的图像,但这样做会污染画布。一旦画布被污染,您就无法再从画布中取出数据。例如,您不能再使用 canvastoBlob()、toDataURL()、orgetImageData() 方法;这样做会引发安全错误。

  这可以防止用户在未经许可的情况下使用图像从远程网站提取信息而暴露私人数据。

  根据这一段的描述,虽然canvas可以读取跨域图片,但这也会造成canvas被污染,导致canvas无法导出。

  可用于标签的图像数据。

  我们先看开篇:

  HTML 规范为图像引入了跨 origin 属性,结合适当的 CORS 标头,允许从外部来源加载的元素定义的图像在画布中使用,就像它们是从当前来源加载一样。

  这意味着如果我们想要

  如果label加载的图片可以被canvas读取,并且可以导出图片数据,那么就应该在label中添加crossorigin属性。crossorigin 属性有两个可选值:anonymous 和 use-credentials。它们之间的区别可以在文末附录中的参考链接中找到。目前,我们可以直接使用 crossorigin='anonymous' 来触发带有跨域请求头 Origin 的 HTTP 请求。

  我们来看看文中示例部分的第一段:

  您必须有一个服务器托管图像,并带有适当的 Access-Control-Allow-Originheader。添加 crossOrigin 属性会生成请求标头。

  关键在这里。除了将 Origin 添加到请求头之外,服务器的响应头还必须收录正确的 Access-Control-Allow-Origin。否则,表示服务器不接受来自客户端的跨域请求。一切为了安全。

  看到这里,我们已经了解了跨域错误报告的基本原理。下一步就是为什么我们即使添加了useCORS:true,还是会报跨域请求错误?

  原因与html2canvas库的工作原理有很大关系。前面提到,html2canvas库要求我们先提供一个DOM节点,然后它读取并解析这个DOM节点,生成一个canvas对象。如果已经在 DOM 节点中使用

  标签,它也会解析这个

  标签的src属性,然后重新创建一个Image对象,给它加上crossOrigin="anonymous"属性,尝试跨域重新读取图片数据。需要注意的是,CDN上的图片一般都会缓存响应头,会缓存在浏览器端,缓存的不仅是图片数据,还有HTTP响应头。所以我们已经找到了问题的根本原因。当html2canvas尝试跨域读取图片数据时,会读取浏览器缓存的数据,而由于我们没有在DOM节点中给出数据

  标签添加了crossorigin="anonymous"属性,所以缓存的数据没有Access-Control-Allow-Origin响应头,导致html2canvas库读取的图片数据污染生成的canvas对象,最终导致canvas 导出数据报错。

  真相已经在这里揭晓了。所以我们要做的也很简单,就是给每个DOM节点

  将 crossorigin="anonymous" 属性添加到标签。回过头来说一下为什么之前的中文文章解决了问题却没有找到问题的根本原因,因为他修改了html2canvas的源码读取图片,并添加了每个Image的src属性创建了一个随机字符串,一不小心就避免了读取缓存数据的问题,但是会导致CDN的缓存被破坏。

  最后总结一下,其实已经说了很多了,但是我们要做的很简单:

  1、添加 useCORS:true 属性;

  2、 给每个要生成的DOM画布

  为标签添加 crossorigin="anonymous" 属性;

  3、 确保你的图片CDN服务器支持CORS访问,这意味着它会返回Access-Control-Allow-Origin等响应头;

  问题5(大boss回):图片无法渲染

  可能你和我一样,认为问题到这里已经彻底解决了,其实不然。就像很多游戏关卡中的大boss一样,在终于杀死了它的第一条生命之后,原来它竟然还有第二条生命。

  事情是这样的,如果要使用生成收录在dom中的canvas

  您的用户之前访问过的图片(例如,您正在改造现有的在线业务)。显然你之前不应该给它

  给标签添加crossorigin="anonymous"属性,那么请注意,此时你用户的浏览器已经将这些图片缓存到本地了,所以即使按照上面的步骤也没有用,因为你在访问图片的时候阅读了所有是没有响应标头的缓存数据,例如 Access-Control-Allow-Origin。

  这个时候你要做的就是把你要生成canvas的dom里面的所有dom都给

  在标签的src中添加任意字符串,只要能重新发起图片读取请求即可,避免读取浏览器缓存数据,如下图:

  ''

  注意,不要添加随机字符串,会破坏CDN缓存,只需添加固定字符串,避免读取浏览器缓存的数据。这是我自己血的教训!所以请不要忽视这一点!

  写在最后

  至此,我想说的基本说完了。其实在解决图片未渲染问题的过程中,我也遇到了一些其他的障碍和麻烦,给我带来了很多麻烦。限于篇幅,我不再赘述。NS。html2canvas库在应用过程中肯定还有一些其他的问题,比如页面性能不一致。这可能是因为 DOM 本身的样式存在兼容性问题,或者库的渲染与 DOM 不同,具体取决于具体情况。情况。本来想补充一些浏览器缓存和CDN缓存的知识,但是这个话题本身就收录了足够的内容。以后再开个博客,慢慢写《彻底理解浏览器缓存》这里NS:)

  在解决这个问题的过程中,我也为自己总结了一些经验,希望对大家有所启发:

  1、善用charles、chrome等开发工具;

  2、认真仔细,大胆假设,仔细验证;

  3、不要轻易放弃;

  4、永远不要太自信。

  参考

  跨域资源共享 (CORS)

  启用 CORS 的图像

  HTMLCanvasElement.toDataURL()

  CORS 设置属性

  图像嵌入元素::crossorigin

  web开发的html2canvas截图如何解决跨域问题?

  html2canvas:JavaScript 截图

  html2canvas在iOS8系统上的兼容性

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线