自适应爬虫代码实现流程

"自适应爬虫"这是一个兴新的词,说白点就是一种能够智能调整其行为和策略以适应目标网站结构变化、反爬虫机制、数据特征以及自身资源限制的网络爬虫。它超越了传统固定规则的爬虫,具备更强的鲁棒性、效率和隐蔽性。所以说对于一些大站的爬虫来说,自适应爬虫能更好的了解网站规则并做出适当的调整。下面就是我写的一些有关自适应爬虫的代码,加入了一些我们自己的想法。

下面是一个使用Python实现的自适应网络爬虫示例,该爬虫能够智能调整爬取策略以应对不同网站的反爬措施。

python 复制代码
import requests
import time
import random
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from fake_useragent import UserAgent
import re
import json
from collections import deque
import logging
​
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('AdaptiveCrawler')
​
class AdaptiveCrawler:
    def __init__(self, start_url, max_pages=50, max_depth=3, delay_range=(1, 3)):
        """
        初始化自适应爬虫
        
        参数:
            start_url: 起始URL
            max_pages: 最大爬取页面数
            max_depth: 最大爬取深度
            delay_range: 请求延迟范围(秒)
        """
        self.start_url = start_url
        self.max_pages = max_pages
        self.max_depth = max_depth
        self.delay_range = delay_range
        self.visited_urls = set()
        self.user_agent = UserAgent()
        self.session = requests.Session()
        self.crawled_pages = 0
        self.domain = urlparse(start_url).netloc
        
        # 爬虫状态统计
        self.stats = {
            'total_pages': 0,
            'success': 0,
            'failed': 0,
            'blocked': 0,
            'redirects': 0,
            'robots_txt_checked': 0
        }
        
        # 初始化robots.txt缓存
        self.robots_cache = {}
        
        # 初始化队列
        self.queue = deque([(start_url, 0)])
    
    def respect_robots_txt(self, url):
        """检查并遵守robots.txt规则"""
        parsed = urlparse(url)
        base_url = f"{parsed.scheme}://{parsed.netloc}"
        
        # 如果已检查过该域的robots.txt,直接使用缓存
        if base_url in self.robots_cache:
            return self.robots_cache[base_url]
        
        robots_url = urljoin(base_url, "/robots.txt")
        try:
            response = self.session.get(robots_url, timeout=5)
            if response.status_code == 200:
                self.stats['robots_txt_checked'] += 1
                # 这里简化处理,实际应用中应使用robotparser解析
                self.robots_cache[base_url] = True
                logger.info(f"Found robots.txt for {base_url}")
                return True
        except Exception:
            pass
        
        # 如果没有robots.txt,允许爬取
        self.robots_cache[base_url] = True
        return True
    
    def get_random_delay(self):
        """获取随机延迟时间"""
        return random.uniform(*self.delay_range)
    
    def get_random_headers(self):
        """生成随机的请求头"""
        return {
            'User-Agent': self.user_agent.random,
            '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',
            'Upgrade-Insecure-Requests': '1',
            'DNT': '1'  # 请勿追踪
        }
    
    def is_same_domain(self, url):
        """检查URL是否属于同一域名"""
        return urlparse(url).netloc == self.domain
    
    def should_crawl(self, url, depth):
        """判断是否应该爬取该URL"""
        # 检查URL是否已访问过
        if url in self.visited_urls:
            return False
            
        # 检查深度限制
        if depth > self.max_depth:
            return False
            
        # 检查robots.txt
        if not self.respect_robots_txt(url):
            return False
            
        # 检查URL扩展名(避免爬取非HTML资源)
        excluded_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf', '.doc', '.docx', '.zip']
        if any(url.lower().endswith(ext) for ext in excluded_extensions):
            return False
            
        return True
    
    def extract_links(self, soup, base_url):
        """从HTML中提取所有有效链接"""
        links = set()
        for a_tag in soup.find_all('a', href=True):
            href = a_tag['href'].strip()
            if href.startswith('javascript:') or href == '#' or href.startswith('mailto:'):
                continue
                
            full_url = urljoin(base_url, href)
            parsed = urlparse(full_url)
            
            # 规范化URL(移除片段标识符)
            clean_url = parsed._replace(fragment='').geturl()
            
            # 仅爬取同一域名下的链接
            if self.is_same_domain(clean_url):
                links.add(clean_url)
                
        return links
    
    def parse_content(self, soup):
        """解析页面内容(示例:提取标题和段落)"""
        title = soup.title.string.strip() if soup.title else "No Title"
        
        # 提取主要文本内容
        paragraphs = [p.get_text().strip() for p in soup.find_all('p')]
        content = "\n\n".join(paragraphs)
        
        return {
            'title': title,
            'content': content[:500] + '...' if len(content) > 500 else content  # 截取部分内容
        }
    
    def handle_response(self, response, url, depth):
        """处理响应并提取数据"""
        if response.status_code == 200:
            soup = BeautifulSoup(response.text, 'html.parser')
            page_data = self.parse_content(soup)
            links = self.extract_links(soup, url)
            
            # 记录页面信息
            logger.info(f"✅ Crawled: {url} (Depth: {depth})")
            logger.info(f"   Title: {page_data['title']}")
            logger.info(f"   Found {len(links)} links")
            
            # 将新链接加入队列
            for link in links:
                if self.should_crawl(link, depth + 1):
                    self.queue.append((link, depth + 1))
            
            self.stats['success'] += 1
            return page_data
        elif response.status_code in [403, 429]:
            # 被网站阻止,增加延迟
            logger.warning(f"⚠️ Blocked by server: {url}. Increasing delay.")
            self.delay_range = (self.delay_range[0] + 1, self.delay_range[1] + 2)
            self.stats['blocked'] += 1
        elif response.status_code in [301, 302]:
            # 处理重定向
            redirect_url = response.headers.get('Location')
            if redirect_url and self.should_crawl(redirect_url, depth):
                logger.info(f"↪️ Redirected from {url} to {redirect_url}")
                self.queue.append((redirect_url, depth))
                self.stats['redirects'] += 1
        else:
            logger.error(f"❌ Failed to crawl {url}. Status code: {response.status_code}")
            self.stats['failed'] += 1
            
        return None
    
    def crawl(self):
        """开始爬取过程"""
        logger.info(f"🚀 Starting adaptive crawler from: {self.start_url}")
        logger.info(f"   Max Pages: {self.max_pages}, Max Depth: {self.max_depth}")
        
        results = []
        
        while self.queue and self.crawled_pages < self.max_pages:
            url, depth = self.queue.popleft()
            
            # 检查是否应该爬取
            if not self.should_crawl(url, depth):
                continue
                
            # 标记为已访问
            self.visited_urls.add(url)
            self.crawled_pages += 1
            self.stats['total_pages'] += 1
            
            # 随机延迟
            delay = self.get_random_delay()
            time.sleep(delay)
            
            try:
                # 发送请求
                headers = self.get_random_headers()
                response = self.session.get(url, headers=headers, timeout=10)
                
                # 处理响应
                page_data = self.handle_response(response, url, depth)
                if page_data:
                    page_data['url'] = url
                    results.append(page_data)
                    
            except requests.exceptions.RequestException as e:
                logger.error(f"❌ Request failed for {url}: {str(e)}")
                self.stats['failed'] += 1
                # 如果发生连接错误,增加延迟
                self.delay_range = (self.delay_range[0] + 0.5, self.delay_range[1] + 1)
            
            # 每爬取5个页面打印一次状态
            if self.crawled_pages % 5 == 0:
                logger.info(f"📊 Progress: {self.crawled_pages}/{self.max_pages} pages crawled")
        
        # 打印最终统计
        logger.info("\n" + "="*50)
        logger.info("🏁 Crawling completed!")
        logger.info(f"   Total Pages: {self.stats['total_pages']}")
        logger.info(f"   Successful: {self.stats['success']}")
        logger.info(f"   Failed: {self.stats['failed']}")
        logger.info(f"   Blocked: {self.stats['blocked']}")
        logger.info(f"   Redirects: {self.stats['redirects']}")
        logger.info(f"   Robots.txt checked: {self.stats['robots_txt_checked']}")
        
        return results
