You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Scrapy爬取Google Play遇分页问题求助:仅获50条结果

解决Scrapy爬取Google Play商店加载更多内容时的400错误问题

看起来你遇到的核心问题是硬编码了Google Play的临时会话参数,而且调用了错误的接口来加载更多应用。Google Play的动态加载依赖于batchexecute接口,而非你用的browserinfo,并且像f.sid、_reqid这类参数是会话临时生成的,硬编码会直接导致400请求错误。

下面我一步步给你解决思路和修正后的代码:

为什么你的现有方法失败?

  • 错误的接口:browserinfo是Google用来收集浏览器环境信息的接口,不是获取应用列表的加载接口,所以即使参数正确也拿不到你要的应用数据。
  • 硬编码临时参数:f.sid、_reqid、at这些参数是Google为每个会话动态生成的,每次请求都会变化,直接复用旧参数会被服务器判定为无效请求,返回400。

正确的解决方案步骤

1. 分析Google Play的加载更多请求

当你滚动页面到底部时,Google Play会发送POST请求到https://play.google.com/_/PlayStoreUi/data/batchexecute接口,请求体里的f.req参数是一个经过URL编码的JSON数组,包含了分页起始位置、搜索关键词等关键信息。

2. 从初始页面提取必要参数

从初始搜索页中提取bl参数(格式类似boq_playuiserver_20240501.08_p0,可从页面的script标签或链接中获取),以及hl(语言)参数,这些是batchexecute请求必须的。

3. 动态构造分页请求

每次请求递增起始索引(比如每次加载48个应用),直到获取的应用总数达到页面显示的257个为止。

修正后的Scrapy代码

import json
import urllib.parse
from scrapy import Spider
from scrapy.http import FormRequest

class PlaySpider(Spider):
    name = 'play'
    allowed_domains = ['play.google.com']
    start_urls = ['https://play.google.com/store/search?q=quotes&c=apps']
    
    total_apps = 257  # 可从初始页面动态提取,替换硬编码值
    current_count = 0
    start_index = 0

    def parse(self, response):
        # 提取bl参数
        bl_param = response.xpath('//script[contains(@src, "boq_playuiserver")]/@src').re_first(r'boq_playuiserver_(\d+\.\d+_p\d+)')
        if not bl_param:
            self.logger.error("Failed to extract bl parameter")
            return
        
        # 提取语言参数hl
        hl_param = response.xpath('//meta[@name="google-play-country"]/@content').get() or 'en'
        
        # 尝试从页面提取总应用数,替换硬编码的total_apps
        total_text = response.xpath('//span[@class="L2o20d"]/text()').get()
        if total_text:
            self.total_apps = int(total_text.replace(',', ''))
        
        # 第一次构造加载请求
        yield self.build_load_more_request(bl_param, hl_param, self.start_index)

    def build_load_more_request(self, bl_param, hl_param, start_index):
        # 构造f.req的JSON内容,定义分页加载规则
        f_req_data = [
            ["qnKhOb", json.dumps([
                None,
                json.dumps([["q", "quotes"], ["c", "apps"]]),
                None,
                [start_index, start_index + 48],  # 每次加载48个,可根据实际调整
                None,
                None,
                []
            ])
            ]
        ]
        f_req_encoded = urllib.parse.quote(json.dumps(f_req_data))
        
        url = f"https://play.google.com/_/PlayStoreUi/data/batchexecute?hl={hl_param}&bl={bl_param}"
        
        formdata = {
            'f.req': f_req_encoded,
        }
        
        return FormRequest(
            url=url,
            formdata=formdata,
            callback=self.parse_load_more,
            meta={
                'bl_param': bl_param,
                'hl_param': hl_param,
                'next_start': start_index + 48
            },
            headers={
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
            }
        )

    def parse_load_more(self, response):
        # 解析batchexecute返回的特殊格式响应
        try:
            # 去掉响应开头的特殊标记,提取JSON内容
            raw_data = response.text.replace(")]}'", "")
            data = json.loads(raw_data)
            
            # 提取应用链接
            app_links = data[0][2]
            if app_links:
                for link in app_links:
                    app_url = f"https://play.google.com{link[1][0]}"
                    self.current_count += 1
                    yield {'app_url': app_url}
            
            # 判断是否需要继续加载下一页
            if self.current_count < self.total_apps:
                next_start = response.meta['next_start']
                yield self.build_load_more_request(
                    response.meta['bl_param'],
                    response.meta['hl_param'],
                    next_start
                )
            else:
                self.logger.info(f"Successfully crawled all {self.total_apps} apps")
                
        except Exception as e:
            self.logger.error(f"Error parsing response: {str(e)}")

额外注意事项

  • 会话保持:Scrapy会自动维护会话Cookie,无需手动硬编码Cookie,避免过期失效。
  • 反爬应对:建议添加合理的User-Agent,控制请求间隔(可通过DOWNLOAD_DELAY设置),避免触发Google的反爬机制导致IP被封禁。
  • 参数动态提取:bl参数和总应用数尽量从页面动态提取,不要硬编码,避免Google更新页面结构后代码失效。

内容的提问来源于stack exchange,提问作者booleantrue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 18:22:34