严正声明

本文仅用于记录爬虫技术研究学习,不供给爬取脚本,所爬数据已删除,读者用于不合法用途形成损失,与本文无关。爬虫技术自身并无违法违规之处,爬什么,怎么爬才是导致锒铛入狱的元凶巨恶。

0x0、导言

感觉间隔前次发文现已过了好久,成果一看才25天,得益于 娃的呱(gū)呱坠地,每天的时刻被延长了许多。当然期间最深的感触莫过于:上班比在家奶娃要轻松太多

知道杰哥的读者都知道,Python实战类的文章创意都来源于生活,休完陪产假没多久,素材就来了。一位 资深产品兼原创音乐人 朋友在群里@我:

秒懂,便是想搞多点数据,恰逢金三银四,杰哥也想了解下Android岗位的行情,干脆就折腾一下~


0x1、思路一:浏览器模仿拜访

翻开招聘站点,查找 “产品司理”,滑动到底部发现公然只要10页:

测验切到第2页,页面URL发生改变:

https://xxx/web/geek/job?query=产品司理&city=101280600&page=2

测验把page改成11,能恳求,但页面数据和第10页是相同的,公然 一次查找成果最多搞到300条数据

不过留意,它是 条件查找,这个条件能搜300条,换个条件又能搜到300条。

换而言之,咱们能够经过 设置不同的条件,来收集更多的数据,留意网页顶部的 修改城市和区域

很显着,咱们只要覆盖尽可能多的 城市-区-地名 组合,就能够收集到大量数据。接着便是批量爬取这三层条件的可选值。F12翻开 开发者东西 开始抓包,对应的接口都很好抓,顺带写出爬取代码,先是 → 城市编码

https://xxx/wapi/zpCommon/data/cityGroup.json

然后到 → 区编码和地名编码

https://xxx/wapi/zpgeek/businessDistrict.json?cityCode={}

能够,万事俱备,只欠拿这些参数去批量调 查找岗位的接口 了:

https://xxx/wapi/zpgeek/search/joblist.json?scene=1&query=产品司理&city={}&experience=&degree=&industry=&scale=&stage=&position=&jobType=&salary=&multiBusinessDistrict={}:25&multiSubway=&page=1&pageSize=30

正当我认为能够挂机去泡杯茶喝喝,成果一运转:

笑死,调这个接口直接触发站点的 反爬,测验处理:

  • 复制粘贴浏览器恳求的一切 恳求头,设置到requests中 → 没用
  • ip拜访次数约束?上 署理地道没用,淦,我还认为设置署理没生效;
  • Cookies问题?复制浏览器恳求里的Cookies塞requests中 → 没用

又细看了一下,发现是 Cookies加密,每次恳求Cookies中的 zp_stoken 都是改变的。

又试了下 M端接口,相同会触发反爬,逆向js太磨人,朋友要得也急,木得时刻慢慢玩破解,直接上传统艺能 浏览器自动化。无脑 pyppeteer 模仿,先跳转登录页,预留满足的时刻登陆:

登陆完就能够关页面了,这一步主要是让浏览器处于 登录态,接着便是 拼接url拜访获得页面源码提取数据

爬取后的数据:

遍历文件核算下一共爬取到多少条:

18663条,还凑合,不过混进来一些 脏东西

需求对数据进行清洗,这儿判别职位包含”**产品”**字眼为有效数据,朋友说最好是Excel,帮人帮到底:

运转后一共输出有效数据13928条,一起区分城市,写入到excel中~

时刻关系,只爬取到 ,并且只包含了北上广深杭的数据,不过朋友说够用了,就先酱吧~


0x2、思路二:手机adb模仿拜访

其实中途还想过从移动端下手,抓了一波包:

2333,接口加密 + 回来数据加密,最便捷的爬取思路仍是自动化啊,在《杰哥带你玩转Android自动化》 中提到过:

一切Android自动化结构和东西中 操作Android设备的功用实现 都基于 adb无障碍服务AccessibilityService

这儿直接用adb来模仿,思路也很简单:无限向上滑动,一起解析布局xml提取所需数据

先写个导出当时布局xml的东西代码:

接着写个依据resourc_id递归,获取目标结点的办法:

翻开导出的布局xml:

能够看到结点对应的资源id,岗位列表 → xxx:id/recyclerView_list,岗位称号 → xxx:id/tv_position_name。

接着干嘛?加上死循环滑动+解析xml提取数据,一步到位?兄嘚想多了,有个难搞的问题:

如何精确操控Recyclerview滑动间隔?

怎么说?

你得保证每次滑动精确滑动到 下三项,并且每一项都要 显示完好,由于 uiautomator dump 出的是当时界面的xml。如果滑动到的方位不对,就会呈现这种缺失结点的错误数据:

滑太多又会丢掉数据,看了一圈没有找到核算RecyclerView滑动间隔的公式,想搞清楚,估量得去啃Recyclerview的源码。

这儿笔者取下巧:每次滑动尽量少的间隔 (不超越一项) + 完好数据校验 (节点数>10) + 结点去重(key:岗位+薪资),直接肝出代码:

挂机午睡,睡醒发现脚本停了,一看数据量:

擦,APP端也约束了查找成果的条数,只能查询30页,并且总数据还不够300条。

