我爱编程

android爬虫

2018-03-31  本文已影响318人  若兮生生

此文目的,多年后当我回忆起往事,再看此文,或许能重燃我的斗志。

作为android开发,爬虫一直是服务器同事做的。我们都使用java语言,既然他行,那我也可以试试。

当时是这样考虑的:从地址栏中得到网络链接,利用retrofit请求数据,返回数据的格式应当是xml,我再用dom解析xml得到data,然后将data保存到数据库,最后打开数据库的表将数据复制到excel中。程序运行于android 模拟机上,文件保存于sd卡。这样的思路虽然麻烦,但整个看下来还是行得通的。

对于android来说,请求网络如同家常便饭,volley,okhttp,retrofit等这些框架用起来不要太好。数据解析有json,fastjson,gson,更有一些插件,做起来也不费吹灰之力。对于爬虫来说,面对的困难仍有三个,第一个是网页分析,毕竟不是做pc开发的,对js、jqury、ajax等这些前端语言了解较少,只初略的了解css,要想找到特定的url以及相应参数及其调用方式还是存在难度;第二个是网页解析,获取url及相应参数。json解析倒是常用,dom解析虽然之前做过,但之前xml的复杂度远不如当前,想着解析的难度,成功解析所需的时间让我吃不准;第三个是将数据复制到excel表格中,虽然litpal数据库整个操作也不难,我也自信只要获取到数据便能写入数据库,而我不确定的是,litpal数据库的表能直接复制到excel中吗?

虽然有疑问,虽然也预估一些难度,但既然方案是可行的,那便可以尝试尝试了。

首先分析目标页面: 1522318655(1).jpg

我需要获取联系人、联系电话、电子邮箱三项。这些数据都在当前页面中,所以我可以get请求,查看当前页数据。于是使用postman软件,查看返回的数据如下

1522380116(1).jpg

当然这样不便查看,可以查看预览模式


1522379291(1).jpg

惊讶的发现,联系人、电话、电子邮箱这些信息竟然都是空的。这就意味着他是动态加载这些信息的。

使用google浏览器,按F12,可以查看页面代码信息。 1522377182(1).jpg
F5刷新之后,我们看到最后一条耗时较多,是明显的网络请求。 1522379631(1).png

我们可以点开,查看他的请求方式及返回数据。


1522380626(1).png 1522380409(1).jpg

果真如所料,这里面返回的数据是json格式,而内容正是我们爬的信息。我们将他的Request URL和Request heads放于postman测试一下。


image.png

到这一步,可以高兴的说,我们已经掌握单条数据的获取规律,其他数据不过是照葫芦画瓢,抓取到所有数据指日可待。
现在的问题就变成:Request URL如何拼凑。

http://www.kq30.com/wap/index/clx/lu/kq10352619608/cjo/39108/fl/27/?_=1522379555582&cl=0.8682621277798113

想查看如何拼凑,先找到哪里调用。“http://www.kq30.com”这是hostname不用管,我们可以在Element页面,ctrl+f全局搜索一下后面几个路径,比如“/wap/index/clx/lu”,

1522467770(1).jpg

很清晰看到有$.getJson(),可以猜测这里是请求json数据,紧接着是两个变量lu和cjo。而这两个值来源于

var lu=$(".cxd").text(); var cjo=$(".cjo").text();

其中cxd和cjo对应的值正是下方的div节点中。

<div class="cxd">kq6257235281</div>
<div class="cjo">37068</div>

至于后面的“{cl:Math.random}”,猜测就是获取一个随机数,应该没啥影响。

经过以上的分析,我们拼凑request url的问题就变为:解析返回的html,获取节点cxd和cjo对应的值。

至于如何解析返回的html,当然不是dom解析了,因为我在搜索html解析的时候,发现了特别好用的Jsoup解析。Jsoup人家自然鼎鼎大名了,只是我孤陋寡闻今天才第一次结识。

gradle 的dependencies闭包中加入下面这条依赖就行。我使用的是kotlin,故而是“implementation ”而不是“compile”

implementation 'org.jsoup:jsoup:1.11.2'

在使用时,也是极其简单,他内部使用HttpConnection封装了网络请求,以下示例是kotlin语句。

   val newDoc = Jsoup.connect(newUrl).get()
   val lu = newDoc.select("div.cxd").html()
   val cjo = newDoc.select("div.cjo").html()

既然已经拿到lu和cjo这两个关键的参数,表明详情页我们已经破解,那接下来的对手就是列表页了。我们要从列表页找出每一行对应的连接,分析其是否存在规律。

1522469746(1).jpg 按下f12,点击左上角箭头,将鼠标移动到列表的一个条目上点击,会弹出对应的代码 1522470543(1).jpg

可以看出,在class="kqb"节点下,有许多的<li class="...">这样的节点,在其子节点下<a href="...">的值就是我们想要的url.

