如何用grequests高效批量请求Glassdoor评论页?规避403并提速
我需要爬取Glassdoor上某公司的所有评论页面,部分公司的评论页面可达数千页。尝试用grequests批量请求时,单次发送超过约100个请求就会收到403错误。
于是我写了如下代码,把请求按每100个一批处理:
import math import time from random import uniform import grequests reviews_url = "https://www.glassdoor.com/Reviews/Apple-Reviews-E1138.htm?" DEFAULT_HEADERS = { # 你的请求头配置 } batch = 100 responses = [] for j in range(math.ceil(num_pages/batch)): print("Batching requests: {}/{}".format(min(num_pages, (j+1)*batch), num_pages)) rs = ( grequests.get( reviews_url.replace(".htm", "_P" + str(k + 1) + ".htm"), headers=DEFAULT_HEADERS, ) for k in range(min(num_pages, j*batch), min(num_pages, (j+1)*batch)) ) responses += grequests.map(rs) time.sleep(uniform(10,15))
这个方案能正常获取数据,但速度太慢,我要处理约8000家公司的评论页。缩短批次间休眠时间又会触发403,有没有更优的实现方法?
精细化控制请求速率
不要固定100个请求为一批,试试更小的批次(20-50个),同时给批次内的每个请求添加随机小间隔(比如0.5-2秒),而不是只在批次结束后休眠。这样能避免瞬间发送大量请求触发反爬,同时比大批次长休眠的效率更高。引入代理IP池
403错误大多是IP被Glassdoor限制了,用稳定的高匿代理IP池轮询每个请求的IP,能大幅降低被封禁的概率。每次请求随机从池中选一个代理,注意定期清理失效代理,保证请求成功率。优化请求头模拟真实浏览器
检查你的DEFAULT_HEADERS,不要固定单一的User-Agent,维护一个常见浏览器的User-Agent列表,每次请求随机选取;同时补充Accept-Language、Referer、Cache-Control等字段,让请求更贴近真实用户行为。改用异步框架+精准限速
替换grequests为aiohttp,搭配aiometer这类限速库,能更精细地控制并发数和请求频率。比如设置每秒最多发送5个请求,并发数不超过10,流量更平稳,不容易触发反爬。示例代码思路:import aiohttp import asyncio from aiometer import run_on_each from random import choice USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15", # 更多UA ] async def fetch_page(session, url): headers = { "User-Agent": choice(USER_AGENTS), "Accept-Language": "en-US,en;q=0.9", "Referer": "https://www.glassdoor.com/" } async with session.get(url, headers=headers) as response: if response.status == 200: return await response.text() # 处理403等错误,比如重试或切换代理 return None async def main(): # 生成所有需要爬取的评论页面URL列表 urls = [reviews_url.replace(".htm", f"_P{page_num}.htm") for page_num in range(1, num_pages+1)] async with aiohttp.ClientSession() as session: # 限制每秒最多5个请求,同时最多10个请求并发 results = await run_on_each( urls, lambda url: fetch_page(session, url), max_at_once=10, max_per_second=5 ) # 处理爬取结果 print(f"成功获取{len([r for r in results if r])}页数据") if __name__ == "__main__": asyncio.run(main())尝试抓取内部API接口
打开浏览器开发者工具,查看加载评论时的网络请求,很多时候Glassdoor会通过XHR请求加载评论数据(JSON格式),直接调用这些API接口比爬取HTML页面效率高得多,反爬限制也可能更宽松。分时段错峰爬取
避开工作日白天的访问高峰,选择凌晨、深夜等时段爬取,此时网站的反爬检测会相对宽松,可适当提高请求速率,减少等待时间。
内容的提问来源于stack exchange,提问作者Dunc

