Python异步爬虫实战:Pixiv图片与元数据高效采集系统构建指南
1. 项目概述:为什么选择 Pixiv 作为爬虫实践对象?
如果你对二次元文化、插画艺术或者仅仅是海量的精美图片感兴趣,那么 Pixiv(简称 P 站)这个名字你一定不陌生。作为一个全球性的插画艺术家社交与作品发布平台,它汇聚了数百万创作者和数以亿计的作品。对于开发者,尤其是数据采集和 Python 学习者而言,Pixiv 是一个极具挑战性又充满价值的“实战练兵场”。它不像一些简单的静态网页,Pixiv 的防护机制、动态加载逻辑、以及庞大的数据量,都让针对它的爬虫项目变得复杂而有趣。这个项目不仅仅是写几行
requests.get()
那么简单,它涉及到登录态管理、反爬策略应对、异步高效抓取、以及大规模数据的存储与去重,几乎涵盖了现代网络爬虫工程中会遇到的大部分核心问题。通过这个项目,你能学到的远不止“如何下载图片”,而是构建一个健壮、可维护、高效率的数据采集系统的完整思路。
2. 核心需求与目标拆解:我们到底要爬什么?
在动手写一行代码之前,明确目标至关重要。针对 Pixiv 的爬虫,需求可以非常多样,我们需要将其具体化。
2.1 常见爬取目标分析
- 按画师(用户)爬取 :这是最直接的需求。给定一个或多个画师的 ID 或主页 URL,爬取他们发布的所有作品(插画、漫画、动图)。这需要处理画师主页的作品列表翻页,并识别出每件作品的唯一标识符(Illust ID)。
- 按标签(Tag)爬取 :Pixiv 的标签系统非常强大。我们可以爬取特定标签(如“原神”、“Fate/GrandOrder”、“風景”)下的热门作品或最新作品。这涉及到标签搜索页面的解析和排序规则的处理。
- 按收藏(Bookmark)爬取 :爬取自己或某个公开用户收藏夹里的作品。这通常需要有效的登录状态(Cookie),因为非公开收藏夹无法访问。
- 排行榜数据爬取 :Pixiv 有日榜、周榜、月榜、原创榜、新人榜等。爬取排行榜是获取高质量作品的捷径,页面结构相对固定。
- 作品详情深度爬取 :除了图片本身,作品标题、描述、上传时间、浏览量、点赞数、收藏数、使用的标签、评论等元数据也极具价值,可用于数据分析或推荐系统。
2.2 项目核心目标设定
对于一个综合性的学习项目,我建议设定一个涵盖多方面的目标,例如: “构建一个能够根据画师ID或标签名,自动爬取其下所有作品的原图(或多种尺寸图),并完整保存作品元数据(标题、画师、标签、统计信息)到本地数据库或文件系统的爬虫系统。”
这个目标包含了用户输入、页面遍历、数据解析、多媒体下载、元数据存储等多个环节,是一个完整的 pipeline。
3. 技术栈选型与核心工具解析
工欲善其事,必先利其器。针对 Pixiv 的特点,我们的技术栈需要精心挑选。
3.1 网络请求库:
httpx
优于
requests
虽然
requests
库简单易用,但在应对现代复杂网页和需要高性能的场景时,
httpx
是更佳选择。
-
异步支持
:
httpx原生支持异步(async/await),这对于需要发起大量网络请求(如下载成千上万张图片)的爬虫来说,性能提升是数量级的。Pixiv 的图片服务器支持并发请求,异步能极大缩短总耗时。 -
HTTP/2 支持
:
httpx默认支持 HTTP/2,在某些情况下可以提升连接效率。 -
兼容性
:
httpx的 API 设计与requests高度相似,学习成本低。
# 同步请求(类似requests)
import httpx
client = httpx.Client(headers=your_headers)
response = client.get('https://www.pixiv.net/')
client.close()
# 异步请求(高性能之选)
import asyncio
import httpx
async def fetch(url):
async with httpx.AsyncClient(headers=your_headers) as client:
response = await client.get(url)
return response
3.2 解析库:
parsel
或
BeautifulSoup
搭配
json
解析
Pixiv 的页面数据大量通过前端 JavaScript 渲染,直接解析 HTML 往往找不到关键数据。观察网络请求会发现,很多数据是以 JSON 格式内嵌在 HTML 的
<script>
标签中,或通过 API 接口返回。
-
parsel: 由 Scrapy 团队开发,兼容 CSS 选择器和 XPath,性能很好。适合快速提取 HTML 中的片段。 -
BeautifulSoup: 老牌解析库,语法灵活,容错性好,适合处理“不干净”的 HTML。 -
核心技巧
: 优先尝试直接寻找 JSON 数据。在页面源码中搜索 `` 或
pixiv.context等关键词,经常能直接找到包含作品列表、用户信息的完整 JSON 对象,这比解析 DOM 树要稳定和高效得多。
import json
import re
from parsel import Selector
html = ... # 获取的页面HTML
selector = Selector(text=html)
# 尝试从js变量中提取json
script_text = selector.xpath('//script[contains(text(), "pixiv.context")]/text()').get()
if script_text:
# 使用正则表达式提取JSON部分
match = re.search(r'pixiv\.context\s*=\s*(\{.*?\});', script_text, re.DOTALL)
if match:
data = json.loads(match.group(1))
# data 中可能包含了作品数据、用户信息等
3.3 异步任务与队列:
asyncio
+
aiofiles
+
aiohttp
(可选)
对于下载任务,我们需要管理成千上万的协程。
-
asyncio: Python 原生的异步IO框架,用于创建和管理事件循环、协程、任务。 -
aiofiles: 提供异步文件读写操作,避免在保存图片时阻塞事件循环。 -
aiohttp: 一个基于asyncio的 HTTP 客户端/服务器框架。虽然httpx.AsyncClient已经很强,但在某些极端定制化场景,aiohttp的底层控制更灵活。本项目用httpx通常足够。
3.4 数据存储:SQLite 或 PostgreSQL
我们需要持久化存储作品元数据。
-
SQLite
: 轻量级,单文件,无需安装数据库服务器。非常适合个人使用、小型项目或原型开发。Python 标准库
sqlite3直接支持。 -
PostgreSQL
: 功能强大的开源关系数据库。如果需要处理极其庞大的数据量(数百万作品),进行复杂查询或未来扩展,PostgreSQL 是更专业的选择。可以使用
asyncpg库进行异步操作。
表结构设计示例(SQLite) :
CREATE TABLE IF NOT EXISTS illusts (
id INTEGER PRIMARY KEY, -- 作品ID,Pixiv唯一标识
title TEXT, -- 作品标题
user_id INTEGER, -- 画师ID
user_name TEXT, -- 画师名
tags TEXT, -- 标签,可以用JSON字符串或逗号分隔存储
width INTEGER, -- 图片宽度
height INTEGER, -- 图片高度
page_count INTEGER, -- 作品页数(漫画多图)
view_count INTEGER, -- 浏览数
like_count INTEGER, -- 点赞数
bookmark_count INTEGER, -- 收藏数
create_date TIMESTAMP, -- 创建时间
upload_timestamp INTEGER, -- 上传时间戳
image_urls TEXT, -- 原图/多尺寸图URL,JSON格式存储
local_path TEXT, -- 本地存储路径
crawled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 爬取时间
);
3.5 反爬应对核心:请求头(Headers)与 Cookies
这是爬取 Pixiv 最关键也是最复杂的一环。
-
User-Agent
: 必须设置为一个常见的浏览器标识,例如
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...。伪装成浏览器是基础中的基础。 -
Referer
: Pixiv 对图片请求有严格的
Referer 检查
。直接请求图片链接会被拒绝。必须在请求图片的
headers中加上Referer: https://www.pixiv.net/或具体作品页的 URL。这是最常见的坑。 -
Cookies
: 要爬取非公开内容(如关注列表、特定用户收藏)或进行大量请求,你需要一个已登录的 Cookie。获取方式是在浏览器中登录 Pixiv 后,从开发者工具(F12)的“网络”或“应用”标签中复制
PHPSESSID等关键 Cookie 字段。重要警告 : 绝对不要在任何公开场合(如 GitHub)提交或分享你的 Pixiv Cookie!这等同于交出你的账号密码,可能导致账号被封禁。应将 Cookie 存储在本地配置文件或环境变量中,并通过
.gitignore确保其不会被提交。
一个基础的请求头示例 :
BASE_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Cache-Control': 'max-age=0',
}
# 在请求具体作品页或API时,需要添加Referer
IMAGE_REFERER = 'https://www.pixiv.net/'
4. 爬虫架构设计与核心流程
一个健壮的爬虫不应该是一堆线性脚本,而应该是一个有清晰模块划分的小型系统。这里我设计一个基于生产者-消费者模式的异步爬虫架构。
4.1 系统模块划分
- 调度器(Scheduler) : 负责管理初始任务队列(如画师ID列表、标签列表),并将任务分发给爬取器。它还需要处理去重,避免重复爬取同一作品。
-
爬取器(Fetcher/Crawler)
: 核心模块。包含网页下载器(使用
httpx)和解析器。负责请求目标页面(画师主页、标签页、作品详情页),并解析出两种数据:一是需要进一步爬取的新任务(如作品详情页链接),二是最终的目标数据(作品元数据和图片URL)。 - 数据管道(Item Pipeline) : 负责处理爬取器提取出的结构化数据。主要做两件事:一是将元数据清洗后存入数据库,二是将图片URL放入下载队列。
- 下载器(Downloader) : 一个高性能的异步下载模块。从下载队列中消费图片URL,并下载到本地文件系统,同时更新数据库中该作品的本地存储路径。
- 配置与状态管理(Config & State) : 管理Cookie、请求头、数据库连接、代理设置(如果需要)、爬取速率限制等全局配置和运行状态。
4.2 核心工作流程
- 初始化 : 读取配置,初始化数据库,建立请求客户端(携带Cookie和Headers)。
- 种子注入 : 用户提供初始画师ID或标签,调度器将其作为初始任务放入队列。
-
页面爬取与解析
:
- 爬取器从队列取一个任务(如画师主页URL)。
- 下载页面HTML。
-
关键步骤
: 解析页面。优先尝试从
<script>标签中提取JSON格式的预加载数据。如果失败,再降级使用CSS选择器/XPath解析HTML。 - 从解析出的数据中,提取作品列表。对于每个作品,生成两个子任务:一是作品详情页任务(用于获取元数据和原图URL),二是可能的新画师任务(如果开启了发现模式)。
-
元数据提取与存储
:
- 爬取器请求作品详情页,同样优先解析内嵌的JSON数据。
-
提取标题、画师、标签、尺寸、统计信息等,并
最重要的是提取原始图片的URL
。Pixiv 的图片URL通常有规律,如
i.pximg.net/img-original/img/2024/05/15/00/00/00/12345678_p0.jpg。 - 数据管道将这批元数据组装成一个“Item”,存入数据库(标记为“已发现,未下载”)。
-
图片下载
:
- 数据管道将图片URL放入下载队列。
-
下载器异步并发地从队列中取URL,
在请求头中务必加上正确的Referer
,然后将图片二进制流保存到本地。文件命名建议包含作品ID和页码(如
12345678_p0.jpg),并建立合理的目录结构(如按画师ID/日期分类)。 -
下载完成后,更新数据库中该条记录的
local_path字段。
- 循环与终止 : 重复步骤3-5,直到调度器中的任务队列为空,且所有下载任务完成。可以设置爬取深度、最大作品数等条件来终止爬虫。
5. 关键代码实现与难点突破
让我们深入到几个具体的技术难点,看看代码如何实现。
5.1 登录态维持与Cookie管理
我们不能每次请求都手动复制Cookie。一个常见的做法是将Cookie保存到文件。
import json
import httpx
from pathlib import Path
COOKIE_FILE = Path('./config/cookies.json')
def load_cookies():
"""从文件加载cookies"""
if COOKIE_FILE.exists():
with open(COOKIE_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def save_cookies(cookies):
"""保存cookies到文件"""
COOKIE_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(COOKIE_FILE, 'w', encoding='utf-8') as f:
json.dump(cookies, f, ensure_ascii=False, indent=2)
def create_client():
"""创建携带cookies的httpx客户端"""
cookies = load_cookies()
# 为Pixiv设置特定的cookies,通常需要`PHPSESSID`和`device_token`
client = httpx.AsyncClient(
headers=BASE_HEADERS,
cookies=cookies,
timeout=httpx.Timeout(30.0),
follow_redirects=True,
http2=True, # 启用HTTP/2
)
return client
# 使用示例
async def test_login(client):
"""测试cookies是否有效,访问个人页面"""
try:
resp = await client.get('https://www.pixiv.net/ajax/user/self')
if resp.status_code == 200 and resp.json().get('error') is False:
print("登录态有效")
return True
else:
print("登录态可能已失效")
# 这里可以触发重新登录流程(需要模拟登录,难度极高,通常建议手动更新cookie)
return False
except Exception as e:
print(f"网络错误: {e}")
return False
5.2 解析作品详情页并获取原图URL
这是爬虫的数据提取核心。我们利用内嵌的JSON数据。
import re
import json
async def fetch_illust_detail(client, illust_id):
"""根据作品ID获取详情和原图URL"""
url = f'https://www.pixiv.net/artworks/{illust_id}'
headers = {**BASE_HEADERS, 'Referer': 'https://www.pixiv.net/'}
try:
resp = await client.get(url, headers=headers)
resp.raise_for_status()
html = resp.text
# 方法1: 从预加载的JSON数据中提取(最可靠)
pattern = r'\"illust\":(\{.*?\}),\"user\"'
# 更健壮的模式,匹配整个`preload`数据
pattern_full = r'window\.__INITIAL_STATE__\s*=\s*(\{.*?\})\s*;'
match = re.search(pattern_full, html, re.DOTALL)
if match:
try:
data = json.loads(match.group(1))
# 导航到作品数据,路径可能类似: data.illust.illustDetail.illustId
# 实际路径需要根据页面结构分析,这里是一个示例
illust_data = data.get('illust', {}).get(illust_id, {})
if not illust_data:
# 尝试其他路径
illust_data = data.get('illustDetail', {}).get('illust', {})
if illust_data:
title = illust_data.get('title')
user_id = illust_data.get('userId')
user_name = illust_data.get('userName')
tags = [tag['tag'] for tag in illust_data.get('tags', {}).get('tags', [])]
page_count = illust_data.get('pageCount', 1)
# 构建原图URL!这是关键
urls = []
for page in range(page_count):
# 单页作品
if page_count == 1:
original_url = illust_data.get('urls', {}).get('original')
# 多页作品(漫画)
else:
original_url = illust_data.get('urls', {}).get('original')
# 多页时,original_url可能是一个模板,需要替换页码
if original_url and '_p0' in original_url:
original_url = original_url.replace('_p0', f'_p{page}')
# 或者从`body`中的`pages`列表获取
pages = illust_data.get('body', {}).get('pages', [])
if pages and page < len(pages):
original_url = pages[page].get('urls', {}).get('original')
if original_url:
urls.append(original_url)
return {
'id': illust_id,
'title': title,
'user_id': user_id,
'user_name': user_name,
'tags': tags,
'page_count': page_count,
'image_urls': urls,
# ... 其他元数据
}
except json.JSONDecodeError as e:
print(f"解析JSON失败: {e}")
# 方法2: 降级到HTML解析(备用方案)
# 使用parsel或BeautifulSoup解析HTML,但信息可能不全,且原图URL可能被隐藏或需要额外请求API
# 这里省略...
return None
except httpx.RequestError as e:
print(f"请求作品 {illust_id} 详情失败: {e}")
return None
5.3 异步图片下载与存储
实现一个支持并发控制、错误重试的下载器。
import asyncio
import aiofiles
from pathlib import Path
import hashlib
class AsyncImageDownloader:
def __init__(self, client, save_dir='./downloads', max_concurrent=5, retries=3):
self.client = client
self.save_dir = Path(save_dir)
self.semaphore = asyncio.Semaphore(max_concurrent) # 控制并发数
self.retries = retries
self.save_dir.mkdir(parents=True, exist_ok=True)
async def download_one(self, image_url, referer, filename=None):
"""下载单张图片"""
async with self.semaphore: # 限制并发
for attempt in range(self.retries):
try:
headers = {'Referer': referer} # Referer至关重要!
resp = await self.client.get(image_url, headers=headers, follow_redirects=True)
resp.raise_for_status()
image_data = resp.content
# 简单校验文件类型
if not resp.content:
raise ValueError("下载内容为空")
# 生成文件名
if not filename:
# 从URL提取或使用哈希
filename = image_url.split('/')[-1]
# 避免非法字符
filename = "".join(c for c in filename if c.isalnum() or c in '._-').rstrip()
save_path = self.save_dir / filename
# 异步写入文件
async with aiofiles.open(save_path, 'wb') as f:
await f.write(image_data)
print(f"下载成功: {filename} -> {save_path}")
return save_path
except (httpx.RequestError, IOError) as e:
wait = 2 ** attempt # 指数退避
print(f"下载失败 (尝试 {attempt+1}/{self.retries}): {image_url}, 错误: {e}, {wait}秒后重试")
await asyncio.sleep(wait)
print(f"下载最终失败: {image_url}")
return None
async def download_many(self, download_tasks):
"""并发下载多个任务,每个任务是一个(url, referer, filename)元组"""
tasks = [self.download_one(url, ref, name) for url, ref, name in download_tasks]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 处理结果
successful = [r for r in results if isinstance(r, Path)]
failed = [tasks[i] for i, r in enumerate(results) if isinstance(r, Exception)]
return successful, failed
6. 反爬策略深度分析与应对方案
Pixiv 作为大型平台,拥有成熟的反爬机制。硬闯只会导致 IP 被封或账号受限。我们必须采取“友好”的策略。
6.1 主要反爬手段识别
-
请求头校验
: 缺失
User-Agent、Referer(尤其是图片请求)会被直接拒绝。 - 频率限制 : 短时间内发起过多请求,会触发速率限制,返回 429 Too Many Requests 或直接暂时封禁 IP。
- 行为模式检测 : 如果请求模式过于规律(如固定间隔毫秒级请求),可能被识别为机器人。
- Cookie 与 Session 验证 : 对于需要登录的页面,无效或过期的 Cookie 会导致返回错误或跳转登录页。
- JavaScript 挑战 : 某些操作(如查看多图)可能需要执行前端 JS 才能获取真实数据,不过 Pixiv 的核心数据目前大多仍在初始 HTML 的 JSON 中。
6.2 实战应对策略
-
完备的请求头
: 如前所述,模拟真实浏览器,特别是
Referer。 -
请求速率控制
: 这是最重要的策略。不要在代码里用无间隔的循环发起请求。
-
固定延迟
: 在每个请求之间加入
await asyncio.sleep(random.uniform(1, 3))。简单但有效。 - 更智能的控制 : 使用令牌桶(Token Bucket)或漏桶(Leaky Bucket)算法。可以定义一个全局的速率限制器。
import time class RateLimiter: def __init__(self, calls_per_second=0.5): self.calls_per_second = calls_per_second self.min_interval = 1.0 / calls_per_second self.last_call = 0 async def acquire(self): elapsed = time.time() - self.last_call wait_for = max(0, self.min_interval - elapsed) if wait_for > 0: await asyncio.sleep(wait_for) self.last_call = time.time() # 在发起请求前调用 limiter = RateLimiter(0.3) # 每秒不超过0.3个请求(约3秒一个) await limiter.acquire() response = await client.get(url) -
固定延迟
: 在每个请求之间加入
- 随机化与人性化 : 在延迟时间中加入随机因子,模拟人类操作的不可预测性。可以随机切换 User-Agent 池(但注意 Cookie 可能绑定特定客户端)。
-
使用代理IP池
: 对于大规模爬取,这是必备方案。当某个 IP 被限制时,自动切换到下一个。可以使用付费代理服务或自建代理。在
httpx.AsyncClient中通过proxies参数设置。注意 : 免费代理大多不稳定、速度慢,且可能存在安全风险。生产环境建议使用可靠的付费代理。
-
处理异常状态码
:
- 429 Too Many Requests : 立即停止,等待更长的时间(如10分钟)再重试,并考虑降低请求频率或更换代理。
- 403 Forbidden : 检查 Cookie 是否失效,Referer 是否正确,或 IP 是否被永久封禁。
- 5xx 服务器错误 : 可能是 Pixiv 服务器问题,等待后重试。
6.3 道德与法律边界
-
遵守
robots.txt: 查看https://www.pixiv.net/robots.txt。虽然它可能允许某些爬虫路径,但大规模爬取通常不被欢迎。 - 尊重版权 : Pixiv 上的作品版权归创作者所有。爬取的数据 仅限个人学习、研究、欣赏之用 。绝对禁止用于任何商业用途、重新分发、或训练AI模型而未获授权。
- 不要给服务器造成负担 : 控制速率,避免 DDoS 式的请求。你的爬虫行为不应该影响正常用户的访问体验。
- 查看用户协议 : 明确 Pixiv 的服务条款中关于数据抓取的规定。
7. 项目优化与高级技巧
当基础功能实现后,可以考虑以下优化点来提升爬虫的健壮性、效率和可维护性。
7.1 断点续爬与状态持久化
爬虫可能因为网络问题、程序崩溃或主动中断而停止。重新开始不应该从头爬起。
- 思路 : 将调度器中的任务队列、正在处理的任务、已完成的任务状态定期保存到磁盘(如 SQLite 或 JSON 文件)。
-
实现
: 使用
atexit注册退出函数,或在收到中断信号(如SIGINT)时保存状态。启动时先加载状态,恢复队列。 -
数据库去重
: 在存入作品数据时,使用
INSERT OR IGNORE或ON CONFLICT DO NOTHING语句,避免重复插入。
7.2 更精细的错误处理与重试机制
为不同类型的错误设计不同的重试策略。
- 网络错误 (超时、连接断开): 立即重试,最多3-5次,配合指数退避。
- 服务器错误 (5xx): 等待一段时间后重试。
- 客户端错误 (4xx,如403、404): 403可能需要更新Cookie,404说明资源不存在,记录日志并跳过,无需重试。
- 速率限制 (429): 等待较长时间(如5-10分钟),并显著降低全局请求频率。
可以引入
tenacity
库来优雅地实现重试逻辑。
7.3 分布式爬虫雏形
如果数据量极大,单机爬取太慢,可以考虑分布式。
-
中心化任务队列
: 使用
Redis或RabbitMQ作为任务队列。多个爬虫节点从同一个队列消费任务。 -
去重
: 使用 Redis 的
Set或Bloom Filter实现全局去重。 - 结果汇总 : 各个节点将爬取到的数据推送到一个中心数据库或文件存储。
- 注意 : 分布式会引入复杂度,如节点通信、状态同步、负载均衡等,需根据实际需求权衡。
7.4 监控与日志
一个成熟的爬虫需要有清晰的运行日志和简单的监控。
-
结构化日志
: 使用
logging模块,配置不同的级别(INFO, WARNING, ERROR),并输出到文件和控制台。记录爬取进度、错误信息、请求状态等。 -
进度可视化
: 可以定期打印进度条,或使用
tqdm库。记录已爬取作品数、成功率、平均速度等。 - 告警 : 对于连续错误、成功率骤降等情况,可以集成邮件或即时通讯工具告警(但这对于个人项目可能过重)。
8. 常见问题排查与实战心得
这里分享一些我实际爬取过程中踩过的坑和解决方案。
8.1 问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 请求返回403 Forbidden |
1. Referer 请求头缺失或错误。
2. Cookie 失效或未设置。 3. IP 被目标网站封禁。 |
1. 检查请求头,确保图片请求的
Referer
是
https://www.pixiv.net/
。
2. 访问个人中心页面测试 Cookie 是否有效。 3. 尝试更换网络环境或使用代理。 |
| 图片下载下来是0字节或损坏文件 |
1. 请求图片时未通过反盗链(Referer)。
2. 服务器返回了错误页面(如验证码)。 |
1.
确保图片下载请求的 headers 中包含
Referer
。
2. 打印响应状态码和内容类型 (
Content-Type
),确认是否是
image/jpeg
等。
|
| 解析不到作品数据,JSON提取失败 |
1. 页面结构已更新。
2. 正则表达式模式不匹配。 3. 需要登录才能查看(如R-18作品)。 |
1. 手动打开目标页面,检查源代码,搜索
illust
或
作品ID
,找到新的数据存放模式。
2. 使用更宽泛的正则或直接使用
json.loads
解析整个
<script>
内容后遍历查找。
3. 确认使用的 Cookie 有效且账号有查看权限。 |
| 爬虫运行缓慢 |
1. 网络延迟。
2. 没有使用异步并发。 3. 请求频率过高触发限流,导致等待或重试。 |
1. 使用异步 (
asyncio
+
httpx.AsyncClient
)。
2. 适当增加并发数(如
Semaphore(10)
)。
3. 必须添加请求间隔 ,建议主请求间隔2-5秒,图片下载可稍快但也要控制。 |
| 数据库插入重复记录 | 程序中断后重启,从上次的页面重新解析,导致重复任务。 |
1. 实现任务去重队列(内存Set或Redis)。
2. 数据库表设置唯一索引(如
illust_id
),使用
INSERT OR IGNORE
。
|
| 遇到验证码 | 请求行为被识别为异常,触发人机验证。 |
1.
立即大幅降低请求频率
,模拟更人类化的操作(随机延迟、滚动页面等)。
2. 更换IP地址(使用代理)。 3. 考虑暂停爬虫一段时间(数小时或一天)。 |
8.2 实操心得与建议
- 从小范围测试开始 : 不要一开始就设定爬取几万张图的目标。先针对一个画师或一个标签,跑通整个流程:登录、解析页面、拿到元数据、下载一张图片。确保每个环节都无误。
- 勤于查看网络请求 : 浏览器开发者工具的“网络”(Network)选项卡是你最好的老师。观察页面加载时发了哪些请求,哪个请求返回了你需要的数据(通常是XHR或Fetch请求),直接模仿这个请求往往事半功倍。
- 数据比图片更重要 : 元数据(标签、作者、时间)的结构化存储,为后续的分析、筛选、搜索提供了可能。设计一个好的数据库 schema 会让你后期省很多事。
- 尊重与克制 : 设置合理的速率限制(比如每秒不超过0.2个请求到主站)。尽量在网站流量较低的时段(例如当地时间的深夜)运行爬虫。你的目标是获取数据,而不是攻击网站。
- 代码要模块化 : 将网络请求、解析、存储、下载分别写成独立的函数或类。这样不仅代码清晰,而且当某个环节需要修改(比如解析规则变了)时,影响范围最小。
- 做好异常处理 : 网络爬虫运行在复杂的环境中,异常是常态。你的代码必须能妥善处理网络超时、连接重置、数据格式意外变化等情况,并记录日志,而不是直接崩溃。
-
版本化管理与配置分离
: 使用 Git 管理代码。将 Cookie、数据库路径、下载目录等配置信息放在单独的配置文件(如
config.yaml或.env文件)中,不要硬编码在脚本里,并通过.gitignore忽略它们。
构建一个完整的 Pixiv 爬虫是一个系统工程,它考验的不仅是 Python 语法,更是对 HTTP 协议、前端知识、反爬策略、异步编程、数据存储和工程化思维的全面理解。当你最终看到爬虫稳定运行,将喜爱的作品有序地保存到本地时,那种成就感是无可替代的。希望这份详细的指南能为你扫清障碍,祝你爬虫愉快!记住,能力越大,责任越大,请务必在法律和道德的框架内合理使用技术。
更多推荐
所有评论(0)