使用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.rhkb.cn/news/221715.html

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

相关文章

天猫数据分析(天猫查数据工具):2023年天猫平台假发行业市场销售数据分析报告

如今,由于人们工作和生活的压力较大,居民脱发问题严重,且脱发群体倾向于80后和90后,逐渐向低龄化发展。除脱发外,在颜值经济的背景下,人们越来越注重外貌和形象,假发作为一种改善发型的工具&…

el-table设置默认选中报错_this.$refs.singleTable.toggleAllSelection is not a function

直接使用以下的方法,报错信息是_this.$refs.singleTable.toggleAllSelection is not a function this.$refs.singleTable.toggleAllSelection()看了网上的解决方法,加了this.$nextTick,代码如下,但还是报错Error in nextTick: "TypeErr…

文件夹数据同步工具 Sync Folders Pro mac支持选项

Sync Folders Pro for Mac 是一款功能强大的文件夹同步工具,旨在帮助用户在 Mac 计算机和移动设备之间创建双向同步。这款软件支持各种文件系统和设备,如 iPhone,iPad,iPod,Android 等。通过这款软件,用户可…

2023 英特尔On技术创新大会直播 | 边云协同加速 AI 解决方案商业化落地

目录 前言边云协同时代背景边缘人工智能边缘挑战英特尔边云协同的创新成果最后 前言 最近观看了英特尔On技术创新大会直播,学到了挺多知识,其中对英特尔高级首席 AI 工程张宇博士讲解的边云协同加速 AI 解决方案商业化落地特别感兴趣。张宇博士讲解了英…

macOS 安装 oh-my-zsh 后 node 报错 command not found : node

最近为了让终端中显示 git 分支的名称,安装了 oh-my-zsh ,安装之后呢,我原先安装的 Volta、 node 都没法用了,报错如下: 这时候粗略判断应该是系统变量出了问题,oh-my-zsh 的变量文件是 ~/.zshrc&#xff0…

C# NPOI导出dataset----Excel绘制Chart图表

仅限XLSX 2007以后版本(2007之前版本不支持) 1、判断文件夹是否存在,不存在则创建 //Application.StartupPath当前项目根目录 if (!Directory.Exists(Application.StartupPath "\Excel")) { …

【C++高阶(七)】C++异常处理的方式

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C   🔝🔝 异常处理的方式 1. 前言2. C语言处理异常的方式…

Windows11编译x265源码生成Visual Studio工程详细步骤

概述 x265是一款开源符合HEVC标准的编码器,也属于VLC项目之一。 由于x265是开源的,因此它得到了广泛的应用和开发。许多开源项目和商业产品都使用x265进行视频压缩处理。同时,x265也支持多种编程语言和平台,使得开发者可以方便地…

Java 中单例模式的常见实现方式

目录 一、什么是单例模式? 二、单例模式有什么作用? 三、常见的创建单例模式的方式 1、饿汉式创建 2、懒汉式创建 3、DCL(Double Checked Lock)双检锁方式创建 3.1、synchronized 同步锁的基本使用 3.2、使用 DCL 中存在的疑…

【MYSQL】-表的操作

💖作者:小树苗渴望变成参天大树🎈 🎉作者宣言:认真写好每一篇博客💤 🎊作者gitee:gitee✨ 💞作者专栏:C语言,数据结构初阶,Linux,C 动态规划算法🎄 如 果 你 …

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

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

车辆违规开启远光灯检测系统:融合YOLO-MS改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着社会的不断发展和交通工具的普及,车辆违规行为成为了一个严重的问题。其中,车辆违规开启远光灯是一种常见的违规行为,给其…

OpenCV技术应用(8)— 如何将视频分解

前言:Hello大家好,我是小哥谈。本节课就手把手教大家如何将一幅图像转化成热力图,希望大家学习之后能够有所收获~!🌈 目录 🚀1.技术介绍 🚀2.实现代码 🚀1.技术介绍 视频是…

Open5GSUeRANSim2:对安装在同一个VM上的OPEN5GS和UERANSIM进行配置和抓取wireshark报文

参考链接: Configuring SCTP & NGAP with UERANSIM and Open5GS on a Single VM for the Open5GS & UERANSIM Series https://www.youtube.com/watch?vINgEX5L5fkE&listPLZqpS76PykwIoqMdUt6noAor7eJw83bbp&index5 Configuring RRC with UERANSI…

Python | Flask测试:发送post请求的接口测试

HTTP/1.1 协议规定的 HTTP 请求方法有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 几种。POST通常用来向服务端提交数据,主要用于提交表单、上传文件。 HTTP 协议是以ASCII码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为…

微信小程序管理奖品(抽奖)

话不多说直接上代码 功能&#xff1a; 使用微信小程序vant-weapp 组件库中的upload组件以及两个input框 最后拿到的值是一个数组对象的形式 主要代码如下&#xff1a; wxml <view wx:for"{{prizes}}" wx:key"index" class"inputs"><i…

IDEA 黑色主题很难看到鼠标

“控制面板”—搜索“鼠标”关键字—选择“更改鼠标设置” 参考&#xff1a; IDEA 黑色主题很难看到鼠标

【实战】如何在Docker Image中轻松运行MySQL

定义 使用Docker运行MySQL有许多优势。它允许数据库程序和数据分离&#xff0c;增强了数据的安全性和可靠性。Docker Image的轻便性简化了MySQL的部署和迁移&#xff0c;而Docker的资源隔离功能确保了应用程序之间无冲突。结合中间件和容器化系统&#xff0c;Docker为MySQL提供…

Android Canvas状态save与restore,Kotlin

Android Canvas状态save与restore&#xff0c;Kotlin private fun f1() {val bitmap BitmapFactory.decodeResource(resources, R.mipmap.pic).copy(Bitmap.Config.ARGB_8888, true)val canvas Canvas(bitmap)val paint Paint(Paint.ANTI_ALIAS_FLAG)paint.color Color.RED…

常用的电源芯片有哪些?怎么分类

科技的发展也带动了电源的发展&#xff0c;因此需要更多的电源管理芯片。说到电源管理芯片&#xff0c;作为工程师最熟悉的芯片之一。所谓电源管理芯片&#xff0c;就是负责电子设备系统中电能的转换、分配、检测等电能管理的芯片。主要负责识别CPU电源范围&#xff0c;产生相应…