基于 requests 的全能扫描王爬虫实践

picture.image

投稿人: Alan

摘要: 全能扫描王是文件扫描留档的重要工具,本文利用requests爬虫将手机客户端的扫描文件,同步至电脑端。

一、背景

在审计工作中,需要大批量扫描文件留档,全能扫描王成为主流的手机端扫描工具。 由于网页端的全能扫描王无法大批量下载的扫描文件,在一定的限制环境下,使用将手机端同步至电脑成为一大需求,本文利用requests爬虫下载扫描文件,tkinter设计交互界面,pyinstaller打包封装exe实现脱机使用。

二、Fiddler抓包

Fiddler安装及证书信任设置不在此赘述。

(1)user/login请求

利用火狐浏览器访问扫描王网页( https://www.camscanner.com/user/login ),并输入用户名和密码点击登录。 在Fiddler中观察执行情况,发现第247号POST请求 user/login 下webform包含上传参数,返回结果json为URL,且在Fiddler抓取的URL中已经访问(第249号),故应为跳转的网址(如图2-1)。

picture.image

图2-1

(2)files/holder请求

该请求为登录后的界面,在Fiddler中仅执行请求返回为0,未获取登录后网页,故可能需要模拟请求头部和Cookie。 在执行带header和cookie的请求后成功获取登录界面,其中cookie包含_csc、 _csl、Hm_lvt_8f0191b1f1b207d4f6e0d42e771d6fde、_oa、_cdn、 JSESSID、Hm_lpvt_8f0191b1f1b207d4f6e0d42e771d6fde、_ct、 _isus、 _cslt、 S2、 _cssu、_csste等参数。 登录后的界面包含了用户已经同步至云端的所有文件列表,但请求结果中未发现文件清单的信息,故猜测可能存在别的网址。 在Fiddler抓取结果中,发现图2-1中第270号请求中返回文件列表(如图2-2)。

picture.image

图2-2

(3)doc/list请求

该请求获取文件列表,在Fiddler中执行带header和cookie的doc/list请求,结果与抓取一致。

(4)doc/downloadpdf请求

在火狐浏览器中下载某一个文件,Fiddler中发现请求 doc/downloadpdfstat/download 与下载相关,其中 doc/downloadpdf 根据提供的文件的唯一标识doc_id返回对应的下载地址(如图2-3),而 stat/download 是确认文件下载状态,故主要关注 doc/downloadpdf 请求。

picture.image

图2-3

三、requests爬虫实现

在第二节的抓包后,着手进行代码实现,考虑到程序为工作的需求,故脱离python环境也能执行,故暂不考虑selenium,而采用requests。 程序主要分为登录( login\_page )、获取文件列表( get\_file\_list )、下载文件( get\_downloadfile\_url\_one )三块。

(1)程序初始化

设置requests.session、headers等变量,并应用logger包将在程序中打log,代码如下:


        
class downloadscan(object):  
    def \_\_init\_\_(self,username,password):  
        self.session = requests.session()  
        self.url = "https://www.camscanner.com/user/login"  
        self.username,self.password =username,password  
        self.respage = None  
        self.headers = {"Host": "www.camscanner.com",\  
                        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:58.0) Gecko/20100101 Firefox/58.0",\  
                        "Accept":"text/plain, */*; q=0.01",\  
                        "Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",\  
                        "Accept-Encoding":"gzip, deflate, br",\  
                        "Content-Type": "application/x-www-form-urlencoded",\  
                        "X-Requested-With":"XMLHttpRequest",\  
                        "Connection":"keep-alive",\  
                        "Cache-Contro":"max-age=0, no-cache",\  
                        "Pragma":"no-cache"}  
        self.file_list = None  
        self.tmp_cookie = None  
        self.download_res = {"filename":None,"address":None}  
        self.status = True  
        self.logger = logging.getLogger(__name_\_)  
        self.logger.setLevel(level = logging.INFO)  
        handler = logging.FileHandler("./camscanner/log/log")  
        handler.setLevel(logging.INFO)  
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')  
        handler.setFormatter(formatter)  
        self.logger.addHandler(handler)  

    

(2)登录login_page

利用requests.session.post实现user/login请求,并设置status获取登录状态(成功or失败),详见login_page函数, 在登录后实现files/holder请求跳转,详见redirect_to_holder函数,并更新登录状态,代码如下:


        
    def login_page(self):  
        #登录  
        params = {"act":"submit","redirect\_uri":"","area\_code":"86","username":self.username,"password":self.password,"rememberme":False}  
        self.respage = self.session.post(self.url,data=params,headers = self.headers)  
        self.logger.info(self.url+" status\_code: "+ str(self.respage.status_code))  
        self.generate_header("login")  
        if str(self.respage.status_code) =="200" and self.status ==True:  
            self.logger.info("++++++++oh my god, login succeed!++++++")  
        else:  
            self.status = False  
    def redirect_to_holder(self):  
        #跳转  
        if "data" in json.loads(self.respage.text):  
            self.url = json.loads(self.respage.text)["data"]  
            self.respage = self.session.get(self.url,headers = self.headers)  
            self.logger.info(self.url+" status\_code: "+ str(self.respage.status_code))  
            self.status = True  
        else:  
            self.status = False  
        if str(self.respage.status_code) =="200" and self.status == True:  
            self.logger.info("++++++++oh my god, redirect login succeed!++++++")   
        else:  
            self.status = False  

    