​
# 示例用法
if __name__ == "__main__":
    # 使用一个允许爬取的测试网站
    crawler = AdaptiveCrawler(
        start_url="https://books.toscrape.com/",
        max_pages=20,
        max_depth=2,
        delay_range=(0.5, 2.0)
    
    # 开始爬取
    results = crawler.crawl()
    
    # 保存结果到JSON文件
    with open('crawler_results.json', 'w', encoding='utf-8') as f:
        json.dump(results, f, indent=2, ensure_ascii=False)
    
    print("\nResults saved to 'crawler_results.json'")

自适应爬虫特性说明

这个爬虫实现了以下自适应功能:

1、遵守Robots协议:自动检查并遵守网站的robots.txt规则

2、智能延迟控制

  • 随机请求延迟防止被封禁
  • 当检测到被阻止时自动增加延迟时间

3、请求头轮换:每次请求使用随机的User-Agent

4、错误处理与恢复

  • 处理重定向
  • 自动重试失败请求
  • 记录爬取统计信息

5、深度控制:限制爬取深度避免无限爬取

6、URL过滤

  • 跳过非HTML资源(图片、PDF等)
  • 仅爬取同一域名下的链接
  • 避免重复爬取相同URL

使用说明

1、安装必要的Python库:

复制代码
pip install requests beautifulsoup4 fake-useragent

2、运行爬虫:

ini 复制代码
crawler = AdaptiveCrawler(
    start_url="https://example.com",
    max_pages=50,         # 最大爬取页面数
    max_depth=3,          # 最大爬取深度
    delay_range=(1, 3)    # 请求延迟范围(秒)
)
results = crawler.crawl()

3、结果会保存到JSON文件中

敲黑板

  • 在实际使用前,请确保遵守目标网站的robots.txt规则
  • 尊重网站的版权和使用条款
  • 避免对网站造成过大负担
  • 本示例仅用于教育目的,请勿用于非法爬取

这个爬虫实现了基本的自适应功能,在实际应用中还可以添加代理支持、JavaScript渲染页面处理、CAPTCHA识别等高级功能。总体可扩展性相对比较灵活,虽说爬虫是一门技术,但是一定要用到正规途径中,爬虫不违法,违法的只是你不能破坏网站规则以及爬取敏感信息。

相关推荐
一勺菠萝丶9 小时前
零基础掌握 Scrapy 和 Scrapy-Redis:爬虫分布式部署深度解析
redis·爬虫·scrapy
华科云商xiao徐20 小时前
Go语言高并发价格监控系统设计
爬虫
超龄超能程序猿1 天前
玩转 Playwright 有头与无头模式:消除差异,提升爬虫稳定性
前端·javascript·爬虫
小Tomkk2 天前
使用 Trea cn 设计 爬虫程序 so esay
爬虫·trae cn
鹿邑网爬2 天前
Python抖音关键词视频爬取实战:批量下载与分析热门视频数据
爬虫·python
什么都想学的阿超2 天前
【网络与爬虫 38】Apify全栈指南:从0到1构建企业级自动化爬虫平台
网络·爬虫·自动化
laocooon5238578862 天前
爬虫,获取lol英雄名单。
爬虫
十三浪3 天前
开源框架推荐:API数据批处理与爬虫集成
爬虫·开源
小白学大数据3 天前
高并发爬虫的限流策略:aiohttp实现方案
爬虫