求比urllib2.urlopen()更快的网页HTML获取方案
批量抓取网页的提速方案
兄弟,我懂你这种批量爬1万+页面卡到怀疑人生的感觉!用urllib2或者单个requests请求挨个跑确实慢得离谱——毕竟每次都要建立TCP连接、等响应,单线程完全把时间浪费在“等待”上。给你几个亲测有效的提速方法,直接上干货:
1. 改用多线程/多进程(入门友好)
单线程的核心问题是“串行等待”,每次必须等前一个请求拿到响应才能继续下一个。用线程池可以同时发起多个请求,把等待的空闲时间利用起来,非常适合网页爬取这种IO密集型任务。
推荐用Python自带的concurrent.futures.ThreadPoolExecutor,代码示例如下:
import requests from concurrent.futures import ThreadPoolExecutor import time def fetch_page(url): try: # 加个浏览器UA,避免被目标网站直接拦截 headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"} response = requests.get(url, headers=headers, timeout=10) return response.text except Exception as e: print(f"请求{url}失败: {e}") return None if __name__ == "__main__": start_time = time.time() query = "hill" # 模拟生成Google自定义搜索的分页URL列表 urls = [f'https://www.googleapis.com/customsearch/v1?key=YOUR_API_KEY&q={query}&start={i}' for i in range(1, 10001, 10)] # 线程数建议设为20-50(根据目标网站抗压力调整,别太激进) with ThreadPoolExecutor(max_workers=30) as executor: results = list(executor.map(fetch_page, urls)) print(f"总耗时: {time.time() - start_time:.2f}秒")
⚠️ 注意:别把线程数开得太大,不然容易触发目标网站的反爬机制,或者Google API的限流——毕竟Google自定义搜索本身有请求配额,免费版额度有限,这点一定要留意!
2. 异步请求(进阶高效)
如果线程池还满足不了你的速度需求,试试异步非阻塞的方式,用aiohttp库。异步可以在同一线程里同时处理多个请求的IO等待,资源占用比线程池更低,速度提升更明显,尤其适合上万级别的请求量。
代码示例:
import aiohttp import asyncio import time async def fetch_page(session, url): try: headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"} async with session.get(url, headers=headers, timeout=10) as response: return await response.text() except Exception as e: print(f"请求{url}失败: {e}") return None async def main(): start_time = time.time() query = "hill" urls = [f'https://www.googleapis.com/customsearch/v1?key=YOUR_API_KEY&q={query}&start={i}' for i in range(1, 10001, 10)] # ClientSession会自动复用连接池,减少TCP握手的开销 async with aiohttp.ClientSession() as session: tasks = [fetch_page(session, url) for url in urls] results = await asyncio.gather(*tasks) print(f"总耗时: {time.time() - start_time:.2f}秒") if __name__ == "__main__": asyncio.run(main())
3. 额外优化小技巧
- 连接池复用:不管是requests还是aiohttp,都默认支持连接池,别手动关闭连接,让库自己管理就行,能省掉大量重复建立TCP连接的时间。
- 设置合理超时:给每个请求加上
timeout参数,避免个别慢请求拖慢整个任务的进度。 - 缓存重复请求:如果你的URL列表里有重复项,先做去重,或者用缓存(比如本地文件、Redis)存储已请求过的页面内容,避免重复劳动。
- 盯紧API配额:再次提醒,你用的是Google自定义搜索API,免费版的请求配额有限,超过了会产生费用,提速前先确认自己的配额够不够!
内容的提问来源于stack exchange,提问作者Vivek Gupta
相关产品推荐
相关产品推荐

