Python爬虫实战:破解Pixiv登录与API数据抓取全流程

📅 2026/8/26 5:15:57
Python爬虫实战:破解Pixiv登录与API数据抓取全流程
1. 项目概述为什么Pixiv爬虫是个“技术活”如果你是个画师或者是个二次元爱好者那你对Pixiv俗称P站肯定不陌生。这个全球最大的插画交流网站简直就是个视觉宝库每天都有海量的高质量作品更新。但问题来了网站本身的功能比如收藏夹、搜索有时候并不能完全满足我们“囤积”美好事物的需求。你想批量下载某个画师的所有作品想按特定标签、收藏数、日期范围来归档图片或者想为自己的AI绘画模型收集特定风格的训练集这时候一个自己写的Pixiv爬虫就成了刚需。但Pixiv爬虫远不是简单的requests.get()加正则表达式就能搞定的。它涉及到复杂的登录验证、反爬虫机制比如著名的PHPSESSID和device_token、动态加载的API接口以及图片资源本身的多种规格和防盗链策略。这不像爬一个静态新闻网站更像是在和网站的安全工程师“斗智斗勇”。网上那些“一行代码爬全网”的教程在Pixiv面前基本都会碰壁。所以这个项目不只是写个脚本更是一次对现代Web反爬策略的实战演练能让你深刻理解会话管理、请求模拟和数据处理。接下来我会以一个完整的、可运行的爬虫项目为蓝本拆解从环境准备、登录破解、数据抓取到本地存储的每一个环节。我会重点解释“为什么”要这么做并分享我在实际开发中踩过的坑和总结的技巧。无论你是Python爬虫新手想挑战高难度副本还是有一定经验想完善自己的工具库这篇内容都能给你提供一条清晰的路径。2. 核心思路与架构设计模拟真人而非攻击在动手写代码之前我们必须明确一个核心原则一个稳健的爬虫应该尽可能地模拟真实用户浏览器的行为而不是试图用高频请求去“攻击”服务器。对于Pixiv这种级别的网站粗暴的爬取不仅容易被封IP和账号也违背了基本的网络礼仪。我们的设计思路是“低调、精准、尊重”。2.1 技术栈选型与理由为什么是这些工具我们来逐一分析Python Requests BeautifulSoup4 (bs4)这是爬虫的黄金基础组合。Python语法简洁生态丰富。Requests库处理HTTP请求简单高效比原生urllib友好太多。BeautifulSoup用于解析HTML虽然Pixiv主要数据来自API但登录页面和部分元信息仍需HTML解析。Requests-HTML (可选但推荐)这个库是Requests作者开发的它内置了一个简易的浏览器引擎可以执行JavaScript。虽然Pixiv的核心内容不依赖JS渲染但有些页面元素或早期的反爬检查可能会用到。用它作为Requests的补充或替代能应对更复杂的情况。浏览器开发者工具 (Chrome DevTools)这是最重要的“非代码”工具。我们需要用它来Network (网络) 面板监听页面加载过程中的所有HTTP请求找到真正返回图片和数据通常是JSON格式的API接口。这是爬虫的“眼睛”。Application (应用) 面板查看和管理Cookies、LocalStorage。Pixiv的登录状态主要靠Cookies维持这里是我们获取关键认证信息的地方。JSON 模块 (Python内置)Pixiv的API返回的数据基本都是JSON格式Python的json模块可以轻松地将它们转换为字典或列表进行操作。文件操作 (os, hashlib)用于创建本地目录、保存图片、生成唯一的文件名比如用MD5哈希值防止重复。注意不建议初学者一上来就用Selenium或Playwright这类浏览器自动化工具。它们虽然能模拟几乎所有用户操作但资源消耗大、速度慢容易被识别为自动化脚本。我们的目标是找到效率更高的API接口直接通信。2.2 爬虫工作流程设计整个爬虫的运作可以抽象为以下几个步骤我画了一个简单的流程图来帮助理解flowchart TD A[开始: 初始化会话br设置请求头] -- B[核心挑战: 模拟登录br获取有效Cookies] B -- C{登录方式选择} C -- 推荐/稳定 -- D[方式一: Cookie直连br从浏览器手动复制] C -- 自动/编程 -- E[方式二: 密码登录br处理验证码与Token] D -- F[登录成功br建立认证会话] E -- F F -- G[构造搜索/用户请求] G -- H[解析API返回的JSON数据] H -- I[提取作品ID列表] I -- J[遍历ID, 请求作品详情页] J -- K[解析详情页, 获取原图链接] K -- L[下载图片至本地br按规则命名归档] L -- M{是否继续下一页?} M -- 是 -- G M -- 否 -- N[结束]这个流程的核心在于登录和API请求。登录是拿到“门票”而找到正确的API并构造合法的请求是拿到“货物”的关键。3. 实战第一步环境准备与登录破解登录是横在Pixiv爬虫面前的第一道也是最高的门槛。Pixiv的登录流程几经改版反爬措施严密。这里我提供两种经过验证的策略第一种更稳定简单第二种更自动化但复杂。3.1 基础环境搭建首先确保你的Python环境建议3.8以上已经就绪然后安装必要的库pip install requests requests-html beautifulsoup4创建一个新的Python文件比如pixiv_crawler.py开始导入模块并建立一个会话对象import requests import json import os import time from bs4 import BeautifulSoup import hashlib # 建立一个会话 (Session) 它可以自动管理Cookies 保持登录状态 session requests.Session() # 设置通用的请求头 模拟浏览器 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, Referer: https://www.pixiv.net/, # Referer对于图片防盗链至关重要 Accept-Language: zh-CN,zh;q0.9,en;q0.8, } session.headers.update(headers)User-Agent告诉服务器我们是什么浏览器Referer告诉服务器我们是从哪个页面跳转过来的很多图片服务器会校验这个字段如果不对会返回403错误。3.2 登录策略一Cookie直连推荐给新手和稳定使用这是目前最稳定、最省事的方法。原理是你手动用浏览器正常登录一次Pixiv然后把浏览器里的Cookie复制出来交给爬虫程序使用。这样爬虫就“继承”了你的登录状态。操作步骤用Chrome或Edge浏览器正常访问https://www.pixiv.net/并登录你的账号。按F12打开开发者工具切换到Application(应用) 标签。在左侧Storage下找到Cookies 点击https://www.pixiv.net。在右侧列表中找到名为PHPSESSID的Cookie 双击其Value值复制那一长串字符。在你的代码中将这个值直接设置到会话的Cookie里。# 将从浏览器复制的 PHPSESSID 值粘贴在这里 phpsessid_value 你复制的那一串很长的字符 # 设置Cookie cookies { PHPSESSID: phpsessid_value, } session.cookies.update(cookies) # 验证登录是否成功访问个人主页 如果返回的HTML中包含你的用户名 说明成功 test_url https://www.pixiv.net/ response session.get(test_url) if 你的用户名 in response.text: # 请替换为你的Pixiv昵称 print(登录成功) else: print(登录失败 请检查PHPSESSID是否正确或已过期。)实操心得PHPSESSID是有有效期的通常是浏览器会话期间或一段时间。如果长时间不用可能需要重新登录复制。这种方式避免了处理密码、验证码等复杂问题非常适合个人小规模、间歇性的爬取需求。3.3 登录策略二模拟密码登录应对Cookie失效如果你想实现全自动登录或者Cookie方式失效了就需要模拟完整的登录流程。这个过程非常复杂因为Pixiv会使用device_token、post_key等动态令牌并可能触发验证码。核心步骤解析获取登录页面 提取关键Token首先GET登录页面从HTML中解析出一个名为post_key的隐藏输入框的值。这个值是每次登录请求必须携带的。构造登录请求POST请求到登录接口需要携带用户名、密码通常是加密的、post_key、device_token等参数。其中device_token可以是一个固定的随机字符串首次登录后服务器会绑定它。处理验证码如果登录失败或触发风控返回的信息可能会要求输入验证码。你需要解析出验证码图片URL下载并人工识别或调用OCR服务然后将验证码填入再次请求。保存Cookies登录成功后服务器返回的响应头里会设置新的Cookies我们的session会自动保存。之后就可以用这个会话来访问需要登录的页面了。由于此方法代码冗长且受Pixiv前端改动影响大这里不展开完整代码但其核心请求如下所示login_url https://accounts.pixiv.net/login?langzh login_api_url https://accounts.pixiv.net/api/login?langzh # 1. 获取登录页和post_key login_page_resp session.get(login_url) soup BeautifulSoup(login_page_resp.text, html.parser) post_key_input soup.find(input, {name: post_key}) post_key post_key_input[value] if post_key_input else # 2. 构造登录数据 (密码加密是前端完成的 这里简化展示 实际需要逆向JS) login_data { pixiv_id: 你的邮箱, password: 你的密码, # 注意 真实密码可能需要经过前端特定算法加密 post_key: post_key, return_to: https://www.pixiv.net/, device_token: your_generated_device_token, # 可自行生成一个UUID source: pc, } # 3. 发送登录请求 resp session.post(login_api_url, datalogin_data) result resp.json() if result.get(error): print(f登录失败: {result.get(message)}) # 可能需要处理验证码 else: print(登录成功)重要警告模拟登录涉及账号安全且Pixiv的加密逻辑可能随时变更。强烈不建议在公开脚本中硬编码你的账号密码。对于自动化需求更安全的做法是定期手动更新Cookie。此方法仅作原理了解。4. 核心抓取逻辑找到真正的数据接口登录成功后我们就要开始找数据了。以“搜索特定标签的作品”为例我们不会去解析搜索结果页的HTML因为那是给人类看的效率低且结构易变。我们要找的是背后为前端提供数据的API。4.1 发现并分析API打开浏览器登录Pixiv在搜索框输入一个标签比如“オリジナル”原创。按F12-Network(网络) 面板 勾选Preserve log(保留日志)。刷新页面或点击搜索。在Network面板里你会看到大量请求。筛选XHR或Fetch类型的请求。寻找包含“search”或“illust”字样、且返回数据是JSON的请求。通常Pixiv的搜索API地址类似于https://www.pixiv.net/ajax/search/artworks/{关键词}?word{关键词}orderdate_dmodeallp{页码}...点击这个请求查看它的Headers(请求头) 和Preview(预览)。在Headers里你需要关注的是Request Headers特别是Cookie和Referer我们的爬虫需要模拟这些。在Preview里你能看到结构化的JSON数据里面包含了作品ID、标题、作者等信息。4.2 构造API请求并解析数据假设我们找到了搜索API的规律就可以用代码来请求了def search_illustrations(keyword, page1, modesafe): 搜索插画 :param keyword: 搜索关键词 :param page: 页码 :param mode: 搜索模式 safe/r18 :return: 作品ID列表 # 这个URL格式是分析出来的 可能随网站更新而变化 search_url fhttps://www.pixiv.net/ajax/search/artworks/{keyword} params { word: keyword, order: date_d, # 按日期排序 mode: mode, p: page, s_mode: s_tag, type: all, lang: zh } # 关键 必须设置Referer 通常就是搜索页的URL headers { Referer: fhttps://www.pixiv.net/tags/{keyword}/artworks } try: response session.get(search_url, paramsparams, headersheaders) response.raise_for_status() # 检查请求是否成功 data response.json() # 解析JSON 提取作品ID。 具体路径需要根据实际API返回结构调整 # 这里是一个示例路径 你需要用 print(json.dumps(data, indent2)) 来查看真实结构 illust_ids [] if data.get(body) and data[body].get(illustManga): for item in data[body][illustManga][data]: illust_id item.get(id) if illust_id: illust_ids.append(illust_id) return illust_ids except requests.exceptions.RequestException as e: print(f搜索请求失败: {e}) return [] except json.JSONDecodeError as e: print(f解析JSON失败: {e}) return []这个函数返回了一个作品ID的列表。ID是Pixiv上每个作品的唯一标识。4.3 获取作品详情与原图链接有了作品ID我们就可以访问作品详情页的API获取更详细的信息特别是原图或大图的URL。def get_illust_detail(illust_id): 获取作品详情 :param illust_id: 作品ID :return: 包含图片URLs等信息的字典 detail_url fhttps://www.pixiv.net/ajax/illust/{illust_id} headers { Referer: fhttps://www.pixiv.net/artworks/{illust_id} } try: response session.get(detail_url, headersheaders) response.raise_for_status() data response.json() illust_info {} if data.get(error): print(f获取作品 {illust_id} 详情失败: {data.get(message)}) return illust_info body data.get(body, {}) illust_info[title] body.get(title, ) illust_info[user_name] body.get(userName, ) illust_info[user_id] body.get(userId, ) illust_info[tags] [tag[tag] for tag in body.get(tags, {}).get(tags, [])] # 获取图片URLs - 这是最关键的部分 urls body.get(urls, {}) # 优先尝试获取原图 如果没有则用大图 original_url urls.get(original) regular_url urls.get(regular) illust_info[image_url] original_url if original_url else regular_url # 处理多图作品 (漫画/多图插画) if body.get(pageCount, 1) 1: pages_url fhttps://www.pixiv.net/ajax/illust/{illust_id}/pages pages_resp session.get(pages_url, headersheaders) pages_data pages_resp.json() illust_info[pages] [page[urls][original] for page in pages_data.get(body, [])] else: illust_info[pages] [illust_info[image_url]] if illust_info[image_url] else [] return illust_info except Exception as e: print(f获取作品 {illust_id} 详情时发生错误: {e}) return {}注意事项Referer头在这里极其重要。Pixiv的图片服务器会严格检查Referer如果请求图片时没有带上正确的来源页即作品详情页服务器会返回403 Forbidden或404 Not Found。这就是为什么我们必须在请求图片的headers里也加上Referer。5. 图片下载与本地化管理拿到图片URL后下载本身很简单但如何有序地保存到本地是需要设计的。5.1 下载单张图片def download_image(image_url, save_path, referer): 下载单张图片 :param image_url: 图片URL :param save_path: 本地保存路径 :param referer: 用于设置Referer头的来源URL if not image_url: return False headers { Referer: referer, # 必须否则图片服务器会拒绝 User-Agent: session.headers[User-Agent] } try: # 流式下载 适合大文件 response session.get(image_url, headersheaders, streamTrue, timeout30) response.raise_for_status() # 检查文件是否已存在 (可选 根据哈希判断更准确) if os.path.exists(save_path): print(f文件已存在跳过: {save_path}) return True with open(save_path, wb) as f: for chunk in response.iter_content(chunk_size8192): if chunk: f.write(chunk) print(f下载成功: {save_path}) return True except requests.exceptions.Timeout: print(f下载超时: {image_url}) except requests.exceptions.RequestException as e: print(f下载失败 {image_url}: {e}) except IOError as e: print(f文件写入失败 {save_path}: {e}) return False5.2 设计本地存储结构一个好的存储结构能让后期查找和管理事半功倍。我推荐按以下规则组织Pixiv_Downloads/ ├── [画师ID]_画师名/ │ ├── [作品ID]_作品标题/ │ │ ├── 0.jpg (或.png, .gif) │ │ ├── 1.jpg (第二张图 如果有) │ │ └── info.json (保存作品元信息 如标题、标签、上传时间) │ └── ... ├── 标签_オリジナル/ │ └── ... (类似画师目录结构) └── 收藏夹/ └── ...对应的目录创建和保存函数import os import json def save_illust_info(illust_info, base_dir./Pixiv_Downloads): 保存作品信息和图片 :param illust_info: 作品信息字典 :param base_dir: 下载根目录 user_id illust_info.get(user_id, unknown) user_name illust_info.get(user_name, unknown).replace(/, _).replace(\\, _) # 处理非法字符 illust_id illust_info.get(id) # 需要从外部传入或info中包含 title illust_info.get(title, untitled).replace(/, _).replace(\\, _)[:50] # 限制长度 处理非法字符 # 创建目录 base_dir/用户ID_用户名/作品ID_标题 save_dir os.path.join(base_dir, f{user_id}_{user_name}, f{illust_id}_{title}) os.makedirs(save_dir, exist_okTrue) # 保存元信息到JSON文件 info_path os.path.join(save_dir, info.json) with open(info_path, w, encodingutf-8) as f: json.dump(illust_info, f, ensure_asciiFalse, indent2) # 下载所有图片页面 pages illust_info.get(pages, []) referer_url fhttps://www.pixiv.net/artworks/{illust_id} for idx, img_url in enumerate(pages): # 根据URL确定文件扩展名 ext os.path.splitext(img_url)[1] or .jpg # 处理可能带查询参数的URL 如 .jpg?xxxx if ? in ext: ext ext.split(?)[0] filename f{idx}{ext} save_path os.path.join(save_dir, filename) download_image(img_url, save_path, referer_url) time.sleep(1) # 非常重要的礼貌性延迟 避免请求过快实操心得time.sleep()是爬虫的“美德”。在请求之间尤其是下载图片添加延迟比如1-3秒可以显著降低对目标服务器的压力减少被封IP的风险。这是“慢就是快”的体现。6. 完整流程串联与高级技巧现在我们把所有模块组合起来形成一个完整的搜索并下载的流程。def main(): # 1. 初始化会话和登录 (使用Cookie法) # ... (初始化session和设置Cookie的代码 见3.2节) keyword オリジナル max_pages 3 # 控制爬取的页数 避免过量 per_page_items 30 # 假设每页30个作品 all_downloaded_ids set() # 用于去重 for page in range(1, max_pages 1): print(f\n 正在搜索第 {page} 页 关键词: {keyword} ) # 2. 搜索作品 获取ID列表 illust_ids search_illustrations(keyword, pagepage) if not illust_ids: print(f第 {page} 页未找到作品或请求失败。) break print(f找到 {len(illust_ids)} 个作品ID。) for illust_id in illust_ids: if illust_id in all_downloaded_ids: print(f作品 {illust_id} 已下载 跳过。) continue # 3. 获取作品详情 print(f处理作品 ID: {illust_id}) illust_info get_illust_detail(illust_id) if not illust_info or not illust_info.get(pages): print(f作品 {illust_id} 详情获取失败或无图片。) continue illust_info[id] illust_id # 把ID也存入信息字典 # 4. 保存作品信息和图片 save_illust_info(illust_info, base_dir./Pixiv_Downloads) all_downloaded_ids.add(illust_id) # 5. 礼貌延迟 避免请求过快 time.sleep(2) # 每页完成后也稍作延迟 time.sleep(3) print(f\n全部任务完成共处理了 {len(all_downloaded_ids)} 个作品。) if __name__ __main__: main()6.1 处理反爬虫策略Pixiv除了Referer检查和登录状态验证还可能采取其他措施频率限制这是最直接的。我们的time.sleep()就是应对这个。如果还遇到429Too Many Requests错误需要进一步增加延迟或使用更复杂的随机延迟如time.sleep(random.uniform(1, 3))。请求头校验除了User-Agent和Referer有些API可能还会检查Accept,Accept-Language,Sec-Fetch-*等头。尽量从浏览器中复制完整的请求头。IP封锁如果单个IP在短时间内发出过多请求可能会被暂时封锁。对于大规模爬取需要考虑使用代理IP池。但这超出了基础爬虫的范围且涉及额外成本和服务。6.2 错误处理与重试机制网络请求充满不确定性健壮的爬虫必须有良好的错误处理。def robust_request(url, session, max_retries3, **kwargs): 带重试机制的请求函数 for attempt in range(max_retries): try: resp session.get(url, timeout10, **kwargs) resp.raise_for_status() return resp except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e: print(f请求 {url} 超时或连接错误 (尝试 {attempt1}/{max_retries}): {e}) if attempt max_retries - 1: wait_time 2 ** attempt # 指数退避 print(f等待 {wait_time} 秒后重试...) time.sleep(wait_time) else: print(f重试 {max_retries} 次后仍失败。) raise except requests.exceptions.HTTPError as e: if resp.status_code 429: print(触发频率限制 等待更长时间...) time.sleep(30) continue else: # 其他HTTP错误 如403, 404 通常重试无用 print(fHTTP错误 {resp.status_code}: {e}) raise return None在download_image和get_illust_detail函数中可以调用这个robust_request来代替直接的session.get。7. 常见问题与排查技巧实录即使按照步骤操作你也可能会遇到各种问题。这里记录了一些典型问题和解决方法。问题现象可能原因排查与解决思路登录失败(Cookie法)1.PHPSESSID值错误或已过期。2. 复制的Cookie不完整缺少其他必要项。1. 重新登录Pixiv复制全新的PHPSESSID。2. 检查浏览器中pixiv.net域名下是否有其他看起来重要的Cookie如device_token一并复制。登录失败(密码法)1. 登录接口或参数已变更。2. 密码未加密或加密方式错误。3. 触发验证码。1. 使用浏览器开发者工具重新录制一次登录过程对比请求参数。2. 逆向登录页面的JavaScript找到密码加密函数。3. 手动处理验证码或寻找可用的OCR服务集成。搜索API返回空数据1. API URL或参数格式已更新。2. 请求头特别是Referer不正确。3. 登录状态失效。1. 再次使用Network面板分析最新的搜索请求。2. 确保Referer头与浏览器中发出的请求完全一致。3. 重新获取有效的Cookie。获取作品详情失败1. 作品ID不存在或已被删除。2. 作品是R-18内容而当前登录模式为safe。3. 请求头缺失。1. 手动在浏览器访问该作品链接确认。2. 在搜索或详情请求中尝试将mode参数改为all或r18需账号设置允许。3. 补全Referer和X-Requested-With等头。图片下载返回403错误1.Referer头缺失或错误最常见。2. 图片URL已过期或需要特定令牌。1.确保下载图片的请求中Referer头设置为该作品详情页的URL如https://www.pixiv.net/artworks/123456。2. 图片URL可能来自urls.original或urls.regular如果都失败尝试从详情页HTML中解析新的图片地址。下载的图片损坏或很小下载到了预览图缩略图而非原图。检查代码中获取的图片URL。确保使用的是urls.original原图如果为空再降级使用urls.regular大图。多图作品要使用/pages接口返回的URL数组。很快被限制访问请求频率过高触发了反爬虫的风控。1.大幅增加请求间隔在关键请求如详情页、图片下载之间加入time.sleep(random.uniform(2, 5))。2. 模拟更真实的人类行为比如随机浏览一些其他页面。3. 考虑使用高质量的代理IP。最后再分享一个小技巧在开发调试阶段善用print(json.dumps(data, indent2))将API返回的复杂JSON数据漂亮地打印出来。这是你理解数据结构最直接的方式。不要猜要看服务器实际返回了什么。同时将关键的中间数据如作品ID列表、图片URL保存到本地的文本文件里这样即使程序中途出错你也有记录可以恢复不必每次都从头开始爬。这个项目最耗时的部分往往是调试和适应网站的变化一个稳定的登录状态和清晰的数据流日志能帮你节省大量时间。