学术应用使用node-http-proxy集成谷歌学术

学术应用使用node-http-proxy集成谷歌学术搞科研的同学肯定离不开谷歌学术,谷歌学术搜索是文献搜索下载一大利器。之前实验室开发了一款学术应用,遗留了历史问题,就是没有解决文献搜索的功能,而这个任务最后落在我的身上。我采用的方案就是集成谷歌学术,但是国内的网络环境,你懂的,自然状态下根本就访问不了谷歌学术的,你得翻墙才能访问。你不能期望使用你开发的学术应用都能翻墙访问谷歌学术(虽然搞科研的人电脑翻墙软件肯定都准备好了!),所以呢我还要给谷歌学术

搞科研的同学肯定离不开谷歌学术,谷歌学术搜索是文献搜索下载一大利器。之前实验室开发了一款学术应用,遗留了历史问题,就是没有解决文献搜索的功能,而这个任务最后落在我的身上。我采用的方案就是集成谷歌学术,但是国内的网络环境,你懂的,自然状态下根本就访问不了谷歌学术的,你得翻墙才能访问。你不能期望使用你开发的学术应用都能翻墙访问谷歌学术(虽然搞科研的人电脑翻墙软件肯定都准备好了!),所以呢我还要给谷歌学术搭建一个代理。不仅要集成谷歌学术一键搜索并下载,还要能导入和分享文献到自己开发的应用,获取文献的BIBTEX并导入到自己的学术应用中,就是要在代理中hack谷歌学术的原始响应,注入相关脚本。(关于BIBTEX可以去维基百科脑补一下。)
废话少说,放效果图过来先:
学术应用使用node-http-proxy集成谷歌学术
学术应用使用node-http-proxy集成谷歌学术

谷歌学术代理

谷歌学术代理默认采用的是node-http-proxy模块实现的,node-http-proxy的使用见博客用node-http-proxy搭建谷歌代理node-http-proxy的Github地址。有人读到这已经急,Talk is cheap, Show me the code! 好好,博主这就废话少说,放码过来。新鲜热乎的代码如下:

/** * Created by Jaye on 15/7/11. */
var config = require('./config');
var http = require('http');
var https = require('https');
var httpProxy = require('http-proxy');
var url = require('url');
var cookie = require('./cookie');
var util = require('util');
var modifyHtml = require('./modify_html');
var querystring = require('querystring');
var PROXY_PORT = config.proxyPort;


var proxy, server;
var cookieArr = [];
var hl = 'zh-CN';
var injected = "";

// 创建代理服务器
proxy = httpProxy.createProxy();

//处理error
proxy.on('error', function (err,req,res) { 
   
    res.writeHead(500, {
        'Content-Type': 'text/plain'
    });
    res.end('Something went wrong. And we are reporting a custom error message.' + err.message);
});

server = http.createServer(function (req, res) { 
   
    //载入需要注入的内容
    injected = fs.readFileSync('./inject.html', 'utf8');
    //解析语言,没有设置默认为zh-CN,重构时可以用url_auth来代替
    var query = querystring.parse(url.parse(req.url).query);
    if(query){
         hl = query.hl?query.hl:'zh-CN';
    }
    //todo:加入权限认证,调用url_auth中的urlAuth函数进行判断,如果auth为true放行,否则拦截 

   // var finalUrl = req.url,
    var finalUrl = 'https://scholar.google.com',
        finalAgent = null,
        parsedUrl = url.parse(finalUrl);

    if (parsedUrl.protocol === 'https:') {
        finalAgent = https.globalAgent;
    } else {
        finalAgent = http.globalAgent;
    }
    //
    proxy.web(req, res, {
        target: finalUrl,
        agent: finalAgent,
        headers: { host: parsedUrl.hostname,
        },
        prependPath:false,
        xfwd:true,
        hostRewrite:config.proxyHost+':'+config.proxyPort,//设置重定向地址,
        protocolRewrite: 'http'//设置重定向协议
    });
});

proxy.on('proxyReq',function(proxyReq,req,res){ 
   
    //如果不去掉这个头字段,浏览器报330错误,无法解码
    if(proxyReq._headers){
       if(proxyReq._headers['accept-encoding']){
           proxyReq._headers['accept-encoding'] = '';
        }
    }

});



