起因是看到github上一段代码,准备直接复制粘贴跑起来,结果却遇到莫名其妙的问题,

#化妆品信息许可    
import json    
import requests    
if __name__ == '__main__':    
id_list = []    
all_data_list = []    
url='http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do'    
headers={    
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36'    
}    
params={    
'method': 'getXkzsList'    
}    
for page in range(1,6):    
page=str(page)    
data={'on': 'true',    
'page': page,    
'pageSize': '15',    
'productName': '',    
'conditionType': '1',    
'applyname': '',    
'applysn': ''}    
dict_data=requests.post(url=url,data=data,params=params,headers=headers).json()    
for dic in dict_data['list']:    
id_list.append(dic['ID'])    
post_url='http://scxk.nmpa.gov.cn:81/xk/itownet/portalAction.do'    
params = {    
'method': 'getXkzsById'    
}    
for id in id_list:    
data={    
'id':id    
}    
detail_json=requests.post(url=url,params=params,data=data,headers=headers).json()    
all_data_list.append(detail_json)    
fp=open('./hz.json','w')    
json.dump(all_data_list,fp=fp,ensure_ascii=False)    
print('over!!!')


爬取后全是加密的js,没找到有用的信息,分析一下原来是某数的最新(2021-12月后)的反爬方案。

常规的做法是调试一下找出来每个ajax访问链接后添加的hKHnQfLv和8X7Yi61c两个参数怎么计算的,同时还有cookie。

另外,熟悉某数的都知道这两个参数是每次动态计算的,使用一次就失效了。

打开firefox developer edition,F12,然后就暂停了,挺正常的反爬手段,属于常用招数,通常几个解决方法:永不在此处暂停,清除所有计时器,使用代理软件直接拿、修改源码删除debugger等等。

不过firefox developer edition有个问题,在循环中的条件断点似乎不起作用,那就换成chrome继续。同时记录一下翻页按钮事件绑定的代码位置。

换上chrome,再重复一下屏蔽debugger,查看按钮绑定的地方似乎没有生成链接后的两个参数,看起来是挺正常的。

那就是重写了send,open之类的,断一下XHR,在最终的网络请求处肯定能找到,再逆推回去。一般而言,如同游戏内存挂一样,从发包处逆推会更容易,避免在各种虚拟机函数里迷失方向。

OK,现在找到生成的地方了,找的过程中多次下断点,遇到函数先别跳进去,先直接打印一下函数结果,看是不是有关联的信息。

生成两个参数的地方依次找到,接下来通常的做法是把生成参数的代码扒出来,做个本地服务或是想办法在python里运行js,扒代码的过程中注意只找纯逻辑的地方,本地运行的和动态调试的多做比较,这样能排除掉一些坑。

但这样听起来挺麻烦的,为什么是扒代码而不是去分析算法,直接重写算法就行了?因为这不是一般人静下心来搞的事,项目开发就是各类风险的控制过程,写算法这样的严重成本超支行为风险太大了。

OK,到此就差不多了。流程上行得通,但是。。。有必要花这么大精力吗?

目标只是去爬点数据,结果搞成了和反爬进行斗争,还是得时刻牢记不忘初心。

上面这个方法是最常用的,也算是最正规的攻防套路吧,那还有其它方案吗?

还真有,也不少人用,比如使用selenium,直接使用浏览器,不管怎么反爬,数据总得显示出来吧?

当然selenium也有一些问题,比如已经被防守了,需要修改很多东西,去掉一些特征,再加上一些手段,也是可以达到目的的。

等等,怎么感觉刚出一个坑,又跳进一个坑?

综上,想来最简单的方案,就是使用类似selenium采集的方式,写一个简单的浏览器,再克服一下selenium本来的问题,让整个流程简化再简化,那不就挺完美吗?

OK,结下来想想需要什么:

  1. 首先是能访问网页的浏览器,简单,用MFC嵌入个IWebBrowser2

  2. 有人会说弄个浏览器,这样比纯代码的访问差多了,因为会去请求图片,css,这些本来不需要的东西,耽误时间浏览,也对,那就再嵌入个代理,让IWebBrowser2连接到代理,然后过滤掉所有的图片,css请求。同时,观察发现和上面的原来一样,只需要网页加载一次获取到cookie和那些加密反爬js的运行环境就行,后续都不需要再加载打开网页了,只需在当前网页上不断地注入js发ajax请求后续的页码就行,详情页同理也是一样,这样就和纯代码的方案差不多了,也不会有浪费时间资源的困扰了,甚至还要主动让程序sleep,避免给网站造成太大负担,毕竟我们只是想爬取点数据,其它任何附带的最好都不要有,同时采集过的内容自己缓存一下,别同样的内容跑去反复采。

  3. 总不能爬取一点东西就去改一遍c++程序的逻辑吧?编译调试都挺费事的,OK,那嵌入个python脚本吧,让python控制浏览器的操作,比如打开网页,开启屏蔽图片CSS等,能够将js代码注入到浏览器中做网络请求,能够点击链接跳转页面,能够获取到网络请求的响应(类似chrome的网络面板),这样相当于是和selenium一样的方法,控制是在python做,具体的业务逻辑还是在“原版”的网页上。

想想大概就这三点吧,大概思路就这些了,这样能够在不做任何侵入的情况下,完成所需的功能,直接注入js发网络请求,你反爬重写了send, 我注入的js发请求你总得也一视同仁一起接管吧,该加密什么的你也得主动加吧?

最后得到的就是纯json的返回值了,在python控制脚本里,根本就不用管反爬的事。

什么,听起来这样实现还是挺难的?如果你之前已经写过类似的东西呢,或者是能获取到已经这样封装好的东西呢?以后都可以无视此类反爬了。

最开始只是一个想法思路,后来搞一搞还真行得通,贴点代码随便看看吧:

    cmd.blockImage()
    cmd.blockCss()

    iLoaded = 0
    
    with UsingMysql(log_time=True) as um:
    
        cmd.nagivate('http://scxk.nmpa.gov.cn:81/xk/itownet/allQyxx/allQyxx.jsp')
        cmd.waitTillLoaded(10000)
        
        page = 1
        
        if page == 1:
            res = cmd.getResponse('itownet/portalAction.do', 30000)
            iLoaded = iLoaded + processList(res, log, um, cmd)
            log.logger.info("load mda_nmpa_scxk_list "+str(iLoaded)+" records")
            cmd.print("load mda_nmpa_scxk_list "+str(iLoaded)+" records")
            
            with open("data\\"+runRound+"p1.json",'w+') as fo:
                fo.writelines(res) 

        while page <= 419:

            if os.path.exists("data\\"+runRound+"p"+str(page)+".json"):
                page = page + 1
                continue

            cmd.clearResponse()

            time.sleep(random.randint(1,5))
            ...

其实写程序最快乐的事,就是随便搞搞,但最后完美满足需求。

大概就这些了,攻防是会永远存在的,有时候避其锋芒,剑走偏锋也能够事半功倍。