2019爬虫项目总结——我在项目中踩的那些坑

2019刚出校门,初到公司,最大的成就是完成了一个全球抓取数据的系统!简单介绍一下这个项目的实现思路以及在项目中踩过的坑,随时告诫自己以后尽量避免!

历时一个半月还多几天,终于通过交付测试了!项目是从全球范围内,通过Google,Bing,雅虎来抓取数据,由于服务器不是特别的好,并且考虑到会有很多的脏数据,我们在实现的时候并没有将数据入库,想要将数据进行持久化的保存,可以使用导出的方式将数据以电子表格的方式导出来!

难点一:

项目经历了一次大的改版,将整个的抓取流程做了一次很大的改动。我们在抓取数据的时候,需要通过关键词搜索出对应的网页url链接,再访问链接深入到官网去抓取我们想要的数据,之后再将抓取到的数据动态的拼接到界面上对应的url链接的后面。因为数据并不能保存到数据库进行持久化存储,并且抓取到的url是一个集合,所以,如何在抓取到url之后还能深入到链接里面去抓取数据就成了一个难点问题!

在大牛的帮助下,我们找到了一种实现思路:

通过页面的开始按钮,给服务端发送一个ajax请求开始抓取数据,数据抓取完之后,将数据返回绑定到界面,给每一个url规定一个唯一的标识,紧接着调用第二个ajax循环遍历抓到的数据,发送请求进行深度抓取,将抓取到的数据返回之后根据标识拼接到对应的url后面,完成一次抓取。

难点二:

这样第一个难点问题就解决了,虽然这个问题解决了,但是如果我们想要中途停止怎么办?这毕竟是一个网站,在抓取数据的时候,我得时时刻刻的开着这个网站,当我感觉数据量差不多了,我不想再抓了,我想将现有的数据导出来,我要停止抓取,然后再导出数据,那么,我要怎么停止呢?这是碰到的第二个难点问题!

为什么这个会是一个难点问题呢?因为我在实现循环遍历的时候一次性将所有的url都遍历完了,有多少条url就发送了多少条的ajax请求。因为ajax是异步的,所以,虽然界面不会因为发送很多的ajax而出现卡死状态,但是请求我已经发送完了,接下来的数据返回就不是我能干预的了,这就是问题点所在。我也尝试当点击停止的时候将返回的数据隐藏掉,表面上看起来是抓取的操作被关闭了,但是,当用户进行第二次搜索操作的时候,由于第一次的搜索并没有结束,所以第二次的搜索会变得很慢。这并不是一种很好的解决方案。

后来通过浏览器的Network发现,既然每个请求都有一个name属性,那么我能不能通过name属性来获取到每一条请求呢?既然成功的状态码是200,那么我能不能通过浏览器告诉服务器,你不要给我返数据了,我不要了。答案当然是可以的了。

<script src = "jquery.js"></script>
<script>
var xhr = $.ajax({type:‘POST‘,
    url:‘b.php‘,
    data:‘‘,
    success:function(){
        alert(‘ok‘);
    }
})
alert(xhr);

console.log(xhr);
</script>
<button id="song">abort</button>
<script>
$(function(){
    $("#song").click(function(){
        alert(‘click‘);
        xhr.abort();
    })
})
</script>

我们在创建ajax请求的时候,将这个ajax赋值给一个变量,然后,我们将在需要的地方,使用这个变量,调用abort方法就可以将未响应(padding)的请求关闭掉了。

当然这种写法只能是将一个ajax请求关闭,我们需要的是批量的关闭,有什么办法吗?

有的,定义一个数组,将所有的ajax,push到里面,然后在点击停止的时候,遍历数组,关闭掉所有的请求!

//定义一个ajax数组
var ajaxArr = [];

