使用Python爬取GooglePlay并从复杂的自定义数据结构中实现解析

文章目录

【作者主页】:吴秋霖
【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作!
【作者推荐】:对JS逆向感兴趣的朋友可以关注《爬虫JS逆向实战》,对分布式爬虫平台感兴趣的朋友可以关注《分布式爬虫平台搭建与开发实战》
还有未来会持续更新的验证码突防、APP逆向、Python领域等一系列文章

  说到GooglePlay,自定义的数据结构,解析起来真的是让人感觉到窒息。而且基本是每间隔一段时间就会稍微的发现变动,解析规则基本持久不了太久可能就会失效,不过都是一些细微的变动,不值一提~

GooglePlay是没有对外提供任何API的,想要爬取相关的数据就需要通过Web端的方式,Git上面也有国外的大佬开源了google-play-scraper,Python跟JS版本的我记得都有,直接导包调用

但是稳定性不够好,也是基于Web端去爬取解析的,一旦结构发生变化,作者维护不够及时的话,自然也就无法使用

之后参考开源的项目,自己重新实现了数据抽取那一块逻辑,并将解析服务部署在了境外服务器上,然后通过远端调用的方式去解析

在这里插入图片描述

在我之前的Cloudflare反爬虫防护绕过文章中,就是以一个第三方的APK下载网站为示例进行的讲解,GooglePlay的APK包如果是在官方网站下载的话会比较麻烦,直接可以用我提到的那个第三方网站取下载就行

主要爬取内容就是APP应用的相关描述信息,如下图所示:

在这里插入图片描述

在点击关于此应用的时候,抓包可以看到重点数据集都嵌套在了HTML源代码中,使用JS函数定义了AF_initDataCallBackdata参数就是加载的数据

在这里插入图片描述

尝试将数据拷贝出来,不过发现太长了,截图都放不下,图片都给整裂开了!反正就是巨长再加上多层嵌套,需要拆解慢慢去写解析规则!

接下来,先实现对data数据的提取,实现代码如下所示:

async def extract_json_block(html, block_id):prefix = re.compile(r"AF_init[dD]ata[cC]all[bB]ack\s*\({[^{}]*key:\s*'" + re.escape(block_id) + ".*?data:")suffix = re.compile(r"}\s*\)\s*;")try:block = prefix.split(html)[1]block = suffix.split(block)[0]except IndexError:raise PlayStoreException("Could not extract block %s" % block_id)block = block.strip()block = re.sub(r"^function\s*\([^)]*\)\s*{", "", block)block = re.sub("}$", "", block)block = re.sub(r", sideChannel: {$", "", block)return block

在这里插入图片描述

如上就是提取出来的数据,拿到数据以后,如果你是小白新手或许会因此放弃,实在是让人头大,现在开始实现解析核心逻辑代码,如下所示:

