Python异步编程与高并发爬虫实战指南

发布时间:2026/9/16 10:22:16
Python异步编程与高并发爬虫实战指南 1. 为什么需要异步编程当你在Python中编写一个简单的爬虫程序时可能会遇到这样的问题程序大部分时间都在等待网络响应而不是真正处理数据。这就是典型的I/O密集型场景而异步编程正是为此而生的解决方案。传统的同步编程模型下当你的爬虫发送一个HTTP请求后整个程序就会阻塞在那里直到收到响应才能继续执行。想象一下如果你要爬取100个网页每个请求耗时1秒那么总耗时就是100秒——即使大部分时间都在等待网络响应。# 传统同步爬虫示例 import requests def fetch(url): response requests.get(url) return response.text urls [http://example.com/page1, http://example.com/page2, ...] for url in urls: content fetch(url) # 这里会阻塞 process(content)2. asyncio核心概念解析2.1 事件循环(Event Loop)事件循环是asyncio的核心它负责调度和执行协程。你可以把它想象成一个高效的交通警察指挥着所有协程的交通。import asyncio async def main(): print(Hello) await asyncio.sleep(1) print(World) # 获取事件循环并运行协程 loop asyncio.get_event_loop() loop.run_until_complete(main())2.2 协程(Coroutine)协程是异步编程的基本单位使用async def定义。与普通函数不同协程可以被暂停和恢复。重要提示仅仅调用协程函数不会执行它必须通过await或事件循环来运行。2.3 Future和TaskFuture代表一个异步操作的最终结果而Task是Future的子类用于包装协程。当你在asyncio中创建任务时实际上是在调度协程的执行。async def my_coroutine(): return 42 # 创建任务 task asyncio.create_task(my_coroutine())3. 构建异步HTTP客户端3.1 aiohttp基础使用aiohttp是Python中流行的异步HTTP客户端/服务器框架。与requests不同它完全基于asyncio构建。import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html await fetch(session, http://python.org) print(html[:200]) # 打印前200个字符 asyncio.run(main())3.2 连接池与超时设置在实际爬虫项目中合理配置连接池和超时参数至关重要# 自定义连接池和超时 connector aiohttp.TCPConnector( limit30, # 最大连接数 limit_per_host5, # 每个主机最大连接数 force_closeTrue, enable_cleanup_closedTrue ) timeout aiohttp.ClientTimeout(total10) # 总超时10秒 async with aiohttp.ClientSession( connectorconnector, timeouttimeout ) as session: # 使用session进行请求4. 高并发爬虫实战4.1 基本并发爬虫实现让我们实现一个能并发爬取多个URL的爬虫async def fetch_url(session, url): try: async with session.get(url) as response: if response.status 200: return await response.text() return None except Exception as e: print(fError fetching {url}: {e}) return None async def crawl(urls): async with aiohttp.ClientSession() as session: tasks [fetch_url(session, url) for url in urls] return await asyncio.gather(*tasks) # 使用示例 urls [http://example.com/page1, http://example.com/page2, ...] results asyncio.run(crawl(urls))4.2 并发控制与限速不加限制的高并发可能会对目标服务器造成压力甚至导致你的IP被封。我们可以使用信号量(Semaphore)来控制并发度async def fetch_with_semaphore(sem, session, url): async with sem: return await fetch_url(session, url) async def controlled_crawl(urls, concurrency10): sem asyncio.Semaphore(concurrency) async with aiohttp.ClientSession() as session: tasks [fetch_with_semaphore(sem, session, url) for url in urls] return await asyncio.gather(*tasks)4.3 生产者-消费者模式对于大规模爬虫生产者-消费者模式更为高效async def producer(queue, urls): for url in urls: await queue.put(url) await queue.put(None) # 结束信号 async def consumer(queue, session, results): while True: url await queue.get() if url is None: break content await fetch_url(session, url) if content: results.append(content) queue.task_done() async def producer_consumer_crawl(urls, concurrency10): queue asyncio.Queue(maxsizeconcurrency*2) results [] async with aiohttp.ClientSession() as session: producers [asyncio.create_task(producer(queue, urls))] consumers [asyncio.create_task(consumer(queue, session, results)) for _ in range(concurrency)] await asyncio.gather(*producers) await queue.join() for c in consumers: c.cancel() return results5. 高级技巧与优化5.1 错误处理与重试机制网络请求难免会遇到各种错误合理的重试机制能提高爬虫的健壮性async def fetch_with_retry(session, url, max_retries3, delay1): for attempt in range(max_retries): try: async with session.get(url) as response: if response.status 200: return await response.text() elif response.status 429: # Too Many Requests await asyncio.sleep(delay * (attempt 1)) continue return None except (aiohttp.ClientError, asyncio.TimeoutError) as e: if attempt max_retries - 1: raise await asyncio.sleep(delay * (attempt 1)) return None5.2 代理与User-Agent轮换为了避免被目标网站封禁我们可以使用代理和随机User-Agentfrom fake_useragent import UserAgent ua UserAgent() async def fetch_with_proxy(session, url, proxyNone): headers {User-Agent: ua.random} try: async with session.get(url, proxyproxy, headersheaders) as response: return await response.text() except Exception as e: print(fError with proxy {proxy}: {e}) return None5.3 性能监控与调试使用asyncio的内置工具监控协程执行async def monitored_crawl(urls): start asyncio.get_event_loop().time() async with aiohttp.ClientSession() as session: tasks [fetch_url(session, url) for url in urls] done, pending await asyncio.wait(tasks, timeout30) print(fCompleted {len(done)} tasks in {asyncio.get_event_loop().time() - start:.2f}s) return [task.result() for task in done]6. 遵守robots.txt与道德爬虫虽然技术让我们能够高效爬取数据但我们必须遵守robots.txt协议和合理的爬取频率import urllib.robotparser async def check_robots_txt(session, base_url): rp urllib.robotparser.RobotFileParser() robots_url f{base_url.rstrip(/)}/robots.txt try: async with session.get(robots_url) as response: if response.status 200: rp.parse((await response.text()).splitlines()) return rp except Exception: pass return None async def ethical_fetch(session, url): base_url /.join(url.split(/)[:3]) rp await check_robots_txt(session, base_url) if rp and not rp.can_fetch(*, url): print(fSkipping {url} due to robots.txt restrictions) return None return await fetch_with_retry(session, url)在实际项目中我通常会设置至少1秒的延迟between requests to the same domain并严格遵守robots.txt中的Crawl-delay指令。这不仅是对目标网站的尊重也能避免因请求过于频繁而导致IP被封。异步编程确实能大幅提升爬虫效率但记住能力越大责任越大。合理控制并发数设置适当的延迟避免对目标网站造成过大压力。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询