//深度访问的函数
function requestPlus(id, url) {
    //获取界面上的ID和网址
    //1.获取所有的tr
    //Ajax进行深度搜索
    if (typeof (url) != undefined) {
        req++;
        $(".logo_div").show();
        //将同步请求变成异步请求,之后所有的ajax都会变成异步请求,设置这个的目的是因为后面有将异步设置为同步,防止点击同步之后再做此功能变成同步,影响速度
        $.ajaxSettings.async = true;
        currentAjax = $.ajax({
            cache: false,       //缓存
            dataType: ‘json‘,
            type: ‘Post‘,        //传参方式
            url: "/home/DepthQuery",  //传到哪里去,调用后台的程序将参数传递过去,通过路由的形式传递
            data: { ‘Id‘: id, ‘url‘: url },
            success: function (data) {
                if (data == null) return;
                var _data = data.Msg;
                //            var html = "<table>";
                var html = "";
                if (_data.length > 0) {
                    for (var i = 0; i < _data.length; i++) {
                        //将取出来的Id与页面上的Id搜索匹配,直接将ID拼接上就可以了
                        var state = $("#hid").val();
                        if (state != "false") {
                            //                            html += "<td>" + _data[0].facebook + "</td>" + "<td>" + _data[0].twitter + "</td>";
                            //获取到facebook,和twitter
                            var facebook = _data[0].facebook;
                            var twitter = _data[0].twitter;
                            var Email = _data[0].Email;
                            var phone = _data[0].Tel

                            $("#" + _data[0].Num + "").children(".facebook1").text(facebook);
                            $("#" + _data[0].Num + "").children(".mailbox1").text(Email);
                            if (Email) { //typeof(Email)!=undefined
                                $("#email").append(Email + ",")
                            }
                            $("#" + _data[0].Num + "").children(".Recom_num1").text(twitter);
                            $("#" + _data[0].Num + "").children(".phone_num1").text(phone);
                        }
                    }
                }
                req--;//标识减一
            },
            error: function () {
                req--;
            }
        });
        ajaxArr.push(currentAjax);
    }
}

//关闭请求
function abortAjax() {
    $.each(ajaxArr, function (i, a) {
        a.abort();
    });
}

这样就实现了批量关闭请求的功能。到此,一个完整的抓取流程就完成了,但是,这样还是存在问题的。

问题一:响应速度过慢

这样虽然实现了功能,但是在实测当中,我们发现数据的返回速度特别慢,到最后时候一条数据能返回十五六分钟!这也是项目中最大的难点,怎么将速度提上去?对,没错,使用线程就可以了,这也是为什么会有一个很大的改版原因。

先说这种实现方式速度慢的原因,也是我最想记录下来的:

我们一开始怀疑是接口返回的慢,导致了整个速度变慢,但是我们通过压力测试,接口的返回速度并不慢,所以,问题到底在哪呢?Ajax发送请求完毕之后就跟它没关系了,对吗?Ajax也是异步请求的,那么数据返回结果到页面的速度也是互不影响的,那么问题在哪?

我们应该知道,js是脚本语言,它是单线程的,单线程怎么能实现异步呢?按道理来讲,异步是基于多线程来实现的,那为什么js是单线程的,却可以实现异步呢?

通过查阅资料,我了解到:Ajax实现异步是通过定时器加队列的方式实现的。就是说,Ajax的异步还是依托单线程来实现的(通过定时器实现异步),每发送一个异步请求,就将这个请求加到一个队列里面去,当有多个异步数据返回的时候,将返回的数据先放到队列里面,等待线程空闲的时候来取数据然后做后续的操作,这就是出现数据慢的原因,不是抓数据满,是绑定数据慢。接下来的改版方式很简单了,把请求扔到后台代码中用多线程做处理就可以了。下一篇再记录任务(Tesk)的相关总结。

参考博客:https://www.cnblogs.com/hutuzhu/p/4301751.html

原文地址:https://www.cnblogs.com/Xiaomoml/p/12227455.html

时间: 2024-08-18 03:28:53

2019爬虫项目总结——我在项目中踩的那些坑的相关文章

使用ffmpeg视频编码过程中踩的一个坑

今天说说使用ffmpeg在写视频编码程序中踩的一个坑,这个坑让我花了好多时间,回头想想,很多时候一旦思维定势真的挺难突破的.下面是不正确的编码结果: 使用ffmpeg做视频编码过程中,首先要新建数据帧,并为数据帧分配相应内存,以便于保存图像数据,为数据帧分配内存需要用到av_image_alloc()这个函数,该函数将根据传入的图像宽.高.图像格式.数据对齐基数等参数进行内存分配. 这其中有一个参数可能会让人迷惑,那就是数据对齐基数这个参数该设置多少?顺便说说为什么要数据对齐,之所以要对齐,主要

ng-zorro-antd中踩过的坑

ng-zorro-antd中踩过的坑 前端项目中,我们经常会使用阿里开源的组件库:ant-design,其提供的组件已经足以满足多数的需求,拿来就能直接用,十分方便,当然了,有些公司会对组件库进行二次封装,改造成极具自家风格的产品. 在本系列的文章中,不谈高大上的东西,不深究底层源码,只分享一些项目中遇到的小问题. 表格(table) --师傅以为是组件库的bug,没想到-- 不知道正在阅读本文的读者有没有经历过: 在使用 <nz-table></nz-table> 的时候,删除表

