1. 项目概述与核心思路拆解
“爬虫爬取二次元网站美女图片”,这个标题听起来简单直接,但背后涉及的技术栈、法律边界和实操细节,远比新手想象的要复杂。作为一名在数据采集领域摸爬滚打多年的从业者,我见过太多人兴致勃勃地开始,却因为忽略关键细节而踩坑,甚至引发不必要的麻烦。今天,我就以这个项目为引子,系统性地拆解一个合规、高效、可持续的图片爬虫应该如何构建,并分享那些你在官方文档里绝对看不到的实战心得。
首先,我们必须明确核心目标:自动化地从指定网站获取结构化的图片数据。这里的“二次元网站”是一个宽泛的指代,它可能是一个动漫图库、一个画师社区,或者一个壁纸分享站。不同网站的技术架构、反爬策略和内容组织形式天差地别,因此,我们的爬虫设计必须足够灵活和健壮。这个项目的价值不仅在于获取图片本身,更在于掌握一套应对复杂网络环境的数据采集方法论,包括请求模拟、数据解析、异常处理、存储优化以及最重要的——合规性考量。盲目爬取不仅可能被封IP,更可能触及法律红线,尤其是涉及个人肖像或版权作品时。
接下来,我会带你走完从环境准备、目标分析、代码编写到部署优化的完整流程。我们将使用 Python 作为主力语言,因为它拥有最丰富、最成熟的爬虫生态库。核心工具链将围绕requests(或aiohttp用于异步)、BeautifulSoup/lxml(用于解析 HTML)、re(正则表达式)以及PIL(用于图片处理)展开。同时,我会重点讲解如何识别和绕过常见的反爬机制,如何设计优雅的重试与存储逻辑,以及如何将爬虫部署为可定时运行的自动化任务。
注意:在开始任何爬虫项目前,请务必仔细阅读目标网站的
robots.txt文件(通常在网站根目录,如https://example.com/robots.txt)和服务条款。明确禁止爬取的目录,请勿触碰。对于个人学习研究,应控制请求频率,避免对目标服务器造成压力。本教程所有技术和思路仅用于合法合规的学习与交流目的。
2. 环境准备与目标网站分析
工欲善其事,必先利其器。在写第一行代码之前,充分的准备工作能让你在后续开发中事半功倍。
2.1 Python 环境与依赖库安装
建议使用 Python 3.8 及以上版本,并通过虚拟环境管理项目依赖,避免污染全局环境。
# 创建并激活虚拟环境(以 venv 为例) python -m venv spider_env source spider_env/bin/activate # Linux/Mac # 或 spider_env\Scripts\activate # Windows # 安装核心依赖 pip install requests beautifulsoup4 lxml pillow # 可选:安装异步库、数据库驱动等 # pip install aiohttp aiofiles pymysql pymongo- requests: 同步 HTTP 库,简单易用,是大多数爬虫项目的起点。
- beautifulsoup4: HTML/XML 解析库,配合
lxml解析引擎,能以“傻瓜式”的方式提取数据,对新手非常友好。 - lxml: 一个高性能的解析库,比 Python 内置的
html.parser更快、更容错,是BeautifulSoup推荐的解析器后端。 - Pillow (PIL): Python 图像处理库,可用于验证下载的图片是否完整、进行格式转换或简单处理。
如果你的目标网站页面动态加载内容较多(即数据通过 JavaScript 异步请求获取),你可能还需要Selenium或Playwright这类浏览器自动化工具,或者直接去分析网站背后的 API 接口。为了聚焦核心流程,我们首先假设目标网站是一个传统的服务端渲染页面,图片链接直接嵌入在 HTML 中。
2.2 目标网站结构侦察与分析
这是爬虫成功与否最关键的一步。你不能对着一个网站盲目地写代码。我们需要像侦探一样,仔细勘察“现场”。
1. 手动浏览,理清逻辑:打开你选定的二次元图片网站。观察图片的展示方式:是瀑布流?还是分页列表?点击“下一页”或滚动加载时,URL 有什么变化?图片是直接以<img src="...">形式嵌入,还是通过背景图background-image或 JavaScript 动态加载?
2. 使用开发者工具(DevTools):这是爬虫工程师的“瑞士军刀”。按 F12 打开,重点关注以下几个面板:
- Elements(元素):查看页面 HTML 结构,找到图片标签(
<img>)及其父容器的规律。例如,所有图片可能都包裹在一个class="gallery-item"的div中。 - Network(网络):这是核心中的核心。刷新页面或点击翻页,观察产生了哪些网络请求。筛选
XHR或Fetch类型的请求,这些往往是获取数据的 API 接口,其返回的 JSON 数据比解析 HTML 更干净。查看请求的Headers,特别是User-Agent,Referer,Cookie以及可能的自定义认证头(如Authorization,X-CSRF-Token)。 - Console(控制台):可以执行简单的 JavaScript 来测试选择器,例如
document.querySelectorAll(‘.gallery-item img’).length来验证能否正确选中所有图片元素。
3. 关键信息记录:将侦察结果整理成文档,至少应包括:
- 网站根URL:如
https://anime-pictures.net - 列表页URL模式:如
https://anime-pictures.net/posts?page=2,其中page参数控制分页。 - 图片详情页URL模式(如果有):点击缩略图进入的页面。
- 图片真实地址(src)的规律:可能是 CDN 链接,如
https://cdn.example.com/img/12345.jpg。注意区分缩略图(thumbnail_src)和原图(original_src)。 - 请求头关键字段:记录下看起来必要的
User-Agent,Referer等。 - 反爬迹象:是否有频繁刷新后出现验证码?请求头中是否有像
X-Requested-With这样的特殊字段?
实操心得:很多网站对Referer(来源页)有检查,如果直接请求图片链接而Referer不是本站,可能会返回 403 错误。因此,在代码中通常需要模拟设置Referer为图片所在页面的 URL。此外,一个容易被忽略的点是Cookie,有时维持一个会话 Cookie 对于访问后续页面是必须的,你可以使用requests.Session()对象来自动管理 Cookie。
3. 核心爬虫逻辑设计与实现
基于侦察结果,我们可以开始设计爬虫的核心逻辑了。一个健壮的爬虫通常包含以下几个模块:请求管理、数据解析、数据存储、异常处理与日志记录。我们将采用循序渐进的策略,先实现一个基础版本,再不断加固和优化。
3.1 基础请求与页面解析
我们假设目标网站是分页列表,每页展示多个图片帖子,点击缩略图会进入详情页,详情页才有高清大图的链接。这是一种非常常见的结构。
首先,实现获取单页帖子列表链接的功能:
import requests from bs4 import BeautifulSoup import time import logging from urllib.parse import urljoin # 配置日志,便于调试 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s') class AnimeImageSpider: def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() # 使用Session保持连接和Cookie # 设置一个合法的浏览器 User-Agent self.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', 'Referer': base_url, # 初始Referer设为首页 } self.session.headers.update(self.headers) def fetch_page(self, url, params=None): """发送HTTP请求,并返回响应文本""" try: # 添加随机延迟,模拟人类操作,避免请求过快 time.sleep(1.5) logging.info(f‘正在请求: {url}’) response = self.session.get(url, params=params, timeout=10) response.raise_for_status() # 如果状态码不是200,抛出HTTPError异常 # 检查编码,避免乱码 response.encoding = response.apparent_encoding or ‘utf-8’ return response.text except requests.exceptions.RequestException as e: logging.error(f‘请求 {url} 失败: {e}’) return None def parse_list_page(self, html_content): """解析列表页,提取所有帖子详情页的链接""" if not html_content: return [] soup = BeautifulSoup(html_content, ‘lxml’) post_links = [] # 假设每个帖子链接都在 class=“post-preview” 的 <a> 标签的 href 属性中 # 你需要根据实际网站结构调整选择器 for link in soup.select(‘.post-preview a[href]’): href = link.get(‘href’) if href and ‘/posts/’ in href: # 过滤出帖子详情页链接 full_url = urljoin(self.base_url, href) post_links.append(full_url) logging.info(f‘本页解析到 {len(post_links)} 个帖子链接’) return post_links # 使用示例 if __name__ == ‘__main__’: spider = AnimeImageSpider(base_url=‘https://anime-pictures.net’) # 获取第一页 first_page_html = spider.fetch_page(‘https://anime-pictures.net/posts’) if first_page_html: post_urls = spider.parse_list_page(first_page_html) for url in post_urls[:3]: # 先打印前三个看看 print(url)这段代码构建了一个爬虫的骨架。fetch_page方法负责发送请求并处理基础网络异常;parse_list_page方法使用 CSS 选择器从 HTML 中提取我们需要的链接。这里的选择器.post-preview a[href]是一个示例,你必须根据实际网站情况修改。使用lxml作为解析器需要确保已安装,它比默认的html.parser更快更强大。
3.2 详情页解析与图片链接提取
获取到帖子详情页的 URL 后,下一步就是进入每个详情页,找到高清图片的真实下载地址。
def parse_detail_page(self, html_content): """解析详情页,提取图片标题和原始图片URL""" if not html_content: return None, None soup = BeautifulSoup(html_content, ‘lxml’) # 1. 提取图片标题(用于命名文件) # 假设标题在 <h1 class=“post-title”> 标签内 title_elem = soup.select_one(‘h1.post-title’) title = title_elem.get_text(strip=True) if title_elem else ‘untitled’ # 清理文件名中的非法字符 import re title = re.sub(r‘[<>:“/\\|?*]’, ‘_’, title)[:100] # 限制长度 # 2. 提取原始图片URL # 情况A: 图片直接在一个高分辨率的img标签里 img_elem = soup.select_one(‘#highres img[src]’) if img_elem: img_url = img_elem.get(‘src’) else: # 情况B: 图片URL可能藏在某个元数据属性里,如>import os from PIL import Image from io import BytesIO def download_image(self, img_url, title, save_dir=‘./downloads’): """下载并保存单张图片""" if not img_url: return False # 为当前详情页设置特定的Referer headers = {‘Referer’: self.base_url} # 可以合并session的headers headers.update(self.session.headers) try: # 图片请求可以适当快一点,但也要有延迟 time.sleep(0.5) response = self.session.get(img_url, headers=headers, timeout=15) response.raise_for_status() # 验证是否为有效图片 try: img = Image.open(BytesIO(response.content)) img.verify() # 验证完整性 except Exception as e: logging.error(f‘图片 {img_url} 下载内容不完整或非图片: {e}’) return False # 创建保存目录 os.makedirs(save_dir, exist_ok=True) # 根据Content-Type或URL后缀确定扩展名 content_type = response.headers.get(‘Content-Type’, ‘’) if ‘jpeg’ in content_type or ‘jpg’ in content_type or img_url.lower().endswith((‘.jpg’, ‘.jpeg’)): ext = ‘.jpg’ elif ‘png’ in content_type or img_url.lower().endswith(‘.png’): ext = ‘.png’ elif ‘gif’ in content_type or img_url.lower().endswith(‘.gif’): ext = ‘.gif’ else: # 默认使用jpg,或者从URL中提取 ext = os.path.splitext(img_url)[1] if not ext: ext = ‘.jpg’ # 生成文件名:标题 + 部分URL哈希(避免重名) import hashlib url_hash = hashlib.md5(img_url.encode()).hexdigest()[:8] safe_title = title.replace(‘ ‘, ‘_’) filename = f‘{safe_title}_{url_hash}{ext}’ filepath = os.path.join(save_dir, filename) # 保存图片 with open(filepath, ‘wb’) as f: f.write(response.content) logging.info(f‘图片已保存: {filepath}’) return True except requests.exceptions.RequestException as e: logging.error(f‘下载图片 {img_url} 失败: {e}’) return False except IOError as e: logging.error(f‘保存图片 {img_url} 失败: {e}’) return False这里我们使用了PIL的verify()方法来快速检查下载的字节流是否是一张完整的图片,这是一个很好的防错措施。文件命名策略结合了标题和 URL 哈希值,既保留了可读性,又避免了因标题重复或含非法字符导致的问题。
3.4 整合与分页爬取
现在,我们把列表爬取、详情解析和图片下载串联起来,并加入分页逻辑。
def crawl(self, start_page=1, max_pages=5, save_dir=‘./downloads’): """主爬取流程""" all_downloaded = [] current_page = start_page while current_page <= max_pages: logging.info(f‘开始爬取第 {current_page} 页’) # 构建列表页URL,假设分页参数是 ‘page’ list_url = f‘{self.base_url}/posts’ params = {‘page’: current_page} list_html = self.fetch_page(list_url, params=params) if not list_html: logging.warning(f‘第 {current_page} 页获取失败,跳过’) current_page += 1 continue post_urls = self.parse_list_page(list_html) if not post_urls: logging.info(f‘第 {current_page} 页无帖子,可能已到末页’) break for post_url in post_urls: # 获取详情页 detail_html = self.fetch_page(post_url) if not detail_html: continue title, img_url = self.parse_detail_page(detail_html) if img_url: success = self.download_image(img_url, title, save_dir) if success: all_downloaded.append((title, img_url)) # 处理完一个帖子后稍作休息 time.sleep(1) current_page += 1 # 翻页间隔可以稍长 time.sleep(2) logging.info(f‘爬取结束,共成功下载 {len(all_downloaded)} 张图片’) return all_downloaded # 运行爬虫 if __name__ == ‘__main__’: spider = AnimeImageSpider(base_url=‘https://anime-pictures.net’) spider.crawl(start_page=1, max_pages=2, save_dir=‘./anime_pics’)这个crawl方法实现了完整的闭环:从起始页开始,循环获取每一页的帖子列表,然后逐个进入详情页下载图片。其中加入了多处time.sleep来控制请求频率,这是体现“友好爬虫”的基本素养,能显著降低被封 IP 的风险。max_pages参数用于控制爬取深度,避免无限爬取。
4. 反爬策略应对与性能优化
一个只能运行几分钟的爬虫是没用的。真实的网站会有各种防御措施。下面我们来加固我们的爬虫,并提升其效率和稳定性。
4.1 常见反爬机制与破解
- User-Agent 检测:我们已经模拟了主流浏览器的 UA,这通常是最基础的一步。可以准备一个 UA 列表轮流使用,增加随机性。
- 请求频率限制:我们已经通过
time.sleep进行了简单控制。更高级的做法是使用随机延迟(如time.sleep(random.uniform(1, 3))),并监控响应状态码,遇到 429(Too Many Requests)时自动延长等待时间。 - IP 封禁:这是最严厉的措施。单机单 IP 频繁请求很容易被封锁。解决方案包括:
- 使用代理 IP 池:从付费或免费的代理服务商获取 IP,在请求时随机切换。代码上,可以给
session.get传递proxies参数。
proxies = { ‘http’: ‘http://your-proxy-ip:port’, ‘https’: ‘https://your-proxy-ip:port’, } response = self.session.get(url, proxies=proxies)- 重要提醒:绝对不要尝试使用任何非法或绕过网络管控的手段获取代理。商业爬虫项目应使用合规的商业代理服务。
- 使用代理 IP 池:从付费或免费的代理服务商获取 IP,在请求时随机切换。代码上,可以给
- JavaScript 渲染与动态内容:如果目标网站大量使用 JS 加载数据(如 React, Vue 构建的单页应用),
requests+BeautifulSoup就无能为力了,因为拿到的 HTML 是空的初始框架。这时有两种主流方案:- 方案A:使用无头浏览器,如
Selenium或Playwright。它们能模拟真实浏览器行为,执行 JS,但速度慢、资源消耗大。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC options = webdriver.ChromeOptions() options.add_argument(‘--headless’) # 无头模式 driver = webdriver.Chrome(options=options) driver.get(url) # 等待某个元素出现,确保页面加载完成 WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, “post-list”))) html = driver.page_source driver.quit() # 然后用 BeautifulSoup 解析 html- 方案B:直接调用数据接口。在 Network 面板找到真正返回数据的 API 请求(通常是 XHR/Fetch),直接模拟这个请求。这比方案A高效得多。你需要分析该请求的 URL、参数、Headers(可能包含加密 Token)。
- 方案A:使用无头浏览器,如
- 验证码:遇到验证码通常意味着你的爬虫行为已被识别为机器人。此时应立刻停止,大幅降低请求频率,或考虑使用付费的打码平台服务(OCR识别),但这会显著增加复杂度和成本。最好的策略是避免触发验证码。
4.2 异步加速与断点续传
当需要爬取大量页面时,同步请求(一个接一个)会非常慢。我们可以使用aiohttp进行异步并发请求,极大提升效率。
import aiohttp import asyncio import aiofiles class AsyncAnimeSpider: def __init__(self, base_url, concurrency=5): self.base_url = base_url self.concurrency = concurrency # 并发数 self.semaphore = asyncio.Semaphore(concurrency) # 控制并发量 async def fetch(self, session, url): """异步获取页面""" async with self.semaphore: # 限制并发 await asyncio.sleep(1) # 异步延迟 try: async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response: response.raise_for_status() return await response.text() except Exception as e: print(f‘请求失败 {url}: {e}’) return None async def download_single_img(self, session, img_url, title, save_dir): """异步下载单张图片""" # 实现逻辑与同步版类似,使用 aiohttp 和 aiofiles pass async def crawl_page(self, page_num): """异步爬取单页""" list_url = f‘{self.base_url}/posts?page={page_num}’ async with aiohttp.ClientSession() as session: html = await self.fetch(session, list_url) # ... 解析列表,获取详情页URL列表 details_urls tasks = [] for detail_url in details_urls: task = asyncio.create_task(self.process_detail(session, detail_url)) tasks.append(task) await asyncio.gather(*tasks) async def main(self, start_page, end_page): tasks = [self.crawl_page(i) for i in range(start_page, end_page+1)] await asyncio.gather(*tasks) # 运行异步爬虫 # asyncio.run(AsyncAnimeSpider(‘https://...’).main(1, 5))异步编程能充分利用网络 IO 的等待时间,但代码复杂度更高,错误处理也更麻烦。对于新手,建议先掌握同步版本。
断点续传:对于大规模爬取,爬虫可能因网络、程序错误而中断。我们需要记录爬取状态。一个简单的方法是将成功下载的图片 URL 记录到一个文件或数据库中。每次启动时,先加载这个记录,过滤掉已经下载过的 URL,再从断点处开始。
import json import os class StatefulSpider(AnimeImageSpider): def __init__(self, base_url, state_file=‘crawl_state.json’): super().__init__(base_url) self.state_file = state_file self.downloaded_urls = self.load_state() def load_state(self): """加载已下载的URL记录""" if os.path.exists(self.state_file): with open(self.state_file, ‘r’, encoding=‘utf-8’) as f: return set(json.load(f)) return set() def save_state(self): """保存状态到文件""" with open(self.state_file, ‘w’, encoding=‘utf-8’) as f: json.dump(list(self.downloaded_urls), f, ensure_ascii=False, indent=2) def download_image(self, img_url, title, save_dir=‘./downloads’): """重写下载方法,加入状态检查""" if img_url in self.downloaded_urls: logging.info(f‘图片已下载,跳过: {img_url}’) return False success = super().download_image(img_url, title, save_dir) if success: self.downloaded_urls.add(img_url) self.save_state() # 每次成功下载后立即保存状态(也可批量保存) return success4.3 存储优化与去重
随着图片数量增多,文件直接堆在文件夹里会难以管理。可以考虑:
- 按日期/标签建立子文件夹:
./downloads/2023-10-27/或./downloads/character_xxx/。 - 使用数据库记录元数据:使用 SQLite 或 MySQL 记录图片的标题、原URL、来源页、下载时间、文件路径、MD5值等。MD5值可用于精确去重(即使URL不同,但内容相同的图片)。
- 链接去重:在爬取列表页时,可能会遇到同一图片出现在不同页面。可以用
set存储已爬取的详情页URL或图片URL,避免重复请求。
5. 工程化部署与调度
当爬虫脚本稳定后,你可能希望它定时自动运行。这时就需要考虑部署。
5.1 日志与监控
完善的日志系统对于排查线上问题至关重要。我们之前使用了logging基础配置,可以进一步升级为按日期滚动的文件日志。
import logging.handlers def setup_logging(): logger = logging.getLogger() logger.setLevel(logging.INFO) # 控制台输出 console_handler = logging.StreamHandler() console_format = logging.Formatter(‘%(asctime)s - %(levelname)s: %(message)s’) console_handler.setFormatter(console_format) logger.addHandler(console_handler) # 文件输出,每天一个文件,保留7天 file_handler = logging.handlers.TimedRotatingFileHandler( ‘spider.log’, when=‘D’, interval=1, backupCount=7, encoding=‘utf-8’ ) file_format = logging.Formatter(‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) file_handler.setFormatter(file_format) logger.addHandler(file_handler)5.2 使用任务调度器
在 Linux 服务器上,最经典的方式是使用Cron。你可以编写一个启动脚本,然后配置 Cron 定时任务。
# 编辑cron任务 crontab -e # 添加一行,例如每天凌晨2点运行一次 0 2 * * * cd /path/to/your/spider && /path/to/your/venv/bin/python main.py >> /path/to/log/cron.log 2>&1在 Windows 上,可以使用任务计划程序。对于更复杂的依赖管理和监控,可以使用像Apache Airflow或Celery这样的专业任务调度框架,但这对于个人项目来说可能过于重型。
5.3 容器化部署(Docker)
为了环境一致性和便于迁移,可以将爬虫 Docker 化。
# Dockerfile FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [“python”, “main.py”]然后构建镜像并运行容器:
docker build -t anime-spider . docker run -v $(pwd)/downloads:/app/downloads -v $(pwd)/logs:/app/logs --rm anime-spider-v参数将本地目录挂载到容器内,这样下载的图片和日志文件就会保存在宿主机上,不会随着容器销毁而丢失。
6. 法律风险与道德规范
这是必须单独强调的一章。技术本身无罪,但使用技术的方式有对错之分。
- 尊重
robots.txt:这是网站与爬虫之间的“君子协议”。如果robots.txt中明确写了Disallow: /或Disallow: /api/,那么你就应该停止爬取或避开这些目录。robotparser模块可以帮助你解析它。 - 遵守网站服务条款:很多网站在用户协议中明确禁止任何形式的自动化数据抓取。违反条款可能导致法律诉讼。
- 版权与肖像权:二次元图片通常是画师或版权方的作品。未经授权,大规模爬取并用于商业用途(如训练AI模型、制作周边售卖)是明确的侵权行为。即使是个人收藏,也应尊重原作者,最好在下载时保留原出处信息,并遵守原图发布页面的使用规定(如“仅限个人欣赏”)。
- 控制访问频率:你的爬虫不应该对目标网站的正常运营造成影响。过快的请求相当于 DDoS 攻击。务必添加足够的延迟,并考虑在服务器流量低的时段(如凌晨)运行。
- 数据用途:明确你爬取数据的目的。用于学习、研究、个人兴趣通常是可接受的灰色地带。但公开传播、用于盈利或任何可能损害网站利益的行为,风险极高。
个人建议:对于个人学习项目,可以挑选一些明确允许爬取或API开放的网站(例如一些提供公开API的图库),或者针对明确声明了采用宽松许可证(如 Creative Commons)的内容进行爬取。将爬虫作为学习网络协议、数据处理和编程的工具,而非获取资源的捷径,才是长久之道。
7. 常见问题排查与实战技巧
即使按照教程一步步来,在实际操作中你还是会遇到各种各样的问题。这里我总结了一份“踩坑实录”,希望能帮你快速排雷。
7.1 请求被拒绝(403 Forbidden)
这是最常见的问题。
- 检查1:User-Agent。确保你的
User-Agent是真实的浏览器字符串,而不是python-requests/2.x.x。 - 检查2:Referer。很多图片服务器会校验
Referer。确保下载图片时,Referer头设置为图片所在页面的 URL。 - 检查3:Cookie 和 Session。有些网站需要登录后才能访问,或者需要维持一个会话。使用
requests.Session()对象,并在首次访问后保持它。对于需要登录的网站,你可能需要先模拟登录,获取有效的cookies。 - 检查4:请求头完整性。用浏览器访问一次目标页面,在开发者工具的 Network 面板里,复制那个成功请求的所有 Headers(包括
Accept,Accept-Language,Accept-Encoding,Connection等),尽量完整地模拟。有些网站会检查这些头是否齐全。 - 检查5:IP被封锁。如果以上都正确,但突然大量返回403或连接超时,很可能IP被临时封禁。立即停止爬虫,等待几小时或更换IP(如切换手机热点、使用合规代理)。
7.2 解析不到数据(返回空列表)
- 检查1:选择器是否正确。网站改版是常事。定期检查你的CSS选择器或XPath是否还能定位到目标元素。使用浏览器的“检查”功能,右键元素“Copy” -> “Copy selector” 可以快速获取选择器。
- 检查2:页面是否是动态加载。如果右键“查看网页源代码”发现没有你想要的数据,但页面上却能看见,那一定是动态加载的。按照4.1节的方法,使用无头浏览器或直接找API接口。
- 检查3:编码问题。虽然
response.encoding通常能自动处理,但有些网站编码声明错误,导致中文乱码。可以尝试response.content.decode(‘gbk’)或‘utf-8’等不同编码。
7.3 下载的图片损坏或尺寸不对
- 检查1:下载的是否是真实图片。有些网站会先返回一张很小的占位图(placeholder),真正的原图需要通过另一个请求获取。仔细分析点击“查看原图”或“下载”按钮时触发的网络请求。
- 检查2:流式下载。对于大图片,使用
response.iter_content(chunk_size=8192)进行流式下载,避免内存不足。 - 检查3:验证文件头。我们之前用
PIL的verify()做了基础校验。你还可以检查文件大小,如果远小于预期,可能下载不完整。
7.4 爬虫运行速度慢
- 优化1:异步并发。如4.2节所述,使用
aiohttp进行异步IO是根本性提速方案。 - 优化2:减少不必要的解析。如果列表页只需要提取链接,就不要用
BeautifulSoup解析整个页面,可以尝试用正则表达式快速匹配,或者用lxml的iterparse进行增量解析。 - 优化3:连接复用。坚持使用
requests.Session(),它底层会保持 TCP 连接,避免重复握手。 - 优化4:调整延迟。在服务器承受能力和你的时间成本间找平衡。如果对方反爬不严,可以适当降低延迟,但务必谨慎。
7.5 数据存储与管理混乱
- 建议1:结构化存储。强烈建议使用轻量级数据库(如 SQLite)。一张表可以记录
id, title, source_url, image_url, file_path, download_time, md5。这样便于搜索、去重和后期管理。 - 建议2:文件命名规范化。不要直接用标题作为文件名,标题可能含有非法字符或过长。采用“时间戳_哈希值.扩展名”或“标题_哈希值.扩展名”的格式更稳妥。
- 建议3:定期备份与清理。设定规则,比如只保留最近3个月的数据,或者定期将老数据归档到冷存储。
爬虫项目是一个系统工程,从简单的几行代码到稳定可用的生产级工具,中间需要不断地迭代、测试和优化。最宝贵的经验往往来自于解决一个又一个具体的错误。希望这篇超详细的指南,能为你提供一个坚实的起点和清晰的排错地图。记住,保持耐心,尊重规则,在技术的边界内探索和创造。