可以看出这些url并无规律,所以我们使用一个list集合来保存这些url.
我们继续使用Jsoup解析列表网页。

 var doc = Jsoup.connect("http://www.kq30.com/zp/ci/kz/37/di/2/p/" + i + "/").data("Referer", "http://www.kq30.com/").get()
 val element = doc.select("div.kqb")
 val link = element.select("a").eachAttr("href")

上述代码中,url中的i代表第几页,后面的.data(key,value)是request header的参数。我也是无意中发现eachAttr("")返回的竟然是一个list集合,真是大快人心啊。这样我就获取到了每一页中的每一条的url.

当然这些url存在重复和非我们所需的,所以有必要做一些筛选。当然这些属于优化,后面再提。现在我们把前面的思路顺着理一遍,再推出接下来要做的事。

首选通过Jsoup解析列表页面,得到当前页所有的详情页连接,再通过Jsoup解析详情页连接,得到lu和cjo的值,最后通过retrofit请求经过lu和cjo拼凑的url,得到目标json数据。好了,有了json数据,接下来的问题就是保存数据到excel了。

在搜索android 保存数据到excel中,我看到这篇文章“https://blog.csdn.net/linzhenxiang123/article/details/53730439”,知道了有两种方式,一种是jxl,一种是poi.我顺便看了这两种方式的优缺点:

1)、如果你用的是jxl.jar包的话模板的Excel表格必须是97-03版的Excel,否则的话 jxl.jar 是没法进行解析的

2)、如果你用的是poi.jar包的话模板的Excel表格是没有要求

可见poi使用的限制性条件较少,而且我安装的是16版的excel,所以果断使用poi了。
下载poi的jar包,"http://mirrors.hust.edu.cn/apache/poi/release/bin/poi-bin-3.17-20170915.tar.gz"
解压后,将jar包一起复制到android studio 工程项目的libs目录下,并右键add as library。
poi配置完毕,那就是简单的使用了

    val wb = HSSFWorkbook()
    val sheet = wb.createSheet("口腔信息")
    val header = sheet.createRow(0)
          header.createCell(0).setCellValue("称呼")
          header.createCell(1).setCellValue("电话")
          header.createCell(2).setCellValue("qq")
          header.createCell(3).setCellValue("座机号")
//设置行宽,20代表字符
        for (i in 0..header.physicalNumberOfCells) {
            sheet.setColumnWidth(i, 255 * 20)
        }
//保存到手机外部存储
 val path = Environment.getExternalStorageDirectory().absolutePath.toString().trim() + "/info.xlsx"
            val os = FileOutputStream(path)
            wb.write(os)
            wb.close()
            os.close()

由于poi是先保存数据,最后统一写入excel文件中,所以我们每次获取到的json数据就 sheet.createRow(i)。先创建第ii行,接着依次header.createCell(i).setCellValue(value),给第i行第i个单元格赋值。

到现在我们克服重重障碍,从解析网页到把数据写入excel,整个流程算是走通了。虽说解决了大方向的问题,然而仍存在一些小问题。

1.效率低下,经测试发现,爬完第一页数据需要30秒,爬完第二页数据需要1分钟,下一页时间总是上一页加30秒,以此类推时间慢的可怕。很明显,代码中出现了数据重复累加,于是检查代码,每次进入新的一页,都将数据清空。

2.数据重复,数据重复的原因在两方面,一方面是相同的url多次请求,另一方面是不同的url返回相同的数据信息。所以分别对url去重和对phoneNumber去重。因为手机号能够唯一标识一位用户。

3.耗时操作,不断请求网络是一个严重的耗时操作,将数据写入excel也是耗时操作。采用异步那是自然。起初只是在activcity里面执行异步,但activity生命周期经常变动,且爬虫所需时间略长,异步任务运行于后台当内存紧张时,有可能被杀死,所以有必要使用service。

4.锁屏断网,我使用的是vivo真机,当锁屏一分钟左右,应用的网络便会断开,同时会报出SocketTimeoutException。针对这个问题,我并没有找到有效的解决办法。只是让手机屏幕常亮来避免这个问题,当然这样的做法很耗电。

5.kotlin无法使用java1.8,在使用poi保存数据到excel中时,2016版的excel,需要使用XSSFWorkbook()的API,XSSFWorkbook()的API是运行在java1.8的环境中,而kotlin使用的java是1.6,所以很尴尬的在kotlin中无法使用。虽然网上有方法说配置kotlin支持1.8

    kotlinOptions {
        jvmTarget = '1.8'
    }

但我试了之后,并没效果,所以最终只能采用HSSFWorkbook()的API。

爬虫虽然有各种脚本框架,其效率与扩展性不知甩我多远,但自己经过不断尝试,努力实现自己的构思,这种快感又岂是直接使用框架所能带来的。值此一役,除了让我熟悉了网页分析、Jsoup与poi的简单使用,更令我振奋的是:大胆尝试,车到山前必有路。

上一篇下一篇

猜你喜欢

热点阅读