资源描述
,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,2018/11/16,#,Python Crawler Development,极客学院,J,互联网,+,职业技能系列,Python,爬虫开发 从入门到实战(微课版),人民邮电出版社,谢乾坤著,第,10,章,Android,原生,App,爬虫,本章,所讲到的爬虫能够无视网站后台和,App,数据传递过程中的任何加密算法,能够爬取任意,Android,原生,App,显示在屏幕上的文本型数据。并且理论上可以突破任何反爬虫机制。,前面,的章节所讲到的爬虫无外乎两种情况:第一种情况,爬虫伪装成浏览器,向服务器要数据;第二种情况,在服务器往浏览器发送数据时,爬虫从中拦截,获取信息,。,这,两种情况,无论是暗号(参数)不对还是行为不对,都会被服务器识别。那么有没有什么办法可以做到几乎毫无痕迹地爬取数据呢?答案是有。当然可能有读者会认为可以使用,Selenium+ChromeDriver,。这种方式只能操作网页。本章将要介绍针对,Android,原生,App,的爬虫。,通过,这一章的学习,你将会掌握如下知识,。,(,1,),Android,测试环境的搭建。,(,2,)使用,Python,操作,Android,手机。,(,3,)使用,Python,操作,Android,手机实现爬虫。,10.1,实现原理,目前,,,Android App,主要有两种实现形式。第一种是,Android,原生,App,。这种,App,的全部或者大部分内容使用,Android,提供的各个接口来开发,例如,Android,版的微信就是一个,Android,原生的,App,。第二种是基于网页的,App,。这种,App,本质上就是一个浏览器,里面的所有内容实际上都是网页。例如,,12306,的,App,就是这样一种基于网页的,App,。,Android,原生,App,爬虫(以下简称,App,爬虫)可以直接读取,Android,原生,App,上面的文本信息,如图,10-1,所示。,图,10-1,App,爬虫直接读取,Android,原生,App,上面的文本信息,例如,想爬,TapTap,这个,App,上面的各个游戏,可以直接从手机屏幕上把游戏的名字读出来。当然,要读游戏描述也是完全没有问题的,如图,10-2,所示。,图,10-2,读取游戏描述,App,爬虫可以实现自动滚动屏幕,自动单击进入详情页。凡是人可以对手机进行的操作,,App,爬虫都可以进行。,UiAutomator,是,Google,官方提供的,Android,自动化图形接口测试框架。通过它可以实现对,Android,设备屏幕的各种操作,或者直接从屏幕上读取文字,。,大部,分系统版本大于,4.1,的,Android,系统,都会内置,UiAutomator,。小米手机原装的,MIUI,系统除外,,MIUI,系统,UiAutomator,被移除了,需要刷开发版或者换其他系统才能使用。,10.1.1,环境搭建,1,安装,JRE,要使用,UiAutomator,操作,Android,手机,首先需要在计算机上安装,Android,的软件开发工具包(,Software Development Kit,SDK,)。要安装,Android SDK,,首先需要安装,Java,运行时环境(,Java Runtime Environment,JRE,)。,对于,Mac OS,,使用,Homebrew,安装,Java,开发套件(,Java SE Development Kit,,,JDK,)。,JRE,包含在了,JDK,里面:,brew update,brew cask install java,对于,Ubuntu,,使用如下命令直接安装,JRE,:,sudo apt-get update,sudo apt-get install default-jre,对于,Windows,,可以访问,8,。在这个页面,首先选择“,Accept License Agreement,”单选按钮,如图,10-3,所示。,图,10-3,首先选择“,Accept License Agreement,”,单,选按钮才能下载,2,安装,Android SDK,安装,完成,JRE,以后,再来安装,Android SDK,。请打开,https:/developer,.OS,与,Ubuntu,系统,本书把这个,tools,文件夹放在“,/book/sdk,”文件夹里面。对于,Windows,系统,本书把这个,tools,文件夹放在“,E:Program Filessdksdk,”文件夹里面,。,图,10-4,在网页最下面下载系统对应的,Android SDK,在,Mac OS,与,Ubuntu,的终端输入并执行如下命令,如图,10-5,所示。,cd,/book/sdk,bin/sdkmanager platform-tools,图,10-5,执行命令安装,platform-tools,Windows,系统直接在,tools,文件夹中打开,CMD,窗口,并输入命令:,bin/sdkmanager.exe platform-tools,输入,完成命令并按,Enter,键,可以看到在终端窗口弹出安装协议,输入,y,并按,Enter,键即可安装“,platform-tools,”,。,需要,注意的是,这里执行这个命令时,程序会从,Google,获取一些数据,此时可能会由于网络问题导致失败。如果遇到网络问题,那么就需要使用能访问,Google,的代理,并且命令也需要做一些修改,修改为:,bin/sdkmanager.exe platform-tools-proxy=http-proxy_host=,代理,IP-proxy-port=,代理端口,安装,完成以后,会在“,/book/sdk,”文件夹或者“,E:Program Filessdksdk,”文件夹下出现一个“,platform-tools,”文件夹。现在需要将,tools,文件夹和,platform-tools,文件夹添加到系统的环境变量中。,3,设置环境变量,对于,使用,Mac OS,或,Ubuntu,系统并安装了,zsh,和,Oh-my-zsh,的读者,请打开,/.zshrc,并检查是否已经有,export PATH,开头的一句话,如果有,请修改为下面所示的代码这样,其中的,tools,和,platform-tools,文件夹的地址请改为实际地址:,export PATH=/Users/kingname/book/sdk/platform-tools:/Users/kingname/book/sdk/tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin,如果,是,Mac OS,或者,Ubuntu,系统且没有安装,zsh,和,Oh-my-zsh,的读者,或者想临时添加环境变量进行测试,可直接在终端执行下面代码,将,tools,和,platform-tools,的地址改为实际地址:,export,PATH=$PATH:/Users/kingname/book/sdk/platform-tools:/Users/kingname/book/sdk/tools,需要,注意的是,这种方法是临时添加环境变量,当前的终端窗口不能关闭。一旦关闭再重新打开,就需要再一次执行上面的代码。,对于,Windows,,打开任意一个文件夹,并在左侧导航窗口中右键单击“计算机”,选择“属性”命令,打开属性面板。在属性面板左侧选择“高级系统设置”选项,进入“高级”选项卡,最后单击右下角的“环境变量”按钮进入“环境变量”对话框,如图,10-6,所示。,图,10-6,单击“环境变量”按钮,4,开启开发者模式,要,通过计算机控制手机,还需要打开,Android,手机的开发者模式。本书以小米手机的,MIUI,开发版和,Android,原生系统为例来演示如何开启开发者模式。,对于,小米手机,MIUI,开发版,依次进入“系统设置”,-,“我的设备”,-,“全部参数”,快速连续单击“,MIUI,版本”这一栏,5,次,开发者模式就会打开。打开以后,回到“系统设置”主界面,依次进入“更多设置”,-,“开发者选项”。在“开发者选项”中分别打开“,USB,调试”、“,USB,安装”和“,USB,调试(安全模式)”这几个开关,系统会弹出警告,选择允许。,图,10-7,双击“,Path,”并添加,tools,文件夹,对于,Andorid,原生系统,以,Google,生产的,Nexus,为例。打开“系统设置”,进入最下方的“系统”,打开系统信息界面。拖动最下方,找到“关于手机”并点击,进入手机状态界面,如图,10-8,所示。,快速,单击“版本号”,5,次,打开开发者选项。,开发,者选项打开以后,就可以在手机信息界面找到进入开发者选项的入口了,如图,10-9,所示。,进入,开发者选项的设置页面,打开“,USB,调试”,如图,10-10,所示。,图,10-8,手机状态界面,图,10-9,开发者选项入口出现在系统设置中,图,10-10,在开发者选项中打开,USB,调试开关,由于,各大厂商对,Android,系统几乎都有自己的定制化,因此不能在这里一,一列举所有型号手机开启开发者模式的操作。对于没有提及的手机型号,请通过网络搜索。,设置,好环境变量以后,在终端窗口输入“,uiautomatorviewer,”并按,Enter,键,如果可以弹出图,10-11,所示的,UI Automator Viewer,窗口,表明环境设置成功。,将,Android,手机连接到计算机上,保持手机屏幕为亮起状态,单击,UI Automator Viewer,左上角文件夹右侧的手机图标,如果能够看到手机屏幕出现在窗口中,则表示一切顺利,环境搭建成功完成。如果在这个过程中手机弹出了任何警告窗口,都选择“运行”或者“确定”。,图,10-11,UI Automator Viewer,窗口,10.1.2,使用,Python,操纵手机,要使用,Python,来操作,UI Automator,从而控制手机,需要安装一个第三方库。,这个库的名字就是,uiautomator,,使用,pip,进行安装,如图,10,-,12,所示。,图,10-12,使用,pip,安装,uiautomator,安装,完成以后运行,Python,并导入,uiautomator,:,from uiautomator import Device,device=Device(),print(device.dump,(),由于,是第一次运行,,uiautomator,会往手机中安装两个没有图标的程序。有一些手机系统可能会弹出窗口询问是否允许安装,单击“继续安装”按钮,如图,10-13,所示,。,图,10-13,系统询问是否安装,单击“继续安装”按钮,安装,完成以后,可以看到终端窗口出现了类似于,XML,的内容,如图,10-14,所示。这说明,uiautomator,这个第三方库成功安装。此时就可以使用,Python,控制手机了。,图,10-14,终端窗口出现,了类似于,XML,的内容,有,一点需要特别说明,,UI Automator Viewer,与,Python uiautomator,不能同时使用。一旦,Python,的,uiautomator,运行过一次,它安装的两个文件就会在手机后台运行,。,这,两个文件会占用,Android,内部的一个叫作,UI hierarchy,的东西,从而导致,SDK,自带的,UI Automator Viewer,一旦尝试获取手机屏幕就会报错,如图,10-15,所示。,图,10-15,一旦运行过,uiautomator,,就会导致,SDK,自带的,UI Automator Viewer,不能获取手机屏幕,与,Selenium,一样,要操作手机上面的元素,首先要找到被操作的东西。以打开微信为例,首先翻到有微信的那一页,如图,10-16,所示。,图,10-16,翻到有微信的一页,编写,如下代码:,from uiautomator import Device,device,=Device(),device(text=,微信,).click,(),运行,以后可以看到,微信自动被点击并打开。,如果,计算机上面只连接了一台,Android,手机,那么初始化设备连接只需要使用,device=Device(),即可。那么如果计算机上连接了很多台手机,该怎么办呢?此时就需要指定手机的串号。要查看手机串号,需要在终端输入以下命令:,adb devices-l,从,输出的内容可以看到手机的串号,如图,10-17,所示。,图,10-17,使用命令查询手机串号,10.1.3,选择器,如何知道有哪些选择器可供使用呢?请执行以下代码:,from uiautomator import Device,device=Device(),print(device.dump(),此时终端会以,XML,输出当前手机屏幕显示的窗口布局信息,如图,10,-,18,所示。,这里,的,XML,就相当于网页中的,HTML,,用来描述窗口上面各个部分的布局信息。,XML,的格式与,HTML,非常像,格式为:,文本,可以,用一个标签来作为选择器,也可以同时使用多个标签来更精确地描述某一个元素。一般来说,操作一个有文字的元素,主要是使用,text,这个属性;如果是从屏幕上读文字,就使用其他的属性。,图,10-18,当前,手机屏幕显示的窗口,布局信息,10.1.4,操作,在选择好元素以后,就需要对它进行操作。最常见的操作,如下:,获得屏幕文字;,滚动屏幕;,滑动屏幕;,点击屏幕;,输入文字;,判断元素是否存在;,点亮关闭屏幕;,操作,实体按键,;,watcher,。,1,获得屏幕文字,如果,要从,Android,手机上读取当前屏幕上显示的文本内容,用到的是一个元素的“,.text,”属性。这里以获取游戏商场类,App,“,TapTap,”当前屏幕的游戏名称为例。,当前,手机上的,TapTap,界面如图,10-19,所示。,从,屏幕上可以看到有,3,个游戏的名字,分别是“霍基,2,”“,yellow,”和“我的星球”。现在的目的是要把这,3,个名字读取下来。首先使用以下代码获取当前屏幕的,XML,布局,如图,10-20,所示。,图,10-19,TapTap,首页,图,10-20,TapTap,首页对应的,XML,布局,从,这个布局可以看到,所有标题的,resource-id,都是“,com.taptap:id,/bottom_app_name,”。如果需要获取第一个游戏“霍基,2,”的名字,可以使用下面这一行代码:,game_1_title=device(resourceId=com.taptap:id/bottom_app_name).text,运行,结果如图,10-21,所示。,图,10-21,获取第一个游戏的名称,但,实际上,当前屏幕有,3,个游戏,它们的,resource-id,都是相同的。如何把,3,个游戏的名字都获取下来呢?方法很简单,那就是“先别急着获取,.text,属性,先寻找元素,再用,for,循环展开,最后再获取,.text,属性”。其代码如下。,game_title_list=device(resourceId=com.taptap:id/bottom_app_name),for title in game_title_list:,print(title.text),运行,结果如图,10-22,所示。,图,10-22,同时获取当前屏幕的,3,个游戏名,2,滚动屏幕,滚动,屏幕对应的操作为“,.scroll(),”。它的操作对象是一个可以滚动的对象。如果手动操作可以把屏幕向上滚动,那么屏幕上应该至少有一个元素是可以滚动的,因此选择器可以写为:,device(scrollable=True),那么,向上滚动一屏可以写为:,device(scrollable=True).scroll.vert.forward(),如果,想向下滚动一屏,则需要把,forward(),换为,backward(),:,device(scrollable=True).scroll.vert.forward(),由于,向上、向下是“垂直方向”,所以代码中使用了,vert,,这是英语单词,vertical,(垂直的)的简写。可能有些,App,会出现左右滚动的情况,这时候就需要使用,horiz,,这是英语单词,horizontal,(水平的)的简写。于是,向左及向右滚动就可以写为:,device(scrollable=True).scroll.horiz.forward()#向右滚动,device(scrollable=True).scroll.horiz.backward()#向左滚动,使用,滚动屏幕的方式,就可以把所有游戏的名字都读取下来,如图,10-23,所示。,图,10-23,滚动屏幕获取所有游戏名称,有,一点需要注意,由于滚动一屏时,有可能前一屏最下面的元素滚动以后刚好到了后一屏的最上面,因此可能出现重复获取同一个游戏名字两次的情况,如图,10-23,中的方框所示。在实际使用时要注意去重处理。,3,滑动屏幕,在,某些情况下,整个窗口布局的,XML,里面,所有元素的,scrollable,属性值全部都是,False,,例如小米手机的桌面。在这种情况下,没有办法使用“,scrollable=True,”来左右滚动桌面,于是就需要使用根据坐标来滑动桌面的“,.swipe(),”方法。,在,Android,系统的屏幕上,左上角为坐标原点,(0,0),,越向下,,y,轴数字越大;越向右,,x,轴数字越大。,“,.swipe(),”这个方法操作的对象是整个手机屏幕,所以不需要为,device,设定选择器。“,.swipe(),”的用法为:,device.swipe(400,600,0,600),它,接收,4,个参数,分别为起始点,x,坐标,起始点,y,坐标,终点,x,坐标,终点,y,坐标。对于左右滑动来说,只需要改变,x,坐标即可。如果要显示右边的一屏,那么起始点的,x,坐标要大于终点的,x,坐标。如果要显示左边的一屏,起始点的,x,坐标要小于终点的,x,坐标。,如果,在实际写代码的时候搞不清,x,坐标哪个大,就亲自把手放在屏幕上滑动进行查看。如果要显示右边的一屏,那么会首先把手放在屏幕右侧,按住然后往左移动,所以此时起始点的,x,坐标要大一些。,4,点击屏幕,选择,一个元素以后,除了获取它上面的文字外,还可以点击。就如同前面通过点击打开微信一样。点击操作“,.click(),”和“,.long_click(),”分别对应短按点击和长按点击,它们可以直接应用于一个被选择出来的元素上。例如:,device(text=,微信,).click(),device(text=,微信,).long_click,(),点击,操作也可以直接应用于坐标位置。例如:,device.click(230,567)#第1个参数为横坐标,x,轴,第2个参数为纵坐标,y,轴,device.long_click(230,567)#第1个参数为横坐标,x,轴,第2个参数为纵坐标,y,轴,在,某些,App,中,可能某一个元素并没有一个独特的标志来让选择器选择。这个时候就可以直接使用坐标来操作。,以,TapTap,的搜索按钮为例,搜索按钮的坐标如图,10-24,所示。,图,10-24,TapTap,搜索按钮,的坐标,对于,这个按钮,在右侧信息窗口中可以看到“,ImageButton640,80,688,128,”中的,640,80,对应了这个搜索按钮的左上角的坐标,,688,128,对应了搜索按钮右下角的坐标。对于一个矩形来说,确定了一对对角的坐标,也就确定了,4,个角的坐标。因此,只要点击这两个坐标之间的位置,就等于点击了这个搜索按钮。,为,保险和准确起见,可以点击这个搜索按钮矩形的正中心位置,也就是,664,104,。那么代码就可以写成:,device.click(664,104,),当然,还有一种更简单的办法,那就是把,Ui Automator Viewer,的窗口拉大一些,将鼠标指针在屏幕上面随意移动,就可以直接看到鼠标指针当前位置的坐标,如图,10-25,所示。,图,10-25,箭头指向当前鼠标,指针,所,在位置的坐标,5,输入文字,既然,进入了搜索界面,那就需要搜索内容了。输入文本使用的操作是“,.set_text(),”。,TapTap,的搜素框对应的,resource-id,为“,com.taptap:id/input_ box,”,如图,10-26,所示。,图,10-26,搜索框,对应的,res,ource_id,因此,可以用这个,resource-id,来作为选择器定位到搜索框,再输入文本,。,例如,搜索名为“汉家江湖”的游戏,那么就使用以下代码:,device(resourceId=com.taptap:id/input_box).set_text(,汉家江湖,),6,判断元素是否存在,由于,手机上面的各个元素加载是需要一定时间的,如果在元素加载出来之前就对其进行操作,就会导致程序报错,如图,10-27,所示。这是因为不存在一个,resource-id,为“,com.taptap:id/input_box33,”的元素。,在,操作一个元素之前,先判断一下它是否存在是比较明智的做法。判断元素是否存在,使用“,.exists,”属性。如果存在,值为,True,,否则为,False,。其用法为:,input_box=device(resourceId=com.taptap:id/input_box),if input_box.exists:,input_box.set_text(汉家江湖),else:,print(,搜索框不存在,),图,10-27,操作一个不存在的元素导致程序报错,7,点亮关闭屏幕,使用,“,.wakeup(),”方法和“,.sleep(),”方法可以点亮或者关闭屏幕。当手机屏幕处于关闭状态时,使用“,.wake(),”方法可以让屏幕点亮;当手机屏幕处于点亮状态时,使用“,.sleep(),”方法可以将其关闭。其用法为:,device.wakeup()#点亮屏幕,device.sleep()#关闭屏幕,8,操作实体按键,Android,一般自带不少实体按键,使用,Python,的,uiautomator,可以模拟这些按键被按下的状态。其使用方法为:,device.press.实体按键名称(),常见,的实体按键名称和作用如表,10-1,所示。,实体按键名称,作用,power,电源键,back,返回键,menu,菜单键,volume_up,音量增大,volume_down,音量减小,home,返回桌面,表,10-1,常见的实体按键名称和作用,9,watcher,使用,Android,手机的时候,经常会遇到这样的情况:手机用着用着,突然弹出一个对话框,提示当前,App,有新版本可用。对于用户来说,遇到这种对话框,又不想升级,那么按一下返回键就可以暂时把这个对话框关闭。但是这种对话框对于使用程序来操作手机的场景来说,就是一个灾难。,假设,现在有几百个游戏,需要通过,TapTap,搜索它们,并获取它们的下载量。正常的流程应该是下面这样。,(,1,)初始位置:搜索框。,(,2,)清空搜索框原有的文字。,(,3,)输入游戏名字。,(,4,)点击搜索按钮。,(,5,)点击第,1,个搜索结果进入游戏详情页。,(,6,)从游戏详情页获取下载量。,(,7,)点击实体按键,back,返回搜索界面。,(,8,)转到步骤(,1,)。,图,10-28,假设手机屏幕当前在详情页,在,正常情况下,可以按照从(,1,)(,8,)的顺序循环往复,直到搜索完所有的游戏。但是如果在第(,6,)步获取了下载量以后,突然弹出了升级提示框,。,此时,由于程序不知道弹出了框,它还在执行第(,7,)步,按下了返回键,。,此时,仅仅是关闭了升级提示的对话框,手机屏幕仍然还处于游戏的详情页,但是程序以为已经返回了搜索界面,于是尝试操作搜索框。可是在详情页根本就没有搜索框,于是就会导致程序报错。,这种情况当然可以使用,exists,来判断搜索框是否存在。但问题在于,即使知道了搜索框不在,却并不能解决该问题。因为根本原因在于手机屏幕和程序出现了不同步,要解决问题,就需要让它们同步。,使用,watcher,可以让程序在找不到元素时自动尝试解决问题。假设目前手机屏幕所在的界面为详情页,如图,10-28,所示。,10,其他操作,Android,的,UI Automator,框架可以实现所有图形界面的操作,只要是人能做的它都能做。,Python,的,uiautomator,库完整实现了这些操作。但是由于一些操作对于开发爬虫来说并没有什么作用,例如双指缩放屏幕、旋转屏幕等操作。本书对于这些暂时用不到的操作省略。感兴趣的读者可以参阅,uiautomator,的官方文档。,10.2,综合应用,10.2.1,单设备应用,设想这样一个场景,你需要每天晚上,23,点给女朋友发送一条晚安微信,但是那个时间你可能正在打游戏,忘记发微信了。,1,微信自动发信器,使用,Python,的,uiautomator,,可以做一个自动发送微信的程序,每天晚上,23,点自动打开微信,打开女朋友的聊天窗口,从数据库里随机选择一条晚安短信,并自动发送出去。,这个,自动发送微信的程序应该具备如下的功能,。,(,1,)自动点亮屏幕。,(,2,)自动从桌面点开微信。,(,3,)自动从微信最近联系人列表中找到女朋友。,(,4,)自动点开聊天窗口,输入内容。,(,5,)自动发送聊天内容。,图,10-29,在没有输入内容时,,微信没有“发送”按钮,为了,能够简化代码,需要首先去掉手机的屏锁,保证一点亮屏幕就能直接进入桌面。虽然,Python,的,uiautomator,是可以操作屏幕实现自动滑动解锁,甚至直接输入密码或者自动找出图像解锁密码的,但是这样会增加工作量,反而不划算。,一般,在,Android,的系统设置中,在“屏锁、密码和指纹”或者“安全设置”里面可以找到去除屏锁的选项。,去除,屏锁以后,使用,USB,与计算机保持连接。这样准备工作就做好了。,点,亮屏幕以后,程序应该要去寻找微信图标,如果当前屏幕页找不到,那么就左右滑动屏幕,直到找到为止。对于小米手机的系统来说,所有图标都是直接摆放在桌面上的,直接左右拖动就能找到。而另外大部分,Android,手机,都有一个“抽屉”的功能,很多,App,都要进入抽屉以后才能找到。对于这种情况,建议把微信的快捷方式添加到桌面,这样可以减少进入抽屉的步骤。,进,入微信以后,寻找名字为“女朋友”的微信号,为了防止女朋友随时改微信名字导致程序失效,建议给女朋友的微信添加一个备注名。之后就直接用这个备注名来寻找。如果当前屏幕找不到,那就向下滚动屏幕,直到找到为止,。,进入,聊天窗口以后,找到输入框,输入文本并单击发送按钮就是很常见的操作了,。,唯一,需要注意的是,对于微信聊天界面,在初始没有输入文字的时候,是没有“发送”按钮的,只有一个表情按钮和一个“,+,”号,如图,10-29,所示。,2,创建,Mac OS,与,Linux,定时任务,对于,Mac OS,与,Linux,,可以使用,crontab,来进行定时。,首先,确定自己的,Python,所在的位置。打开终端,输入,which python,,屏幕上会出现,Python,的路径,如图,10-30,所示。,图,10-30,查看当前,Python,的位置,将,这个路径复制下来,并以下面这个格式写到程序的第,1,行,如图,10-31,所示。,#!Python路径,在,终端进入这个程序所在的文件夹,为这个程序添加可执行权限:,chmod+x example_automatic_wechat.py,这样,操作以后,只要在终端进入这个,.py,文件所在的文件夹,并输入,./example_automatic_wechat.py,,就可以运行这个程序。,图,10-31,将,Python,的路径添加到程序的第,1,行,权限,添加好以后,就可使用,crontab,来设置定时执行了。在终端输入,crontabe,,打开,crontab,的编辑界面。如果是第一次使用,crontab,,那么打开以后里面应该是空白的,如图,10-32,所示。,图,10-32,第一次使用,crontab,,,打开以后应该是空白,在,英文输入法的状态下,按下键盘的,i,键进入输入模式,此时左下角会出现“,-INSERT-,”。输入以下内容:,0 23*/Users/kingname/book/chapter_10/program/example_automatic_wechat.py,注意,,把文件路径修改为实际路径。输入完成以后,如图,10-33,所示。,图,10-33,创建一条定时任务在每天,23,点运行程序,输入,完成以后,按,Esc,键,确保左下角的“,-INSERT-,”消失。然后按,Shift+:,组合键,输入英文冒号再输入,wq,,这,3,个字符会出现在左下角,如图,10-34,所示,。,按下,Enter,键,定时任务就保存好了。这样每到晚上,23,点,0,分,程序就会自动打开微信发送晚安信息了。,图,10-34,输入,:wq,3,Windows,创建定时任务,要,在,Windows,中创建定时任务,首先需要在,example_automatic,_ wechat.py,文件所在的文件夹下面创建一个,run_automatic_wechat.txt,文件,在文件里面输入,python3 example_automatic_wechat.py,后保存,并把文件名,run_automatic_wechat.txt,改为,run_automatic_wechat.bat,。此时,如果双击这个,run_automatic_wechat.bat,,应该可以看到自动发微信的程序成功运行,。,打开,CMD,,输入以下命令来创建定时任务,使程序在每天的,23,点自动运行:,schtasks/create/tn run_automatic_wechat/tr C:run_automatic_wechat/sc DAILY/st 23:00:00,10.2.2,多设备应用,(群控),1,实现原理,使用,uiautomator,来做爬虫开发,最主要的瓶颈在于速度。因为屏幕上的元素加载是需要时间的,这个时间受到手机性能和网速的多重限制。因此比较好的办法是使用多台,Android,手机实现分布式抓取。,使用,USBHub,扩展计算机的,USB,口以后,一台计算机控制,30,台,Android,手机是完全没有问题的。只要能实现良好的调度和任务派分逻辑,就可以大大提高数据的抓取效率。,2,另一种多线程,在,第,4,章介绍了一种比较简单的实现多线程的方式,即使用,multiprocess.dummy,来实现。那种方式的代码看起来非常简单,但是不方便做各种定制化操作。因此本小节将会介绍,Python,中另一种实现多线程的办法,threading.Thread,。,为了,便于管理和调度各个子线程,将子线程定义为一个类是比较好的做法,每台手机对应一个线程。而要将子线程定义为一个类,就需要这个类继承于,threading.Thread,。,一,个做线程的类,其代码结构如下:,class PhoneThread(threading.Thread):,def _init_(self):,threading.Thread._init_(self),def run(self):,pass,这,段代码实现了一个什么事情都不做的子线程类。其中,在“,._init_(),”初始化方法的第一行必须有,threading.Thread._init,_(,self),,否则会导致程序报错。“,.run(),”方法是子线程的入口,启动子线程后,它会从,run(),方法开始执行。,要,启动一个子线程,需要先将它初始化为一个实例,然后,调用,.,start(),方法。例如:,phone=PhoneThread(),phone.start(),当,调用,.start(),方法以后,子线程类里面的,.run(),方法中的代码就会被运行。,需要,注意的是,一个子线程实例的生命只有一次。一旦它里面的代码被运行完成,生命就结束了,这个实例就再也不能用了,只能重新创建新的子线程实例。通过子线程实例的“,.is_alive(),”方法可以查看它是否还活着,如图,10-35,所示。,图,10-35,判断子线程是否还活着,为了,防止子线程提前死掉,就需要将子线程实现功能的代码放到,while,循环里面,让它一直不停地工作,只有工作完成以后才允许它死掉。,由于,子线程是被主线程启动的,因此必须要让主线程在所有子线程都结束以后才能结束。否则,一旦主线程运行完成,所有子线程都会被强行关闭。因此,在主线程启动了所有的子线程以后,必须“做点什么事”来等待,例如轮询检查是否所有的子线程都结束了,如图,10-36,所示。,图,10-36,主线程轮询检查是否所,有的子,线程都结束了,在,图,10-36,所示的这段代码中,由于子线程会随机睡,10,500s,,主线程不知道它们什么时候才会全部结束。因此主线程要在每,60s,检查一下,10,个子线程的状态,直到发现所有的子线程都结束了,主线程才能跳出,while,循环。,3,应用实例,整个,系统的架构可以设计成图,10-37,所示的形式。,主,线程负责启动子线程,读数据库,得到需要手机处理的各种目标,并将目标存放在,Redis,里面,。,手机,子线程循环不断地从,Redis,中读取目标,在手机上面抓取并将原始数据保存到,Redis,中。另一个负责数据处理的子线程负责将手机抓取的原始数据清洗并放入数据库中,。,这里,使用一个例子来演示这个架构的使用。使用两台,Android,手机,从,TapTap,上面抓取,20,个游戏的安装量或者预订量。,首先,创建一个文本文件,target,,用来保存,20,个目标游戏的名称。当然读者也可以使用数据库来保存。这,20,个游戏的名称如图,10-38,所示。,图,10-37 App,爬虫系统架构的形式,图,10-38,20,个目标游戏名称,正常,情况下,在搜素框输入内容并单击搜索图标即可出现结果,如图,10-39,所示。,但是,在少数情况下,可能由于网速问题或者手机性能问题,在点了搜索图标以后,并没有出现搜索结果,如图,10-40,所示。,图,10-39,正常情况下直接搜索,出结果,图,10-40,点了搜索图标却没有反应,匹配,游戏名字的时候,用的是“,textContains=game_name,”而不是“,text=name,”。,textContains,的意思为文本包含某些字符即可。这是因为有一些游戏在名字的前后会有特殊符号,例如炉石传说,名字上多了书名号,显然“炉石传说不等于炉石传说”,但是“炉石传说包含炉石传说”,如图,10-41,所示。,图,10-41,炉石传说的搜索结果包含书名号,在,主线程中调用子线程的代码如下:,for serial in serial_list:,phone_thread=PhoneThread(serial),phone_thread.start(),phone_list.append(phone_thread),while phone_list:,phone_list=x for x in phone_list if x.is_alive(),time.sleep(5),通过,一个,for,循环创建多个,PhoneThread,的实例,每一个对应于一个手机串号,。,创建,完成以后调用,.st
展开阅读全文