分析
现象
雪球页面的地址如下:https://xueqiu.com/P/ZH3256684
通过移动设备打开的话,页面如下,能直接看到投资组合的信息。
但是,如果通过非移动设备打开的话,显示的是登录页面,而且可以看到页面地址也换了。
根据浏览器窗口大小?
第一个猜测,雪球是根据访问设备的浏览器窗口的大小,来展示不同的内容。
如果是这样的话,那么这件事情非常简单,通过iframe标签,嵌入雪球的页面,同时设置iframe标签的尺寸,设置为移动设备的大小。
可以直接拖动浏览器窗口的大小,验证我们的猜想。就像这样:
实证检验,发现不是。
根据浏览器的UA?
第二个猜测,雪球是根据浏览器的UA(User-Agent)。
通过Chrome浏览器,模拟移动设备,很容易验证。
确实是根据浏览器的UA。
给iframe指定UA
所以,第一个思路,给iframe指定UA。
实现
iframe标签:
1 2 3 <div style ="text-align:center" > <iframe id ="ZH3256684" src ="https://xueqiu.com/P/ZH3256684" scrolling ="no" style ="width: 330px; height: 500px;" frameborder ="0" /> </iframe > </div >
指定UA:
1 2 3 4 5 window .onload = function ( ) { let ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1" let ifr = document .getElementById('ZH3256684' ); ifr.contentWindow.navigator.userAgent = ua; }
window.onload
,是为了避免页面没加载完成,ifr
为空,或者ifr
还没有contentWindow
。
不可行的原因
但是,这种方法并不行,因为无法对跨域的iframe直接修改其navigator.userAgent
属性。
报错如下:
1 2 Uncaught DOMException: Blocked a frame with origin "http://localhost:4000" from accessing a cross-origin frame. at window.onload (http://localhost:4000/xueqiu:112:23)
基于nginx反代
所以,第二种方案,基于nginx反代。
具体的反向代理配置可以参考:《未分类【计算机】:一种基于nginx的反向代理策略》
但是,雪球禁止了通过nginx反代。
基于express转发
思路
最后,我直接通过Postman请求https://xueqiu.com/P/ZH3256684
,在请求的时候,配置Headers中的User-Agent
,发现可以拿到想要的HTML。
而且,这个HTML嵌入博客,可以正确的展示。
所以,接下来的关键是这个HTML得是实时获取的。
整体设计
基于node.js
的express
,写一个简单的接口。整体设计如下:
博客请求接口
接口请求雪球
注意,配置Headers中的User-Agent
。
雪球返回HTML报文给接口
接口返回HTML报文给博客
博客在前端展示
实现
如果是Get请求,可以直接通过iframe标签的src属性配置接口地址。
1 2 3 <div style ="text-align:center" > <iframe src =【接口地址】 scrolling ="no" style ="width: 100%; height: 500px;" frameborder ="0" /> </iframe > </div >
接口代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 const express = require ('express' )const https = require ('https' );const app = express()app.listen(9000 , () => { console .log('express server running' ) }) app.get('/get' ,function (req,res ) { let xqId = req.query.xqid let xqPath = '/P/' + xqId var options = { 'method' : 'GET' , 'hostname' : 'xueqiu.com' , 'path' : xqPath, 'headers' : { 'User-Agent' : 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Mobile Safari/537.36' , }, 'maxRedirects' : 20 }; var xqReq = https.request(options, function (xqRes ) { var chunks = []; xqRes.on("data" , function (chunk ) { chunks.push(chunk); }); xqRes.on("end" , function (chunk ) { var body = Buffer.concat(chunks); bs = body.toString() bs = bs.replace("</head>" ,"<script>window.onload = function () {let fid = document.getElementById('footer');if (fid){document.body.removeChild(fid);}let topbar = document.getElementById('top-bar');if (topbar){ document.body.removeChild(topbar);}let error404 = document.getElementById('error_404');if (error404){ error404.setAttribute('style', 'padding-top:0px');}}</script></head>" ) bs = bs.replace('href="/p/faq"' ,'href="/p/faq" target="_blank"' ) bs = bs.replace('href="/xz"' ,'href="/xz" target="_blank"' ) res.send(bs); }); xqRes.on("error" , function (error ) { res.send(error); }); }); xqReq.end(); }) app.get('/*' ,function (req,res ) { res.redirect('https://xueqiu.com' + req.path) })
部分代码解释
1 2 3 app.get('/*' ,function (req,res ) { res.redirect('https://xueqiu.com' + req.path) })
点击雪球页面后,对于来自雪球页面的请求,一律重定向回雪球。
1 2 3 4 5 6 7 8 xqRes.on("end" , function (chunk ) { var body = Buffer.concat(chunks); bs = body.toString() bs = bs.replace("</head>" ,"<script>window.onload=function(){let fid = document.getElementById('footer');document.body.removeChild(fid)}</script></head>" ) bs = bs.replace('href="/p/faq"' ,'href="/p/faq" target="_blank"' ) bs = bs.replace('href="/xz"' ,'href="/xz" target="_blank"' ) res.send(bs); });
拿到HTML报文后,修改代码。
移除底部下载APP的Footer 为没有target="_blank"
的a
标签,添加该内容
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。