在爬虫开发中,性能优化是绕不开的核心话题。当需要抓取大量数据时,单线程爬虫的效率堪比蜗牛爬行——每秒处理几个请求的龟速让人抓狂。于是开发者们开始寻找加速方案,多线程和协程成为两大主流选择。但它们究竟谁更适合爬虫场景?本文通过真实测试数据,用通俗易懂的方式拆解两者的差异。

想象你是一个快递员,单线程模式就像一次只能送一个包裹,送完才能接下一单。而并发模式相当于同时开着电动车和无人机送货,效率自然翻倍。爬虫的瓶颈主要有三处:
传统单线程爬虫的流程是:发送请求→等待响应→解析内容→存储数据→循环。其中"等待响应"阶段占用了90%以上的时间,这正是并发技术大显身手的地方。
多线程就像同时开多个浏览器窗口访问网站,每个线程独立运行,互不干扰。Python中通过threading模块实现,配合Queue管理任务队列。
我们用Scrapy框架改造了一个多线程爬虫,测试目标为某电商网站的商品列表页(共1000个URL):
import threading
import queue
import requests
class ThreadCrawler:
def __init__(self, thread_num=10):
self.task_queue = queue.Queue()
self.result_queue = queue.Queue()
self.thread_num = thread_num
def worker(self):
while True:
url = self.task_queue.get()
try:
resp = requests.get(url, timeout=10)
self.result_queue.put((url, resp.status_code))
except Exception as e:
self.result_queue.put((url, str(e)))
finally:
self.task_queue.task_done()
def run(self, urls):
for url in urls:
self.task_queue.put(url)
for _ in range(self.thread_num):
t = threading.Thread(target=self.worker)
t.daemon = True
t.start()
self.task_queue.join()
线程数 | 耗时(秒) | CPU占用 | 内存占用 | 成功请求数 |
|---|---|---|---|---|
1 | 427 | 15% | 85MB | 1000 |
5 | 112 | 45% | 120MB | 998 |
10 | 68 | 70% | 180MB | 995 |
20 | 55 | 85% | 320MB | 987 |
发现规律:
协程像是一个超级多任务处理能手,可以在单个线程内切换执行多个任务。遇到I/O操作时主动让出CPU,等操作完成后再恢复执行。Python中通过asyncio+aiohttp实现。
改造为协程版本(使用aiohttp):
import asyncio
import aiohttp
async def fetch(session, url):
try:
async with session.get(url, timeout=10) as resp:
return url, resp.status
except Exception as e:
return url, str(e)
async def coroutine_crawler(urls, concurrency=100):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
def run_coroutine(urls, concurrency):
loop = asyncio.get_event_loop()
results = loop.run_until_complete(coroutine_crawler(urls[:concurrency]))
# 分批处理避免内存爆炸
for i in range(1, len(urls)//concurrency +1):
batch = coroutine_crawler(urls[i*concurrency:(i+1)*concurrency], concurrency)
results += loop.run_until_complete(batch)
loop.close()
return results
协程数 | 耗时(秒) | CPU占用 | 内存占用 | 成功请求数 |
|---|---|---|---|---|
10 | 72 | 20% | 95MB | 1000 |
50 | 38 | 35% | 110MB | 1000 |
100 | 28 | 50% | 120MB | 999 |
500 | 25 | 75% | 150MB | 992 |
惊人发现:
实际项目中,纯多线程或纯协程都不是最优解。推荐组合方案:
示例混合架构代码片段:
from concurrent.futures import ThreadPoolExecutor
import asyncio
async def process_image(image_data):
# 协程下载图片
pass
def resize_image(image_path):
# 线程池处理图片压缩
pass
async def main():
# 协程获取图片列表
images = await fetch_image_list()
# 协程下载图片
download_tasks = [process_image(img) for img in images]
await asyncio.gather(*download_tasks)
# 线程池处理压缩
with ThreadPoolExecutor(max_workers=4) as executor:
for img in images:
executor.submit(resize_image, img.path)
Q1:被网站封IP怎么办? A:立即启用备用代理池,建议使用住宅代理(如站大爷IP代理),配合每请求更换IP策略。更高级的做法是:
Q2:协程数量设置多少合适? A:遵循"CPU核心数×2~5"原则。测试发现:
Q3:多线程和协程能一起用吗? A:可以!典型场景:
loop.run_in_executor()实现协同工作Q4:如何选择爬虫框架? A:根据需求决定:
requests+协程或ScrapyScrapy-Redis或PySpiderSelenium+协程或PlaywrightQ5:反爬策略还有哪些? A:常见手段:
time.sleep(random.uniform(0.5,3)))通过本文的测试数据和实战案例可以看出,对于现代爬虫开发:
爬虫性能优化没有银弹,理解底层原理比盲目追求技术堆砌更重要。建议开发者根据实际场景进行AB测试,用数据说话才是硬道理。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。