使用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 = None

    def __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的包名即可爬取并解析数据,如下所示:

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Hbase的安装配置

注:本文默认已经完成hadoop的下载以及环境配置 1.上传zookeeper和hbase压缩包到指令路径并且解压 (理论上讲,hbase其实内置了zookeeper,我们也可以不另外下载,另外下载的目的在于减少组件间依赖性) cd /home mkir hbase cd /hom…

本地MinIO存储服务如何创建Buckets并实现公网访问上传文件

文章目录 前言1. 创建Buckets和Access Keys2. Linux 安装Cpolar3. 创建连接MinIO服务公网地址4. 远程调用MinIO服务小结5. 固定连接TCP公网地址6. 固定地址连接测试 前言 MinIO是一款高性能、分布式的对象存储系统,它可以100%的运行在标准硬件上,即X86等…

穿越时空的电商之旅:探索古老文化中的现代商机

随着全球化的发展,电商成为商业领域中的一匹黑马,以其便捷、高效的特性迅速改变着人们的购物方式。然而,电商并非仅限于当代社会,它也可以穿越时空,走进古老的文化中,挖掘出丰富的商机。本文将带领读者进行…

Opencv入门6(读取彩色视频并转换为对数极坐标视频)

源码如下&#xff1a; #include <opencv2/opencv.hpp> #include <iostream> int main(int argc, char* argv[]) { cv::namedWindow("Example2_11", cv::WINDOW_AUTOSIZE); cv::namedWindow("Log_Polar", cv::WINDOW_AUTOSIZE); c…

OLED主题展厅:领略新一代显示技术的无限可能

欢迎来到【OLED主题展厅】&#xff0c;这里是OLED应用的未来&#xff0c;是科技与艺术的完美结合。在这里&#xff0c;我们将带您领略新一代OLED显示屏的独特魅力&#xff0c;展示其在商显领域的最新技术和创新解决方案。 一、新一代OLED显示屏的应用案例和示范 在【OLED主题展…

《信息安全工程师教材》-蒋建春、信息安全完全参考手册-Mark Rhodes Ousley、CISSP官方学习指南第九版、ISO27002学习

文章目录 介绍关系学习顺序重复内容学习方法建议学习时间可能重合的部分更详细的学习计划 介绍 《信息安全工程师教材》- 蒋建春&#xff1a;这本教材可能是针对中国地区信息安全工程师的认证考试而编写的&#xff0c;它可能会涵盖信息安全的基本概念、技术和政策&#xff0c;特…

生成对抗网络与人工智能的完美融合:创新、艺术与未来

导言 生成对抗网络&#xff08;GAN&#xff09;作为一种深度学习框架&#xff0c;以其独特的生成能力引起广泛关注。生成对抗网络&#xff08;GAN&#xff09;与人工智能的结合不仅在科学领域引起了巨大的关注&#xff0c;也在艺术、医学等多个领域催生了令人振奋的创新。本文将…

代码随想录算法训练营第九天 | 28、找出字符串中第一个匹配项的下标、459. 重复的子字符串

28、找出字符串中第一个匹配项的下标 题目链接&#xff1a;28、找出字符串中第一个匹配项的下标 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 …

使用Docker部署Nexus Maven私有仓库并结合Cpolar实现远程访问

文章目录 1. Docker安装Nexus2. 本地访问Nexus3. Linux安装Cpolar4. 配置Nexus界面公网地址5. 远程访问 Nexus界面6. 固定Nexus公网地址7. 固定地址访问Nexus Nexus是一个仓库管理工具&#xff0c;用于管理和组织软件构建过程中的依赖项和构件。它与Maven密切相关&#xff0c;可…

abaqus复合材料与混凝土、opensees钢筋混凝土

专题课程的通知 一、培训背景&#xff1a; ABAQUS作为现阶段应用最广泛的有限元仿真模拟软件&#xff0c;优秀的分析能力和模拟复杂系统的可靠性使得ABAQUS被各国的工业和科学研究中广泛采用。通过合理的建模和分析&#xff0c;可以更好地理解复合材料的力学行为&#xff0c;…

探索统计学:Python中的Statsmodels库统计推断

写在开头 统计推断是数据科学中的一个核心领域,它通过从样本中提取信息来对整个总体进行推断。在实际的数据分析中,我们常常需要了解样本的特征,并基于这些样本推断总体的性质。这正是统计学的魅力所在。在本文中,我们将深入研究统计推断的各个方面,着重介绍在Python中应…

Ubuntu 常用命令之 gzip 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 gzip 是一个在 Linux 和 Unix 系统中常用的文件压缩工具。它的名字来源于 GNU zip&#xff0c;作为一个自由软件&#xff0c;它是 GNU 项目的一部分。gzip 命令通常用于压缩文件&#xff0c;以节省磁盘空间&#xff0c;或者减小文…

IDEA 黑色主题很难看到鼠标

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

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

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

本地生活团购外卖怎么做?一招教你轻易入行!

如果说今年生意不好做的话&#xff0c;那么年初做本地生活服务这个赛道的现在是喜忧参半。喜的是在本地生活干团购和外卖把钱给挣上了。忧的是官方清退了所有的全国本地生活服务商。通过官方渠道基本是没的玩了。本来还想着干个三五年。实现车子、房子、票子自由。这计划全落空…

vue属性绑定指令

在vue中&#xff0c;可以使用v-bind&#xff1a;指令&#xff0c;为了元素的属性动态绑定值 简写是英文的 &#xff1a; 在使用v-bind的属性绑定期间&#xff0c;如果绑定内容需要就行动态拼接&#xff0c;则字符串的外面应该包裹单引号&#xff0c;例如: v-bind案例&#x…

Java 中的内部类的定义

目录 一、成员内部类 二、静态内部类 三、局部内部类 四、匿名内部类 一、成员内部类 public class InnerClass {String name;private Integer age;static String hobby;/*** 成员内部类* 1、成员内部类中只能定义非静态属性和方法* 2、成员内部类中可以访问外部类的成员&a…

14.中位数贪心

中位数贪心 定理&#xff1a;将数组 a 中的所有元素变为 a 的中位数是最优的。 如何理解&#xff1f; 假定所有的 nums[i] 均位于数轴上的 nums 的位置&#xff0c;要求我们在数轴上找出一个点 t&#xff0c;使得所有 nums[i] 到 t 的距离之和最小。 容易证明 t 不可能位于…

【昆明*线上同步】最新ChatGPT/GPT4科研实践应用与AI绘图技术及论文高效写作

详情点击查看福利&#xff1a;【昆明*线上同步】最新ChatGPT/GPT4科研实践应用与AI绘图技术及论文高效写作 目标&#xff1a; 1、熟练掌握ChatGPT提示词技巧及各种应用方法&#xff0c;并成为工作中的助手。 2、通过案例掌握ChatGPT撰写、修改论文及工作报告&#xff0c;提供…

Actuator内存泄露及利用Swagger未授权自动化测试实现

目录 0x00 前言 0x01 Actuator 泄露及利用 1、Actuator heapdump 内存泄露 2、知道泄露后如何进一步利用 3、如何发现 Actuator 泄露&#xff08;白盒/黑盒&#xff09; 0x02 Swagger自动化测试 1、什么是Swagger&#xff1f; 2、PostmanBurpSuiteXray 联动 3、思考 0x…