如何让社交平台分享跳转等待页时展示目标链接的Metadata?
实现跳转等待页分享时展示目标链接的Metadata
核心问题很明确:社交平台的分享爬虫(比如微信、Facebook、Twitter的爬虫)不会执行页面里的JavaScript,只会读取页面初始HTML中的<meta>标签。你现在用#哈希传递目标链接,依赖前端JS解析处理,但爬虫根本拿不到这个目标链接,更没法动态修改meta标签,所以只能显示等待页本身的空Metadata。
要解决这个问题,必须在服务器端处理请求,提前获取目标链接的Metadata并填充到页面的meta标签里,具体步骤如下:
1. 修改链接传参方式
把原来的#哈希传参改成查询参数,比如从your-wait-page.com#https://target-url.com改成your-wait-page.com?url=https://target-url.com。因为部分爬虫会忽略URL中的哈希部分,查询参数更稳定可靠。
2. 服务器端获取并填充Metadata
用服务器端代码(比如Node.js、PHP、Python等)接收请求,解析目标链接,然后抓取目标页面的Metadata,再动态渲染等待页的meta标签。
示例:Node.js/Express实现
假设你用Node.js搭建服务器,步骤如下:
- 安装依赖:
npm install express axios cheerio
- 编写服务器代码:
const express = require('express'); const axios = require('axios'); const cheerio = require('cheerio'); const app = express(); // 渲染等待页的模板函数 function renderWaitPage(targetMeta) { // 默认Metadata,当目标页无法获取时使用 const defaultMeta = { title: '等待跳转...', description: '请稍等,即将为您跳转至目标页面', image: '你的默认图片URL' }; const meta = {...defaultMeta, ...targetMeta}; return ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${meta.title}</title> <meta property="og:title" content="${meta.title}" /> <meta property="og:description" content="${meta.description}" /> <meta property="og:image" content="${meta.image}" /> <meta property="og:image:width" content="300" /> <meta property="og:image:height" content="300" /> </head> <body> <!-- 原等待页HTML内容,仅修改JS的链接解析逻辑 --> <div trbidi="on"> <div style="padding: 0;"> <div id="makingdifferenttimer" style="display: none;"> <div id="notes_url" style="font-family: Electrolize,lolblogger,Tahoma; font-size: 16px; margin-top: 16px;"> <div id="url_qu"> . </div> </div> </div> <div id="mdtimer"> <div style="font-family: Electrolize,lolblogger,Tahoma; text-align: center;"> <b>Please wait...</b> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> <!-- 原倒计时JS代码保持不变 --> <script type="text/javascript"> //<![CDATA[ !function(a,b,c,d){function g(b,c){this.element=b,this.settings=a.extend({},f,c),this.settings.fontSize||(this.settings.fontSize=this.settings.radius/1.2),this.settings.strokeWidth||(this.settings.strokeWidth=this.settings.radius/4),this._defaults=f,this._name=e,this._init()}var e="countdown360",f={radius:15.5,strokeStyle:"#2E64FE",strokeWidth:d,fillStyle:"#8ac575",fontColor:"#2E64FE",fontFamily:"Electrolize,lolblogger,Tahoma",fontSize:d,fontWeight:700,autostart:!0,seconds:20,label:["second","Seconds"],startOverAfterAdding:!0,onComplete:function(){}};g.prototype={getTimeRemaining:function(){var a=this._secondsLeft(this.getElapsedTime());return a},getElapsedTime:function(){return Math.round(((new Date).getTime()-this.startedAt.getTime())/1e3)},extendTimer:function(a){var b=parseInt(a),c=Math.round(((new Date).getTime()-this.startedAt.getTime())/1e3);this._secondsLeft(c)+b<=this.settings.seconds&&this.startedAt.setSeconds(this.startedAt.getSeconds()+parseInt(a))},addSeconds:function(a){var b=Math.round(((new Date).getTime()-this.startedAt.getTime())/1e3);this.settings.startOverAfterAdding?(this.settings.seconds=this._secondsLeft(b)+parseInt(a),this.start()):this.settings.seconds+=parseInt(a)},start:function(){this.startedAt=new Date,this._drawCountdownShape(3.5*Math.PI,!0),this._drawCountdownLabel(0),this.interval=setInterval(jQuery.proxy(this._draw,this),1e3)},stop:function(a){clearInterval(this.interval),a&&a()},_init:function(){this.settings.width=2*this.settings.radius+2*this.settings.strokeWidth,this.settings.height=this.settings.width,this.settings.arcX=this.settings.radius+this.settings.strokeWidth,this.settings.arcY=this.settings.arcX,this._initPen(this._getCanvas()),this.settings.autostart&&this.start()},_getCanvas:function(){var b=a('<canvas id="countdown360_'+a(this.element).attr("id")+'" width="'+this.settings.width+'" height="'+this.settings.height+'"><span id="countdown-text" role="status" aria-live="assertive"></span></canvas>');return a(this.element).prepend(b[0]),b[0]},_initPen:function(b){this.pen=b.getContext("2d"),this.pen.lineWidth=this.settings.strokeWidth,this.pen.strokeStyle=this.settings.strokeStyle,this.pen.fillStyle=this.settings.fillStyle,this.pen.textAlign="center",this.pen.textBaseline="middle",this.ariaText=a(b).children("#countdown-text"),this._clearRect()},_clearRect:function(){this.pen.clearRect(0,0,this.settings.width,this.settings.height)},_secondsLeft:function(a){return this.settings.seconds-a},_drawCountdownLabel:function(a){this.ariaText.text(b),this.pen.font=this.settings.fontWeight+" "+this.settings.fontSize+"px "+this.settings.fontFamily;var b=this._secondsLeft(a),c=1===b?this.settings.label[0]:this.settings.label[1],d=this.settings.label&&2===this.settings.label.length,e=this.settings.width/2;d?y=this.settings.height/2-this.settings.fontSize/6.2:y=this.settings.height/2,this.pen.fillStyle=this.settings.fillStyle,this.pen.fillText(b+1,e,y),this.pen.fillStyle=this.settings.fontColor,this.pen.fillText(b,e,y),d&&(this.pen.font="normal small-caps "+this.settings.fontSize/3+"px "+this.settings.fontFamily,this.pen.fillText(c,this.settings.width/2,this.settings.height/2+this.settings.fontSize/2.2))},_drawCountdownShape:function(a,b){this.pen.fillStyle=this.settings.fillStyle,this.pen.beginPath(),this.pen.arc(this.settings.arcX,this.settings.arcY,this.settings.radius,1.5*Math.PI,a,!1),this.pen.fill(),b&&this.pen.stroke()},_draw:function(){var a=Math.round(((new Date).getTime()-this.startedAt.getTime())/1e3),b=3.5*Math.PI-2*Math.PI/this.settings.seconds*a;this._clearRect(),this._drawCountdownShape(3.5*Math.PI,!1),a<this.settings.seconds?(this._drawCountdownShape(b,!0),this._drawCountdownLabel(a)):(this._drawCountdownLabel(this.settings.seconds),this.stop(),this.settings.onComplete())}},a.fn[e]=function(b){var c;return this.each(function(){c=a.data(this,"plugin_"+e),c||(c=new g(this,b),a.data(this,"plugin_"+e,c))}),c}}(jQuery,window,document); //]]> </script> <br /> <div class="time_dir" style="text-align: center;"> <div id="container"> <div id="countdown"></div> <script charset="utf-8" type="text/javascript"> var countdown=$("#countdown").countdown360({radius:90,seconds:3,fontColor:"#ffffff",strokeStyle:"#ff8000",fillStyle:"#ff4000",autostart:!1,onComplete:function(){console.log("done"),$(".download_faster").show(),$(".dowen_bum").show(),$("#container").remove()}});countdown.start(); </script> </div> </div> <script type="text/javascript"> jQuery(document).ready(function(){var a=2;setInterval(function(){$("#mdtimer span").text(a--),0==a&&($("#makingdifferenttimer").delay(1e3).fadeIn(1e3),$("#mdtimer").hide(1e3).fadeOut('fast'))},1e3)}); $(document).ready(function(){ // 修改:从查询参数获取目标链接,替代原hash解析逻辑 var targetUrl = new URLSearchParams(window.location.search).get('url'); $("#url_qu").html('<center><b>👇 Product link ready 👇🏻</b><br><br></center><a href="'+targetUrl+'" style="font-size: 18px;font-family:Electrolize,lolblogger,Tahoma;line-height: 35px;font-weight: 600;color: #FFFF;background:#0000ff;display: table;padding: 0px 61px 0 61px;margin: auto;box-shadow: 0px 1px 1px rgba(255,255,255,0.35) inset, 0px 1px 1px rgba(0,0,0,0.3);border-radius: 30px;" rel="nofollow"><i class="fa fa-arrow-down" aria-hidden="true"/>Click Here</a>'); setTimeout(function(){window.location.href = targetUrl},11010); }); </script> <div><br /></div> </div> </div> <div style="text-align: center;"><center></center></div> </div> </div> </body> </html> `; } // 处理等待页请求 app.get('/', async (req, res) => { const targetUrl = req.query.url; let targetMeta = {}; if (targetUrl) { try { // 模拟爬虫UA,避免被目标网站拦截 const response = await axios.get(targetUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; FacebookBot/1.0; +http://www.facebook.com/externalhit_uatext.php)' } }); const $ = cheerio.load(response.data); // 提取目标页的og标签,没有则降级取普通meta或title targetMeta.title = $('meta[property="og:title"]').attr('content') || $('title').text(); targetMeta.description = $('meta[property="og:description"]').attr('content') || $('meta[name="description"]').attr('content'); targetMeta.image = $('meta[property="og:image"]').attr('content'); } catch (err) { console.error('获取目标页Metadata失败:', err); // 失败则使用默认Metadata } } // 渲染页面并返回 res.send(renderWaitPage(targetMeta)); }); app.listen(3000, () => { console.log('服务器运行在http://localhost:3000'); });
3. 关键注意事项
- 爬虫UA模拟:目标网站可能会拦截非爬虫的请求,所以要设置User-Agent为社交平台爬虫的UA,比如FacebookBot、Twitterbot等。
- 缓存机制:频繁请求目标页会被封,建议把提取到的Metadata缓存起来(比如用Redis),相同目标链接一段时间内直接用缓存内容。
- 链接合法性验证:要校验目标链接是否合法,防止恶意链接或XSS攻击。
- 静态页面限制:如果你的等待页是纯静态HTML(没有服务器端),那这个需求无法实现,因为客户端JS无法被爬虫识别,只能改用服务器端渲染方案。
内容的提问来源于stack exchange,提问作者ibrahim ramy
相关产品推荐
相关产品推荐

