一、起因:
昨天有个打算,爬个中小学试卷的数据,但是发现这个网站比我之前爬的数据难分析,到不是因为页面数据难分析,我要爬的页面数据很简单,到时URL地址把我难到了!
首先页面的URL地址符是这样子的(ps:真实页面地址就不贴了,毕竟不是什么光彩的事情):
主地址:域名用:http://hmy666.cn代替
http://hmy666.cn/' + 【学年阶段】 + '/' + 【课程】 + '/' + 【年级】 + '/1/' + 【数字】 + '.htm'
首先:
学年阶段的可选值为:高中,初中,小学;
课程的可选值为:语文,数学,英语,政治,历史,地理,生物,化学,物理;
年级的可选值为:一年级,二年级,三年级,四年级,五年级,六年级;
我的目的是要爬取8888页的数据;
开始分析:
如果单纯是页面的URL地址栏数字变化而已,那么爬取的是易如反掌,我只要弄个for循环,然后地址栏数字相应增加就好了,这个也是我爬过最多的一种方式。但是这个页面的地址符有四个可选值,相应的可选值变化又是随机的,举个例子:
http://hmy666.cn/' + 【小学】 + '/' + 【数学】 + '/' + 【1年级】 + '/1/' + 【10】 + '.htm'http://hmy666.cn/' + 【小学】 + '/' + 【英语】 + '/' + 【3年级】 + '/1/' + 【18】 + '.htm'http://hmy666.cn/' + 【初中】 + '/' + 【数学】 + '/' + 【1年级】 + '/1/' + 【104】 + '.htm'http://hmy666.cn/' + 【高中】 + '/' + 【化学】 + '/' + 【3年级】 + '/1/' + 【1005】 + '.htm'http://hmy666.cn/' + 【小学】 + '/' + 【语文】 + '/' + 【1年级】 + '/1/' + 【1152】 + '.htm'
(1)URL地址符的解决方案:
首先上面都是随机性的,不过还好都是可选范围的随机,页面的id的幸亏也是可选范围的随机,不然我真的没办法做了,如果来个编码,把它加密,我就肯定选择放弃,虽然也可以做,就是用相应的编码去解密,不过我还没试过,先不乱说了。
所以我对应的开始制作可选范围的数组,设计如下:
//中文数组用于生成标签,拼音数组用于生成urllet Class = ['小学', '初中', '高中'] let grade = ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级']; let subject = ['语文', '数学', '英语', '政治', '地理', '生物', '化学', '历史', '物理'] let Class_p = ['xiaoxue', 'chuzhong', 'gaozhong'] let grade_p = ['nianji1', 'nianji2', 'nianji3', 'nianji4', 'nianji5', 'nianji6']; let subject_p = ['yuwen', 'shuxue', 'yingyu', 'zhengzhi', 'dili', 'shengwu', 'huaxue', 'lishi', 'wuli']
上面的数组分为两类,拼音的字符串数组是用来匹配页面的地址符的,中文的字符串数组是我用来生成标签的,以插入数据库,到时候才可以根据现应的标签反馈给用户,供于分类与搜索用的。
设计好了基本的数组类型,我开始设计我的逻辑思路:
由于地址符对应的页面是随机的,我只能用穷举法,但是你想一下,用穷举法,你看看需要多少次,先看一下我算的一笔账:
首先是8888个页面 * 学年阶段【3个可选值】* 年级阶段【小学有六个可选值,中学三个可选值】* 课程【小学三个可选值,中学九个可选值】
算一笔精确一点的:
小学:8888 * 6 * 3 = 159984
初中+高中:2 * 8888 * 3 * 9 = 479952
所以小学+初中+高中 = 639936
所以本来是访问8888个页面就好,现在要访问639936,从千级别到十万级别,多了72倍,假设我访问一个页面加上保存数据只用一秒就好(ps:我抓取的时候还真是设置一秒访问一个页面),首先一分钟60秒,一个小时60分钟等于3600秒,所以我总共要访问:177.76个小时合计为:7.406666天,差不多得爬一个星期。
(2)试图优化方案:
单纯穷举法,首先撇开长时间的爬取被封ip号的可能性,这个可以用代理池,不过可能需要钱,这个先不考虑,因为我也没用过需要钱的代理;
所以我想,我是不是可以创建一个数组,这个数组保存着许多个对象,对象中会有一个索引值,即ID,因为我是穷举,我生成的对象会有639936个对象,一个对象代表一个访问的网页,我从1开始访问,每次访问到一个对象,就把这个索引删掉。
打个比方,我不是创建了6千多万个对象吗,这6千多万个对象都有很多重复的ID,里面只有8888个对应的ID与学年,与年级与课程全部正确的。669936除以8888等于72,就说明我相同ID的对象,每一个相同ID都有72个,所以我一旦访问到这个正确的,就可以把其他相同ID的对象删掉,减少一部分访问量,画个图表示一下:
所以这个方法可以达到减少访问次数的目的,具体减少多少还得看运气,访问到一个页面,数组对象就会减少几十个,这样就提高了访问成功的效率。
另外,其实程序还可以优化的地方,比如一年级小学没有英语,二年级才有,如果对其进行处理,那么可以减少8888个对象,减少了8888秒,但是实际不会减少这么多,因为我只要请求到响应的id就进行数组对象删除操作,不过我觉得还是可以提高一点性能的,
可以看到只要查询到数据,数组长度就会减少:
这是我测试请求前1000个页面的结果,不过后来的请求结果很多都是404,大概是初中高中的数据大部分在1000.HTML后面吧,越到后面越难遍历
最后还剩下2万多对象没有删掉,然而已经遍历到高三了:
数据库请求到500多条:
难道是请求漏掉的,答案不是的,其实除了这些常见科目,它还有一些作文啊,科学啊这些我没有爬取,这些应该就是剩下的漏网之鱼,1000个网页爬到500多,还行吧,就是速度慢了点。爬了的确7个多小时。可以看到我的优化方案,不断删除数组还是有效果的,因为出去29000条数组还没删除,我总共大概删了43000个对象,也就是我访问了43000个网页,原本时间应该是43000秒=11.94179个小时,现在只用了7个多小时,效率提高50%
所以接下来我打算爬取剩下的7888个网页,打算同时运行20个程序,放在我的服务器上面耗,这样应该明天早上就可以得到这些结果了。