浏览器爬虫数据采集可视化解析实现之单页面的可视化解析

1 前言

在进行数据采集时,也即我们说的爬虫,抓到数据后通常需要进行解析。在PC端通常需要通过写代码进行解析。对于抓取业务较简单的场景,重复写代码完成这样的工作十分耗时。我们希望通过抓取到页面数据后,在页面上进行点选目标元素,就可以直接获得需要的数据,这样提高工作效率。我们在市面上见到的工具有Portia及八抓鱼等工具即通过选择的形式实现解析。本文将介绍实现的主要原理。只要明白了这些原理,其余类似Portia及八抓鱼的复杂解析功能也就很容易做了。最终效果如下,根据生成的解析代码,数据采集和解析可一步完成。
在这里插入图片描述
在这里插入图片描述

2.功能分析

为了实现点击选择解析的效果,需要获得document对象。而我们直接通过iframe展示可点选解析的页面会存在跨域请求问题。因此这个工具通过前端与后端两部分组成。前后两端需要实现的能力如下。

后端

  • 接收前端http请求。
  • 从http请求中解析出访问的目标网站地址,访问目标网站,最终返回结果。

前端:

  • 点击元素,并生成css选择器。
  • 元素变色,鼠标移到元素上或从元素上移开后元素的背景色会发生变化。当交替单击元素时,元素的背景色也会发生变化。
  • 生成解析数据,单击元素后生成要提取的数据。
  • 生成解析代码,单击元素后生成可解析目标html页面的代码。
  • 元素名称及选择器可编辑,方便自定义修改。

3.实现分析

根据2对功能的分析,为了快速实现演示,我们会使用到如下技术。

  • nodejs,服务端转发http请求
  • vue,实现前端数据双向绑定及计算属性的生成。
  • medv/finder,用来实现css选择器的生成。
  • browserify,将nodejs模块转换为html页面可使用的模块。

3.1 服务端实现

服务端在中功能较简单,监听一个端口,转发http请求。

const request = require('request');
const app = require('express');
const router = app.Router();
const jsdom = require("jsdom");
const {JSDOM} = jsdom;
var iconv = require('iconv-lite');

