Python爬虫避坑指南:从入门到放弃?

35 阅读7分钟

还记得我刚学Python爬虫那会儿,天真地以为几行requests加BeautifulSoup就能走天下。结果迎面撞上反爬机制、频繁被封IP、页面结构一变代码就崩……踩过无数坑后我才明白,写出健壮的爬虫不仅是技术活,更是对耐心和细心的考验。今天我想分享这些用教训换来的经验,希望能帮你少走些弯路。

a4.png

我们来深入探讨一下用 Python 写爬虫时常见的误区及其解决方案。这对于初学者甚至一些有经验的开发者都很有帮助,可以避免很多“坑”。

常见误区与解决方案

误区一:忽视法律法规与道德规范(Robots.txt)

  • 表现:不顾及目标网站的 robots.txt 协议,粗暴抓取所有数据,甚至抓取个人隐私等敏感信息,可能引发法律风险。

  • 解决方案

    1. 遵守 Robots 协议:使用 urllib.robotparser 模块来解析和遵守目标网站的 robots.txt

      from urllib.robotparser import RobotFileParser
      rp = RobotFileParser()
      rp.set_url('https://www.example.com/robots.txt')
      rp.read()
      can_scrape = rp.can_fetch('YourBotName', 'https://www.example.com/some/page.html')
      if can_scrape:
          # 进行爬取
      else:
          # 跳过爬取
      
    2. 尊重版权和隐私:只爬取公开且允许爬取的数据,不爬取未授权的内容(如付费内容、用户个人信息等)。

    3. 设置友好速率:在请求之间添加延迟(如 time.sleep()),避免对目标网站服务器造成过大压力。rate 参数在 robots.txt 中可能有建议。

误区二:缺乏必要的请求头模拟

  • 表现:使用默认的请求头(尤其是 User-Agent 是明显的 python-requests/2.xx.x),极易被网站识别为爬虫并封禁 IP。

  • 解决方案

    1. 模拟真实浏览器:总是设置常见的请求头,特别是 User-Agent。可以准备一个列表随机选择,增加隐蔽性。

      import requests
      headers = {
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
          'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
          'Accept-Language': 'en-US,en;q=0.5',
          'Connection': 'keep-alive',
      }
      response = requests.get('https://httpbin.org/headers', headers=headers)
      
    2. 处理 Cookie:对于需要登录的页面,使用 requests.Session() 来自动管理 Cookies,模拟登录状态。

误区三:错误处理与异常机制不足

  • 表现:代码没有 try-except 块,遇到网络波动、HTTP 错误(404, 500)、解析错误时程序直接崩溃,非常脆弱。

  • 解决方案

    1. 包装请求代码:对网络请求进行异常捕获。

    2. 检查响应状态码:总是检查 response.status_code 是否为 200。

      import requests
      from requests.exceptions import RequestException
      import time
      ​
      def get_page(url):
          try:
              response = requests.get(url, headers=headers, timeout=10)
              if response.status_code == 200:
                  return response.text
              else:
                  print(f'获取页面失败,状态码: {response.status_code}')
                  return None
          except RequestException as e:
              print(f'请求发生错误: {e}')
              return None
      ​
      html = get_page('https://example.com')
      if not html:
          # 处理错误情况,如重试、记录日志等
          pass
      
    3. 实现重试机制:对于可能 transient(临时性)的错误(如网络超时),可以使用 tenacity 等库实现自动重试。

误区四:过度依赖页面结构(脆弱的解析器)

  • 表现:解析代码(XPath, CSS Selector)直接写死,一旦网站前端改版,爬虫立即失效。

  • 解决方案

    1. 选择稳定的标识符:尽量选择 id, class 等不太容易变化的属性或结构进行解析。避免使用绝对路径。

    2. 代码解耦:将选择器字符串统一放在代码开头或配置文件中,方便网站改版后集中修改。

      # config.py
      SELECTORS = {
          'title': 'div.product-name h1::text',
          'price': 'span.price::text',
          # ...
      }
      ​
      # main.py
      from config import SELECTORS
      title = selector.css(SELECTORS['title']).get()
      
    3. 添加断言:在解析关键数据后,检查数据是否存在或符合预期格式,及早发现解析失败。

误区五:处理 JavaScript 渲染页面方法不当

  • 表现:直接用 requests 请求动态网页(如 SPA),获取到的 HTML 是空的或不完整,因为数据由 JS 加载。

  • 解决方案

    1. 分析 API:打开浏览器开发者工具(F12)-> Network -> XHR,寻找真正提供数据的 Ajax API 接口。直接用 requests 调用这个接口往往更高效。

    2. 使用渲染工具:如果数据没有独立接口,必须渲染 JS,则使用:

      • Selenium:模拟真实浏览器操作,功能强大但速度慢,资源消耗大。

        from selenium import webdriver
        from selenium.webdriver.chrome.options import Options
        ​
        options = Options()
        options.headless = True # 无头模式,不显示浏览器窗口
        driver = webdriver.Chrome(options=options)
        driver.get("https://example.com")
        html = driver.page_source
        # ... 解析 html
        driver.quit()
        
      • Splash:一个带有 HTTP API 的轻量级 JavaScript 渲染服务,常与 Scrapy 配合使用。

      • Playwright / Puppeteer:现代浏览器自动化库,比 Selenium 更强大和快速。