app_detail_ds_block = 'ds:7'
app_details_mapping = {'title': [app_detail_ds_block, 1, 2, 0, 0],'developer_name': [app_detail_ds_block, 1, 2, 68, 0],'developer_link': [app_detail_ds_block, 1, 2, 68, 1, 4, 2],'price_inapp': [app_detail_ds_block, 1, 2, 19, 0],'category': [app_detail_ds_block, 1, 2, 79, 0, 0, 1, 4, 2],'video_link': [app_detail_ds_block, 1, 2, 100, 1, 2, 0, 2],'icon_link': [app_detail_ds_block, 1, 2, 95, 0, 3, 2],'num_downloads_approx': [app_detail_ds_block, 1, 2, 13, 1],'num_downloads': [app_detail_ds_block, 1, 2, 13, 2],'published_date': [app_detail_ds_block, 1, 2, 10, 0],'published_timestamp': [app_detail_ds_block, 1, 2, 10, 1, 0],'pegi': [app_detail_ds_block, 1, 2, 9, 0],'pegi_detail': [app_detail_ds_block, 1, 2, 9, 2, 1],'os': [app_detail_ds_block, 1, 2, 140, 1, 1, 0, 0, 1],'rating': [app_detail_ds_block, 1, 2, 51, 0, 1],'description': [app_detail_ds_block, 1, 2, 72, 0, 1],'price': [app_detail_ds_block, 1, 2, 57, 0, 0, 0, 0, 1, 0, 2],'num_of_reviews': [app_detail_ds_block, 1, 2, 51, 2, 1],'developer_email': [app_detail_ds_block, 1, 2, 69, 1, 0],'developer_address': [app_detail_ds_block, 1, 2, 69, 2, 0],'developer_website': [app_detail_ds_block, 1, 2, 69, 0, 5, 2],'developer_privacy_policy_link': [app_detail_ds_block, 1, 2, 99, 0, 5, 2],'data_safety_list': [app_detail_ds_block, 1, 2, 136, 1],'updated_on': [app_detail_ds_block, 1, 2, 145, 0, 0],'app_version': [app_detail_ds_block, 1, 2, 140, 0, 0, 0]
}async def find_item_from_json_mapping(google_app_detail_request_result, app_detail_mapping):ds_json_block = app_detail_mapping[0]json_block_raw = await extract_json_block(google_app_detail_request_result, ds_json_block)json_block = json.loads(json_block_raw)return await get_nested_item(json_block, app_detail_mapping[1:])

app_details_mapping则是解析数据的核心,索引基本上变动较小!因为数据都是在list中多级嵌套,所以需要花费一点精力时间去分析,app_detail_ds_block前段时间我记得是ds:5,这个倒是会偶尔变动

class PlayStoreException(BaseException):def __init__(self, *args):if args:self.message = args[0]else:self.message = Nonedef __str__(self):if self.message:return "PlayStoreException, {0}".format(self.message)else:return "PlayStoreException raised"class GooglePlayStoreScraper(object):def __init__(self):self.PLAYSTORE_URL = "https://play.google.com"self.PROXIES = {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}async def _app_connection(self, url, sleeptime=2, retry=0):for _ in range(retry + 1):try:async with aiohttp.ClientSession() as session:async with session.get(url, proxy=self.PROXIES) as response:return await response.text()except aiohttp.ClientError:if sleeptime > 0:await asyncio.sleep(sleeptime)raise PlayStoreException(f"Could not connect to : {url}")async def get_nested_item(self, item_holder, list_of_indexes):index = list_of_indexes[0]if len(list_of_indexes) > 1:return await get_nested_item(item_holder[index], list_of_indexes[1:])else:return item_holder[index]async def get_app_details(self, app_id, country="nl", lang="nl"):url = f"{self.PLAYSTORE_URL}/store/apps/details?id={quote_plus(app_id)}&hl={lang}&gl={country}"request_result = await self._app_connection(url, retry=1)app = {'id': app_id, 'link': url}for k, v in app_details_mapping.items():try:app[k] = await self.find_item_from_json_mapping(request_result, v)except PlayStoreException:raise PlayStoreException(f"Could not parse Play Store response for {app_id}")except Exception as e:self._log_error(country, f'App Detail error for {app_id} on detail {k}: {str(e)}')app.setdefault('errors', []).append(k)app['developer_link'] = self.PLAYSTORE_URL + app.get('developer_link', '')app['category'] = app.get('category', '').replace('/store/apps/category/', '')if 'data_safety_list' in app:app['data_safety_list'] = ', '.join(item[1] for item in app['data_safety_list'] if len(item) > 1)soup = BeautifulSoup(request_result, 'html.parser')list_of_categories = ', '.join(', '.join(category.text for category in element.find_all('span')) for element in soup.find_all('div', {'class': 'Uc6QCc'}))app['list_of_categories'] = list_of_categories if list_of_categories else app.setdefault('errors', []) + ['list_of_categories']if 'errors' in app:plural = 's' if len(app['errors']) > 1 else ''app['errors'] = f"Detail{plural} not found for key{plural}: {', '.join(app['errors'])}"return app

