ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python爬虫实战:从漫画站批量下载图片并合并PDF

Python爬虫实战:从漫画站批量下载图片并合并PDF 很多时候大家学爬虫都卡在“想练手但找不到合适的案例”这一步。登录、加密、验证码、滑块一套组合拳下来新手直接懵圈。我当初也走过这段弯路后来发现漫画站反而是一个特别适合拿来练手的靶场图片地址清晰、目录结构规整、几乎没有强加密而且“整话下载合并PDF”这个需求链路完整做完了非常有成就感。这篇教程就基于一个典型的无加密漫画站点带你完整跑通从页面解析、高清图片批量下载到一键合并PDF的整个流程。全程使用Python标准库加两三个常用的第三方库不用逆向、不用过验证码靠的是老老实实的HTTP请求和页面分析。读完你不仅能得到一份能用的脚本更重要的是搞明白爬虫抓取静态页面的整套思路后面遇到其他类似站点也能自己动手拆解。1. 项目拆解爬虫目标、合规边界与技术选型1.1 先搞清楚这活儿到底在做什么这个项目的核心流程就三步找到某一话漫画的所有图片地址 → 按顺序下载高清原图 → 按页码合并成一个PDF文件。听起来简单但里面有几个坑不踩不知道。第一步“找到所有图片地址”关键在于分析网页结构。漫画阅读页面通常是一个HTML页面页面上有一堆img标签或者是JavaScript动态加载的图片列表。无加密站点的意思就是图片的真实地址就明晃晃地写在网页源码里没有经过加密、混淆、或者需要额外计算token才能访问。你只需要找到图片URL的规律或者直接提取img标签的src属性就行。第二步“下载高清原图”要处理的是请求头、并发控制、图片完整性校验。很多网站对图片服务器有防盗链设置如果不带Referer头返回的可能是403或者一张“图片不存在”的占位图直接存下来就是一堆废文件。并发下载能提速但如果并发太高很容易触发服务器的频率限制IP被临时封禁得不偿失。第三步“合并PDF”最稳妥的方式是先用Pillow库把图片统一处理成RGB模式的JPG再按文件名顺序合并。这里有个非常容易踩的坑文件排序问题。如果你用os.listdir()直接遍历文件夹1.jpg、2.jpg、10.jpg这种文件名会被排成1、10、2因为字典序不是自然数序。合并出来的PDF页码全部乱掉漫画根本没法看。1.2 关于网站选择与合规使用的提醒在动手之前必须先说清楚边界。爬虫本身是一项中性技术但用什么目标网站练手、抓下来的内容怎么用是有讲究的。我建议你把目标锁定在以下几种类型一是作者明确允许转载或标注了“可自由下载”的站点二是版权过期的公有领域作品三是自己购买的电子版漫画做个人备份。国内很多漫画平台的内容都是有版权的你爬下来自己看可以但如果上传到网盘分享、二次发布那就是侵权。整个行业的生态已经很脆弱了别因为练个手给创作者添堵。本文的代码示例基于一个虚构的、无加密的漫画阅读站结构来写URL结构参考了常见的静态站点格式目的是讲清楚技术原理。你在实际应用时请务必确认目标网站是否允许爬取建议先看一下网站的robots.txt同时控制请求频率不要影响网站正常访问。另外提醒一句本文所有代码均为技术教学用途请勿用于任何侵权或非法场景。1.3 技术栈选择为什么是这几个库这个项目用到的库非常精简我把选型和理由一次说清楚。库名用途选型理由requests发送HTTP请求最主流的HTTP库接口简单支持Session、自定义Headers处理Cookie和Referer非常方便BeautifulSoup4解析HTML容错能力强新手友好几行代码就能提取出所有img标签虽然性能不如lxml但对这个项目规模来说完全够用Pillow图片处理能读取图片尺寸、转换格式、转RGB模式。合并PDF前必须用它过一遍否则非RGB模式的图片在部分PDF阅读器里会显示异常img2pdf图片转PDF专门干这一件事不引入额外依赖处理大文件时内存占用比Pillow直接保存PDF要低得多。实测几百张高清图片合并速度非常稳为什么不推荐用urllib官方库虽然不需要安装但写起来冗长处理请求头、超时重试都很麻烦requests能把代码量缩减三分之一。为什么不用Selenium这个项目里页面是静态的图片地址直接写在HTML里用浏览器自动化反而又慢又容易被检测。真正的高手会用最合适的工具而不是最复杂的工具。2. 环境准备从零开始装好Python和依赖库2.1 Python及开发环境安装如果你电脑上还没有Python环境先去Python官网下载3.9以上版本。安装时记得勾选Add Python to PATH这一步漏掉的话后面在终端里输入python会提示“不是内部或外部命令”非常折腾。装完之后打开终端验证一下python --version能正常输出版本号就说明环境ok。我建议顺手装一个virtualenv虚拟环境工具把项目的依赖隔离起来不会和系统全局环境产生冲突。你以后装其他Python项目时就会发现这个习惯有多重要。2.2 安装依赖库这个项目只需要四个库一条命令全装好pip install requests beautifulsoup4 pillow img2pdf如果你在安装过程中遇到下载慢的问题可以切换国内镜像源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 pillow img2pdf装完之后可以验证一下是否安装成功python -c import requests; import bs4; import PIL; import img2pdf; print(All deps ok)看到All deps ok就说明依赖全部就位。2.3 抓包分析前的准备工具写爬虫不能靠瞎猜一定要“先看再写”。浏览器开发者工具是你的第一信息来源。我建议准备工作做两步第一步打开目标漫画的阅读页面按下F12进入开发者工具切到Network面板刷新页面找到文档请求在Response里搜索jpg或png你会看到图片地址列表。第二步切到Elements面板查看img标签的具体结构确认图片URL是直接写在src属性里还是写在一个>import requests from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/comic/vol-1/chapter-5.html, } def get_image_urls(page_url): resp requests.get(page_url, headersHEADERS, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) img_urls [] for img in soup.select(div.reader img): url img.get(src) or img.get(data-src) if url and url.startswith(http): img_urls.append(url.strip()) return img_urls urls get_image_urls(https://example.com/comic/vol-1/chapter-5.html) print(f找到 {len(urls)} 张图片)这里重点说一下Referer头。漫画网站的图片服务器通常配置了防盗链直接请求图片URL会返回403但浏览器访问没问题因为请求头里带了来源页面地址。我们在requests里加上Referer就是为了伪装成浏览器在页面里加载图片。这是整个项目最关键的技巧之一没有它你下载下来的图片大概率全是坏文件。3.2 第二步并发下载高清图片提取到图片列表之后下一步就是下载。逐个下载当然也行但太慢了一话漫画动辄几十张图串行下载可能要等一分多钟。我在这里用一个简单的线程池控制在5个并发既提速又不至于把服务器打挂。import os import time from concurrent.futures import ThreadPoolExecutor def download_image(args): img_url, save_path, referer args for retry in range(3): try: headers HEADERS.copy() headers[Referer] referer resp requests.get(img_url, headersheaders, timeout15) if resp.status_code 200 and len(resp.content) 10000: with open(save_path, wb) as f: f.write(resp.content) return True except Exception as e: print(f第{retry1}次重试: {img_url} {e}) time.sleep(1) return False def download_chapter(page_url, save_dir): os.makedirs(save_dir, exist_okTrue) img_urls get_image_urls(page_url) tasks [] for idx, url in enumerate(img_urls, start1): ext os.path.splitext(url.split(?)[0])[1] or .jpg save_path os.path.join(save_dir, f{idx:03d}{ext}) tasks.append((url, save_path, page_url)) with ThreadPoolExecutor(max_workers5) as pool: results list(pool.map(download_image, tasks)) print(f成功 {sum(results)} 张失败 {len(results) - sum(results)} 张)注意两个细节。第一个是文件名用{idx:03d}格式化1会被写成00110写成010这样后面排序才正确。第二个是下载后检查了len(resp.content) 10000这是为了过滤掉那些“图片不存在”的占位图。很多网站返回的占位图大小只有几KB直接把这种情况识别出来能省去后面PDF里冒出空白页的麻烦。3.3 第三步按顺序合并PDF图片下载完成之后合并PDF这一步其实是最容易翻车的。因为Pillow对图片模式有要求PNG格式的图片在转PDF时如果带透明通道生成的PDF在部分阅读器里显示会是黑色背景。稳妥的做法是统一转成RGB模式的JPG。import glob from PIL import Image import img2pdf def merge_to_pdf(image_dir, output_pdf): image_files sorted( glob.glob(os.path.join(image_dir, *.jpg)) glob.glob(os.path.join(image_dir, *.png)) ) if not image_files: print(目录下没有图片文件) return # 方法一img2pdf 直接合并速度快占用内存低 with open(output_pdf, wb) as f: f.write(img2pdf.convert([os.path.abspath(img) for img in image_files])) # 如果上面报错说明有图片不是RGB模式用Pillow洗一遍 # images [] # for img in image_files: # im Image.open(img).convert(RGB) # images.append(im) # images[0].save(output_pdf, save_allTrue, append_imagesimages[1:]) print(fPDF已生成: {output_pdf}) merge_to_pdf(chapter_05, chapter_05.pdf)推荐优先用img2pdf方案它的优势是直接把图片文件转成PDF的JPEG流不需要把图片全部加载到内存里几十张高清大图合并时内存占用非常稳定。如果转换过程中报错提示cannot write mode之类的就换成Pillow方案先把图片模式统一转成RGB再保存。到这里一话漫画的下载和PDF合并就全部完成了。整个脚本跑下来从拿到URL到输出PDF不到30行核心代码这也是我选这个项目做教学案例的原因——它足够简单却涵盖了爬虫开发的所有关键环节。4. 进阶优化让脚本更健壮、更聪明4.1 多话批量下载与断点续传实战中你会发现只看一话不过瘾想把整部漫画几十话都下载下来。这时候需要再加一个“章节列表页”的解析逻辑从目录页提取所有话的链接然后循环调用下载函数。def get_all_chapters(comic_url): resp requests.get(comic_url, headersHEADERS, timeout10) soup BeautifulSoup(resp.text, html.parser) chapters [] for link in soup.select(ul.chapter-list a): href link.get(href) title link.get_text(stripTrue) if href.startswith(http): chapters.append((title, href)) return chapters断点续传的逻辑更实用。漫画下载到一半断网了如果从头再来既浪费时间又浪费流量。可以在每次下载成功后写一个记录文件标记这一话已下载完成。下次启动脚本时先检查标记已完成的跳过。4.2 请求频率控制与错误重试礼貌爬虫的核心是“控制节奏”。我一般在下载循环中加入随机延时让请求间隔在0.5到1.5秒之间浮动模拟真人阅读的节奏。这个技巧不能完全规避封禁但能有效降低触发反爬的概率。import random time.sleep(random.uniform(0.5, 1.5))另外requests库的Session对象也值得用起来。Session会自动保存Cookie并对所有请求复用底层的TCP连接对同一域名的请求性能提升非常明显。把之前的全局HEADERS换成Session后请求速度和稳定性都会上一个台阶。4.3 图像质量与清晰度的确认漫画网站为了加载速度页面里显示的往往是压缩过的预览图真正的高清原图藏在不同的路径下。我在抓包分析时发现很多站点会在原文件命名上做文章比如cover.jpg是预览图cover_2x.jpg才是高清版或者图片URL里带?qualityhigh这样的参数。判断图片是否高清的方法很简单下载后用Pillow读取图片的尺寸。from PIL import Image im Image.open(001.jpg) print(im.size)单页漫画的宽高比大概在0.7左右宽度小于高度宽度一般在700到1200像素之间。如果宽度只有300像素那肯定是预览图需要回到页面源码里找原始图片的地址规律。这就是为什么我一直强调“先抓包分析再写代码”的原因下载一堆模糊图片再返工那才叫真的浪费时间。5. 常见问题与排查技巧实录5.1 请求返回403图片下载失败这是我遇到最多的问题九成是防盗链拦截。解决方法是确认请求头里带了Referer且Referer的值是漫画页面本身的URL而不是漫画网站的首页。有些站点还需要User-Agent配合把浏览器的UA完整复制过来就行。如果带上了Referer还是403那就是更严格的反爬策略比如校验Cookie或增加动态参数。这种情况对新手来说就有点复杂了我建议直接换一个目标站练手不必死磕。5.2 下载的图片打不开或者PDF里页面顺序错乱图片打不开通常是下载不完整或文件损坏看看是不是网络超时后重试逻辑没生效检查一下文件大小是否合理。PDF顺序错乱九成是文件名排序问题我见过有人用sort()排序文件名结果第10页排到了第2页前面。检查一下你的文件名是否用了补零格式化没有的话改过来。5.3 页面结构改了代码立刻失效爬虫代码失效是常态因为网站的HTML结构会频繁调整。这也是爬虫开发和常规软件开发最大的不同你的代码依赖的是别人的页面结构而不是自己定义的接口。解决办法是写代码时把解析逻辑尽量集中到一个函数里比如get_image_urls()这样页面结构调整时只需要改这一个函数。另外可以在解析完后打印图片数量方便快速判断解析是否正常。这个习惯能让你在维护爬虫时少掉不少头发。5.4 常见问题速查表现象可能原因解决思路返回403缺少Referer或UA补全请求头Referer设为漫画阅读页图片全部几KB网站返回占位图检查图片URL后缀尝试替换为原图路径PDF页序混乱文件名排序错误使用补零格式化文件名001, 002...下载速度很慢单线程串行下载改用线程池并发数控制在3-5某张图一直失败单张URL失效增加重试逻辑失败3次后跳过并记录程序运行中报SSL错误证书链问题临时加verifyFalse或用Session解决6. 我踩过的一些坑与最终建议最后分享几点真实的感受。第一个坑是贪多。我第一次写这类脚本时想着直接把整部漫画全部下载下来结果跑了半小时后IP被封了前功尽弃。后来学乖了每次只下载一话下载完停几秒再继续反而稳定得多。爬虫的精髓不是“多快”而是“多稳”能持续运行不被封比单次速度快十倍更重要。第二个坑是忽略图片顺序。我第一次合并PDF时随便拿文件夹里的文件列表就开搞合并出来的PDF前几页是正常的翻到后面页码全乱了后来才意识到是字典序排序导致的顺序错乱。从那以后凡是涉及文件批处理的操作我都会先检查一遍文件名的排序规则。第三个坑是不设超时重试。早期代码里没有超时设置遇到网络抖动的图片URL程序会卡在那里一动不动看起来就像死循环。后来不管写什么请求都会加上timeout15参数和三次重试逻辑彻底告别了卡死问题。这个项目本身虽然简单但你可以顺着它往多个方向深入。比如说如果遇到图片懒加载的网站可以用Selenium模拟滚动触发加载如果想提高下载效率可以学习asyncio异步请求如果想把脚本变成图形界面工具可以学习PyQt或Tkinter封装成一键工具。爬虫的学习路径就是这样一个简单的起点能带出无数延伸的技术分支。希望你学完这个教程后不只是拿到了一个能跑通的脚本更重要的是建立了“先分析、再编码、后验证”的爬虫开发习惯。这套思路放到任何网站上都比死记硬背一段代码有用得多。
返回列表