Android 开发中踩过的坑之十一: 团队协作的坑

工作中,android的坑很多,一部分是android源码自身的逻辑陷阱, 但跟多的是自己和同事们由于种种原因埋下的坑. 提高面向对象的能力,并恰当的实现在代码中,能够极大的减少坑人和被坑几率. 面向对象的最大特征是复用, 复用的目的是减少工作量,减少错误几率,提高工作效率. 总结几个自己的体会,以自勉: 1 在编码前明确代码的模块框架, 定义最简单的接口. 有人也许说, 书生造反,十年不成, 在工期紧张或者其他类似敏捷编程的大背景下, 先干起来才是硬道理. 事实上, 工期紧张也许正是因为之前的

ionic2+angular2中踩的那些坑

好久没写什么东西了,最近在做一个ionic2的小东西,遇到了不少问题,也记录一下,避免后来的同学走弯路. 之前写过一篇使用VS2015开发ionic1的文章,但自己还没摸清门道,本来也是感兴趣就学习了一下.后来看到TypeScript,觉得这个真不错,强类型,有点类似c#的感觉,而且如果写错了编辑器都可以感知出来,于是就开始看ionic2.ionic2是基于angular2的,语法跟以前有了很大的变化.但自己写原生app写惯了,反而觉得这种方式更方便一些.每个页面都是一个组件,组件里也可以套组件

Android 开发中踩过的坑之九: 发布一个aar的注意事项

现在Android支持aar格式发布一个模块, 提供给其他人使用. aar其实是jar和一些资源文件的zip包. 解决了过去jar包不能分享资源的局限. 1 要尽量避免定义内部接口, 这其实是一个编程习惯, 接口interface最好是独立定义, 避免定义在类的内部. 因为当你发布aar时, 内部的接口在混淆后会独立成一个外部的接口Outer$InnerInterface. 然后麻烦来了, 别人在实现这个类的时候必须也写成XXX implement Outer$InnerInterface{}的

Spring Data JPA中踩过的坑

说老实话,Spring Data JPA很好用,上次使用还是2013年,当时只是完成Java Bean和数据库中表的映射. 最近想起来用Spring Data JPA的起因是手头有一个项目,源代码是用原生SQL+JDBC实现的,在第一次部署时要初始化数据库,还hardcode了很多数据库配置参数.正好最近有空,就打算用Spring Boot Data JPA(spring-boot-starter-data-jpa)改造一下,仔细看了一下源代码发现和几年前已经天差地别,如果你的业务逻辑不是特别复

Android 开发中踩过的坑之十: 谨慎处理动画的开始和结束

Android提供多种动画机制, 从面相对象的方式到直接实现底层onDraw的方式, 给予了足够的控件实现希望的效果, 无论时使用哪种方式实现动画, 都要谨慎的处理动画的两个状态, 开始和结束 需要关注的问题有: 1 动画开始的时候是否需要重新初始化内存? 对于需要重复展示的动画, 应当避免每次都new新的内存, 否则在动画展示过程, 内存会不断增加, 而gc合适回收, 是不确定的. 也许当gc时, 你已经在OOM的边缘了. 2 动画开始时是否是从某个中间状态开始的? 动画从某个中间状态开始,

Android 开发中踩过的坑之七:尽量避免使用Acitivity当做Context

这坑容易埋, 却不容易发现. 比如启动一个页面, 需要用到一个单例的工具类Utils, 初始化Utils需要一个Context参数, 直接传入Activity.this. 然后这个单例的Utils就会一直持有Activity.this, 导致整个Acitivity不能被GC. 而如果代码中大量的使用Utils, 又不能确认到底谁最先初始化了Utils, 使得内存泄露成了灵异事件难以发现. 所以, 正确的使用方式是: activity.this.getApplicationContext(); g

Android 开发中踩过的坑之六:几个关于View的tips

这几个点, 不算是坑, 但是也确实浪费了我一些时间 1.ScrollView的高设置成"wrap_content"会缩的很小,  ScrollView内只允许嵌套一个View, 并且不要将他的高度设置为"wrap_content", 否则它会缩小到很短的样子. ListView也是一样. 2.ListView中的Item如果有不同的样式,最好使用getItemViewType()来区别 事实上, 在ListView的Item完全可以用一种View布局来控制Visia