背景:
做一个Chrome的TamperMoney脚本,采集超星慕课全部学校:http://passport2.chaoxing.com/login?fid=145&refer=http://i.mooc.chaoxing.com。
这是一个二级联动的,左侧是城市、右上是26个字母,右下是要采集的学校列表,想要一次性把全部数据采集下来,必须不断点击城市和字母进行切换,数据是通过ajax异步加载后渲染到页面的。
总的来说是希望前端抓取到学校的名称和ID,用$.ajax传到自己写的ashx的方法中,再存到数据库里。
坑一:首先遇到的问题就是这样采集的数据由于跨域问题,无法$.ajax到localhost:1066/r.ashx?method=a 中,解决方式是使用JSONP
$.ajax({ url: "http://localhost:1066/handle/r.ashx?method=updateChaoXingSchool", type: "get", dataType:"jsonp", //使用jsonp jsonp:"jsonpCallback", jsonpCallback:"success_jsonpCallback", data: {schoolId:id,name:name,letter:lt}, success:function(result){ csl++; }, error:function(data){ } });
后端context.response.write也必须和jsonpCallback中制定的方法名一致才行,里面的内容倒是无关紧要:
context.Response.Write("success_jsonpCallback({"result":true})");
思路一:使用$.each嵌套循环来做,但是$.each是异步的,而JSONP也是异步的且无法强制设定为同步,就算是可以同步,也会阻挡网页导致点击事件失效,这样切换城市和字母又无法实现了。
思路二:使用for循环+sitetimeout,因为for循环是同步方法,settimeout可以控制$.ajax的运行间隔。 用过才深刻理解了setTimeout是个异步方法,换用了各种方式设定setTimeout(如i*1000)效果也非常差,即使顺序和内容采集到是对的,也会导致浏览器卡顿崩溃。
思路三:前面的尝试让我彻底放弃了使用循环来实现,改变使用定时器SetInternal实现:定时监控整个网页的情况,来判定如何执行采集。原理:
1、点击启动按钮:获取城市对象集合、当前学校对象集合;设定初始值,如起始城市、字母、学校的 Inex值。
2、设定定时器A,每0.1秒采集当前页面的学校,当前学校Index自增
3、if(当前学校Index==当前页面总学校数)
then
清除定时器A,避免在切换字母的过程中继续采集
点击切换下一个字母
else
采集学校数据……
4、 设定定时器B
5、
if(当前字母Index==26)
then
清除定时器B,避免在切换城市的过程中继续采集
点击切换下一个城市
else
采集学校数据……
最终代码:
// ==UserScript== // @name 超星采集学校 // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match http://passport2.chaoxing.com/* // @require http://code.jquery.com/jquery-1.10.0.min.js // @grant none // ==/UserScript== //当前总 var cl=$(".zw_s_li a").length; var ll=26; var sl=$(".zw_m_li a").length; //当前进度 var ccl=0; var cll=1; var csl=0; //当前对象 var city=$(".zw_s_li a"); var school=$(".zw_m_li a"); var letter=$(".zw_m_t_li a"); var timerA; var timerB; function getSchool(){ if(cll>ll){ clearInterval(timerB); //如果字母都过完了,就换城市 ccl++; cll=1;//重置字母 $(city[ccl]).click(); setTimeout(function(){ $(letter[1]).click(); timerB=setInterval(function(){ getSchool(); },1000); },2000); } else if(ccl>cl){ clearInterval(timerB); return; } else if(csl==sl){ //如果学校采集完了,就换字母 clearInterval(timerB); cll++; console.log($(letter[cll]).attr("id")); $(letter[cll]).click(); setTimeout(function(){ school=$(".zw_m_li a"); csl=0; sl=school.length; timerB=setInterval(function(){ getSchool(); },1000); },2000); } else{ //否则一直采集学校 console.log(ccl+"_"+cll+"/"+ll+"_"+csl+"/"+sl); var id=$(school[csl]).attr("id"); var name=$(school[csl]).attr("name"); var lt=$(letter[cll]).attr("id"); csl++; insertSchool(id,name,lt); getSchool(); } } function insertSchool(id,name,lt){ $.ajax({ url: "http://localhost:1066/handle/r.ashx?method=updateChaoXingSchool", type: "get", dataType:"jsonp", jsonp:"jsonpCallback", jsonpCallback:"success_jsonpCallback", data: {schoolId:id,name:name,letter:lt}, success:function(result){ }, error:function(data){ } }); } (function() { $(".zw_m_box").before("<input type='button' value='采集' style='200px;height:35px;text-align:center;font-size:16px;' id='caiji'>"); $("#caiji").click(function(){ cl=$(".zw_s_li a").length; sl=$(".zw_m_li a").length; city=$(".zw_s_li a"); school=$(".zw_m_li a"); letter=$(".zw_m_t_li a"); getSchool(); }); })();