/** * [在响应返回到客户端时,重写html并注入js脚本] * @param {[type]} proxyRes [description] * @param {[type]} request [description] * @param {[type]} response [description] * @return {[type]} [description] */
proxy.on('proxyRes',function(proxyRes,request,response){ 
   

    if(proxyRes.headers && proxyRes.headers[ 'set-cookie' ]){
       cookieArr =  cookie.parseGoogleCookies(proxyRes.headers['set-cookie']); 
       proxyRes.headers['set-cookie']=cookieArr;
    } 

    //inject js,rewrite html body
    if( proxyRes.headers &&
        proxyRes.headers[ 'content-type' ] &&
        proxyRes.headers[ 'content-type' ].match( 'text/html' ) ) {

        var _end = response.end,
            chunks,
            _writeHead = response.writeHead,
            _write = response.write;

        response.writeHead = function(){ 
   
            if( proxyRes.headers && proxyRes.headers[ 'content-length' ] ){
                response.setHeader(
                    'content-length',
                    parseInt( proxyRes.headers[ 'content-length' ], 10 ) + injected.length
                );
            }

            //不设置可能出现少量乱码
            response.setHeader( 'transfer-encoding', proxyRes.headers['transfer-encoding'] );

            // Disable cache for all http as well
            response.setHeader( 'cache-control', 'no-cache' );

            _writeHead.apply( this, arguments );
        };

        response.write = function( data ) { 
   
            if( chunks ) {
                chunks += data;
            } else {
                chunks = data;
            }
        };

        response.end = function() { 
   
            if( chunks && chunks.toString ) {
                _end.apply( this, [ modifyHtml( chunks.toString(),hl , injected) ] );
            }else {
                _end.apply( this, arguments );
           }    

        };
    }
});


console.log('listening on port ' + PROXY_PORT);
server.listen(PROXY_PORT);

代理的功能跟谷歌代理的动能类似,这里不再赘述。谷歌代理见博文用node-http-proxy搭建谷歌代理,下面我主要讲修改cookie来启用谷歌学术的设置功能,有设置功能的谷歌学术才够高大上!谷歌学术的设置是通过cookie来做的,需要完整的功能必须解决cookie的问题。我下面的例子是设置显示导入链接。谷歌学术的默认设置是隐藏导入链接的,需要到设置里勾选显示导入[BibTex、EndNote、RefMan、RefWorks]的链接,见下图:

  • 点击右上角我的著作引用情况右边的三角图标,选择设置
    学术应用使用node-http-proxy集成谷歌学术
  • 然后选择显示导入BibTeX的链接
    学术应用使用node-http-proxy集成谷歌学术
  • 点击保存后自动跳转到搜索结果,将会显示导入BibTeX(谷歌学术的原始显示)
    学术应用使用node-http-proxy集成谷歌学术
    修改谷歌学术的cookie技术原理挺简单的,我们在proxyRes返回之前对set-cookie进行解析并修改相关字段即可,对应以上代码:
if(proxyRes.headers && proxyRes.headers[ 'set-cookie' ]){ cookieArr = cookie.parseGoogleCookies(proxyRes.headers['set-cookie']); proxyRes.headers['set-cookie']=cookieArr; } 

我们再看看parseGoogleCookies函数做了什么事情,代码如下:


/** * 解析google scholar返回的cookie **/
    parseGoogleCookies: function (cookies) {
        // console.log(cookies);
        var cookieArr = [];
        if (cookies && cookies.length > 0) {
            for (var i = 0; i < cookies.length; i++) {
                var cookieItem = cookieUtil.parse(cookies[i]);
                if (cookieItem.domain) {
                 // delete cookieItem.domain;
                      cookieItem.domain = proxyHost;
                }
                if (cookieItem.path) {
                    cookieItem.path = '/';
                }
                var tempArr = [];
                for (var key in cookieItem) {
                    if (key === 'expires' || key === 'path') {
                        tempArr.push(key+'='+cookieItem[key]);
                    } else {
                        tempArr.push(key+'='+cookieItem[key]);
                    }
                }
                cookieArr.push(tempArr.join('; '));
            };
        }
        return cookieArr;
    }

如果不够熟悉http cookie,请到谷歌先脑补一下相关的知识,这里推荐一篇博客全面解读HTTP Cookie。这里选取有助于理解上面程序的两点。谷歌cookie的用途:Cookie也被用来记忆用户自定义的一些功能。用户在设置自定义特征的时候,仅仅是保存在用户的浏览器中,在下一次访问的时候服务器会根据用户本地的cookie来表现用户的设置。例如google将搜索设置(使用语言、每页的条数,以及打开搜索结果的方式等等)保存在一个COOKIE里。cookie的Domain and Path
的作用:定义Cookie的生效作用域,只有当域名和路径同时满足的时候,浏览器才会将Cookie发送给Server。如果没有设置Domain和Path的话,他们会被默认为当前请求页面对应值。下面简单讲解一下cookie的http实现:

以访问http://blog.noobsky.com为例

  • Step1.客户端发起http请求到Server