想收集更多数据,能够像思路一相同,经过切换不同的 查找条件 来达到,检测到数据好久没增加了,就模仿点击切换城市:


0x3、思路三:Xposed Hook

说实话,思路二这种模仿拜访解析xml的办法不太靠谱,并且还 。咱们能够换个角度:

恳求回来的数据是 加密 的,但设置到UI控件上的数据是 解密 过的。

所以最简单的思路便是 → Hook UI控件设置数据的办法,拿到解密过的数据

看了下APK,没加密,直接 jadx反编译,adb获取当时页面的包名和类名:

定位到 GeekSearchActivity

结合APP页面,不难看出 GeekSearchResultFragment 用于显示查找成果,跟下:

SearchPositionFragment 显着用于显示查找到的岗位,测验定位布局xml,搜 R.layout. 没找着,看来是做了 资源混淆

在Fragment初始化布局,一般会把代码写到 onCreateView() 中,但没在这个类里找到,估量是在父类里进行了封装,跟一下:SearchBaseFragmentBaseAwareFragmentLazyLoadFragment

吼,定义了抽象办法 getLayoutResId() 用于设置布局,回到 SearchPositionFragment 搜下这个办法:

翻开布局文件:

定位到了Recyclerview,跟上面咱们adb导出的xml id一致,哈哈,从控件资源id着手定位目标代码也是一个小技巧。

Recyclerview设置数据,肯定是经过 Adapter,搜一下,发现了 SearchPositionAdapter,不过源码中没找到设置数据的办法,又是封装了好几层。

溯源后发现,顶层父类是 com.chad.library.adapter.base.BaseQuickAdapter,哈,这不便是开源库CymChad/BaseRecyclerViewAdapterHelper 么?这个库设置数据的办法很简单 setNewData()addData(),搜一下发现这儿调用到了:

Hook这两个办法,就能够拿到数据啦,这儿要留意一点:

由于子类SearchPositionAdapter并没有重写这两个办法,调用的是父类办法,所以需求 Hook父类

直接写出Hook代码:

运转后翻开招聘软件,能够看到陆续输出一些日志,毕竟Hook的是父类,简直一切列表设置数据都会调这两个办法。

来到查找页,输入查找词查找,上拉加载更多,能够看到岗位相关的数据被打印出来:

但都是自定义数据类型,咱们需求的是它里边岗位信息的字段,直接定位 SearchPositionItemModel

显着具体数据类型是这个泛型父类:

data是父类的私有特点,这儿经过 反射 来获取,需求修改下拜访权限:

运转后重复之前的操作,发现还套了一层:

翻开 SearchPositionBean

嗯哼,这便是岗位信息相关的字段,能够按需反射获取,也能够直接遍历获取,这儿直接打印出来:

运转操作后的输出日志:

能够看到具体的岗位信息了,但仍旧有嵌套的Bean,按上面的办法还得翻开对应Bean的源码,抠字段,有些蠢了。完全能够写一个 递归获取字段的办法,这儿顺带将输出信息组合成Json的形式,方便收集到的数据保存。

运转操作后的输出日志:

看着还不是很规整,不过Copy到Json格式化东西是没问题的:

如果有数据收集需求,调个第三方json库格式化下,然后 导出文件 或许 调用自己写的数据上传接口(推荐,直接入库乐滋滋)

接着处理第二个问题 → 单次查找成果30页约束,从哪里下手呢?现象:

列表滑动到底部,触发上拉加载Loading,然后列表添加数据,当加载到30页后,直接不显示上拉Loading。

跟下这个上拉加载的组件:ZPUIRefreshLayoutcom.scwang.smart.refresh.layout.SmartRefreshLayout

哟,这不是改写库么 scwang90/SmartRefreshLayout,老朋友了,它供给了一个禁用上拉加载的API:

refreshLayout.setEnableLoadMore(false); //是否启用上拉加载功用

由于SmartRefreshLayout自带混淆,所以没法直接搜这个办法,先找到控件实例:SearchPositionFragmentSearchBaseFragment

接着回到 SearchPositionFragment,敞开 正则查找,搜 this.d.*?(false)

就两个匹配成果,很显着是第一个,判别 geekSearchCardResponse == null 就禁用上拉加载,而它是由 gVar.f41043b 赋值的,参数类型是 g,跟下:

哦吼,不知道都是些啥,hook下这个办法,把入参的字段打印出来:

运转后,第一次加载和上拉加载更多输出日志如下:

不难看出:a → 当时页数,e → 数据总条数,每次加载15条数据。

滑动到30页时,b这个字段为空,有两种可能:后台真的没有回来 或许 后台有回来但客户端做了约束

我在原先打日志的基础上,又加了判空,到第30页时,我发现有数据:

搜了下<30,<=30,<31,<=31,>30 都没匹配到想要的成果,em…不知道具体做了什么操作。

那就粗暴点,直接Hook SmartRefreshLayout#b(Boolean),把入参设置为true,即:制止关闭上拉加载

接着adb无限滚动:

能够数据不止30页,看了下数据也没有重复,问题二处理~

非常简单,不过本节也到这了,读者感兴趣的话,能够自己试着往下扒,比如:恳求的结构规则响应数据的解密 等,谢谢~