误区六:同步阻塞式请求,效率低下

  • 表现:使用简单的 for 循环 + time.sleep() 进行请求,每个请求都等待上一个完成,速度极慢。

  • 解决方案

    1. 多线程/多进程:使用 concurrent.futures 线程池。

      from concurrent.futures import ThreadPoolExecutor, as_completed
      import requests
      ​
      urls = [...] # 一个很大的URL列表
      ​
      def download_url(url):
          return requests.get(url).text
      ​
      with ThreadPoolExecutor(max_workers=10) as executor: # 控制并发数
          future_to_url = {executor.submit(download_url, url): url for url in urls}
          for future in as_completed(future_to_url):
              html = future.result()
              # 处理html
      
    2. 异步IO(推荐) :使用 aiohttp + asyncio,性能极高,是专业爬虫的首选。

      import aiohttp
      import asyncio
      ​
      async def fetch(session, url):
          async with session.get(url) as response:
              return await response.text()
      ​
      async def main():
          urls = [...]
          async with aiohttp.ClientSession() as session:
              tasks = [fetch(session, url) for url in urls]
              htmls = await asyncio.gather(*tasks)
              for html in htmls:
                  # 处理html
                  pass
      ​
      asyncio.run(main())
      

      注意:高并发请求一定要谨慎,控制速率,否则相当于对网站发起 DOS 攻击。

误区七:数据存储与管理混乱

  • 表现:将所有数据一股脑打印到控制台或写入一个巨大的文件,不利于后续处理和分析。

  • 解决方案

    • 结构化存储:根据数据量和用途选择合适的存储方式。

      • JSON/CSV 文件:适合中小规模数据。
      • 数据库(SQLite, MySQL, PostgreSQL, MongoDB) :适合大规模、结构化数据,方便查询和管理。使用 ORM(如 SQLAlchemy)或数据库驱动(如 pymysql, psycopg2)。

误区八:单打独斗,忽视成熟框架

  • 表现:所有功能都从 requestsBeautifulSoup 开始自己写,重复造轮子,项目难以维护和扩展。

  • 解决方案

    • 使用 Scrapy 框架:对于大型、复杂的爬虫项目,Scrapy 是行业标准。它提供了:

      • 高性能的异步处理。
      • 内置的中间件、管道(Pipeline)、调度器系统,功能强大且易于扩展。
      • 丰富的生态系统(如 Scrapy-Redis 用于分布式爬虫)。
    • 即使是小项目,也可以借鉴它的设计思想。

误区九:IP 被封禁应对策略单一

  • 表现:一个 IP 地址疯狂请求,很快被网站封禁,爬虫无法继续工作。

  • 解决方案

    1. 代理IP池:使用第三方代理IP服务(如芝麻代理、快代理等)或自建代理IP池,轮流使用不同IP发送请求。

      import requests
      proxies = {
          'http': 'http://10.10.1.10:3128',
          'https': 'http://10.10.1.10:1080',
      }
      requests.get('http://example.org', proxies=proxies, timeout=10)
      
    2. 降低请求频率:合理设置下载延迟 (DOWNLOAD_DELAY in Scrapy)。

    3. 识别验证码:接入打码平台(如超级鹰)进行验证码识别(通常作为最后手段)。

总结:最佳实践 checklist

  1. [法律] 检查 robots.txt,尊重网站规则。
  2. [请求] 设置合理的请求头(UA, Referer, Cookie等)。
  3. [健壮性] 添加完善的异常处理(网络、解析)和重试机制。
  4. [解析] 编写健壮的解析代码,选择相对稳定的选择器,并与核心逻辑解耦。
  5. [动态内容] 优先寻找隐藏的 API 接口,必要时使用 Selenium/Splash。
  6. [效率] 对于大量请求,使用异步(aiohttp)或并发(线程池)来提高效率。
  7. [反爬] 使用代理IP池、控制访问速率、处理验证码来应对反爬虫。
  8. [存储] 设计良好的数据存储方案(文件/数据库)。
  9. [工程化] 大型项目优先考虑使用 Scrapy 框架。
  10. [日志] 为爬虫添加详细的日志记录,方便排查问题。

遵循这些原则和解决方案,你就能写出更加健壮、高效且合规的 Python 爬虫。

回头看这段爬虫学习之旅,从最初的横冲直撞到如今能成熟考虑法律伦理、性能优化和系统设计,每个坑都让我受益匪浅。爬虫世界没有一劳永逸的银弹,唯有保持敬畏、持续学习才是正道。希望我的这些经验能成为大家爬虫路上的垫脚石,帮助大家写出更优雅强大的程序。