1. 项目概述与核心价值
最近在整理一些资料时,需要批量下载某个特定博客里的图片,手动一张张右键另存为,效率低不说,还容易出错。网上找了一圈,发现现成的工具要么功能臃肿,要么限制颇多。于是,我决定自己动手,用Python写一个专门针对特定博客平台的图片下载器。今天要分享的,就是这个名为naver-blog-image-downloader-python的小工具。
简单来说,这是一个命令行工具,你只需要给它一个博客文章的URL,它就能自动解析页面,找出文章中所有的图片链接,并把它们批量下载到你的本地文件夹里。整个过程完全自动化,支持断点续传、错误重试,还能根据图片在文章中的顺序自动重命名,保持图片的原始逻辑。无论是做设计参考、资料归档,还是内容分析,这个小工具都能帮你省下大量重复劳动的时间。
它的核心价值在于“精准”和“高效”。精准,是因为它专门针对特定博客平台的页面结构进行解析,能准确抓取文章正文里的图片,避开侧边栏、广告等无关内容。高效,则体现在其纯命令行的操作方式和多线程下载能力上,处理几十上百张图片也就是敲一行命令、等几分钟的事。接下来,我就从设计思路到代码实现,把这个工具的里里外外都拆解一遍。
2. 项目整体设计与思路拆解
2.1 为什么选择Python与命令行模式?
首先解释一下技术选型。选择Python,几乎是这类轻量级自动化脚本的首选。生态丰富是其一,requests用于网络请求,BeautifulSoup或lxml用于HTML解析,aiohttp配合asyncio可以实现高效的异步下载,这些库都非常成熟稳定。其二是开发效率高,Python语法简洁,能让我们快速实现核心逻辑,把精力集中在业务规则(比如特定博客的页面结构)上,而不是底层细节。
采用命令行(CLI)模式而非图形界面(GUI),是基于工具的使用场景考虑的。这个工具的目标用户,大概率是开发者、数据分析师或者有一定技术背景的内容工作者。对于他们来说,命令行更加灵活:可以轻松集成到自动化流水线中,可以通过脚本批量处理多个链接,也可以在无图形界面的服务器上运行。一个简单的python downloader.py [URL]命令,远比打开一个软件、点击按钮、选择文件夹要来得直接和可编程。
2.2 核心工作流程设计
整个工具的工作流程,可以概括为“输入-解析-获取-下载-整理”五个步骤,形成一个清晰的管道(Pipeline)。
- 输入与校验:用户通过命令行参数传入目标博客文章的URL。工具首先需要校验这个URL的格式是否有效,以及是否属于目标博客平台的域名,避免无意义的请求。
- 页面获取与解析:使用
requests库模拟浏览器访问,获取目标页面的HTML源代码。这里的关键是设置合适的请求头(User-Agent),模拟真实浏览器,防止被目标网站的反爬机制拦截。拿到HTML后,利用BeautifulSoup根据目标博客平台的页面结构特征,定位到文章正文所在的DOM节点。 - 图片链接提取与过滤:在正文节点中,查找所有的 `` 标签,提取
src属性。这里会遇到几个问题:图片链接可能是相对路径,需要拼接成绝对URL;链接可能指向缩略图,我们需要的是原图;还可能混入一些网站图标、表情等非目标图片。因此,需要设计一套过滤规则,例如通过链接中包含的关键字(如“blogfiles”、“postfiles”等平台特有的路径)或图片尺寸属性来筛选出我们真正需要的高质量文章配图。 - 下载任务执行:将提取到的所有有效图片链接构造成下载任务列表。为了提高效率,引入线程池(
concurrent.futures.ThreadPoolExecutor)进行并发下载。每个下载任务需要包含错误重试机制(比如重试3次)和超时控制。同时,要实现断点续传功能,即检查本地是否已存在同名文件,如果存在且文件大小与服务器上的一致,则跳过下载,避免重复劳动和流量浪费。 - 文件命名与保存:下载的图片需要有序保存。最简单的方案是按提取顺序编号(如 001.jpg, 002.png)。更友好的方案是,尝试从图片链接或HTML标签的
alt、title属性中提取有意义的描述,经过清洗(移除非法文件名字符)后作为文件名的一部分。所有图片统一保存到用户指定(或默认)的文件夹中,文件夹以文章标题或URL的标识来命名,便于管理。
这个流程设计,确保了工具的鲁棒性和用户体验。下面,我们就深入到每个环节的代码实现细节中去。
3. 核心模块解析与实操要点
3.1 环境准备与依赖安装
工欲善其事,必先利其器。在开始编码之前,我们需要搭建好Python环境并安装必要的第三方库。我强烈建议使用虚拟环境(Virtual Environment)来管理项目依赖,这样可以避免不同项目间的库版本冲突。
# 1. 创建项目目录并进入 mkdir naver-blog-image-downloader && cd naver-blog-image-downloader # 2. 创建Python虚拟环境(这里使用venv,确保你安装的Python版本在3.7以上) python -m venv venv # 3. 激活虚拟环境 # 在Windows上: venv\Scripts\activate # 在macOS/Linux上: source venv/bin/activate # 激活后,命令行提示符前通常会显示 (venv) # 4. 安装核心依赖库 pip install requests beautifulsoup4 lxml # requests: 用于发送HTTP请求,获取网页内容。 # beautifulsoup4: 用于解析HTML,提取所需数据。它是纯Python库,解析速度尚可,但语法非常友好。 # lxml: 是BeautifulSoup的一个解析器后端,速度比Python内置的`html.parser`快很多,推荐安装。注意:
lxml库的安装可能需要系统级的C库支持。在Linux上,你可能需要先安装libxml2和libxslt的开发包。例如在Ubuntu上可以运行sudo apt-get install libxml2-dev libxslt1-dev python3-dev。如果安装lxml失败,可以暂时使用html.parser,但解析大量页面时性能会有差距。
除了这三个核心库,为了后续实现更高效的下载,我们还会用到aiohttp和asyncio来实现异步IO,用tqdm来显示美观的进度条。我们可以在项目初期先不安装,等需要时再添加。
# 后续优化时可能用到的库 pip install aiohttp tqdm3.2 页面解析与图片链接提取策略
这是整个项目的“大脑”,也是最需要针对目标平台定制化的部分。不同博客平台的页面结构千差万别,我们的解析器必须足够“聪明”和“健壮”。
首先,我们定义一个函数来获取页面内容:
import requests from bs4 import BeautifulSoup import re def fetch_page(url, headers=None): """ 获取指定URL的页面内容。 Args: url (str): 目标网页URL。 headers (dict, optional): 自定义请求头。默认为None,使用内置默认头。 Returns: str: 页面的HTML文本。如果请求失败,返回None。 """ if headers is None: # 模拟一个常见的Chrome浏览器请求头,降低被屏蔽的风险 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' } try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 如果状态码不是200,抛出HTTPError异常 # 有些网站可能使用非UTF-8编码,这里可以尝试自动检测或根据响应头指定 response.encoding = response.apparent_encoding return response.text except requests.exceptions.RequestException as e: print(f"请求页面失败: {url}") print(f"错误信息: {e}") return None拿到HTML后,下一步就是解析并提取图片链接。这里以假设的目标博客平台结构为例。我们需要通过浏览器开发者工具(F12)仔细分析其页面。
假设我们发现,目标博客的文章正文都包裹在一个id="postViewArea"的div标签内,并且文章中的图片都是 `` 标签,其src属性指向一个包含 “blogfiles” 字符串的CDN链接。
def extract_image_urls(html, base_url): """ 从HTML中提取目标图片的URL。 Args: html (str): 网页HTML文本。 base_url (str): 原始博客文章URL,用于拼接相对路径。 Returns: list: 提取到的图片绝对URL列表。 """ if not html: return [] soup = BeautifulSoup(html, 'lxml') image_urls = [] # 策略1:定位文章正文区域。这是最精准的方式,能有效避开侧边栏、页眉页脚的图片。 # 你需要根据目标网站的实际结构修改这里的选择器。 post_area = soup.find('div', id='postViewArea') # 示例选择器 if not post_area: # 如果找不到特定ID的区域,可以尝试其他通用选择器,比如特定的class # 或者作为保底方案,直接解析整个soup,但这样噪声会很大。 print("警告:未找到指定的文章正文区域,将尝试全局搜索,结果可能不准确。") post_area = soup # 在正文区域中查找所有img标签 for img_tag in post_area.find_all('img'): src = img_tag.get('src') if not src: continue # 清洗和补全URL img_url = clean_and_complete_url(src, base_url) # 策略2:过滤。只保留我们感兴趣的图片。 # 例如,只保留来自特定CDN的、可能是大图的链接。 if is_target_image(img_url, img_tag): image_urls.append(img_url) # 去重。同一张图片可能在页面中出现多次(比如懒加载的占位符和原图)。 unique_image_urls = list(dict.fromkeys(image_urls)) return unique_image_urls def clean_and_complete_url(url, base_url): """清洗和补全图片URL为绝对地址。""" # 去除URL开头结尾可能存在的空格和引号 url = url.strip(' "\'') # 如果已经是完整的HTTP/HTTPS链接,直接返回 if url.startswith(('http://', 'https://')): return url # 如果是相对路径(以/开头),则拼接基域名 if url.startswith('/'): # 从base_url中提取协议和域名 from urllib.parse import urlparse parsed_base = urlparse(base_url) base_domain = f"{parsed_base.scheme}://{parsed_base.netloc}" return base_domain + url # 其他情况(如相对路径不以/开头),可以尝试基于base_url的路径进行拼接 # 这里简化处理,直接返回原url(但很可能无效)。更健壮的做法是使用urllib.parse.urljoin from urllib.parse import urljoin return urljoin(base_url, url) def is_target_image(url, img_tag): """判断一个图片URL是否是我们需要下载的目标。""" # 规则1:URL中必须包含特定关键词(根据目标网站CDN特征调整) target_keywords = ['blogfiles', 'postfiles', 'post'] # 示例关键词 if not any(keyword in url for keyword in target_keywords): return False # 规则2:可选的,通过img标签的属性过滤。例如,过滤掉很小的图标(width/height属性) width = img_tag.get('width') height = img_tag.get('height') if width and height: # 如果宽高属性存在且是数字,判断是否过小(比如小于50像素的可能是图标) try: if int(width) < 50 or int(height) < 50: return False except ValueError: pass # 如果宽高不是数字,忽略此规则 # 规则3:过滤掉常见的非内容图片,如表情、图标、追踪像素等 exclude_patterns = ['.gif', 'icon.', 'logo.', 'spacer.', 'pixel.'] if any(pattern in url.lower() for pattern in exclude_patterns): return False return True实操心得:
- 选择器的稳定性:依赖于
id或class的选择器可能因网站改版而失效。在编写时,尽量选择那些看起来是核心内容容器、不太会变动的选择器。可以准备几个备选选择器,依次尝试。 - 过滤规则是门艺术:过滤规则需要不断调整和测试。最好的方法是先不加过滤,把所有
img的src都打印出来,观察规律,然后逐步添加规则。过于严格的过滤会漏掉图片,过于宽松则会下载很多垃圾文件。 - 处理懒加载:现代网站大量使用懒加载技术,初始的
img标签的src可能是一个占位图,真正的图片地址在>src = img_tag.get('data-src') or img_tag.get('data-lazy-src') or img_tag.get('src')
3.3 稳健的图片下载与文件管理
提取到图片URL列表后,就进入了下载环节。这个环节需要考虑网络异常、文件存储、命名冲突等问题。
首先,我们设计一个通用的下载函数,包含重试机制:
import os from urllib.parse import urlparse import requests def download_image(image_url, save_dir, filename=None, retries=3, timeout=30): """ 下载单张图片到指定目录。 Args: image_url (str): 图片的URL。 save_dir (str): 保存图片的本地目录。 filename (str, optional): 指定的文件名(不含路径)。如果为None,则从URL或自动生成。 retries (int): 下载失败重试次数。 timeout (int): 请求超时时间(秒)。 Returns: bool: 下载成功返回True,否则返回False。 str: 成功时返回保存的文件路径,失败时返回错误信息。 """ if not filename: # 从URL中提取文件名 parsed_url = urlparse(image_url) filename = os.path.basename(parsed_url.path) if not filename: # 如果URL路径没有文件名,则生成一个基于时间戳的默认名 import time filename = f"image_{int(time.time()*1000)}.jpg" # 确保文件名是安全的(移除非法字符) filename = re.sub(r'[<>:"/\\|?*]', '_', filename) # 构建完整的保存路径 save_path = os.path.join(save_dir, filename) # 检查文件是否已存在且完整(简易版:检查文件是否存在) if os.path.exists(save_path): print(f"文件已存在,跳过下载: {filename}") return True, save_path for attempt in range(retries): try: headers = {'User-Agent': 'Mozilla/5.0 ...'} # 复用之前的请求头 response = requests.get(image_url, headers=headers, timeout=timeout, stream=True) response.raise_for_status() # 获取文件大小(用于可能的进度显示) total_size = int(response.headers.get('content-length', 0)) # 以二进制写入模式打开文件 with open(save_path, 'wb') as f: # 使用iter_content以块的方式写入,避免大文件占用过多内存 chunk_size = 8192 for chunk in response.iter_content(chunk_size=chunk_size): if chunk: f.write(chunk) print(f"下载成功: {filename} -> {save_path}") return True, save_path except requests.exceptions.RequestException as e: print(f"下载失败 (尝试 {attempt + 1}/{retries}): {image_url}") print(f"错误: {e}") if attempt == retries - 1: return False, str(e) # 可选:等待一段时间后重试 import time time.sleep(2 ** attempt) # 指数退避 return False, "达到最大重试次数"接下来,我们需要一个管理函数来组织批量下载,并生成有意义的文件名。一个常见的需求是按图片在文章中出现的顺序编号。
def download_all_images(image_urls, base_url, output_dir='downloaded_images'): """ 批量下载图片列表。 Args: image_urls (list): 图片URL列表。 base_url (str): 源文章URL,用于生成文件夹名。 output_dir (str): 输出根目录。 Returns: list: 成功下载的文件路径列表。 """ # 创建以文章标题或URL标识命名的子文件夹 import re # 尝试从URL中提取一个简洁的标识,例如最后一部分路径 folder_name = re.sub(r'[^\w\-_\. ]', '_', os.path.basename(urlparse(base_url).path)) or 'blog_post' save_dir = os.path.join(output_dir, folder_name) os.makedirs(save_dir, exist_ok=True) print(f"图片将保存至: {save_dir}") successful_downloads = [] for index, img_url in enumerate(image_urls, start=1): # 生成顺序文件名,保留原始扩展名 ext = os.path.splitext(urlparse(img_url).path)[1] if not ext: ext = '.jpg' # 默认扩展名 filename = f"{index:03d}{ext}" # 格式化为001.jpg, 002.png等 success, result = download_image(img_url, save_dir, filename) if success: successful_downloads.append(result) else: print(f"图片下载失败,已跳过: {img_url}") print(f"\n下载完成!成功: {len(successful_downloads)} / 总数: {len(image_urls)}") return successful_downloads注意事项:
- 文件命名冲突:使用顺序编号(如
001.jpg)可以有效避免命名冲突,但丢失了原始文件名可能包含的信息。折中方案是使用“序号_原始文件名”的格式,但需要确保原始文件名是安全的。 - 网络礼仪与法律风险:批量下载图片务必遵守目标网站的
robots.txt协议,并尊重版权。本工具仅限用于个人学习、归档或已获得授权的场景。切勿用于大量抓取、盗用他人内容等非法或不道德用途。 - 错误处理:上述代码进行了基本的错误处理。在生产环境中,可能需要更细致的日志记录,将成功和失败的任务分别记录到文件,方便后续排查和手动补下。
4. 完整实现与进阶优化
4.1 整合与命令行接口
现在,我们把所有模块整合起来,并添加命令行参数解析功能,让工具变得易用。
# main.py import argparse import sys from pathlib import Path def main(): parser = argparse.ArgumentParser(description='特定博客平台图片下载器') parser.add_argument('url', help='目标博客文章的URL') parser.add_argument('-o', '--output', default='downloaded_images', help='图片保存的根目录 (默认: downloaded_images)') parser.add_argument('-t', '--threads', type=int, default=5, help='并发下载线程数 (默认: 5)') args = parser.parse_args() # 1. 获取页面 print(f"正在获取页面: {args.url}") html = fetch_page(args.url) if not html: sys.exit(1) # 2. 提取图片链接 print("正在解析页面并提取图片链接...") image_urls = extract_image_urls(html, args.url) if not image_urls: print("未在页面中找到目标图片。") sys.exit(0) print(f"共找到 {len(image_urls)} 张图片。") # 3. 下载图片 # 注意:当前的 download_all_images 是顺序下载。我们需要将其改造成支持多线程。 successful_files = download_all_images_concurrent(image_urls, args.url, args.output, args.threads) print(f"所有任务处理完毕。成功下载 {len(successful_files)} 个文件。") if __name__ == '__main__': main()我们需要实现一个支持多线程的download_all_images_concurrent函数:
from concurrent.futures import ThreadPoolExecutor, as_completed def download_all_images_concurrent(image_urls, base_url, output_dir='downloaded_images', max_workers=5): """使用线程池并发下载图片。""" # 创建保存目录(同前) import re, os from urllib.parse import urlparse folder_name = re.sub(r'[^\w\-_\. ]', '_', os.path.basename(urlparse(base_url).path)) or 'blog_post' save_dir = os.path.join(output_dir, folder_name) os.makedirs(save_dir, exist_ok=True) print(f"图片将保存至: {save_dir}") # 准备下载任务参数 download_tasks = [] for index, img_url in enumerate(image_urls, start=1): ext = os.path.splitext(urlparse(img_url).path)[1] or '.jpg' filename = f"{index:03d}{ext}" download_tasks.append((img_url, save_dir, filename)) successful_downloads = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: # 提交所有任务 future_to_url = {executor.submit(download_image, *task): task for task in download_tasks} # 使用tqdm显示进度条(如果安装了tqdm库) try: from tqdm import tqdm futures = tqdm(as_completed(future_to_url), total=len(download_tasks), desc="下载进度") except ImportError: futures = as_completed(future_to_url) print("开始并发下载...") for future in futures: task = future_to_url[future] img_url, save_dir, filename = task try: success, result = future.result() if success: successful_downloads.append(result) else: print(f"下载失败: {img_url} - {result}") except Exception as e: print(f"任务执行异常: {img_url} - {e}") return successful_downloads现在,这个工具就可以通过命令行运行了:
python main.py https://example-blog.com/post/123 -o my_pictures -t 104.2 性能优化:异步IO与速率限制
当需要下载的图片数量非常多(比如上百张)时,即使使用多线程,也可能遇到性能瓶颈或对目标服务器造成过大压力。此时,可以考虑使用异步IO(asyncio+aiohttp)来获得更高的并发效率和更低的资源占用。同时,必须加入速率限制(Rate Limiting)和随机延迟,做一个有礼貌的爬虫。
这里给出一个异步下载函数的简化示例:
import aiohttp import asyncio import aiofiles async def download_image_async(session, img_url, save_path, semaphore): """异步下载单张图片。""" async with semaphore: # 使用信号量控制并发数,避免瞬间发起过多请求 try: async with session.get(img_url, timeout=aiohttp.ClientTimeout(total=30)) as response: response.raise_for_status() async with aiofiles.open(save_path, 'wb') as f: await f.write(await response.read()) return True, save_path except Exception as e: return False, str(e) async def download_all_async(image_urls, save_dir, max_concurrent=5): """异步批量下载。""" semaphore = asyncio.Semaphore(max_concurrent) connector = aiohttp.TCPConnector(limit=0) # 不限制连接总数,由semaphore控制 timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session: tasks = [] for index, img_url in enumerate(image_urls, start=1): # ... 生成文件名和路径 ... task = download_image_async(session, img_url, save_path, semaphore) tasks.append(task) # 可选:在任务间加入微小随机延迟,进一步分散请求 # await asyncio.sleep(random.uniform(0.1, 0.3)) results = await asyncio.gather(*tasks, return_exceptions=True) # 处理结果...优化要点:
- 信号量(Semaphore):控制同时进行的最大请求数,防止连接数爆炸。
- 随机延迟:在发起请求前加入
asyncio.sleep(random.uniform(0.5, 1.5)),可以更友好地对待目标服务器,降低被封IP的风险。 - 连接器配置:
aiohttp.TCPConnector(limit=0)配合信号量使用,可以更精细地控制连接池。
4.3 功能扩展与实用技巧
一个基础工具完成后,可以根据实际需求添加更多实用功能:
- 配置文件:将目标博客平台的选择器规则、过滤关键词、请求头等信息写入一个配置文件(如
config.yaml或config.json),使工具更容易适配不同的网站,而无需修改代码。 - 支持多种输入源:不仅支持单个URL,还可以支持一个包含多个URL的文本文件作为输入,进行批量处理。
- 更智能的命名:除了顺序编号,可以尝试从图片的
alt文本或文章标题中提取关键词作为文件名前缀。 - 下载后处理:集成简单的图片处理功能,比如使用
PIL(Pillow) 库统一转换格式、调整大小或添加水印。 - 生成报告:下载完成后,生成一个HTML或Markdown格式的报告,包含原文链接、下载时间、以及所有图片的缩略图预览,方便管理和检索。
5. 常见问题与排查技巧实录
在实际使用过程中,你可能会遇到各种各样的问题。下面是我在开发和测试中遇到的一些典型情况及其解决方法。
5.1 问题一:抓取不到任何图片
- 症状:程序运行没有报错,但最终提示“找到0张图片”。
- 排查思路:
- 检查网络与URL:首先手动在浏览器中打开目标URL,确认页面能正常加载,并且图片可见。
- 检查请求头:网站可能对非浏览器的请求进行了屏蔽。尝试在
fetch_page函数中添加更完整的请求头,模拟浏览器。除了User-Agent,有时还需要Referer、Accept-Language等。headers = { 'User-Agent': 'Mozilla/5.0 ...', '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', 'Referer': 'https://www.google.com/', # 有时需要设置一个来源页 } - 检查页面结构:网站可能已经改版,或者你使用的选择器(如
id='postViewArea')不正确。使用print(soup.prettify())打印一部分解析后的HTML,或者用浏览器的开发者工具仔细检查文章正文的真实HTML结构,更新extract_image_urls函数中的选择器。 - 处理动态加载:如果图片是通过JavaScript动态加载的(比如滚动到位置才加载),那么直接请求HTML是拿不到的。这种情况需要用到
Selenium或Playwright这类浏览器自动化工具来模拟用户操作,获取渲染后的完整页面源码。这是一个更复杂的主题,但思路是先用这些工具获取完整HTML,再交给BeautifulSoup解析。
5.2 问题二:下载的图片是损坏的、尺寸很小或是占位符
- 症状:图片能下载,但打开后是裂图、模糊小图或者统一的“加载中”图片。
- 排查思路:
- 检查链接过滤规则:这通常是因为过滤规则
is_target_image不够准确,没能过滤掉缩略图或懒加载占位符。仔细分析你下载到的“坏图”的URL特征,将其加入排除列表。同时,确保你的规则能正确匹配到原图URL(原图URL可能隐藏在>import urllib.parse # 在从URL提取文件名时 filename = os.path.basename(urllib.parse.unquote(parsed_url.path)) # 先进行URL解码 # 然后再进行非法字符替换更健壮的做法是,将所有非ASCII字符也替换掉或进行转码,确保文件名的通用性。
- 检查链接过滤规则:这通常是因为过滤规则
5.5 问题速查表
| 问题现象 | 可能原因 | 解决方案 | ||
|---|---|---|---|---|
找不到图片 (image_urls为空) | 1. 选择器错误 2. 网站需要JS渲染 3. 请求被屏蔽 | 1. 更新extract_image_urls中的选择器2. 使用 Selenium 获取页面 3. 完善请求头,添加 Referer、Cookie | ||
| 下载的图片是占位符或小图 | 1. 抓取到的是缩略图链接 2. 懒加载未处理 | 1. 分析原图URL模式,修改过滤/替换规则 2. 优先抓取 >下载速度慢,部分失败 | 1. 并发数过高被限速 2. 网络不稳定 3. 服务器响应慢 | 1. 降低-t参数值2. 增加超时时间和重试次数 3. 在请求间添加随机延迟 |
| 文件名乱码或创建失败 | 1. 文件名含非法字符 2. 编码问题 | 1. 使用re.sub过滤非法字符2. 对URL进行 urllib.parse.unquote解码 | ||
| 程序报SSL证书错误 | 目标网站证书问题 | 在requests.get()中添加verify=False参数(不安全,仅用于测试) |
这个工具从构思到实现,再到不断优化以应对各种边界情况,是一个典型的“发现问题-解决问题”的编程实践。它麻雀虽小,五脏俱全,涉及了HTTP请求、HTML解析、文件操作、并发编程、错误处理等多个核心知识点。最重要的是,它解决了一个真实、具体的需求。你可以基于这个框架,轻松地修改解析规则,将其适配到其他任何你需要的博客或内容平台上去。