使用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
相关产品推荐
相关产品推荐