router.get('/proxy', (req, res) => {

    let requestUrl = req.query.urltofetch;

    request({
        url:requestUrl,
        encoding: null
    },(error,response,body) => {
        if(!error && response && response.statusCode === 200) {
            let encoding = response.headers["content-type"].split(";")[1].split("=")[1];
            let buf = iconv.decode(body,encoding).toString();
            let outHTML = new JSDOM(buf).window.document.documentElement.outerHTML;
            res.status(200).end(outHTML);
        } else {
            res.status(400).end(body);
        }
    });

3.2 前端实现

3.2.1 css选择器生成

我们会用到@medv/finder,安装后通过browserify转换为html页面可使用的模块。browserify转换时使用了-r参数。

npm install @medv/finder
browserify -r through  > finder.js

随后在页面引入即可使用。

<script src="./finder.js"></script>

可以通过下面的代码看到点击生成选择器的效果。

document.addEventListener("click",e => {
    const selector = finder(event.target);
    console.log(selector);
});

在这里插入图片描述

3.3.2 鼠标移动元素变色

鼠标移到元素上或从元素上移开后元素的背景色会发生变化。因此涉及到2个事件的处理onmouseover与onmouseout。

onmouseover与onmouseout事件仅影响背景色变化。

    let iframeWindow = window;
    let lastTarget = null;
    let currentTarget = null;
    iframeWindow.onmouseover = function(e) {
      currentTarget = e.target;
      if (currentTarget !== lastTarget && !currentTarget.clicked) {
        currentTarget.style.backgroundColor = '#56121745';
        if (lastTarget && !lastTarget.clicked) { 
            lastTarget.style.backgroundColor = null;
        }
      }
      lastTarget = currentTarget;
    };

    iframeWindow.onmouseout = function(){
      if (lastTarget && !lastTarget.clicked) lastTarget.style.backgroundColor = null;
    };
() => {
      if (lastTarget && !lastTarget.clicked) lastTarget.style.backgroundColor = null;
    }
() => {
      if (lastTarget && !lastTarget.clicked) lastTarget.style.backgroundColor = null;
    }

3.3.3 鼠标单击的处理

交替单击要改变背景色,同时要生成解析后的数据与解析的代码。我们vue来保存这些数据,并与表单元素相绑定。此外当在输入框中输入网页地址点击访问后还要产生请求。

我们vue来保存这些数据,并做表单数据绑定及响应。此外增加了fetch方法,当单击按钮时触发,向后端发起http请求。computed是计算属性,当我们保存的选择器信息发生变化时,会生成响应的代码。

    appVm = new Vue({
        el: '#app',
        data: {
            selectedEl: {}
        },
        methods: {
            del: function (value) {
                value.el.click();
                value.el.style.backgroundColor = null;
            },
            fetch:function(e) {
                let url = document.getElementById("urlToFetch").value;
                let req = `http://${location.host}/chrome?urltofetch=${url}`;
                axios.get(req).then(response => {
                    writeHtmltoIframe(response.data,url);
                }).catch(error => {
                    alert(error);
                });
            }
        },
        computed:{
            selectedElExpansion:function(){
                let expansions = [];
                for(key in this.selectedEl) {
                    let value = this.selectedEl[key];
                    expansions.push(`"${value.name}":document.querySelector("${value.selector}").innerHTML`);
                }
                return "{" + expansions.join(",") + "}";
            }
        }

为了展示单击产生的数据我们需要一个简单的html页面。在html页面中我们将通过vue的双向绑定和计算属性功能,来展示对应的数据。

<head>
    <meta charset="UTF-8">
    <title>parse</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="./finder.js"></script>

</head>
<body>
<div id="app">
    <h1>生成请求</h1>
    <p1>{{selectedElExpansion}}</p1>
    <hr />
    <h1>在线解析</h1>
    <input id="urlToFetch"
           type="text"
           style="width:70%;"
           placeholder="URL e.g. https://www.baidu.com" />
    <input id="fetch" type="button" value="采集" v-on:click="fetch"/>
    <div v-for="(value,key) in selectedEl">
        <input v-model="selectedEl[key].name"/>
        <input v-model="selectedEl[key].selector" />
        {{value.el.innerText}}
        <label v-on:click="del(value)">delete</label>
    </div>
    <iframe id="browser" scrolling="yes" title="onlineParser"
            sandbox="allow-forms allow-scripts allow-same-origin allow-popups"
            style="width: 70%; height: 500px;"></iframe>
</div>

接着处理鼠标单击元素的事件。当单击后生成选择器,并将元素对象与选择器保存在vue中。为什么这里我会参杂原生Js和Vue的写法。因此学习开发这个功能时,Vue是后学会的。所以这个demo没全部用Vue写。而且现在很晚了,暂时不想改了。

    String.prototype.hashCode = function () {
        let hash = 0, i, chr;
        if (this.length === 0) return hash;
        for (i = 0; i < this.length; i++) {
            chr = this.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0; // Convert to 32bit integer
        }
        return hash;
    };
    
        iframeWindow.onclick = function(e) => {

            if(appVm.$data.clickPicked !== "parse") {
                return;
            }

            const {target} = e;
            e.stopPropagation();
            e.preventDefault();

            // 记录当前目标是否被点击过
            const selector = finder(target, {
                root: iframeDocument
            });

            if (target.clicked) {
                target.clicked = false;
                appVm.$delete(appVm.$data.selectedEl, selector.hashCode());
            } else {
                target.clicked = true;
                let element = iframeDocument.querySelector(selector);
                element.selector = selector;

                let selectInfo = {
                    name:selector.hashCode(),
                    el:iframeDocument.querySelector(selector),
                    selector:selector
                };

                appVm.$set(appVm.$data.selectedEl, selector.hashCode(),selectInfo);
            }
        }

单击时,为选择的文本生成一个默认名称,随后使用者可通过文本框对这个名称进行编辑。

最后就剩下了当向后端发出请求后返回,返回的页面数据展示在iframe中。当我们接收到数据时,打开新的文档,将数据写入即可。Object.freeze是防止某些网站直接就通过location重定向,导致当前页面不可解析。

    function writeHtmltoIframe(html,url) {
        const browserIframe = document.getElementById("browser");
        const iframeWindow = browserIframe.contentWindow;
        const iframeDocument = iframeWindow.document;

        iframeDocument.open();
        Object.freeze(iframeWindow.location);
        iframeDocument.write(`<base href="${url}" target="_self">`);
        iframeDocument.write(html);
        // 未关闭文档输出流的情况下浏览器会提示一直处于加载中
        iframeDocument.close();

这样一个简单可视化解析功能就实现了。其它的复杂功能,如多选元素,分类选择也是原理也是类似的。

4. 总结

本文介绍了PC数据可视化解析的基本原理,只要了解这些,其它更进一步的东西自然也就明白了。当然基于iframe的可视化解析还是存在很多问题。但通过iframe可以简化问题,使我们学习实现原理。

5.参考

[1]vue api data,https://cn.vuejs.org/v2/api/#data
[2]vue服务端渲染,https://ssr.vuejs.org/zh/
[3]browserify,https://javascript.ruanyifeng.com/tool/browserify.html#toc1
[4]require(’./expample.js).default详解,https://www.cnblogs.com/cangqinglang/p/10445256.html
[5]express-static.express静态资源管理中间件
[6]axios,前端https库,https://www.kancloud.cn/yunye/axios/234845
[7]clipboard设置数据,https://stackoverflow.com/questions/23211018/copy-to-clipboard-with-jquery-js-in-chrome,
[8]clipboard.js,https://clipboardjs.com/
[9]jsdom,https://github.com/jsdom/jsdom

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值