(3)请求headers设置


        
    def generate_header(self,action):  
        if action=="login":          
            self.headers["Referer"] = self.url  
            self.headers["Accept"]  ="text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"  
            self.headers["Upgrade-Insecure-Requests"] = "1"  
            self.tmp_cookie = requests.utils.dict_from_cookiejar(self.respage.cookies)  
            if "\_csc" in self.tmp_cookie and "JSESSID" in self.tmp_cookie and "S2" in self.tmp_cookie and "\_cssu" in self.tmp_cookie:  
                self.headers["Cookie"]  ="\_csc=%s;\_csl=zh-cn;\_oa=86,%s;JSESSID=%s;Hm\_lvt\_8f0191b1f1b207d4f6e0d42e771d6fde=%s;\_ct=1;\_isus=1;\_cslt=1;S2=%s;\_cssu=%s;\_csste=%s;Hm\_lvt\_8f0191b1f1b207d4f6e0d42e771d6fde=%s;\_cssrd=1"%(self.tmp_cookie["\_csc"],self.username,self.tmp_cookie["JSESSID"],int(time.time()),self.tmp_cookie["S2"],self.tmp_cookie["\_cssu"],int(time.time()),int(time.time()))  
            else:  
                self.status = False  
                self.logger.warning(self.url+"登陆失败,headers设置失败")  
        else:  
            self.headers["Cache-Contro"] ='no-cache'  
            self.headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"  
            self.headers["Accept"]  = "text/plain, */*; q=0.01"  
            self.headers["Referer"] = 'https://www.camscanner.com/files/holder'  
            if "Upgrade-Insecure-Requests" in self.headers:  
                self.headers.pop("Upgrade-Insecure-Requests")  
            if "\_csc" in self.tmp_cookie and "JSESSID" in self.tmp_cookie and "S2" in self.tmp_cookie and "\_cssu" in self.tmp_cookie:  
                self.headers["Cookie"]  ="\_csc=%s;\_csl=zh-cn;\_oa=86,%s;JSESSID=%s;Hm\_lvt\_8f0191b1f1b207d4f6e0d42e771d6fde=%s;\_ct=1;\_isus=1;\_cslt=1;S2=%s;\_cssu=%s;\_csste=%s;Hm\_lvt\_8f0191b1f1b207d4f6e0d42e771d6fde=%s;\_cssrd=1"%(self.tmp_cookie["\_csc"],self.username,self.tmp_cookie["JSESSID"],int(time.time()),self.tmp_cookie["S2"],self.tmp_cookie["\_cssu"],int(time.time()),int(time.time()))  
            else:  
                self.status = False  
                self.logger.warning(self.url+"登陆失败,headers设置失败")  

    

(4)获取文件列表


        
    def get\_file\_list(self):  
        self.url = "https://www.camscanner.com/doc/list"  
        self.respage = self.session.post(self.url,headers = self.headers)  
        #self.save\_all()  
        self.logger.info(self.url+" status\_code: "+ str(self.respage.status_code))  
        self.file_list = eval(self.respage.text)["data"]["list"]  
        self.logger.info("++++++++enheng, get file list succeed+++++++++++")  
           (5)下载文件  
    def get\_downloadfile\_url\_one(self,doc\_id,title):  
        self.generate_header("get download url")  
        self.url = "https://www.camscanner.com/doc/downloadpdf"  
        self.params = {"json\_download":json.dumps({"docs":["%s.jdoc"%doc_id]})}  
        times = 0  
        tmp = None  
        while (times<5 and tmp == None):  
            self.respage = self.session.post(self.url,data = self.params,headers = self.headers)  
            times +=1  
            if "data" in self.respage.json():  
                tmp = self.respage.json()["data"]  
        self.download_res["address"] = tmp  
        self.download_res["filename"] = title + tmp[tmp.rfind("."):]  
        self.logger.info("get %s address is : %s"%(title,tmp))  
        urllib.request.urlretrieve(self.download_res["address"],self.download_res["filename"])  
        self.logger.info("%s,下载完成"%self.download_res["filename"])  

    

四、tkniter交互、pyinstaller封装exe

为方便程序可以脱离python环境使用,利用tkniter包设计简单的交互界面,主要设置登录界面(图4-1),若登录网页版全能版扫描王后成功后,跳转同步界面(图4-2),并在本地生成user_info文件,下次直接自动登录,跳过登录界面,点击开始同步(图4-3),将本地已经下载的文件跳过,直接下载未下载的文件,并更新本地文件列表,针对同步失败、切换账号、密码更改的需求,设置重新登录功能,将删除原有user_info文件,重新生成(图4-4),运行结果如图4-1至4-4:

picture.image

4-1登录界面

picture.image

4-2登录成功

picture.image

4-3开始同步

picture.image

4-4重新登录

将测过的代码进行封装,本文运行较为简单的命令:


        
pyinstaller -F -w downloadscan.py -i camscanner.ico  

    

五、结语

下载本文全部代码 (downloadscan.py) 实现及封装程序 (downloadscan.exe) ,请扫描下方二维码关注公众号后回复“ 抓包 ”获取

picture.image

0
0
0
0
评论
未登录
暂无评论