avatar


雪球页面嵌入博客的方案

《我在雪球的投资组合》

相关的投资组合已经被删除,但依旧是一个雪球页面嵌入博客的例子。

分析

现象

雪球页面的地址如下: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.jsexpress,写一个简单的接口。整体设计如下:

  1. 博客请求接口
  2. 接口请求雪球
    注意,配置Headers中的User-Agent
  3. 雪球返回HTML报文给接口
  4. 接口返回HTML报文给博客
  5. 博客在前端展示

实现

如果是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');

// 创建Web服务器
const app = express()

// 启动,监听8080端口
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标签,添加该内容

关于node.js中的express,可以参考《基于JavaScript的前端开发入门:3.Node.js》的"express"部分,而且可以利用基于阿里云的Serverless,这样会便捷。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/19911
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区