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

下面是一个使用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识别等高级功能。总体可扩展性相对比较灵活,虽说爬虫是一门技术,但是一定要用到正规途径中,爬虫不违法,违法的只是你不能破坏网站规则以及爬取敏感信息。