在完成上面爬虫程序核心入口的实现以后,基本上用采集到数据解析都已经完成,只需要调用get_app_details函数,传人需要爬取的目标APP的包名即可爬取并解析数据,如下所示:

在这里插入图片描述

  好了,到这里又到了跟大家说再见的时候了。创作不易,帮忙点个赞再走吧。你的支持是我创作的动力,希望能带给大家更多优质的文章

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/236138.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理综合3

41、计算机操作的最小时间单位是__________。A A. 时钟周期 B. 指令周期 C. CPU周期 D. 外围设备 42、微程序控制器中,机器指令与微指令的关系是__________。B A. 每一条机器指令由一条微指令来执行 B. 每一条机器指令由一段用微指令编成…

在做题中学习(35):判断字符是否唯一

面试题 01.01. 判定字符是否唯一 - 力扣(LeetCode) 思路:1.用哈希表(创建另一个数组存储)然后和原数组一一比对。 时间复杂度O(N) 空间复杂度 O(N) 2.位图&#xff08…

使用物理机的burpsuite抓取虚拟机的请求包(虚拟机代理配置)

关于burpsuite抓取本地浏览器的请求包大家应该都会配置吧 我也是第一次配抓取虚拟机的包,最开始遇到了些问题,这里简单给大家分享一下 下面以Windows系统下的Firefox浏览器为例: 首先我还是先添加了一个小狐狸(foxyproxy&#…

ubuntu保存分辨率失效解决办法

在VM虚拟机中,遇到修改ubuntu分辨率后,重启后又重置的解决办法。 目前我的ubuntu版本是:ubuntu 18.04.6 版本。 1.首先,在你喜欢的目录建立一个.sh 脚本文件。 终端执行命令:sudo vim xrandr.sh 2.按 i 进入编辑状…

神经网络:优化器和全连接层

SGD(随机梯度下降) 随机梯度下降的优化算法在科研和工业界是很常用的。 很多理论和工程问题都能转化成对目标函数进行最小化的数学问题。 举个例子:梯度下降(Gradient Descent)就好比一个人想从高山上奔跑到山谷最低…

【51单片机系列】C51中的中断系统扩展实验

本文是关于51单片机中断系统的扩展实验。 文章目录 一、 扩展实验一:使用外部中断0控制蜂鸣器,外部中断1控制直流电机二、扩展实验二:修改定时器初值,设定3秒钟的定时时间让LED模块闪烁三、扩展实验三:使用定时器1和数…

华为OD机试 - 区间交集 - 深度优先搜索dfs算法(滥用)(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述备注用例1、输入2、输出3、说明 四、解题思路1、核心思路:2、具体步骤 五、Java算法源码再重新读一遍题目,看看能否优化一下~解题步骤也简化了很多。 六、效果展示1、输入2、输出3、说明 华为OD机试 2…

Screen记录窗口输出日志

screen是Linux窗口管理器,用户可以建立多个screen会话,每个screen会话又可以建立多个window窗口,每一个窗口就像一个可操作的真实的ssh终端一样。 screen详解:http://www.linuxidc.com/Linux/2013-10/91612.htm Linux Screen超简…

C++_动态二维数组的两种方法

介绍 本文主要介绍使用 动态二维数组的两种方法 (PS:仅作创建 动态二维数组参考,详细使用方法根据需求自行改变) 第一种:连续存储结构的 二维动态数组(需固定 列 大小,可通过下标访问) 缺点: 1.需要在设计二维数组前写死 列 的大小 2.空间利用率不高 优点…

PostgreSQL使用docker部署,设置密码失效问题处理

文章目录 问题描述使用场景排查解决修改已有的文件卷使用SQL初始化 问题描述 PostgreSQL使用docker虚拟化部署,使用docker-compose管理,配置了密码部署在客户现场时,客户的安全扫描,反馈测到PostgreSQL数据库弱密码漏洞查看docke…

神经网络学习小记录76——Tensorflow2设置随机种子Seed来保证训练结果唯一

神经网络学习小记录76——Tensorflow2设置随机种子Seed来保证训练结果唯一 学习前言为什么每次训练结果不同什么是随机种子训练中设置随机种子 学习前言 好多同学每次训练结果不同,最大的指标可能会差到3-4%这样,这是因为随机种子没有设定导致的&#x…

网络安全:专科及普通本科的温柔乡

当代普通大学生的现状是卷又卷不过、躺又躺不平,把大把的青春都荒废在了思考我应该做什么才能有前途的问题上面。当然,这里说的是那些普通学历且对自己的职业生涯甚至是人生没有规划的大学生,包括专科、普通一本二本,并非985、211…

MySQL-2

复习 1. Data数据–>DB数据库–>DBMS数据库管理系统常见DBMS: MySQL oracle sql server db2 … redis Mongodb两大功能: 定义DDL 操纵DML 2. 表table创建表, 行和列 3. MySQL数据类型数据类型分成三大类:数值型、字符型、日期时间类4. 关于列属性…

【JVM基础】 JVM 如何加载一个类以及类加载机制

文章目录 1、什么时候一个类会被加载?1、包含 main 方法的主类2、非 包含 main 方法的主类,什么时候去加载? 3、类加载器如何加载一个类?1、验证阶段:2、准备阶段:3、解析阶段:4、初始化&#x…

PHP开发日志——循环和条件语句嵌套不同,效率不同(循环内加入条件语句,条件语句判断后加入循环,array_map函数中加入条件语句)

十多年前开发框架时,为了效率不断试过各种代码写法,今天又遇到了,想想php8时代会不会有所变化,结果其实也还是和当年一样,但当年没写博客,但现在可以把数据记录下来了。 PHP_loop_ireflies_dark_forest 项目…

20231220将NanoPC-T4(RK3399)开发板的Android10的SDK按照Rockchip官方挖掘机开发板编译打包刷机之后启动跑飞

20231220将NanoPC-T4(RK3399)开发板的Android10的SDK按照Rockchip官方挖掘机开发板编译打包刷机之后启动跑飞 2023/12/20 17:19 简略步骤:rootrootrootroot-X99-Turbo:~/3TB$ tar --use-compress-programpigz -xvpf rk3399-android-10.git-20210201.tgz rootrootro…

递归算法:二叉树前序、中序、后序遍历解析与递归思想深度剖析

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《linux深造日志》 《高效算法》 ⛺️生活的理想,就是为了理想的生活! 文章目录 一、二叉树的遍历1.1 链式结构二叉树的创建1.1 二叉树结构图 二、 前序遍历代码演示:2.1 前序遍历递…

苏宁易购商品详情API:电商实时数据

一、引言 在当前的电商行业中,数据是最为宝贵的资源之一。如何获取实时、准确的数据,对于电商业务的运营和优化至关重要。作为中国领先的电商平台之一,苏宁易购提供了丰富的API接口,其中包括商品详情API,以便第三方开…

STL stack练习

CSTL之stack栈容器 - 数据结构教程 - C语言网CSTL之stack栈容器1.再谈栈回顾一下之前所学的栈,栈是一种先进后出的数据结构,而实现方式需要创建多个结构体,通过链式的方式进行实现,这是标准的栈的思路,而在STL中栈可以…

一键在线获取APP公钥、包名、签名及备案信息方法介绍

​ 目录 一键在线获取APP公钥、包名、签名及备案信息方法介绍 摘要 引言 一键获取APP包信息 操作步骤 ​编辑 解析报告 总结 致谢 关键词 参考资料 声明 摘要 本文介绍了一款在线APP解析工具,可以一键获取APP的公钥、包名、签名等基础信息,…