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

news2024/12/24 3:21:48

文章目录

【作者主页】:吴秋霖
【作者介绍】: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的包名即可爬取并解析数据,如下所示:

在这里插入图片描述

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

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

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

相关文章

高速视频采集卡设计方案:620-基于PCIe的高速视频采集卡

一、产品概述 基于PCIe的高速视频采集卡,通过PCIe3.0X8传输到存储计算服务器,实现信号的分析、存储。 北京太速科技 产品固化FPGA逻辑,适配视频连续采集,缓存容量2GB,开源的PCIe QT客户端软件&#xff0c…

Kafka--Kafka日志索引详解以及生产常见问题分析与总结

一、Kafka的Log日志梳理 ​ 这一部分数据主要包含当前Broker节点的消息数据(在Kafka中称为Log日志)。这是一部分无状态的数据,也就是说每个Kafka的Broker节点都是以相同的逻辑运行。这种无状态的服务设计让Kafka集群能够比较容易的进行水平扩展。比如你需要用一个新…

数据库原理及应用·数据库系统结构

2.1 数据模型的概念 2.1.1 什么是数据模型 数据模型(Data Model) 是对现实世界数据特征的模拟和抽象,用来描述数据是如何组织、存储和操作的。 数据模型应满足如下三个条件: 能比较真实地模拟现实世界 容易为人所理解 便于在计…

设计模式(三)-结构型模式(3)-装饰模式

一、为何需要装饰模式(Decorator)? 在软件设计中,某个对象会组合很多不同的功能,如果把所有功能都写在这个对象所在的类里,该类会包含很多复杂的代码逻辑,导致代码不美观且难以维护。于是就有了再定义一些…

腾讯AI Lab C++开发日常实习 一面

我们是校企联合专业(深大腾班),所以腾讯给了我们这个实习的机会,据说面试比一般日常实习的面试简单,记录人生第一次实习面试 上来先自我介绍 我介绍了学校专业和求职意向和开发经历,问没了? …

文具品牌企业网站建设的作用是什么

文具的应用非常广泛,不仅是学生、有些行业也会频繁使用,市场中大小文具品牌也是比较多,对文具品牌商和大经销商而言,批发远比零售更好,但在实际经营中,却也面临不少痛点: 1、拓客难 中小品牌商…

【解刊】1个月录用,18天见刊!CCF-A类顶刊,中科院基金委主办,国人占比97%!

计算机类 • 好刊解读 今天小编带来Springer旗下计算机领域顶刊,高分区高影响因子,新晋CCF-A类推荐,如您有投稿需求,可作为重点关注!后文有相关领域真实发表案例,供您投稿参考~ 01 期刊简介 Science Chi…

重生奇迹MU觉醒战士攻略

剑士连招技巧:生命之光:PK前起手式,增加血上限。 雷霆裂闪:眩晕住对手,剑士PK战士第一技能,雷霆裂闪是否使用好关系到胜负。 霹雳回旋斩:雷霆裂闪后可以选择用霹雳回旋斩跑出一定范围(因为对手…

springboot云HIS医院信息管理系统源码

通过云HIS平台,可以减少医院投资,无需自建机房和系统,快速实现信息化服务。系统升级及日常维护服务有云平台提供,无需配备专业IT维护人员进行系统维护。 一、his系统和云his系统的区别 His系统和云his系统是两种不同的计算平台,它们在技术架构上存在很大的差异。下…

【jvm从入门到实战】(九) 垃圾回收(2)-垃圾回收器

垃圾回收器是垃圾回收算法的具体实现。 由于垃圾回收器分为年轻代和老年代,除了G1之外其他垃圾回收器必须成对组合进行使用 垃圾回收器的组合使用关系图如下。 常用的组合如下: Serial(新生代) Serial Old(老年代) Pa…

免 费 搭 建 小程序商城,打造多商家入驻的b2b2c、o2o、直播带货商城

在数字化时代,电商行业正经历着前所未有的变革。鸿鹄云商的saas云平台以其独特的架构和先进的理念,为电商行业带来了全新的商业模式和营销策略。该平台涉及多个平台端,包括平台管理、商家端、买家平台、微服务平台等,涵盖了pc端、…

java定义三套场景接口方案

一、背景 在前后端分离开发的背景下,后端java开发人员现在只需要编写接口接口。特别是使用微服务开发的接口。resful风格接口。那么一般后端接口被调用有下面三种场景。一、不需要用户登录的接口调用,第二、后端管理系统接口调用(需要账号密…

【进阶篇】YOLOv8实现K折交叉验证——解决数据集样本稀少和类别不平衡的难题,让你的模型评估更加稳健

YOLOv8专栏导航:点击此处跳转 K折交叉验证 K折交叉验证(K-Fold Cross-Validation)是一种常用的机器学习模型评估方法,可以帮助我们评估模型的性能,特别适用于数据集相对较小的情况。 在K折交叉验证中,将原…

JMeter接口测试高阶——精通JMeter接口测试之BeanShell及调用java和python脚本

文章目录 一、BeanShell组件二、BeanShell自带的语法(BeanShell常用变量和语法)1.log打印2.vars用来操作JMeter的局部变量(只能在一个线程组里面使用的变量)3.props用来操作JMeter的全局变量(能够跨线程组取值的变量&a…

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

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

python实现一个图片查看器——可拖动、缩放和颜色画笔

目录 0 前言1 准备工作2 窗口布局3 图片显示功能3 图片拖拽功能4 图片缩放功能(难度大)5 画笔功能6 颜色选择功能后记源码 0 前言 在现如今的数字时代,我们对于图片的需求越来越大。无论是在工作中,还是在日常生活中,…

SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(1)g2o库的安装

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址: SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

PostGIS轨迹分析——横跨180°经线

问题描述 在处理AIS数据中,经常会遇到轨迹线横穿180经线的情况,这种数据绘制到地图上显示的非常乱,如下图所示: 数据模拟 在geojson.io上模拟一条轨迹线,可以看到轨迹显示的非常好,红框里面的经纬度超过…

使用Alpha Vantage API和Python进行金融数据分析

Alpha Vantage通过一套强大且开发者友好的数据API和电子表格,提供实时和历史的金融市场数据。从传统资产类别(例如股票、ETF、共同基金)到经济指标,从外汇汇率到大宗商品,从基本数据到技术指标,Alpha Vanta…