GET / HTTP/1.1
Host: blog.noobsky.com
(这里是省去了User-Agent,Accept等字段)
  • Step2. 服务器返回http response,其中可以包含Cookie设置
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name1=value1
Set-Cookie: name2=value2; Expires=Wed, 06 Jun 2066 18:18:18 GMT
(content of page)
  • Step3. 后续访问blog.noobsky.com的相关页面
GET /archives HTTP/1.1
Host: blog.noobsky.com
Cookie: name1=value1; name2=value2
Accept: */*

第一次访问服务器的适合,服务器返回的头字段里通过set-cookie设置cookie,后面再次访问服务器时,客户端会自动的带上相关的cookie字段。这里需要注意的是要想客户端自动带上相关的cookie字段,cookie的domain和path字段必须要跟你访问的server服务。比如你在第一次访问blog.noobsky.com的时候通过技术手段把服务器response中的set-cookie中的domain域(默认值为blog.noobsky.com)修改为coolshell.cn的话,你下次再访问blog.noobsky.com的相关页面时,将不会带上相关的cookie,因为cookie的domain不匹配。同理通过访问代理然后代理访问scholar.google.com时,scholar.google.com服务器返回的response的set-cookie中的domain域的值是scholar.google.com,下次再访问代理时,因为domain域不匹配,将不会自动带上相关的cookie,所以我们在proxyRes返回给客户端之前,需要hack掉相关的cookie域,我们只需把cookie的domain域修改为proxyHost(代理主机),path域设置为'/'

if (cookieItem.domain) {
    cookieItem.domain = proxyHost;
}
if (cookieItem.path) {
    cookieItem.path = '/';
}

下次再访问代理主机时,因为domain域匹配,就会自动带上相关的cookie,谷歌学术就能实现记忆用户自定义的功能啦,你就可以随心所欲使用谷歌学术的设置功能!

除了能使用设置功能我们还需要把谷歌学术的默认显示导入BibTeX更改为导入我的网站,并修改默认的点击事件。默认的点击事件是跳转另一页面显示相应地BibTeX,我需要的点击事件是获取BibTeX数据,并推送给我的学术应用,cool!方法很简单:利用cheerio库修改response的html中导入BibTeX,然后注入相关的js脚本,js脚本修改默认点击事件!
* 通过重写response.end函数hack返回的html页面

response.end = function() { 
   
    if( chunks && chunks.toString ) {
        _end.apply( this, [ modifyHtml( chunks.toString(),hl , injected) ] );
    }else {
        _end.apply( this, arguments );
    }    

};
  • 修改html的函数如下:
function(str,lang,inject){
    $ = cheerio.load(str);
    var str = config.zhStr;
    if(lang.toLowerCase()=='en'){
        str = config.enStr;
    }
    //修改显示文本
    $(".gs_nta.gs_nph").each(function(i,elem){
        $(this).text(str);
    });
    str = $.html();
        // Add or script to the page,注入脚本
    if( str.indexOf( '</body>' ) > -1 ) {
        str = str.replace( '</body>', inject + '</body>' );
    } else if ( str.indexOf( '</html>' ) > -1 ){
        str = str.replace( '</html>', inject + '</html>' );
    } else {
        str = str + inject;
    }

    return str;
}
  • 注入的js脚本
<script type="text/javascript"> $(document).ready(function () { 
     $(".gs_nta.gs_nph").each(function () { 
     $(this).bind("click", getData); }); function getData() { 
     var pdfUrl; if ($(this).parent().parent().prev(".gs_ggs.gs_fl")) { pdfUrl = $(this).parent().parent().prev(".gs_ggs.gs_fl").children("div.gs_md_wp.gs_ttss").children("a").attr("href"); } var path = $(this).attr("href"); $.ajax({ type: "get", url: path, async: false, success: function (data) { 
     alert("获取BibTex成功!" + data + "pdfUrl:" + pdfUrl); }, error: function () { 
     alert("获取BibTex失败!"); } }); return false; } }); </script>

这里需要注意的是,推送BibTeX到自己的学术应用中存在跨域问题,这里采用JSONP的方式。激动人心的时刻来了,我们来看一下最终的效果

  • hack原始html后的显示
    学术应用使用node-http-proxy集成谷歌学术
  • 修改后的点击事件
    学术应用使用node-http-proxy集成谷歌学术
    详细代码见Github上的google-scholar-proxy

本文链接:http://blog.noobsky.com/2015/11/25/学术应用使用node-http-proxy集成谷歌学术/

–EOF–

今天的文章学术应用使用node-http-proxy集成谷歌学术分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/26141.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注