综述
这是某次对某dz论坛开发爬虫的经验。需要对该论坛某个板块下所有的帖子进行爬取。
流程
该dz论坛所有帖子都有唯一的pid,因此首先需要获取帖子的pid。只需要在该板块选择按发帖时间排序即可,链接会诸如&orderby=dateline&filter=author&orderby=dateline&page=4
其中page属性就是第几页,遍历即可,然后解析得到某一页中的thread_id再保存。我是顺便获取了帖子标题,保存在一起。
之后遍历帖子id,开始获取具体每个帖子里的内容。顺利的话是可以直接/thread-XXXXXX-1-1.html
获取页面html,之后就可以得到想要的东西了,bs,xpath,正则请君自便。然而这时候遇到了小问题,触发dz反采集,此时返回仅是一段卸载html中的js代码,通过运行后浏览器会自动跳转到加上一段_dsign
参数的网址,才会真正得到我们想要的html。
js跳转的破解
这里小贴一段js代码
1
| function wh(wh_){function i(){return getName();};return i();return 'wh'}u1='7';Kv=function(){'Kv';var _K=function(){return '&'}; return _K();};_bggyv = window;I4G=function(I4G_){'return I4G';return I4G_;};function mATa(mATa_){function _m(mATa_){function thr(){return getName();}function mATa_(){}return thr();return mATa_}; return _m(mATa_);}_mL17x = 'href';z8Pb='=59';_sknuV = 'replace';function xm(){'xm';function _x(){return '9'}; return _x();}function qAH(qAH_){function _q(qAH_){function f3(){return getName();}function qAH_(){}return f3();return qAH_}; return _q(qAH_);}function q5jb(q5jb_){function tid(){return getName();};return tid();return 'q5jb'}BY=function(){'BY';var _B=function(){return '5'}; return _B();};eysb=function(){'return eysb';return 'vie';};function getName(){var caller=getName.caller;if(caller.name){return caller.name} var str=caller.toString().replace(/[\s]*/g,"");var name=str.match(/^function([^\(]+?)\(/);if(name && name[1]){return name[1];} else {return '';}}_RtoD2 = 'assign';_YJ3qI = location;function EJXS(){'return EJXS';return 'p?m'}function y2W(){'return y2W';return '46'}Ok=function(){'Ok';var _O=function(){return 'r'}; return _O();};function az(az_){function _a(az_){function w(){return getName();}function az_(){}return w();return az_}; return _a(az_);}xj4z=function(){'xj4z';var _x=function(){return 'od='}; return _x();};yx='f';location[_RtoD2]((function(){'return n1';return (function(){return '/';})();})()+yx+(function(){'return pl';return (function(){return 'o';})();})()+Ok()+(function(){'return Sm78';return 'um.'})()+(function(Z3a_){'return Z3a';return Z3a_})('ph')+EJXS()+xj4z()+eysb()+az('dL')+mATa('nssv')+(function(){'return fp';return 'e'})()+'ad'+Kv()+q5jb('G92j')+z8Pb+u1+I4G('74')+(function(){'return V1Yh';return '75&'})()+'_d'+(function(){'return Do';return (function(){return 's';})();})()+wh('K5')+(function(){'return OOkp';return 'gn='})()+(function(xLy_){'return xLy';return xLy_})('ff')+BY()+xm()+y2W()+qAH('s55'));
|
这段js代码经过混淆,不过并不长,可以很容易知道,它运行了诸如location.href = "real_url"
、location.assign("real_url")
、location.replace("real_url")
的代码,浏览器运行后自动会跳转到对应网址。而且几乎每次运行都会返回随机的js代码。
这里不得不在python中直接调用js代码了。而在(网上找到的)一众方法中,pyv8过于古老,已经不好安装了;js2py不知为何会报错;最终只能使用pyexecjs了。不过这pyexecjs似乎也有段时间没人维护了,而且功能有点奇怪,只能compile编译后,使用call调用其中的函数。
而经过上述分析中,我们的目标就是获取"real_url"
这个东西,好在这种js环境没有location,window之类的对象,跳转命令无法自动执行,甚至还会报错。我们可以在其前面注入一段js代码,定义一下location,window,顺便解决报错问题,经过后面的代码对location中的属性(主要就是href)赋值后,再构造一个可以传出该属性的函数,这样就马上可以获得我们想要的地址了。对于函数式的跳转方法,我就定义assign和replace两个函数是对href属性的赋值就行了。
实际过程中发现,还有一种方法,就是直接对location赋值,即location = 'real_url'
,这样的话就在传出地址的函数里再加一条,判断location是否为String类型就行了,具体是用诸如ll.constructor==String
来判断的。
如此,便基本解决问题了。